From 01f9d117ab007172037e5c8ec7cb0d5994e8f019 Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Sun, 7 Mar 2021 21:36:51 -0800 Subject: [PATCH 01/20] Created new C# impl for .NET 5.0 --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c42c5ca62f..d22c317fcf 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,7 @@ DOCKERIZE = # Implementation specific settings # -IMPLS = ada ada.2 awk bash basic bbc-basic c chuck clojure coffee common-lisp cpp crystal cs d dart \ +IMPLS = ada ada.2 awk bash basic bbc-basic c chuck clojure coffee common-lisp cpp crystal cs cs.2 d dart \ elisp elixir elm erlang es6 factor fantom forth fsharp go groovy gnu-smalltalk \ guile haskell haxe hy io java js jq julia kotlin livescript logo lua make mal \ matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp pike plpgsql \ @@ -199,6 +199,7 @@ common-lisp_STEP_TO_PROG = impls/common-lisp/$($(1)) cpp_STEP_TO_PROG = impls/cpp/$($(1)) crystal_STEP_TO_PROG = impls/crystal/$($(1)) cs_STEP_TO_PROG = impls/cs/$($(1)).exe +cs.2_STEP_TO_PROG = impls/cs.2/bin/Debug/net5.0/$($(1)).dll d_STEP_TO_PROG = impls/d/$($(1)) dart_STEP_TO_PROG = impls/dart/$($(1)).dart elisp_STEP_TO_PROG = impls/elisp/$($(1)).el From 4a9e078f9916fc993d681b6e530dcda7ec62389c Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Sun, 7 Mar 2021 21:51:30 -0800 Subject: [PATCH 02/20] Step 0 and general configuration --- Makefile | 2 +- impls/cs.2/.gitignore | 3 +++ impls/cs.2/run | 2 ++ impls/cs.2/step0_repl.cs | 47 ++++++++++++++++++++++++++++++++++++ impls/cs.2/step0_repl.csproj | 9 +++++++ 5 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 impls/cs.2/.gitignore create mode 100755 impls/cs.2/run create mode 100644 impls/cs.2/step0_repl.cs create mode 100644 impls/cs.2/step0_repl.csproj diff --git a/Makefile b/Makefile index d22c317fcf..0a9315ce2a 100644 --- a/Makefile +++ b/Makefile @@ -199,7 +199,7 @@ common-lisp_STEP_TO_PROG = impls/common-lisp/$($(1)) cpp_STEP_TO_PROG = impls/cpp/$($(1)) crystal_STEP_TO_PROG = impls/crystal/$($(1)) cs_STEP_TO_PROG = impls/cs/$($(1)).exe -cs.2_STEP_TO_PROG = impls/cs.2/bin/Debug/net5.0/$($(1)).dll +cs.2_STEP_TO_PROG = impls/cs.2/$($(1)).cs d_STEP_TO_PROG = impls/d/$($(1)) dart_STEP_TO_PROG = impls/dart/$($(1)).dart elisp_STEP_TO_PROG = impls/elisp/$($(1)).el diff --git a/impls/cs.2/.gitignore b/impls/cs.2/.gitignore new file mode 100644 index 0000000000..2bba72c8da --- /dev/null +++ b/impls/cs.2/.gitignore @@ -0,0 +1,3 @@ +.vscode +bin +obj diff --git a/impls/cs.2/run b/impls/cs.2/run new file mode 100755 index 0000000000..3700fc9579 --- /dev/null +++ b/impls/cs.2/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec dotnet run $(dirname $0)/${STEP:-stepA_mal} "${@}" diff --git a/impls/cs.2/step0_repl.cs b/impls/cs.2/step0_repl.cs new file mode 100644 index 0000000000..3203b925d7 --- /dev/null +++ b/impls/cs.2/step0_repl.cs @@ -0,0 +1,47 @@ +using System; + +namespace mal +{ + class step0_repl + { + + static string READ(string input) + { + return input; + } + + static string EVAL(string input) + { + return input; + } + + static string PRINT(string input) + { + return input; + } + + static string rep(string input) + { + string read = READ(input); + string evaled = EVAL(read); + string printed = PRINT(evaled); + return printed; + } + + static void Main(string[] args) + { + + string line = null; + do + { + Console.Write("user> "); + line = Console.ReadLine(); + if (line != null) + { + Console.WriteLine(rep(line)); + } + } while (line != null); + } + } +} + diff --git a/impls/cs.2/step0_repl.csproj b/impls/cs.2/step0_repl.csproj new file mode 100644 index 0000000000..a91d0a9719 --- /dev/null +++ b/impls/cs.2/step0_repl.csproj @@ -0,0 +1,9 @@ + + + + exe + net5.0 + mal.step0_repl + + + From d1e4b2f357d53b9a0a1895ebc067e88666fb4cf3 Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Tue, 9 Mar 2021 00:29:16 -0800 Subject: [PATCH 03/20] Modified 'run' and Makefiles for compiled code --- Makefile | 2 +- impls/cs.2/Makefile | 5 +++++ impls/cs.2/run | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 impls/cs.2/Makefile diff --git a/Makefile b/Makefile index 0a9315ce2a..d22c317fcf 100644 --- a/Makefile +++ b/Makefile @@ -199,7 +199,7 @@ common-lisp_STEP_TO_PROG = impls/common-lisp/$($(1)) cpp_STEP_TO_PROG = impls/cpp/$($(1)) crystal_STEP_TO_PROG = impls/crystal/$($(1)) cs_STEP_TO_PROG = impls/cs/$($(1)).exe -cs.2_STEP_TO_PROG = impls/cs.2/$($(1)).cs +cs.2_STEP_TO_PROG = impls/cs.2/bin/Debug/net5.0/$($(1)).dll d_STEP_TO_PROG = impls/d/$($(1)) dart_STEP_TO_PROG = impls/dart/$($(1)).dart elisp_STEP_TO_PROG = impls/elisp/$($(1)).el diff --git a/impls/cs.2/Makefile b/impls/cs.2/Makefile new file mode 100644 index 0000000000..f9af55d808 --- /dev/null +++ b/impls/cs.2/Makefile @@ -0,0 +1,5 @@ +bin/Debug/net5.0/step%.dll: step%.cs + dotnet build ./step$*.csproj + +clean: + \rm -rf bin obj diff --git a/impls/cs.2/run b/impls/cs.2/run index 3700fc9579..9634198abf 100755 --- a/impls/cs.2/run +++ b/impls/cs.2/run @@ -1,2 +1,2 @@ #!/bin/bash -exec dotnet run $(dirname $0)/${STEP:-stepA_mal} "${@}" +exec dotnet $(dirname $0)/bin/Debug/net5.0/${STEP:-stepA_mal}.dll "${@}" From 70d5395956067ab1bb95c8601d137c189b412fd1 Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Tue, 9 Mar 2021 00:29:39 -0800 Subject: [PATCH 04/20] Step 1 --- impls/cs.2/printer.cs | 71 +++++++++ impls/cs.2/reader.cs | 227 +++++++++++++++++++++++++++++ impls/cs.2/step1_read_print.cs | 57 ++++++++ impls/cs.2/step1_read_print.csproj | 9 ++ impls/cs.2/types.cs | 59 ++++++++ 5 files changed, 423 insertions(+) create mode 100644 impls/cs.2/printer.cs create mode 100644 impls/cs.2/reader.cs create mode 100644 impls/cs.2/step1_read_print.cs create mode 100644 impls/cs.2/step1_read_print.csproj create mode 100644 impls/cs.2/types.cs diff --git a/impls/cs.2/printer.cs b/impls/cs.2/printer.cs new file mode 100644 index 0000000000..0a6bf12daa --- /dev/null +++ b/impls/cs.2/printer.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; + +namespace mal +{ + class printer + { + static Dictionary ESCAPE = new Dictionary() + { + {'\n', "\\n"}, {'"', "\\\""}, {'\\', "\\\\"} + }; + + public static string pr_str(MalType malType, bool print_readably = false) + { + if (malType is MalList) + { + MalList malList = (MalList)malType; + string closingBracket = (malList.openingBracket == "(") ? ")" : "]"; + IList strings = new List(); + foreach (MalType item in malList.items) + { + strings.Add(pr_str(item)); + } + string joined = string.Join(" ", strings); + return string.Format("{0}{1}{2}", malList.openingBracket, joined, closingBracket); + } + else if (malType is MalInteger) + { + return ((MalInteger)malType).value.ToString(); + } + else if (malType is MalSymbol) + { + return ((MalSymbol)malType).value.ToString(); + } + else if (malType is MalString) + { + if (print_readably) + { + string value = ((MalString)malType).value; + string output = ""; + foreach (char c in value) + { + output += ESCAPE.GetValueOrDefault(c, c.ToString()); + } + return string.Format("\"{0}\"", output); + } + else + { + return string.Format("\"{0}\"", ((MalString)malType).value); + } + } + else if (malType is MalHashmap) + { + MalHashmap hashMap = (MalHashmap) malType; + List strings = new List(); + foreach (var kv in hashMap.values) + { + strings.Add(pr_str(kv.Key, true)); + strings.Add(pr_str(kv.Value, true)); + } + string joined = string.Join(" ", strings); + return string.Format("{0}{1}{2}", "{", joined, "}"); + } + else + { + throw new Exception(string.Format("Unknown type to print: {0}", malType.ToString())); + } + } + + } +} \ No newline at end of file diff --git a/impls/cs.2/reader.cs b/impls/cs.2/reader.cs new file mode 100644 index 0000000000..6915a9cd26 --- /dev/null +++ b/impls/cs.2/reader.cs @@ -0,0 +1,227 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace mal +{ + class Reader + { + static string TOKENIZER_REGEX = "[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:\\\\.|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}('\"`,;)]*)"; + static Regex TOKENIZER_INSTANCE = new Regex(TOKENIZER_REGEX); + static Dictionary UNESCAPE = new Dictionary() + { + {'n', "\n"}, {'"', "\""}, {'\\', "\\"} // TODO add \t and others + }; + static Dictionary QUOTING = new Dictionary() + { + {"'", "quote"}, {"`", "quasiquote"}, {"~", "unquote"}, {"~@", "splice-unquote"}, {"@", "deref"} + }; + + IList tokens; + int position; + + Reader(IList tokens) + { + this.tokens = tokens; + this.position = 0; + } + + string next() + { + if (position >= tokens.Count) + { + // throw new ArgumentOutOfRangeException("Went beyond all the tokens"); + return null; + } + else + { + string val = peek(); + position++; + return val; + } + } + + string peek() + { + return (position < tokens.Count) ? tokens[position] : null; + } + + static IList tokenize(string input) + { + IList tokens = new List(); + Match match = TOKENIZER_INSTANCE.Match(input); + while (match.Success) + { + string value = match.Groups[1].Value; + if (value?.Length > 0) + { + tokens.Add(value); + } + match = match.NextMatch(); + } + + return tokens; + } + + public static MalType read_str(string input) + { + var tokens = tokenize(input); + var reader = new Reader(tokens); + return read_form(reader); + } + + public static MalType read_form(Reader reader) + { + string first = reader.peek(); + if (first == "^") // expect two other forms + { + reader.next(); // drop the '^' + MalHashmap metadata = read_hashmap(reader); + MalType value = read_form(reader); + List items = new List(){ new MalSymbol("with-meta"), value, metadata}; + MalList listWithMeta = new MalList(items); + return listWithMeta; + } + else if (QUOTING.ContainsKey(first)) + { + string quotation = QUOTING.GetValueOrDefault(reader.next(), null); + MalType quoted = read_form(reader); + List items = new List() + { + new MalSymbol(quotation), + quoted + }; + MalList quotedList = new MalList(items); + return quotedList; + } + else if (CLOSING_BRACKETS.ContainsValue(first)) + { + return null; // empty form + } + else if (first == "{") + { + return read_hashmap(reader); + } + else if (CLOSING_BRACKETS.ContainsKey(first)) + { + return read_list(reader); + } + else + { + return read_atom(reader); + } + } + + public static Dictionary CLOSING_BRACKETS = new Dictionary() { + {"(", ")"}, {"[", "]"}, {"{", "}"} + }; + static MalList read_list(Reader reader) + { + string openingBracket = reader.next(); + string closingBracket = CLOSING_BRACKETS.GetValueOrDefault(openingBracket); + + List items = new List(); + string peeked = null; + do + { + var item = read_form(reader); + if (item != null) + { + items.Add(item); + } + peeked = reader.peek(); + } while (peeked != null && peeked != closingBracket); + + // confirm the list has valid closing parens + var closing = reader.next(); + if (closing == closingBracket) + { + return new MalList(items, openingBracket); + } + else + { + throw new Exception("Expression has unbalanced parenthesis"); + } + } + + static MalHashmap read_hashmap(Reader reader) + { + MalList kvs = read_list(reader); + if (kvs.items.Count % 2 == 1) + { + throw new Exception("Hashmap needs an even number of forms"); + } + Dictionary pairs = new Dictionary(); + for (int i = 0; i < kvs.items.Count; i += 2) + { + MalType key = kvs.items[i]; + MalType val = kvs.items[i+1]; + pairs.Add(key, val); + } + return new MalHashmap(pairs); + } + static MalType read_atom(Reader reader) + { + // Attempt to parse a number first + string item = reader.next(); + if (item.StartsWith("\"")) + { + if (item.Length > 1 && item.EndsWith("\"")) + { + string source = item.Substring(1, item.Length - 2); + string unescaped = unescape(source); + return new MalString(unescaped); // length minus the quotes + } + else + { + throw new Exception("String contains unbalanced quotes"); + } + } + try + { + int intValue = int.Parse(item); + return new MalInteger(intValue); + } + catch + { + return new MalSymbol(item); + } + } + + static string unescape(string input) + { + bool escaping = false; + string output = ""; + foreach (char c in input) + { + if (!escaping && c == '\\') + { + escaping = true; + continue; + } + else if (escaping) + { + if (UNESCAPE.ContainsKey(c)) + { + output += UNESCAPE[c]; + escaping = false; + } + else + { + throw new Exception("String contains unbalanced escaped characters"); + } + } + else + { + output += c; + } + + } + if (escaping) + { + throw new Exception("String contains unbalanced escaped characters"); + } + return output; + } + } +} diff --git a/impls/cs.2/step1_read_print.cs b/impls/cs.2/step1_read_print.cs new file mode 100644 index 0000000000..a3b506df44 --- /dev/null +++ b/impls/cs.2/step1_read_print.cs @@ -0,0 +1,57 @@ +using System; + +namespace mal +{ + class step1_read_print + { + + static MalType READ(string input) + { + return Reader.read_str(input); + } + + static MalType EVAL(MalType input) + { + return input; + } + + static string PRINT(MalType input) + { + return printer.pr_str(input, true); + } + + static string rep(string input) + { + MalType read = READ(input); + MalType evaled = EVAL(read); + string printed = PRINT(evaled); + return printed; + } + + static void Main(string[] args) + { + // TESTS + var test = rep("^{a 1} [1 2 3]"); + + string line = null; + do + { + Console.Write("user> "); + line = Console.ReadLine(); + if (line != null) + { + try + { + Console.WriteLine(rep(line)); + } + catch (Exception ex) + { + + Console.WriteLine(ex.Message); + } + } + } while (line != null); + Console.WriteLine(); + } + } +} diff --git a/impls/cs.2/step1_read_print.csproj b/impls/cs.2/step1_read_print.csproj new file mode 100644 index 0000000000..5715cce324 --- /dev/null +++ b/impls/cs.2/step1_read_print.csproj @@ -0,0 +1,9 @@ + + + + exe + net5.0 + mal.step1_read_print + + + diff --git a/impls/cs.2/types.cs b/impls/cs.2/types.cs new file mode 100644 index 0000000000..d39fd575e4 --- /dev/null +++ b/impls/cs.2/types.cs @@ -0,0 +1,59 @@ +using System.Collections.Generic; + +namespace mal +{ + public class MalType + { + } + + public class MalList : MalType + { + public IList items { get; } + public string openingBracket { get; } + + public MalList(IList items, string openingBracket = "(") + { + this.items = items; + this.openingBracket = openingBracket; + } + } + + class MalInteger : MalType + { + public int value { get; } + public MalInteger(int value) + { + this.value = value; + } + } + + class MalSymbol : MalType + { + public string value { get; } + + public MalSymbol(string value) + { + this.value = value; + } + } + + class MalString : MalType + { + public string value { get; } + + public MalString(string value) + { + this.value = value; + } + } + + class MalHashmap : MalType + { + public Dictionary values { get; } + + public MalHashmap(Dictionary values) + { + this.values = values; + } + } +} \ No newline at end of file From a6292f5bee341d68b6c92ff6877d43ccddd37573 Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Wed, 10 Mar 2021 23:49:03 -0800 Subject: [PATCH 05/20] Step 2 --- impls/cs.2/printer.cs | 23 ++++-- impls/cs.2/reader.cs | 13 ++-- impls/cs.2/step1_read_print.cs | 2 +- impls/cs.2/step2_eval.cs | 137 +++++++++++++++++++++++++++++++++ impls/cs.2/step2_eval.csproj | 9 +++ impls/cs.2/types.cs | 25 ++++++ 6 files changed, 196 insertions(+), 13 deletions(-) create mode 100644 impls/cs.2/step2_eval.cs create mode 100644 impls/cs.2/step2_eval.csproj diff --git a/impls/cs.2/printer.cs b/impls/cs.2/printer.cs index 0a6bf12daa..430daa6d51 100644 --- a/impls/cs.2/printer.cs +++ b/impls/cs.2/printer.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using System.Collections.Generic; namespace mal @@ -37,12 +38,12 @@ public static string pr_str(MalType malType, bool print_readably = false) if (print_readably) { string value = ((MalString)malType).value; - string output = ""; + StringBuilder output = new StringBuilder(); foreach (char c in value) { - output += ESCAPE.GetValueOrDefault(c, c.ToString()); + output.Append(ESCAPE.GetValueOrDefault(c, c.ToString())); } - return string.Format("\"{0}\"", output); + return string.Format("\"{0}\"", output.ToString()); } else { @@ -51,16 +52,24 @@ public static string pr_str(MalType malType, bool print_readably = false) } else if (malType is MalHashmap) { - MalHashmap hashMap = (MalHashmap) malType; + MalHashmap hashMap = (MalHashmap)malType; List strings = new List(); - foreach (var kv in hashMap.values) + foreach (var keyValuePair in hashMap.values) { - strings.Add(pr_str(kv.Key, true)); - strings.Add(pr_str(kv.Value, true)); + strings.Add(pr_str(keyValuePair.Key, true)); + strings.Add(pr_str(keyValuePair.Value, true)); } string joined = string.Join(" ", strings); return string.Format("{0}{1}{2}", "{", joined, "}"); } + else if (malType is MalKeyword) + { + return ((MalKeyword)malType).name; + } + else if (malType is MalFunction) + { + return ""; + } else { throw new Exception(string.Format("Unknown type to print: {0}", malType.ToString())); diff --git a/impls/cs.2/reader.cs b/impls/cs.2/reader.cs index 6915a9cd26..a45bd4d548 100644 --- a/impls/cs.2/reader.cs +++ b/impls/cs.2/reader.cs @@ -30,7 +30,6 @@ string next() { if (position >= tokens.Count) { - // throw new ArgumentOutOfRangeException("Went beyond all the tokens"); return null; } else @@ -78,7 +77,7 @@ public static MalType read_form(Reader reader) reader.next(); // drop the '^' MalHashmap metadata = read_hashmap(reader); MalType value = read_form(reader); - List items = new List(){ new MalSymbol("with-meta"), value, metadata}; + List items = new List() { new MalSymbol("with-meta"), value, metadata }; MalList listWithMeta = new MalList(items); return listWithMeta; } @@ -124,7 +123,7 @@ static MalList read_list(Reader reader) string peeked = null; do { - var item = read_form(reader); + MalType item = read_form(reader); if (item != null) { items.Add(item); @@ -155,7 +154,7 @@ static MalHashmap read_hashmap(Reader reader) for (int i = 0; i < kvs.items.Count; i += 2) { MalType key = kvs.items[i]; - MalType val = kvs.items[i+1]; + MalType val = kvs.items[i + 1]; pairs.Add(key, val); } return new MalHashmap(pairs); @@ -164,7 +163,11 @@ static MalType read_atom(Reader reader) { // Attempt to parse a number first string item = reader.next(); - if (item.StartsWith("\"")) + if (item.StartsWith(":")) + { + return new MalKeyword(item); + } + else if (item.StartsWith("\"")) { if (item.Length > 1 && item.EndsWith("\"")) { diff --git a/impls/cs.2/step1_read_print.cs b/impls/cs.2/step1_read_print.cs index a3b506df44..3457c1a387 100644 --- a/impls/cs.2/step1_read_print.cs +++ b/impls/cs.2/step1_read_print.cs @@ -31,7 +31,7 @@ static string rep(string input) static void Main(string[] args) { // TESTS - var test = rep("^{a 1} [1 2 3]"); + // var test = rep("^{a 1} [1 2 3]"); string line = null; do diff --git a/impls/cs.2/step2_eval.cs b/impls/cs.2/step2_eval.cs new file mode 100644 index 0000000000..5f189bbf59 --- /dev/null +++ b/impls/cs.2/step2_eval.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace mal +{ + class step2_eval + { + + static MalType READ(string input) + { + return Reader.read_str(input); + } + + public static MalType EVAL(MalType ast, Dictionary env) + { + if (ast is not MalList) + { + return eval_ast(ast, env); + } + else + { + MalList astList = (MalList)ast; + if (astList.items.Count == 0) + { + return astList; + } + else + { + MalList evaluated = (MalList)(eval_ast(ast, env)); + if (astList.isList()) + { + MalFunction func = (MalFunction)evaluated.items[0]; + MalType retVal = func.function(evaluated.items.Skip(1).ToList()); + return retVal; + } + else + { + return evaluated; // vector + } + } + } + } + + static string PRINT(MalType input) + { + return printer.pr_str(input, true); + } + + static string rep(string input) + { + MalType read = READ(input); + MalType evaled = EVAL(read, repl_env); + string printed = PRINT(evaled); + return printed; + } + + public static MalType eval_ast(MalType ast, Dictionary env) + { + if (ast is MalSymbol) + { + MalSymbol astSymbol = (MalSymbol)ast; + if (env.ContainsKey(astSymbol.value)) + { + return env.GetValueOrDefault(astSymbol.value); + } + else + { + throw new Exception(string.Format("Unknown symbol: {0}", astSymbol.value)); + } + } + else if (ast is MalList) + { + MalList astList = (MalList)ast; + List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); + return new MalList(evaluated, astList.openingBracket); // important: preserve the bracket + } + else if (ast is MalHashmap) + { + MalHashmap astHashmap = (MalHashmap)ast; + Dictionary newKVs = new Dictionary(); + foreach (KeyValuePair kv in astHashmap.values) + { + newKVs.Add(EVAL(kv.Key, repl_env), EVAL(kv.Value, repl_env)); + } + return new MalHashmap(newKVs); + } + else + { + return ast; + } + } + + public static Dictionary repl_env = new Dictionary() + { + // {"+", (IList args) => new MalInteger( args.Select(n => ((MalInteger)n).value).Sum() ) }, + {"+", new MalFunction( + (IList args) => new MalInteger( ((MalInteger) args[0]).value + ((MalInteger) args[1]).value ) + )}, + {"-", new MalFunction( + (IList args) => new MalInteger( ((MalInteger) args[0]).value - ((MalInteger) args[1]).value ) + )}, + {"*", new MalFunction( + (IList args) => new MalInteger( ((MalInteger) args[0]).value * ((MalInteger) args[1]).value ) + )}, + {"/", new MalFunction( + (IList args) => new MalInteger( ((MalInteger) args[0]).value / ((MalInteger) args[1]).value ) + )}, + }; + + static void Main(string[] args) + { + // TESTS + // var test = rep("[0 (+ 1 2)]"); + + string line = null; + do + { + Console.Write("user> "); + line = Console.ReadLine(); + if (line != null) + { + try + { + Console.WriteLine(rep(line)); + } + catch (Exception ex) + { + + Console.WriteLine(ex.Message); + } + } + } while (line != null); + Console.WriteLine(); + } + } +} diff --git a/impls/cs.2/step2_eval.csproj b/impls/cs.2/step2_eval.csproj new file mode 100644 index 0000000000..5235b1d949 --- /dev/null +++ b/impls/cs.2/step2_eval.csproj @@ -0,0 +1,9 @@ + + + + exe + net5.0 + mal.step2_eval + + + diff --git a/impls/cs.2/types.cs b/impls/cs.2/types.cs index d39fd575e4..25f9aee75f 100644 --- a/impls/cs.2/types.cs +++ b/impls/cs.2/types.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; namespace mal @@ -16,6 +17,11 @@ public MalList(IList items, string openingBracket = "(") this.items = items; this.openingBracket = openingBracket; } + + public bool isList() + { + return this.openingBracket == "("; + } } class MalInteger : MalType @@ -56,4 +62,23 @@ public MalHashmap(Dictionary values) this.values = values; } } + + class MalFunction : MalType + { + public Func, MalType> function { get; } + public MalFunction(Func, MalType> function) + { + this.function = function; + } + } + + class MalKeyword : MalType + { + public string name { get; } + + public MalKeyword(string name) + { + this.name = name; + } + } } \ No newline at end of file From 6cda36eb203ab0893dd4fa7992e008afef2e2774 Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Sat, 13 Mar 2021 23:46:05 -0800 Subject: [PATCH 06/20] Step 3 --- impls/cs.2/env.cs | 44 ++++++++++ impls/cs.2/step3_env.cs | 161 ++++++++++++++++++++++++++++++++++++ impls/cs.2/step3_env.csproj | 9 ++ impls/cs.2/types.cs | 20 +++++ 4 files changed, 234 insertions(+) create mode 100644 impls/cs.2/env.cs create mode 100644 impls/cs.2/step3_env.cs create mode 100644 impls/cs.2/step3_env.csproj diff --git a/impls/cs.2/env.cs b/impls/cs.2/env.cs new file mode 100644 index 0000000000..9cabd21870 --- /dev/null +++ b/impls/cs.2/env.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; + +namespace mal +{ + class Env + { + public Env outer { get; } + public Dictionary data { get; } + public Env(Env outer) + { + this.outer = outer; + this.data = new Dictionary(); + } + + public void set(MalSymbol key, MalType value) + { + if (data.ContainsKey(key)) + { + data.Remove(key); + } + this.data.Add(key, value); + } + + public MalType find(MalSymbol key) + { + return this.data.GetValueOrDefault(key, outer?.find(key)); + } + + public MalType get(MalSymbol key) + { + MalType found = find(key); + if (found != null) + { + return found; + } + else + { + throw new Exception(string.Format("Value for '{0}' not found", key.value)); + } + } + + } +} \ No newline at end of file diff --git a/impls/cs.2/step3_env.cs b/impls/cs.2/step3_env.cs new file mode 100644 index 0000000000..2e89f9561c --- /dev/null +++ b/impls/cs.2/step3_env.cs @@ -0,0 +1,161 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace mal +{ + class step3_env + { + + static MalType READ(string input) + { + return Reader.read_str(input); + } + + public static MalType EVAL(MalType ast, Env env) + { + if (ast is not MalList) + { + return eval_ast(ast, env); + } + else + { + MalList astList = (MalList)ast; + if (astList.items.Count == 0) + { + return astList; + } + else + { + + MalType first = astList.items.First(); + if (first is MalSymbol) + { + MalSymbol firstSymbol = (MalSymbol)first; + if (firstSymbol.value == "def!") + { + MalSymbol symbol = (MalSymbol)astList.items[1]; + MalType value = EVAL(astList.items[2], env); + env.set(symbol, value); + return value; + } + else if (firstSymbol.value == "let*") + { + MalList bindings = (MalList)astList.items[1]; + MalType expression = astList.items[2]; + Env newEnv = new Env(env); + for (int i = 0; i < bindings.items.Count; i += 2) + { + MalSymbol name = (MalSymbol)bindings.items[i]; + MalType value = EVAL(bindings.items[i + 1], newEnv); + newEnv.data.Add(name, value); + } + return EVAL(expression, newEnv); + } + + } + + MalList evaluated = (MalList)(eval_ast(ast, env)); + if (astList.isList()) + { + // Function application + MalFunction func = (MalFunction)evaluated.items[0]; + MalType retVal = func.function(evaluated.items.Skip(1).ToList()); + return retVal; + } + else + { + return evaluated; // vector + } + } + } + } + + static string PRINT(MalType input) + { + return printer.pr_str(input, true); + } + + static string rep(string input) + { + MalType read = READ(input); + MalType evaled = EVAL(read, repl_env); + string printed = PRINT(evaled); + return printed; + } + + public static MalType eval_ast(MalType ast, Env env) + { + if (ast is MalSymbol) + { + MalSymbol astSymbol = (MalSymbol)ast; + return env.get(astSymbol); + } + else if (ast is MalList) + { + MalList astList = (MalList)ast; + List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); + return new MalList(evaluated, astList.openingBracket); // important: preserve the bracket + } + else if (ast is MalHashmap) + { + MalHashmap astHashmap = (MalHashmap)ast; + Dictionary newKVs = new Dictionary(); + foreach (KeyValuePair kv in astHashmap.values) + { + newKVs.Add(EVAL(kv.Key, env), EVAL(kv.Value, env)); + } + return new MalHashmap(newKVs); + } + else + { + return ast; + } + } + + public static Env repl_env = new Env(null); + + static void Main(string[] args) + { + repl_env.set( + new MalSymbol("+"), + new MalFunction((IList args) => new MalInteger(((MalInteger)args[0]).value + ((MalInteger)args[1]).value)) + ); + repl_env.set( + new MalSymbol("-"), + new MalFunction((IList args) => new MalInteger(((MalInteger)args[0]).value - ((MalInteger)args[1]).value)) + ); + repl_env.set( + new MalSymbol("*"), + new MalFunction((IList args) => new MalInteger(((MalInteger)args[0]).value * ((MalInteger)args[1]).value)) + ); + repl_env.set( + new MalSymbol("/"), + new MalFunction((IList args) => new MalInteger(((MalInteger)args[0]).value / ((MalInteger)args[1]).value)) + ); + + // TESTS + // var test = rep("(let* (c 2) (+ 1 c))"); + + string line = null; + do + { + Console.Write("user> "); + line = Console.ReadLine(); + if (line != null) + { + try + { + Console.WriteLine(rep(line)); + } + catch (Exception ex) + { + + Console.WriteLine(ex.Message); + } + } + } while (line != null); + Console.WriteLine(); + } + } +} diff --git a/impls/cs.2/step3_env.csproj b/impls/cs.2/step3_env.csproj new file mode 100644 index 0000000000..28cc3f23c2 --- /dev/null +++ b/impls/cs.2/step3_env.csproj @@ -0,0 +1,9 @@ + + + + exe + net5.0 + mal.step3_env + + + diff --git a/impls/cs.2/types.cs b/impls/cs.2/types.cs index 25f9aee75f..493ff31e62 100644 --- a/impls/cs.2/types.cs +++ b/impls/cs.2/types.cs @@ -41,6 +41,16 @@ public MalSymbol(string value) { this.value = value; } + + public override int GetHashCode() + { + return value.GetHashCode(); + } + + public override bool Equals(object other) + { + return ((other is MalSymbol) && ((MalSymbol)other).value == value); + } } class MalString : MalType @@ -80,5 +90,15 @@ public MalKeyword(string name) { this.name = name; } + + public override int GetHashCode() + { + return name.GetHashCode(); + } + + public override bool Equals(object other) + { + return ((other is MalKeyword) && ((MalKeyword)other).name == name); + } } } \ No newline at end of file From 00dfbf23fba593e415ea2b177d76383097645722 Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Sun, 14 Mar 2021 18:28:46 -0700 Subject: [PATCH 07/20] Step 4 --- impls/cs.2/core.cs | 57 ++++++++++ impls/cs.2/env.cs | 30 ++++- impls/cs.2/printer.cs | 21 ++-- impls/cs.2/reader.cs | 8 ++ impls/cs.2/step4_if_fn_do.cs | 182 +++++++++++++++++++++++++++++++ impls/cs.2/step4_if_fn_do.csproj | 9 ++ impls/cs.2/types.cs | 109 +++++++++++++++++- 7 files changed, 398 insertions(+), 18 deletions(-) create mode 100644 impls/cs.2/core.cs create mode 100644 impls/cs.2/step4_if_fn_do.cs create mode 100644 impls/cs.2/step4_if_fn_do.csproj diff --git a/impls/cs.2/core.cs b/impls/cs.2/core.cs new file mode 100644 index 0000000000..0011ff4c15 --- /dev/null +++ b/impls/cs.2/core.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace mal +{ + class core + { + public static Dictionary ns = new Dictionary() + { + {"list", new MalFunction((IList args) => new MalList(args)) }, + {"list?", new MalFunction((IList args) => + ((args[0] is MalList) && ((MalList)args[0]).isList())? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE)}, + {"empty?", new MalFunction((IList args) => + ((args[0] is MalList) && ((MalList)args[0]).items.Count == 0)? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE)}, + {"count", new MalFunction((IList args) => { + if (args.Count == 0 || args[0] == MalNil.MAL_NIL) return new MalInteger(0); + else return new MalInteger( ((MalList)args[0]).items.Count ); + })}, + {"+", new MalFunction((IList args) => new MalInteger(((MalInteger)args[0]).value + ((MalInteger)args[1]).value))}, + {"-", new MalFunction((IList args) => new MalInteger(((MalInteger)args[0]).value - ((MalInteger)args[1]).value))}, + {"*", new MalFunction((IList args) => new MalInteger(((MalInteger)args[0]).value * ((MalInteger)args[1]).value))}, + {"/", new MalFunction((IList args) => new MalInteger(((MalInteger)args[0]).value / ((MalInteger)args[1]).value))}, + + {"<", new MalFunction((IList args) => (((MalInteger)args[0]).value < ((MalInteger)args[1]).value) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE )}, + {"<=", new MalFunction((IList args) => (((MalInteger)args[0]).value <= ((MalInteger)args[1]).value) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE )}, + {">", new MalFunction((IList args) => (((MalInteger)args[0]).value > ((MalInteger)args[1]).value) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE )}, + {">=", new MalFunction((IList args) => (((MalInteger)args[0]).value >= ((MalInteger)args[1]).value) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE )}, + + {"=", new MalFunction((IList args) => (args[0].Equals(args[1])) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE)}, + + // string functions + {"pr-str", + new MalFunction((IList args) => { + List printed = args.Select(it => printer.pr_str(it, true)).ToList(); + return new MalString(string.Join(" ", printed)); + })}, + {"str", + new MalFunction((IList args) => { + List printed = args.Select(it => printer.pr_str(it, false)).ToList(); + return new MalString(string.Join("", printed)); + })}, + {"prn", + new MalFunction((IList args) => { + List printed = args.Select(it => printer.pr_str(it, true)).ToList(); + Console.WriteLine(string.Join(" ", printed)); + return MalNil.MAL_NIL; + })}, + {"println", + new MalFunction((IList args) => { + List printed = args.Select(it => printer.pr_str(it, false)).ToList(); + Console.WriteLine(string.Join(" ", printed)); + return MalNil.MAL_NIL; + })}, + }; + } +} \ No newline at end of file diff --git a/impls/cs.2/env.cs b/impls/cs.2/env.cs index 9cabd21870..bad2dd84ce 100644 --- a/impls/cs.2/env.cs +++ b/impls/cs.2/env.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace mal { @@ -7,18 +8,37 @@ class Env { public Env outer { get; } public Dictionary data { get; } - public Env(Env outer) + public Env(Env outer, IList binds = null, IList exprs = null) { this.outer = outer; this.data = new Dictionary(); + if (binds != null && exprs != null) + { + for (int i = 0; i < binds.Count; i++) + { + MalSymbol key = binds[i]; + + // If the key is the symbol '&', collect the rest of the exprs in a list and bind it to the next binds + if (key.value == "&") + { + MalSymbol nextKey = binds[i + 1]; + List remaining = exprs.Skip(i).ToList(); + data.Add(nextKey, new MalList(remaining)); + return; + } + else + { + if (data.ContainsKey(key)) { data.Remove(key); } + MalType value = exprs[i]; + data.Add(key, value); + } + } + } } public void set(MalSymbol key, MalType value) { - if (data.ContainsKey(key)) - { - data.Remove(key); - } + if (data.ContainsKey(key)) { data.Remove(key); } this.data.Add(key, value); } diff --git a/impls/cs.2/printer.cs b/impls/cs.2/printer.cs index 430daa6d51..b1b04eec60 100644 --- a/impls/cs.2/printer.cs +++ b/impls/cs.2/printer.cs @@ -1,6 +1,7 @@ using System; -using System.Text; using System.Collections.Generic; +using System.Linq; +using System.Text; namespace mal { @@ -17,11 +18,7 @@ public static string pr_str(MalType malType, bool print_readably = false) { MalList malList = (MalList)malType; string closingBracket = (malList.openingBracket == "(") ? ")" : "]"; - IList strings = new List(); - foreach (MalType item in malList.items) - { - strings.Add(pr_str(item)); - } + List strings = malList.items.Select(it => pr_str(it, print_readably)).ToList(); string joined = string.Join(" ", strings); return string.Format("{0}{1}{2}", malList.openingBracket, joined, closingBracket); } @@ -47,7 +44,7 @@ public static string pr_str(MalType malType, bool print_readably = false) } else { - return string.Format("\"{0}\"", ((MalString)malType).value); + return ((MalString)malType).value; } } else if (malType is MalHashmap) @@ -68,7 +65,15 @@ public static string pr_str(MalType malType, bool print_readably = false) } else if (malType is MalFunction) { - return ""; + return "#"; + } + else if (malType is MalBoolean) + { + return ((MalBoolean)malType).value ? "true" : "false"; + } + else if (malType is MalNil) + { + return "nil"; } else { diff --git a/impls/cs.2/reader.cs b/impls/cs.2/reader.cs index a45bd4d548..b75aaa4c10 100644 --- a/impls/cs.2/reader.cs +++ b/impls/cs.2/reader.cs @@ -16,6 +16,10 @@ class Reader { {"'", "quote"}, {"`", "quasiquote"}, {"~", "unquote"}, {"~@", "splice-unquote"}, {"@", "deref"} }; + static Dictionary LITERALS = new Dictionary() + { + {"nil", MalNil.MAL_NIL}, {"true", MalBoolean.MAL_TRUE}, {"false", MalBoolean.MAL_FALSE} + }; IList tokens; int position; @@ -167,6 +171,10 @@ static MalType read_atom(Reader reader) { return new MalKeyword(item); } + else if (LITERALS.ContainsKey(item)) + { + return LITERALS.GetValueOrDefault(item); + } else if (item.StartsWith("\"")) { if (item.Length > 1 && item.EndsWith("\"")) diff --git a/impls/cs.2/step4_if_fn_do.cs b/impls/cs.2/step4_if_fn_do.cs new file mode 100644 index 0000000000..acba143f93 --- /dev/null +++ b/impls/cs.2/step4_if_fn_do.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace mal +{ + class step4_if_fn_do + { + + static MalType READ(string input) + { + return Reader.read_str(input); + } + + public static MalType EVAL(MalType ast, Env env) + { + if (ast is not MalList) + { + return eval_ast(ast, env); + } + else + { + MalList astList = (MalList)ast; + if (astList.items.Count == 0) + { + return astList; + } + else + { + + MalType first = astList.items.First(); + if (first is MalSymbol) + { + MalSymbol firstSymbol = (MalSymbol)first; + if (firstSymbol.value == "def!") + { + MalSymbol symbol = (MalSymbol)astList.items[1]; + MalType value = EVAL(astList.items[2], env); + env.set(symbol, value); + return value; + } + else if (firstSymbol.value == "let*") + { + MalList bindings = (MalList)astList.items[1]; + MalType expression = astList.items[2]; + Env newEnv = new Env(env); + for (int i = 0; i < bindings.items.Count; i += 2) + { + MalSymbol name = (MalSymbol)bindings.items[i]; + MalType value = EVAL(bindings.items[i + 1], newEnv); + newEnv.data.Add(name, value); + } + return EVAL(expression, newEnv); + } + else if (firstSymbol.value == "do") + { + List evalAstd = astList.items.Skip(1).Select(item => EVAL(item, env)).ToList(); + return evalAstd.Last(); + } + else if (firstSymbol.value == "if") + { + MalType test = astList.items[1]; + MalType testResult = EVAL(test, env); + if (testResult == MalNil.MAL_NIL || testResult == MalBoolean.MAL_FALSE) + { + return (astList.items.Count > 3) ? EVAL(astList.items[3], env) : MalNil.MAL_NIL; + } + else + { + return EVAL(astList.items[2], env); + } + } + else if (firstSymbol.value == "fn*") + { + MalList argNames = (MalList)astList.items[1]; + MalType funcBody = astList.items[2]; + List argSymbs = new List(); + foreach (MalType arg in argNames.items) { if (arg is MalSymbol) argSymbs.Add((MalSymbol)arg); } + return new MalFunction( + (IList argValues) => + { + Env funcEnv = new Env(env, argSymbs, argValues); + return EVAL(funcBody, funcEnv); + } + ); + } + + } + + MalList evaluated = (MalList)(eval_ast(ast, env)); + if (astList.isList()) + { + // Function application + MalFunction func = (MalFunction)evaluated.items[0]; + MalType retVal = func.function(evaluated.items.Skip(1).ToList()); + return retVal; + } + else + { + return evaluated; // vector + } + } + } + } + + static string PRINT(MalType input) + { + return printer.pr_str(input, true); + } + + static string rep(string input) + { + MalType read = READ(input); + MalType evaled = EVAL(read, repl_env); + string printed = PRINT(evaled); + return printed; + } + + public static MalType eval_ast(MalType ast, Env env) + { + if (ast is MalSymbol) + { + MalSymbol astSymbol = (MalSymbol)ast; + return env.get(astSymbol); + } + else if (ast is MalList) + { + MalList astList = (MalList)ast; + List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); + return new MalList(evaluated, astList.openingBracket); // important: preserve the bracket + } + else if (ast is MalHashmap) + { + MalHashmap astHashmap = (MalHashmap)ast; + Dictionary newKVs = new Dictionary(); + foreach (KeyValuePair kv in astHashmap.values) + { + newKVs.Add(EVAL(kv.Key, env), EVAL(kv.Value, env)); + } + return new MalHashmap(newKVs); + } + else + { + return ast; + } + } + + public static Env repl_env = new Env(null); + + static void Main(string[] args) + { + // Load the built-in functions + foreach(var pair in core.ns) { repl_env.set(new MalSymbol(pair.Key), pair.Value); } + + // Functions defined on Mal itself + rep("(def! not (fn* (a) (if a false true)))"); + + // TESTS + // var test = rep("(if (> 3 3) 1 2)"); + + string line = null; + do + { + Console.Write("user> "); + line = Console.ReadLine(); + if (line != null) + { + try + { + Console.WriteLine(rep(line)); + } + catch (Exception ex) + { + + Console.WriteLine(ex.Message); + } + } + } while (line != null); + Console.WriteLine(); + } + } +} diff --git a/impls/cs.2/step4_if_fn_do.csproj b/impls/cs.2/step4_if_fn_do.csproj new file mode 100644 index 0000000000..f55f5eed7f --- /dev/null +++ b/impls/cs.2/step4_if_fn_do.csproj @@ -0,0 +1,9 @@ + + + + exe + net5.0 + mal.step4_if_fn_do + + + diff --git a/impls/cs.2/types.cs b/impls/cs.2/types.cs index 493ff31e62..8f96c2d2e3 100644 --- a/impls/cs.2/types.cs +++ b/impls/cs.2/types.cs @@ -3,9 +3,7 @@ namespace mal { - public class MalType - { - } + public class MalType { } public class MalList : MalType { @@ -22,6 +20,28 @@ public bool isList() { return this.openingBracket == "("; } + + public override string ToString() + { + return string.Format("", items.ToString()); + } + + public override int GetHashCode() + { + return items.GetHashCode(); + } + + public override bool Equals(object other) + { + if (other is not MalList) return false; + MalList otherList = (MalList)other; + if (otherList.items.Count != items.Count) return false; + for (int i = 0; i < items.Count; i++) + { + if (!items[i].Equals(otherList.items[i])) return false; + } + return true; + } } class MalInteger : MalType @@ -31,6 +51,21 @@ public MalInteger(int value) { this.value = value; } + + public override string ToString() + { + return string.Format("", value.ToString()); + } + + public override int GetHashCode() + { + return value.GetHashCode(); + } + + public override bool Equals(object other) + { + return ((other is MalInteger) && ((MalInteger)other).value == value); + } } class MalSymbol : MalType @@ -51,6 +86,11 @@ public override bool Equals(object other) { return ((other is MalSymbol) && ((MalSymbol)other).value == value); } + + public override string ToString() + { + return string.Format("", value.ToString()); + } } class MalString : MalType @@ -61,15 +101,42 @@ public MalString(string value) { this.value = value; } + + public override string ToString() + { + return string.Format("", value.ToString()); + } + + public override int GetHashCode() + { + return value.GetHashCode(); + } + + public override bool Equals(object other) + { + return ((other is MalString) && ((MalString)other).value == value); + } } class MalHashmap : MalType { public Dictionary values { get; } - public MalHashmap(Dictionary values) + public MalHashmap(Dictionary values) { this.values = values; } + + public override int GetHashCode() { return values.GetHashCode(); } + + public override bool Equals(object other) { - this.values = values; + if (other is not MalHashmap) return false; + MalHashmap otherMap = (MalHashmap)other; + if (otherMap.values.Count != values.Count) return false; + foreach (var kv in values) + { + MalType key = kv.Key; + if (values.GetValueOrDefault(key) != otherMap.values.GetValueOrDefault(key)) return false; + } + return true; } } @@ -100,5 +167,37 @@ public override bool Equals(object other) { return ((other is MalKeyword) && ((MalKeyword)other).name == name); } + + public override string ToString() + { + return string.Format("", name.ToString()); + } + } + + class MalNil : MalType + { + public static MalNil MAL_NIL = new MalNil(); + + private MalNil() { } + public override string ToString() + { + return ""; + } + } + + class MalBoolean : MalType + { + public static MalBoolean MAL_TRUE = new MalBoolean(true); + public static MalBoolean MAL_FALSE = new MalBoolean(false); + public bool value { get; } + + MalBoolean(bool value) + { + this.value = value; + } + public override string ToString() + { + return string.Format("", value.ToString()); + } } } \ No newline at end of file From e0078bed00e887698fef1f61b76dff9005c3d12c Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Tue, 16 Mar 2021 23:23:10 -0700 Subject: [PATCH 08/20] Step 5 --- impls/cs.2/printer.cs | 4 + impls/cs.2/step5_tco.cs | 209 ++++++++++++++++++++++++++++++++++++ impls/cs.2/step5_tco.csproj | 9 ++ impls/cs.2/types.cs | 16 +++ 4 files changed, 238 insertions(+) create mode 100644 impls/cs.2/step5_tco.cs create mode 100644 impls/cs.2/step5_tco.csproj diff --git a/impls/cs.2/printer.cs b/impls/cs.2/printer.cs index b1b04eec60..f4c26fd742 100644 --- a/impls/cs.2/printer.cs +++ b/impls/cs.2/printer.cs @@ -67,6 +67,10 @@ public static string pr_str(MalType malType, bool print_readably = false) { return "#"; } + else if (malType is MalFnTco) + { + return "#"; + } else if (malType is MalBoolean) { return ((MalBoolean)malType).value ? "true" : "false"; diff --git a/impls/cs.2/step5_tco.cs b/impls/cs.2/step5_tco.cs new file mode 100644 index 0000000000..86892ccad7 --- /dev/null +++ b/impls/cs.2/step5_tco.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace mal +{ + class step5_tco + { + + static MalType READ(string input) + { + return Reader.read_str(input); + } + + public static MalType EVAL(MalType ast, Env env) + { + while (true) + { + if (ast is not MalList) + { + return eval_ast(ast, env); + } + else + { + MalList astList = (MalList)ast; + if (astList.items.Count == 0) + { + return astList; + } + else + { + + MalType first = astList.items.First(); + if (first is MalSymbol) + { + MalSymbol firstSymbol = (MalSymbol)first; + if (firstSymbol.value == "def!") + { + MalSymbol symbol = (MalSymbol)astList.items[1]; + MalType value = EVAL(astList.items[2], env); + env.set(symbol, value); + return value; + } + else if (firstSymbol.value == "let*") + { + MalList bindings = (MalList)astList.items[1]; + MalType expression = astList.items[2]; + Env newEnv = new Env(env); + for (int i = 0; i < bindings.items.Count; i += 2) + { + MalSymbol name = (MalSymbol)bindings.items[i]; + MalType value = EVAL(bindings.items[i + 1], newEnv); + newEnv.data.Add(name, value); + } + env = newEnv; + ast = expression; + continue; + } + else if (firstSymbol.value == "do") + { + int butLast = astList.items.Count - 2; + + // produce a side-effect and then just forget + astList.items.Skip(1).Take(butLast).Select(item => eval_ast(item, env)).ToList(); + ast = astList.items.Last(); + continue; + } + else if (firstSymbol.value == "if") + { + MalType test = astList.items[1]; + MalType testResult = EVAL(test, env); + if (testResult == MalNil.MAL_NIL || testResult == MalBoolean.MAL_FALSE) + { + ast = (astList.items.Count > 3) ? astList.items[3] : MalNil.MAL_NIL; + } + else + { + ast = astList.items[2]; + } + continue; + } + else if (firstSymbol.value == "fn*") + { + MalList argNames = (MalList)astList.items[1]; + MalType funcBody = astList.items[2]; + List argSymbs = new List(); + foreach (MalType arg in argNames.items) { if (arg is MalSymbol) argSymbs.Add((MalSymbol)arg); } + MalFunction fn = new MalFunction( + (IList argValues) => + { + Env funcEnv = new Env(env, argSymbs, argValues); + return EVAL(funcBody, funcEnv); + } + ); + return new MalFnTco(funcBody, argSymbs, env, fn); + } + + } + + MalType evaluated = eval_ast(ast, env); + if (evaluated is not MalList) return evaluated; + MalList evaluatedList = (MalList)evaluated; + + if (evaluatedList.isList()) + { + // Function application + MalType funcFirst = evaluatedList.items[0]; + if (funcFirst is MalFunction) + { + MalFunction func = (MalFunction)funcFirst; + MalType retVal = func.function(evaluatedList.items.Skip(1).ToList()); + return retVal; + } + else // MalFnTco + { + MalFnTco fnTco = (MalFnTco) funcFirst; + List fnArgs = evaluatedList.items.Skip(1).ToList(); + Env newEnv = new Env(fnTco.env, fnTco.@params, fnArgs); + ast = fnTco.ast; + env = newEnv; + continue; + } + } + else + { + return evaluated; // vector and others + } + } + } + } + } + + static string PRINT(MalType input) + { + return printer.pr_str(input, true); + } + + static string rep(string input) + { + MalType read = READ(input); + MalType evaled = EVAL(read, repl_env); + string printed = PRINT(evaled); + return printed; + } + + public static MalType eval_ast(MalType ast, Env env) + { + if (ast is MalSymbol) + { + MalSymbol astSymbol = (MalSymbol)ast; + return env.get(astSymbol); + } + else if (ast is MalList) + { + MalList astList = (MalList)ast; + List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); + return new MalList(evaluated, astList.openingBracket); // important: preserve the bracket + } + else if (ast is MalHashmap) + { + MalHashmap astHashmap = (MalHashmap)ast; + Dictionary newKVs = new Dictionary(); + foreach (KeyValuePair kv in astHashmap.values) + { + newKVs.Add(EVAL(kv.Key, env), EVAL(kv.Value, env)); + } + return new MalHashmap(newKVs); + } + else + { + return ast; + } + } + + public static Env repl_env = new Env(null); + + static void Main(string[] args) + { + // Load the built-in functions + foreach (var pair in core.ns) { repl_env.set(new MalSymbol(pair.Key), pair.Value); } + + // Functions defined on Mal itself + rep("(def! not (fn* (a) (if a false true)))"); + + // TESTS + // var test = rep("(if (> 3 3) 1 2)"); + + string line = null; + do + { + Console.Write("user> "); + line = Console.ReadLine(); + if (line != null) + { + try + { + Console.WriteLine(rep(line)); + } + catch (Exception ex) + { + + Console.WriteLine(ex.Message); + } + } + } while (line != null); + Console.WriteLine(); + } + } +} diff --git a/impls/cs.2/step5_tco.csproj b/impls/cs.2/step5_tco.csproj new file mode 100644 index 0000000000..77116216f9 --- /dev/null +++ b/impls/cs.2/step5_tco.csproj @@ -0,0 +1,9 @@ + + + + exe + net5.0 + mal.step5_tco + + + diff --git a/impls/cs.2/types.cs b/impls/cs.2/types.cs index 8f96c2d2e3..3c147c5e93 100644 --- a/impls/cs.2/types.cs +++ b/impls/cs.2/types.cs @@ -200,4 +200,20 @@ public override string ToString() return string.Format("", value.ToString()); } } + + class MalFnTco : MalType + { + public MalType ast { get; set; } + public List @params { get; set; } // 'params' is a reserved word + public Env env { get; set; } + public MalFunction fn { get; set; } + + public MalFnTco(MalType ast, List @params, Env env, MalFunction fn) + { + this.ast = ast; + this.@params = @params; + this.env = env; + this.fn = fn; + } + } } \ No newline at end of file From b02769067e736b2e83874ac937110ad602d77761 Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Thu, 18 Mar 2021 18:20:23 -0700 Subject: [PATCH 09/20] Step 6 --- impls/cs.2/core.cs | 48 ++++++++ impls/cs.2/printer.cs | 5 + impls/cs.2/step6_file.cs | 231 +++++++++++++++++++++++++++++++++++ impls/cs.2/step6_file.csproj | 9 ++ impls/cs.2/types.cs | 10 ++ 5 files changed, 303 insertions(+) create mode 100644 impls/cs.2/step6_file.cs create mode 100644 impls/cs.2/step6_file.csproj diff --git a/impls/cs.2/core.cs b/impls/cs.2/core.cs index 0011ff4c15..0619aa860f 100644 --- a/impls/cs.2/core.cs +++ b/impls/cs.2/core.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; namespace mal { @@ -52,6 +54,52 @@ class core Console.WriteLine(string.Join(" ", printed)); return MalNil.MAL_NIL; })}, + + // Step 6 + {"read-string", + new MalFunction((IList args) => { + MalString arg = (MalString)args[0]; + return Reader.read_str(arg.value); + })}, + + {"slurp", + new MalFunction((IList args) => { + MalString malFilename = (MalString)args[0]; + string path = malFilename.value; + string contents = File.ReadAllText(path); + return new MalString(contents); + })}, + + {"atom", new MalFunction((IList args) => { return new MalAtom(args[0]); })}, + + {"atom?", new MalFunction((IList args) => { + return (args[0] is MalAtom) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE; + })}, + + {"deref", new MalFunction((IList args) => { return ((MalAtom)args[0]).value; })}, + + {"reset!", new MalFunction((IList args) => { + MalAtom atom = (MalAtom)args[0]; + MalType newValue = args[1]; + atom.value = newValue; + return newValue; + })}, + + {"swap!", new MalFunction((IList args) => { + MalAtom atom = (MalAtom)args[0]; + List fnArgs = new List(){ atom.value }; + fnArgs.AddRange(args.Skip(2).ToList()); + MalType fnOrFnTco = args[1]; + MalType newValue = MalNil.MAL_NIL; + if (fnOrFnTco is MalFunction) { + newValue = ((MalFunction)fnOrFnTco).function(fnArgs); + } else { + newValue = ((MalFnTco)fnOrFnTco).fn.function(fnArgs); + } + atom.value = newValue; + return newValue; + })}, + }; } } \ No newline at end of file diff --git a/impls/cs.2/printer.cs b/impls/cs.2/printer.cs index f4c26fd742..8db1946f82 100644 --- a/impls/cs.2/printer.cs +++ b/impls/cs.2/printer.cs @@ -79,6 +79,11 @@ public static string pr_str(MalType malType, bool print_readably = false) { return "nil"; } + else if (malType is MalAtom) + { + MalAtom atom = (MalAtom)malType; + return string.Format("(atom {0})", pr_str(atom.value, print_readably)); + } else { throw new Exception(string.Format("Unknown type to print: {0}", malType.ToString())); diff --git a/impls/cs.2/step6_file.cs b/impls/cs.2/step6_file.cs new file mode 100644 index 0000000000..88d37d8ae6 --- /dev/null +++ b/impls/cs.2/step6_file.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace mal +{ + class step6_file + { + + static MalType READ(string input) + { + return Reader.read_str(input); + } + + public static MalType EVAL(MalType ast, Env env) + { + while (true) + { + if (ast is not MalList) + { + return eval_ast(ast, env); + } + else + { + MalList astList = (MalList)ast; + + // Remove any comments from the ast + List nonComments = astList.items.Where(it => !(it is MalSymbol && ((MalSymbol)it).value.StartsWith(";"))).ToList(); + astList = new MalList(nonComments); + + if (astList.items.Count == 0) + { + return astList; + } + else + { + + MalType first = astList.items.First(); + if (first is MalSymbol) + { + MalSymbol firstSymbol = (MalSymbol)first; + if (firstSymbol.value == "def!") + { + MalSymbol symbol = (MalSymbol)astList.items[1]; + MalType value = EVAL(astList.items[2], env); + env.set(symbol, value); + return value; + } + else if (firstSymbol.value == "let*") + { + MalList bindings = (MalList)astList.items[1]; + MalType expression = astList.items[2]; + Env newEnv = new Env(env); + for (int i = 0; i < bindings.items.Count; i += 2) + { + MalSymbol name = (MalSymbol)bindings.items[i]; + MalType value = EVAL(bindings.items[i + 1], newEnv); + newEnv.data.Add(name, value); + } + env = newEnv; + ast = expression; + continue; + } + else if (firstSymbol.value == "do") + { + int butLast = astList.items.Count - 2; + + // produce side-effects and then just forget + List sideEffects = astList.items.Skip(1).Take(butLast).ToList(); + eval_ast(new MalList(sideEffects), env); + + ast = astList.items.Last(); + continue; + } + else if (firstSymbol.value == "if") + { + MalType test = astList.items[1]; + MalType testResult = EVAL(test, env); + if (testResult == MalNil.MAL_NIL || testResult == MalBoolean.MAL_FALSE) + { + ast = (astList.items.Count > 3) ? astList.items[3] : MalNil.MAL_NIL; + } + else + { + ast = astList.items[2]; + } + continue; + } + else if (firstSymbol.value == "fn*") + { + MalList argNames = (MalList)astList.items[1]; + MalType funcBody = astList.items[2]; + List argSymbs = new List(); + foreach (MalType arg in argNames.items) { if (arg is MalSymbol) argSymbs.Add((MalSymbol)arg); } + MalFunction fn = new MalFunction( + (IList argValues) => + { + Env funcEnv = new Env(env, argSymbs, argValues); + return EVAL(funcBody, funcEnv); + } + ); + return new MalFnTco(funcBody, argSymbs, env, fn); + } + + } + + MalType evaluated = eval_ast(ast, env); + if (evaluated is not MalList) return evaluated; + MalList evaluatedList = (MalList)evaluated; + + if (evaluatedList.isList()) + { + // Function application + MalType funcFirst = evaluatedList.items[0]; + if (funcFirst is MalFunction) + { + MalFunction func = (MalFunction)funcFirst; + MalType retVal = func.function(evaluatedList.items.Skip(1).ToList()); + return retVal; + } + else // MalFnTco + { + MalFnTco fnTco = (MalFnTco)funcFirst; + List fnArgs = evaluatedList.items.Skip(1).ToList(); + Env newEnv = new Env(fnTco.env, fnTco.@params, fnArgs); + ast = fnTco.ast; + env = newEnv; + continue; + } + } + else + { + return evaluated; // vector and others + } + } + } + } + } + + static string PRINT(MalType input) + { + return printer.pr_str(input, true); + } + + static string rep(string input) + { + MalType read = READ(input); + MalType evaled = EVAL(read, repl_env); + string printed = PRINT(evaled); + return printed; + } + + public static MalType eval_ast(MalType ast, Env env) + { + if (ast is MalSymbol) + { + MalSymbol astSymbol = (MalSymbol)ast; + return env.get(astSymbol); + } + else if (ast is MalList) + { + MalList astList = (MalList)ast; + List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); + return new MalList(evaluated, astList.openingBracket); // important: preserve the bracket + } + else if (ast is MalHashmap) + { + MalHashmap astHashmap = (MalHashmap)ast; + Dictionary newKVs = new Dictionary(); + foreach (KeyValuePair kv in astHashmap.values) + { + newKVs.Add(EVAL(kv.Key, env), EVAL(kv.Value, env)); + } + return new MalHashmap(newKVs); + } + else + { + return ast; + } + } + + public static Env repl_env = new Env(null); + + static void Main(string[] args) + { + // Load the built-in functions + foreach (var pair in core.ns) { repl_env.set(new MalSymbol(pair.Key), pair.Value); } + + // Define 'eval' + repl_env.data.Add(new MalSymbol("eval"), new MalFunction((IList args) => + { + MalType ast = args[0]; + return EVAL(ast, repl_env); + })); + + // Functions defined on Mal itself: + // - define 'not' + rep("(def! not (fn* (a) (if a false true)))"); + // - define 'load-file' + rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\"))))))"); + + // ARGV + List malArgs = new List(); + malArgs.AddRange(args.Select(arg => new MalString(arg)).ToList()); + repl_env.data.Add(new MalSymbol("*ARGV*"), new MalList(malArgs)); + + // TESTS + // rep("(load-file \"../../tests/computations.mal\")"); + + string line = null; + do + { + Console.Write("user> "); + line = Console.ReadLine(); + if (line != null) + { + try + { + Console.WriteLine(rep(line)); + } + catch (Exception ex) + { + + Console.WriteLine(ex.Message); + } + } + } while (line != null); + Console.WriteLine(); + } + } +} diff --git a/impls/cs.2/step6_file.csproj b/impls/cs.2/step6_file.csproj new file mode 100644 index 0000000000..c0f7949dc3 --- /dev/null +++ b/impls/cs.2/step6_file.csproj @@ -0,0 +1,9 @@ + + + + exe + net5.0 + mal.step6_file + + + diff --git a/impls/cs.2/types.cs b/impls/cs.2/types.cs index 3c147c5e93..3b17b78a4a 100644 --- a/impls/cs.2/types.cs +++ b/impls/cs.2/types.cs @@ -216,4 +216,14 @@ public MalFnTco(MalType ast, List @params, Env env, MalFunction fn) this.fn = fn; } } + + class MalAtom : MalType + { + public MalType value { get; set; } + + public MalAtom(MalType value) + { + this.value = value; + } + } } \ No newline at end of file From bca11374aff82e1660ba0b3ba4b031570636a40e Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Sat, 20 Mar 2021 00:36:07 -0700 Subject: [PATCH 10/20] Step 7 part 1 --- impls/cs.2/core.cs | 30 ++++ impls/cs.2/step7_quote.cs | 295 ++++++++++++++++++++++++++++++++++ impls/cs.2/step7_quote.csproj | 9 ++ 3 files changed, 334 insertions(+) create mode 100644 impls/cs.2/step7_quote.cs create mode 100644 impls/cs.2/step7_quote.csproj diff --git a/impls/cs.2/core.cs b/impls/cs.2/core.cs index 0619aa860f..50b0e9cdb8 100644 --- a/impls/cs.2/core.cs +++ b/impls/cs.2/core.cs @@ -100,6 +100,36 @@ class core return newValue; })}, + // Step 7 + {"cons", + new MalFunction((IList args) => { + MalType head = args[0]; + MalList rest = (MalList)args[1]; + List newList = new List(){ head }; + newList.AddRange(rest.items); + return new MalList(newList); + })}, + + {"concat", + new MalFunction((IList args) => { + List newList = new List(); + foreach (MalType arg in args) + { + if (arg is MalList) + { + MalList argList = (MalList)arg; + newList.AddRange(argList.items); + } + } + return new MalList(newList); + })}, + + {"vec", + new MalFunction((IList args) => { + MalList head = (MalList)args[0]; + return new MalList(head.items, "["); + })}, + }; } } \ No newline at end of file diff --git a/impls/cs.2/step7_quote.cs b/impls/cs.2/step7_quote.cs new file mode 100644 index 0000000000..5d7288b208 --- /dev/null +++ b/impls/cs.2/step7_quote.cs @@ -0,0 +1,295 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace mal +{ + class step7_quote + { + + static MalType READ(string input) + { + return Reader.read_str(input); + } + + public static MalType EVAL(MalType ast, Env env) + { + while (true) + { + if (ast is not MalList) + { + return eval_ast(ast, env); + } + else + { + MalList astList = (MalList)ast; + + // Remove any comments from the ast + List nonComments = astList.items.Where(it => !(it is MalSymbol && ((MalSymbol)it).value.StartsWith(";"))).ToList(); + astList = new MalList(nonComments); + + if (astList.items.Count == 0) + { + return astList; + } + else + { + + MalType first = astList.items.First(); + if (first is MalSymbol) + { + MalSymbol firstSymbol = (MalSymbol)first; + if (firstSymbol.value == "def!") + { + MalSymbol symbol = (MalSymbol)astList.items[1]; + MalType value = EVAL(astList.items[2], env); + env.set(symbol, value); + return value; + } + else if (firstSymbol.value == "let*") + { + MalList bindings = (MalList)astList.items[1]; + MalType expression = astList.items[2]; + Env newEnv = new Env(env); + for (int i = 0; i < bindings.items.Count; i += 2) + { + MalSymbol name = (MalSymbol)bindings.items[i]; + MalType value = EVAL(bindings.items[i + 1], newEnv); + newEnv.data.Add(name, value); + } + env = newEnv; + ast = expression; + continue; + } + else if (firstSymbol.value == "do") + { + int butLast = astList.items.Count - 2; + + // produce side-effects and then just forget + List sideEffects = astList.items.Skip(1).Take(butLast).ToList(); + eval_ast(new MalList(sideEffects), env); + + ast = astList.items.Last(); + continue; + } + else if (firstSymbol.value == "if") + { + MalType test = astList.items[1]; + MalType testResult = EVAL(test, env); + if (testResult == MalNil.MAL_NIL || testResult == MalBoolean.MAL_FALSE) + { + ast = (astList.items.Count > 3) ? astList.items[3] : MalNil.MAL_NIL; + } + else + { + ast = astList.items[2]; + } + continue; + } + else if (firstSymbol.value == "fn*") + { + MalList argNames = (MalList)astList.items[1]; + MalType funcBody = astList.items[2]; + List argSymbs = new List(); + foreach (MalType arg in argNames.items) { if (arg is MalSymbol) argSymbs.Add((MalSymbol)arg); } + MalFunction fn = new MalFunction( + (IList argValues) => + { + Env funcEnv = new Env(env, argSymbs, argValues); + return EVAL(funcBody, funcEnv); + } + ); + return new MalFnTco(funcBody, argSymbs, env, fn); + } + else if (firstSymbol.value == "quote") + { + return astList.items[1]; + } + else if (firstSymbol.value == "quasiquoteexpand") + { + return quasiquote(astList.items[1]); + } + else if (firstSymbol.value == "quasiquote") + { + return EVAL(quasiquote(astList.items[1]), env); + } + + } + + MalType evaluated = eval_ast(ast, env); + if (evaluated is not MalList) return evaluated; + MalList evaluatedList = (MalList)evaluated; + + if (evaluatedList.isList()) + { + // Function application + MalType funcFirst = evaluatedList.items[0]; + if (funcFirst is MalFunction) + { + MalFunction func = (MalFunction)funcFirst; + MalType retVal = func.function(evaluatedList.items.Skip(1).ToList()); + return retVal; + } + else // MalFnTco + { + MalFnTco fnTco = (MalFnTco)funcFirst; + List fnArgs = evaluatedList.items.Skip(1).ToList(); + Env newEnv = new Env(fnTco.env, fnTco.@params, fnArgs); + ast = fnTco.ast; + env = newEnv; + continue; + } + } + else + { + return evaluated; // vector and others + } + } + } + } + } + + static MalType quasiquote(MalType ast) + { + if (ast is MalList) + { + MalList astList = (MalList)ast; + if (astList.items.Count > 0) + { + MalType head = astList.items[0]; + if (head is MalSymbol && ((MalSymbol)head).value == "unquote") + { + return astList.items[1]; + } + } + + List result = new List(); + + // Iterate over each element elt of ast in reverse order + for (int i = astList.items.Count - 1; i >= 0; i--) + { + MalType elt = astList.items[i]; + + // If elt is a list starting with the "splice-unquote" symbol + if (elt is MalList && ((MalList)elt).items.Count >= 2 && ((MalList)elt).items[0].Equals(new MalSymbol("splice-unquote"))) + { + result = new List(){ + new MalSymbol("concat"), + ((MalList)elt).items[1], + new MalList(result) + }; + } + else + { + result = new List() + { + new MalSymbol("cons"), + quasiquote(elt), + new MalList(result) + }; + } + } + + return new MalList(result, astList.openingBracket); + } + else if (ast is MalHashmap || ast is MalSymbol) + { + // If ast is a map or a symbol, return a list containing: the "quote" symbol, then ast + return new MalList(new List() { new MalSymbol("quote"), ast }); + } + else return ast; + } + + static string PRINT(MalType input) + { + return printer.pr_str(input, true); + } + + static string rep(string input) + { + MalType read = READ(input); + MalType evaled = EVAL(read, repl_env); + string printed = PRINT(evaled); + return printed; + } + + public static MalType eval_ast(MalType ast, Env env) + { + if (ast is MalSymbol) + { + MalSymbol astSymbol = (MalSymbol)ast; + return env.get(astSymbol); + } + else if (ast is MalList) + { + MalList astList = (MalList)ast; + List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); + return new MalList(evaluated, astList.openingBracket); // important: preserve the bracket + } + else if (ast is MalHashmap) + { + MalHashmap astHashmap = (MalHashmap)ast; + Dictionary newKVs = new Dictionary(); + foreach (KeyValuePair kv in astHashmap.values) + { + newKVs.Add(EVAL(kv.Key, env), EVAL(kv.Value, env)); + } + return new MalHashmap(newKVs); + } + else + { + return ast; + } + } + + public static Env repl_env = new Env(null); + + static void Main(string[] args) + { + // Load the built-in functions + foreach (var pair in core.ns) { repl_env.set(new MalSymbol(pair.Key), pair.Value); } + + // Define 'eval' + repl_env.data.Add(new MalSymbol("eval"), new MalFunction((IList args) => + { + MalType ast = args[0]; + return EVAL(ast, repl_env); + })); + + // Functions defined on Mal itself: + // - define 'not' + rep("(def! not (fn* (a) (if a false true)))"); + // - define 'load-file' + rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\"))))))"); + + // ARGV + List malArgs = new List(); + malArgs.AddRange(args.Select(arg => new MalString(arg)).ToList()); + repl_env.data.Add(new MalSymbol("*ARGV*"), new MalList(malArgs)); + + // TESTS + // rep("(load-file \"../../tests/computations.mal\")"); + rep("(quasiquote [])"); + + string line = null; + do + { + Console.Write("user> "); + line = Console.ReadLine(); + if (line != null) + { + try + { + Console.WriteLine(rep(line)); + } + catch (Exception ex) + { + + Console.WriteLine(ex.Message); + } + } + } while (line != null); + Console.WriteLine(); + } + } +} diff --git a/impls/cs.2/step7_quote.csproj b/impls/cs.2/step7_quote.csproj new file mode 100644 index 0000000000..09cfc46bf9 --- /dev/null +++ b/impls/cs.2/step7_quote.csproj @@ -0,0 +1,9 @@ + + + + exe + net5.0 + mal.step7_quote + + + From 56ab69cdf493dd759fe82989e00d6a444718e4f7 Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Sun, 21 Mar 2021 21:57:15 -0700 Subject: [PATCH 11/20] MalSeq, MalVector; MalList is a subclass of MalSeq --- impls/cs.2/core.cs | 20 +++++++-------- impls/cs.2/printer.cs | 10 ++++---- impls/cs.2/reader.cs | 12 ++++++++- impls/cs.2/step1_read_print.cs | 1 + impls/cs.2/step2_eval.cs | 10 ++++++-- impls/cs.2/step3_env.cs | 27 +++++++++---------- impls/cs.2/step4_if_fn_do.cs | 43 +++++++++++++++---------------- impls/cs.2/step5_tco.cs | 45 ++++++++++++++++---------------- impls/cs.2/step6_file.cs | 45 ++++++++++++++++---------------- impls/cs.2/step7_quote.cs | 47 +++++++++++++++++----------------- impls/cs.2/types.cs | 34 ++++++++++++++---------- 11 files changed, 158 insertions(+), 136 deletions(-) diff --git a/impls/cs.2/core.cs b/impls/cs.2/core.cs index 50b0e9cdb8..af61dc95d9 100644 --- a/impls/cs.2/core.cs +++ b/impls/cs.2/core.cs @@ -11,13 +11,13 @@ class core public static Dictionary ns = new Dictionary() { {"list", new MalFunction((IList args) => new MalList(args)) }, - {"list?", new MalFunction((IList args) => - ((args[0] is MalList) && ((MalList)args[0]).isList())? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE)}, + {"list?", new MalFunction((IList args) => (args[0] is MalList) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE)}, {"empty?", new MalFunction((IList args) => - ((args[0] is MalList) && ((MalList)args[0]).items.Count == 0)? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE)}, + (args[0] is MalSeq && ((MalSeq)args[0]).items.Count == 0) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE + )}, {"count", new MalFunction((IList args) => { if (args.Count == 0 || args[0] == MalNil.MAL_NIL) return new MalInteger(0); - else return new MalInteger( ((MalList)args[0]).items.Count ); + else return new MalInteger( ((MalSeq)args[0]).items.Count ); })}, {"+", new MalFunction((IList args) => new MalInteger(((MalInteger)args[0]).value + ((MalInteger)args[1]).value))}, {"-", new MalFunction((IList args) => new MalInteger(((MalInteger)args[0]).value - ((MalInteger)args[1]).value))}, @@ -90,7 +90,7 @@ class core List fnArgs = new List(){ atom.value }; fnArgs.AddRange(args.Skip(2).ToList()); MalType fnOrFnTco = args[1]; - MalType newValue = MalNil.MAL_NIL; + MalType newValue; if (fnOrFnTco is MalFunction) { newValue = ((MalFunction)fnOrFnTco).function(fnArgs); } else { @@ -104,7 +104,7 @@ class core {"cons", new MalFunction((IList args) => { MalType head = args[0]; - MalList rest = (MalList)args[1]; + MalSeq rest = (MalSeq)args[1]; List newList = new List(){ head }; newList.AddRange(rest.items); return new MalList(newList); @@ -115,9 +115,9 @@ class core List newList = new List(); foreach (MalType arg in args) { - if (arg is MalList) + if (arg is MalSeq) { - MalList argList = (MalList)arg; + MalSeq argList = (MalSeq)arg; newList.AddRange(argList.items); } } @@ -126,8 +126,8 @@ class core {"vec", new MalFunction((IList args) => { - MalList head = (MalList)args[0]; - return new MalList(head.items, "["); + MalSeq head = (MalSeq)args[0]; + return new MalVector(head.items); })}, }; diff --git a/impls/cs.2/printer.cs b/impls/cs.2/printer.cs index 8db1946f82..116668906a 100644 --- a/impls/cs.2/printer.cs +++ b/impls/cs.2/printer.cs @@ -14,13 +14,13 @@ class printer public static string pr_str(MalType malType, bool print_readably = false) { - if (malType is MalList) + if (malType is MalSeq) { - MalList malList = (MalList)malType; - string closingBracket = (malList.openingBracket == "(") ? ")" : "]"; - List strings = malList.items.Select(it => pr_str(it, print_readably)).ToList(); + MalSeq malSeq = (MalSeq)malType; + string closing = (malSeq is MalList) ? ")" : "]"; + List strings = malSeq.items.Select(it => pr_str(it, print_readably)).ToList(); string joined = string.Join(" ", strings); - return string.Format("{0}{1}{2}", malList.openingBracket, joined, closingBracket); + return string.Format("{0}{1}{2}", malSeq.openingBracket, joined, closing); } else if (malType is MalInteger) { diff --git a/impls/cs.2/reader.cs b/impls/cs.2/reader.cs index b75aaa4c10..441c8f8155 100644 --- a/impls/cs.2/reader.cs +++ b/impls/cs.2/reader.cs @@ -105,6 +105,10 @@ public static MalType read_form(Reader reader) { return read_hashmap(reader); } + else if (first == "[") + { + return read_vector(reader); + } else if (CLOSING_BRACKETS.ContainsKey(first)) { return read_list(reader); @@ -139,7 +143,7 @@ static MalList read_list(Reader reader) var closing = reader.next(); if (closing == closingBracket) { - return new MalList(items, openingBracket); + return new MalList(items); } else { @@ -147,6 +151,12 @@ static MalList read_list(Reader reader) } } + static MalVector read_vector(Reader reader) + { + MalList values = read_list(reader); + return new MalVector(values.items); + } + static MalHashmap read_hashmap(Reader reader) { MalList kvs = read_list(reader); diff --git a/impls/cs.2/step1_read_print.cs b/impls/cs.2/step1_read_print.cs index 3457c1a387..92e77e5567 100644 --- a/impls/cs.2/step1_read_print.cs +++ b/impls/cs.2/step1_read_print.cs @@ -32,6 +32,7 @@ static void Main(string[] args) { // TESTS // var test = rep("^{a 1} [1 2 3]"); + rep("[+ 1 2]"); string line = null; do diff --git a/impls/cs.2/step2_eval.cs b/impls/cs.2/step2_eval.cs index 5f189bbf59..4ae481a852 100644 --- a/impls/cs.2/step2_eval.cs +++ b/impls/cs.2/step2_eval.cs @@ -28,7 +28,7 @@ public static MalType EVAL(MalType ast, Dictionary env) else { MalList evaluated = (MalList)(eval_ast(ast, env)); - if (astList.isList()) + if (astList is MalList) { MalFunction func = (MalFunction)evaluated.items[0]; MalType retVal = func.function(evaluated.items.Skip(1).ToList()); @@ -73,7 +73,13 @@ public static MalType eval_ast(MalType ast, Dictionary env) { MalList astList = (MalList)ast; List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); - return new MalList(evaluated, astList.openingBracket); // important: preserve the bracket + return new MalList(evaluated); + } + else if (ast is MalVector) + { + MalVector astVector = (MalVector)ast; + List evaluated = astVector.items.Select(item => EVAL(item, env)).ToList(); + return new MalVector(evaluated); } else if (ast is MalHashmap) { diff --git a/impls/cs.2/step3_env.cs b/impls/cs.2/step3_env.cs index 2e89f9561c..697c5b51d7 100644 --- a/impls/cs.2/step3_env.cs +++ b/impls/cs.2/step3_env.cs @@ -41,7 +41,7 @@ public static MalType EVAL(MalType ast, Env env) } else if (firstSymbol.value == "let*") { - MalList bindings = (MalList)astList.items[1]; + MalSeq bindings = (MalSeq)astList.items[1]; MalType expression = astList.items[2]; Env newEnv = new Env(env); for (int i = 0; i < bindings.items.Count; i += 2) @@ -50,23 +50,17 @@ public static MalType EVAL(MalType ast, Env env) MalType value = EVAL(bindings.items[i + 1], newEnv); newEnv.data.Add(name, value); } + return EVAL(expression, newEnv); } } + // Function application MalList evaluated = (MalList)(eval_ast(ast, env)); - if (astList.isList()) - { - // Function application - MalFunction func = (MalFunction)evaluated.items[0]; - MalType retVal = func.function(evaluated.items.Skip(1).ToList()); - return retVal; - } - else - { - return evaluated; // vector - } + MalFunction func = (MalFunction)evaluated.items[0]; + MalType retVal = func.function(evaluated.items.Skip(1).ToList()); + return retVal; } } } @@ -95,7 +89,13 @@ public static MalType eval_ast(MalType ast, Env env) { MalList astList = (MalList)ast; List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); - return new MalList(evaluated, astList.openingBracket); // important: preserve the bracket + return new MalList(evaluated); + } + else if (ast is MalVector) + { + MalVector astVector = (MalVector)ast; + List evaluated = astVector.items.Select(item => EVAL(item, env)).ToList(); + return new MalVector(evaluated); } else if (ast is MalHashmap) { @@ -136,6 +136,7 @@ static void Main(string[] args) // TESTS // var test = rep("(let* (c 2) (+ 1 c))"); + rep("(let* [z 9] z)"); string line = null; do diff --git a/impls/cs.2/step4_if_fn_do.cs b/impls/cs.2/step4_if_fn_do.cs index acba143f93..d3c4eb1568 100644 --- a/impls/cs.2/step4_if_fn_do.cs +++ b/impls/cs.2/step4_if_fn_do.cs @@ -41,15 +41,15 @@ public static MalType EVAL(MalType ast, Env env) } else if (firstSymbol.value == "let*") { - MalList bindings = (MalList)astList.items[1]; - MalType expression = astList.items[2]; - Env newEnv = new Env(env); - for (int i = 0; i < bindings.items.Count; i += 2) - { - MalSymbol name = (MalSymbol)bindings.items[i]; - MalType value = EVAL(bindings.items[i + 1], newEnv); - newEnv.data.Add(name, value); - } + MalSeq bindings = (MalSeq)astList.items[1]; + MalType expression = astList.items[2]; + Env newEnv = new Env(env); + for (int i = 0; i < bindings.items.Count; i += 2) + { + MalSymbol name = (MalSymbol)bindings.items[i]; + MalType value = EVAL(bindings.items[i + 1], newEnv); + newEnv.data.Add(name, value); + } return EVAL(expression, newEnv); } else if (firstSymbol.value == "do") @@ -72,7 +72,7 @@ public static MalType EVAL(MalType ast, Env env) } else if (firstSymbol.value == "fn*") { - MalList argNames = (MalList)astList.items[1]; + MalSeq argNames = (MalSeq)astList.items[1]; MalType funcBody = astList.items[2]; List argSymbs = new List(); foreach (MalType arg in argNames.items) { if (arg is MalSymbol) argSymbs.Add((MalSymbol)arg); } @@ -87,18 +87,11 @@ public static MalType EVAL(MalType ast, Env env) } + // Function application MalList evaluated = (MalList)(eval_ast(ast, env)); - if (astList.isList()) - { - // Function application - MalFunction func = (MalFunction)evaluated.items[0]; - MalType retVal = func.function(evaluated.items.Skip(1).ToList()); - return retVal; - } - else - { - return evaluated; // vector - } + MalFunction func = (MalFunction)evaluated.items[0]; + MalType retVal = func.function(evaluated.items.Skip(1).ToList()); + return retVal; } } } @@ -127,7 +120,13 @@ public static MalType eval_ast(MalType ast, Env env) { MalList astList = (MalList)ast; List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); - return new MalList(evaluated, astList.openingBracket); // important: preserve the bracket + return new MalList(evaluated); + } + else if (ast is MalVector) + { + MalVector astVector = (MalVector)ast; + List evaluated = astVector.items.Select(item => EVAL(item, env)).ToList(); + return new MalVector(evaluated); } else if (ast is MalHashmap) { diff --git a/impls/cs.2/step5_tco.cs b/impls/cs.2/step5_tco.cs index 86892ccad7..bee3d490b2 100644 --- a/impls/cs.2/step5_tco.cs +++ b/impls/cs.2/step5_tco.cs @@ -43,7 +43,7 @@ public static MalType EVAL(MalType ast, Env env) } else if (firstSymbol.value == "let*") { - MalList bindings = (MalList)astList.items[1]; + MalSeq bindings = (MalSeq)astList.items[1]; MalType expression = astList.items[2]; Env newEnv = new Env(env); for (int i = 0; i < bindings.items.Count; i += 2) @@ -81,7 +81,7 @@ public static MalType EVAL(MalType ast, Env env) } else if (firstSymbol.value == "fn*") { - MalList argNames = (MalList)astList.items[1]; + MalSeq argNames = (MalSeq)astList.items[1]; MalType funcBody = astList.items[2]; List argSymbs = new List(); foreach (MalType arg in argNames.items) { if (arg is MalSymbol) argSymbs.Add((MalSymbol)arg); } @@ -101,29 +101,22 @@ public static MalType EVAL(MalType ast, Env env) if (evaluated is not MalList) return evaluated; MalList evaluatedList = (MalList)evaluated; - if (evaluatedList.isList()) + // Function application + MalType funcFirst = evaluatedList.items[0]; + if (funcFirst is MalFunction) { - // Function application - MalType funcFirst = evaluatedList.items[0]; - if (funcFirst is MalFunction) - { - MalFunction func = (MalFunction)funcFirst; - MalType retVal = func.function(evaluatedList.items.Skip(1).ToList()); - return retVal; - } - else // MalFnTco - { - MalFnTco fnTco = (MalFnTco) funcFirst; - List fnArgs = evaluatedList.items.Skip(1).ToList(); - Env newEnv = new Env(fnTco.env, fnTco.@params, fnArgs); - ast = fnTco.ast; - env = newEnv; - continue; - } + MalFunction func = (MalFunction)funcFirst; + MalType retVal = func.function(evaluatedList.items.Skip(1).ToList()); + return retVal; } - else + else // MalFnTco { - return evaluated; // vector and others + MalFnTco fnTco = (MalFnTco) funcFirst; + List fnArgs = evaluatedList.items.Skip(1).ToList(); + Env newEnv = new Env(fnTco.env, fnTco.@params, fnArgs); + ast = fnTco.ast; + env = newEnv; + continue; } } } @@ -154,7 +147,13 @@ public static MalType eval_ast(MalType ast, Env env) { MalList astList = (MalList)ast; List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); - return new MalList(evaluated, astList.openingBracket); // important: preserve the bracket + return new MalList(evaluated); // important: preserve the bracket + } + else if (ast is MalVector) + { + MalVector astList = (MalVector)ast; + List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); + return new MalVector(evaluated); // important: preserve the bracket } else if (ast is MalHashmap) { diff --git a/impls/cs.2/step6_file.cs b/impls/cs.2/step6_file.cs index 88d37d8ae6..baa60a47ab 100644 --- a/impls/cs.2/step6_file.cs +++ b/impls/cs.2/step6_file.cs @@ -48,7 +48,7 @@ public static MalType EVAL(MalType ast, Env env) } else if (firstSymbol.value == "let*") { - MalList bindings = (MalList)astList.items[1]; + MalSeq bindings = (MalSeq)astList.items[1]; MalType expression = astList.items[2]; Env newEnv = new Env(env); for (int i = 0; i < bindings.items.Count; i += 2) @@ -88,7 +88,7 @@ public static MalType EVAL(MalType ast, Env env) } else if (firstSymbol.value == "fn*") { - MalList argNames = (MalList)astList.items[1]; + MalSeq argNames = (MalSeq)astList.items[1]; MalType funcBody = astList.items[2]; List argSymbs = new List(); foreach (MalType arg in argNames.items) { if (arg is MalSymbol) argSymbs.Add((MalSymbol)arg); } @@ -108,29 +108,22 @@ public static MalType EVAL(MalType ast, Env env) if (evaluated is not MalList) return evaluated; MalList evaluatedList = (MalList)evaluated; - if (evaluatedList.isList()) + // Function application + MalType funcFirst = evaluatedList.items[0]; + if (funcFirst is MalFunction) { - // Function application - MalType funcFirst = evaluatedList.items[0]; - if (funcFirst is MalFunction) - { - MalFunction func = (MalFunction)funcFirst; - MalType retVal = func.function(evaluatedList.items.Skip(1).ToList()); - return retVal; - } - else // MalFnTco - { - MalFnTco fnTco = (MalFnTco)funcFirst; - List fnArgs = evaluatedList.items.Skip(1).ToList(); - Env newEnv = new Env(fnTco.env, fnTco.@params, fnArgs); - ast = fnTco.ast; - env = newEnv; - continue; - } + MalFunction func = (MalFunction)funcFirst; + MalType retVal = func.function(evaluatedList.items.Skip(1).ToList()); + return retVal; } - else + else // MalFnTco { - return evaluated; // vector and others + MalFnTco fnTco = (MalFnTco)funcFirst; + List fnArgs = evaluatedList.items.Skip(1).ToList(); + Env newEnv = new Env(fnTco.env, fnTco.@params, fnArgs); + ast = fnTco.ast; + env = newEnv; + continue; } } } @@ -161,7 +154,13 @@ public static MalType eval_ast(MalType ast, Env env) { MalList astList = (MalList)ast; List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); - return new MalList(evaluated, astList.openingBracket); // important: preserve the bracket + return new MalList(evaluated); + } + else if (ast is MalVector) + { + MalVector astVector = (MalVector)ast; + List evaluated = astVector.items.Select(item => EVAL(item, env)).ToList(); + return new MalVector(evaluated); } else if (ast is MalHashmap) { diff --git a/impls/cs.2/step7_quote.cs b/impls/cs.2/step7_quote.cs index 5d7288b208..0ca61dee6b 100644 --- a/impls/cs.2/step7_quote.cs +++ b/impls/cs.2/step7_quote.cs @@ -48,7 +48,7 @@ public static MalType EVAL(MalType ast, Env env) } else if (firstSymbol.value == "let*") { - MalList bindings = (MalList)astList.items[1]; + MalSeq bindings = (MalSeq)astList.items[1]; MalType expression = astList.items[2]; Env newEnv = new Env(env); for (int i = 0; i < bindings.items.Count; i += 2) @@ -88,7 +88,7 @@ public static MalType EVAL(MalType ast, Env env) } else if (firstSymbol.value == "fn*") { - MalList argNames = (MalList)astList.items[1]; + MalSeq argNames = (MalSeq)astList.items[1]; MalType funcBody = astList.items[2]; List argSymbs = new List(); foreach (MalType arg in argNames.items) { if (arg is MalSymbol) argSymbs.Add((MalSymbol)arg); } @@ -120,29 +120,22 @@ public static MalType EVAL(MalType ast, Env env) if (evaluated is not MalList) return evaluated; MalList evaluatedList = (MalList)evaluated; - if (evaluatedList.isList()) + // Function application + MalType funcFirst = evaluatedList.items[0]; + if (funcFirst is MalFunction) { - // Function application - MalType funcFirst = evaluatedList.items[0]; - if (funcFirst is MalFunction) - { - MalFunction func = (MalFunction)funcFirst; - MalType retVal = func.function(evaluatedList.items.Skip(1).ToList()); - return retVal; - } - else // MalFnTco - { - MalFnTco fnTco = (MalFnTco)funcFirst; - List fnArgs = evaluatedList.items.Skip(1).ToList(); - Env newEnv = new Env(fnTco.env, fnTco.@params, fnArgs); - ast = fnTco.ast; - env = newEnv; - continue; - } + MalFunction func = (MalFunction)funcFirst; + MalType retVal = func.function(evaluatedList.items.Skip(1).ToList()); + return retVal; } - else + else // MalFnTco { - return evaluated; // vector and others + MalFnTco fnTco = (MalFnTco)funcFirst; + List fnArgs = evaluatedList.items.Skip(1).ToList(); + Env newEnv = new Env(fnTco.env, fnTco.@params, fnArgs); + ast = fnTco.ast; + env = newEnv; + continue; } } } @@ -190,7 +183,7 @@ static MalType quasiquote(MalType ast) } } - return new MalList(result, astList.openingBracket); + return new MalList(result); } else if (ast is MalHashmap || ast is MalSymbol) { @@ -224,7 +217,13 @@ public static MalType eval_ast(MalType ast, Env env) { MalList astList = (MalList)ast; List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); - return new MalList(evaluated, astList.openingBracket); // important: preserve the bracket + return new MalList(evaluated); + } + else if (ast is MalVector) + { + MalVector astVector = (MalVector)ast; + List evaluated = astVector.items.Select(item => EVAL(item, env)).ToList(); + return new MalVector(evaluated); } else if (ast is MalHashmap) { diff --git a/impls/cs.2/types.cs b/impls/cs.2/types.cs index 3b17b78a4a..d46efeb847 100644 --- a/impls/cs.2/types.cs +++ b/impls/cs.2/types.cs @@ -5,25 +5,19 @@ namespace mal { public class MalType { } - public class MalList : MalType + public class MalSeq : MalType { public IList items { get; } public string openingBracket { get; } - - public MalList(IList items, string openingBracket = "(") + public MalSeq(IList items, string openingBracket) { this.items = items; this.openingBracket = openingBracket; } - public bool isList() - { - return this.openingBracket == "("; - } - public override string ToString() { - return string.Format("", items.ToString()); + return string.Format("<{0} {1}>", this.GetType().Name, items.ToString()); } public override int GetHashCode() @@ -33,17 +27,31 @@ public override int GetHashCode() public override bool Equals(object other) { - if (other is not MalList) return false; - MalList otherList = (MalList)other; - if (otherList.items.Count != items.Count) return false; + if (other is not MalSeq) return false; + IList otherItems = ((MalSeq)other).items; + if (otherItems.Count != items.Count) return false; for (int i = 0; i < items.Count; i++) { - if (!items[i].Equals(otherList.items[i])) return false; + if (!items[i].Equals(otherItems[i])) return false; } return true; } } + public class MalVector : MalSeq + { + public MalVector(IList items) : base(items, "[") + { + } + } + + public class MalList : MalSeq + { + public MalList(IList items) : base(items, "(") + { + } + } + class MalInteger : MalType { public int value { get; } From bb66413ae7275390206bf65a58651e12c540e7d1 Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Mon, 22 Mar 2021 21:20:10 -0700 Subject: [PATCH 12/20] Step 7 part 2 --- impls/cs.2/core.cs | 1 + impls/cs.2/step7_quote.cs | 33 ++++++++++++++++++++++----------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/impls/cs.2/core.cs b/impls/cs.2/core.cs index af61dc95d9..72c5fae04a 100644 --- a/impls/cs.2/core.cs +++ b/impls/cs.2/core.cs @@ -127,6 +127,7 @@ class core {"vec", new MalFunction((IList args) => { MalSeq head = (MalSeq)args[0]; + if (head is MalVector) return head; return new MalVector(head.items); })}, diff --git a/impls/cs.2/step7_quote.cs b/impls/cs.2/step7_quote.cs index 0ca61dee6b..916faf06d7 100644 --- a/impls/cs.2/step7_quote.cs +++ b/impls/cs.2/step7_quote.cs @@ -111,7 +111,8 @@ public static MalType EVAL(MalType ast, Env env) } else if (firstSymbol.value == "quasiquote") { - return EVAL(quasiquote(astList.items[1]), env); + ast = quasiquote(astList.items[1]); + continue; } } @@ -144,28 +145,30 @@ public static MalType EVAL(MalType ast, Env env) static MalType quasiquote(MalType ast) { - if (ast is MalList) + if (ast is MalSeq) { - MalList astList = (MalList)ast; - if (astList.items.Count > 0) + MalSeq astSeq = (MalSeq)ast; + if (ast is MalList && astSeq.items.Count > 0) { - MalType head = astList.items[0]; + MalType head = astSeq.items[0]; if (head is MalSymbol && ((MalSymbol)head).value == "unquote") { - return astList.items[1]; + return astSeq.items[1]; } } List result = new List(); // Iterate over each element elt of ast in reverse order - for (int i = astList.items.Count - 1; i >= 0; i--) + for (int i = astSeq.items.Count - 1; i >= 0; i--) { - MalType elt = astList.items[i]; + MalType elt = astSeq.items[i]; // If elt is a list starting with the "splice-unquote" symbol if (elt is MalList && ((MalList)elt).items.Count >= 2 && ((MalList)elt).items[0].Equals(new MalSymbol("splice-unquote"))) { + // replace the current result with a list containing: the "concat" symbol, + // the second element of elt, then the previous result. result = new List(){ new MalSymbol("concat"), ((MalList)elt).items[1], @@ -174,6 +177,8 @@ static MalType quasiquote(MalType ast) } else { + // replace the current result with a list containing: the "cons" symbol, + // the result of calling quasiquote with elt as argument, then the previous result result = new List() { new MalSymbol("cons"), @@ -183,6 +188,13 @@ static MalType quasiquote(MalType ast) } } + if (ast is MalVector) + { + // when ast is a vector, return a list containing: the "vec" symbol, + // then the result of processing ast as if it were a list not starting with quote + result = new List() { new MalSymbol("vec"), new MalList(result) }; + } + return new MalList(result); } else if (ast is MalHashmap || ast is MalSymbol) @@ -190,7 +202,7 @@ static MalType quasiquote(MalType ast) // If ast is a map or a symbol, return a list containing: the "quote" symbol, then ast return new MalList(new List() { new MalSymbol("quote"), ast }); } - else return ast; + else return ast; // Else return ast unchanged } static string PRINT(MalType input) @@ -267,8 +279,7 @@ static void Main(string[] args) repl_env.data.Add(new MalSymbol("*ARGV*"), new MalList(malArgs)); // TESTS - // rep("(load-file \"../../tests/computations.mal\")"); - rep("(quasiquote [])"); + // rep("`[unquote 0]"); string line = null; do From f1050999687cf412ba373bb10a78b69090707043 Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Fri, 26 Mar 2021 00:06:51 -0700 Subject: [PATCH 13/20] Step 8 --- impls/cs.2/core.cs | 23 +++ impls/cs.2/step8_macros.cs | 356 +++++++++++++++++++++++++++++++++ impls/cs.2/step8_macros.csproj | 9 + impls/cs.2/types.cs | 4 + 4 files changed, 392 insertions(+) create mode 100644 impls/cs.2/step8_macros.cs create mode 100644 impls/cs.2/step8_macros.csproj diff --git a/impls/cs.2/core.cs b/impls/cs.2/core.cs index 72c5fae04a..0aaa97dfb3 100644 --- a/impls/cs.2/core.cs +++ b/impls/cs.2/core.cs @@ -131,6 +131,29 @@ class core return new MalVector(head.items); })}, + {"nth", + new MalFunction((IList args) => { + MalSeq seq = (MalSeq)args[0]; + MalInteger index = (MalInteger)args[1]; + return seq.items[index.value]; + })}, + + {"first", + new MalFunction((IList args) => { + if (args[0] == MalNil.MAL_NIL) return MalNil.MAL_NIL; + MalSeq seq = (MalSeq)args[0]; + if (seq == null || seq.items.Count == 0) return MalNil.MAL_NIL; + return seq.items[0]; + })}, + + {"rest", + new MalFunction((IList args) => { + if (args[0] == MalNil.MAL_NIL) return new MalList(new List()); + MalSeq seq = (MalSeq)args[0]; + if (args[0] == MalNil.MAL_NIL || seq == null || seq.items.Count == 0) return new MalList(new List()); + return new MalList( seq.items.Skip(1).ToList() ); + })}, + }; } } \ No newline at end of file diff --git a/impls/cs.2/step8_macros.cs b/impls/cs.2/step8_macros.cs new file mode 100644 index 0000000000..dc5b043bd7 --- /dev/null +++ b/impls/cs.2/step8_macros.cs @@ -0,0 +1,356 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace mal +{ + class step8_macros + { + + static MalType READ(string input) + { + return Reader.read_str(input); + } + + public static MalType EVAL(MalType ast, Env env) + { + while (true) + { + if (ast is not MalList) + { + return eval_ast(ast, env); + } + else + { + MalList astList = (MalList)ast; + + // Remove any comments from the ast + List nonComments = astList.items.Where(it => !(it is MalSymbol && ((MalSymbol)it).value.StartsWith(";"))).ToList(); + astList = new MalList(nonComments); + + if (astList.items.Count == 0) + { + return astList; + } + else + { + // Macroexpand before special forms + MalType expanded = macroexpand(astList, env); + if (expanded is not MalList) return eval_ast(expanded, env); + else + { + ast = (MalList)expanded; + astList = (MalList)expanded; + } + + MalType first = astList.items.First(); + if (first is MalSymbol) + { + MalSymbol firstSymbol = (MalSymbol)first; + if (firstSymbol.value == "def!") + { + MalSymbol symbol = (MalSymbol)astList.items[1]; + MalType value = EVAL(astList.items[2], env); + env.set(symbol, value); + return value; + } + if (firstSymbol.value == "defmacro!") + { + MalSymbol symbol = (MalSymbol)astList.items[1]; + MalType value = EVAL(astList.items[2], env); + if (value is MalFunction) ((MalFunction)value).is_macro = true; + if (value is MalFnTco) ((MalFnTco)value).is_macro = true; + env.set(symbol, value); + return value; + } + else if (firstSymbol.value == "let*") + { + MalSeq bindings = (MalSeq)astList.items[1]; + MalType expression = astList.items[2]; + Env newEnv = new Env(env); + for (int i = 0; i < bindings.items.Count; i += 2) + { + MalSymbol name = (MalSymbol)bindings.items[i]; + MalType value = EVAL(bindings.items[i + 1], newEnv); + newEnv.data.Add(name, value); + } + env = newEnv; + ast = expression; + continue; + } + else if (firstSymbol.value == "do") + { + int butLast = astList.items.Count - 2; + + // produce side-effects and then just forget + List sideEffects = astList.items.Skip(1).Take(butLast).ToList(); + eval_ast(new MalList(sideEffects), env); + + ast = astList.items.Last(); + continue; + } + else if (firstSymbol.value == "if") + { + MalType test = astList.items[1]; + MalType testResult = EVAL(test, env); + if (testResult == MalNil.MAL_NIL || testResult == MalBoolean.MAL_FALSE) + { + ast = (astList.items.Count > 3) ? astList.items[3] : MalNil.MAL_NIL; + } + else + { + ast = astList.items[2]; + } + continue; + } + else if (firstSymbol.value == "fn*") + { + MalSeq argNames = (MalSeq)astList.items[1]; + MalType funcBody = astList.items[2]; + List argSymbs = new List(); + foreach (MalType arg in argNames.items) { if (arg is MalSymbol) argSymbs.Add((MalSymbol)arg); } + MalFunction fn = new MalFunction( + (IList argValues) => + { + Env funcEnv = new Env(env, argSymbs, argValues); + return EVAL(funcBody, funcEnv); + } + ); + return new MalFnTco(funcBody, argSymbs, env, fn); + } + else if (firstSymbol.value == "quote") + { + return astList.items[1]; + } + else if (firstSymbol.value == "quasiquoteexpand") + { + return quasiquote(astList.items[1]); + } + else if (firstSymbol.value == "quasiquote") + { + ast = quasiquote(astList.items[1]); + continue; + } + else if (firstSymbol.value == "macroexpand") + { + return macroexpand(astList.items[1], env); + } + + } + + MalType evaluated = eval_ast(ast, env); + if (evaluated is not MalList) return evaluated; + MalList evaluatedList = (MalList)evaluated; + + // Function application + MalType funcFirst = evaluatedList.items[0]; + if (funcFirst is MalFunction) + { + MalFunction func = (MalFunction)funcFirst; + MalType retVal = func.function(evaluatedList.items.Skip(1).ToList()); + return retVal; + } + else // MalFnTco + { + MalFnTco fnTco = (MalFnTco)funcFirst; + List fnArgs = evaluatedList.items.Skip(1).ToList(); + Env newEnv = new Env(fnTco.env, fnTco.@params, fnArgs); + ast = fnTco.ast; + env = newEnv; + continue; + } + } + } + } + } + + static bool is_macro_call(MalType ast, Env env) + { + if (ast is MalList) + { + MalType head = ((MalList)ast).items[0]; + if (head is MalSymbol && env.find((MalSymbol)head) != null) + { + MalType value = env.get((MalSymbol)head); + if (value is MalFunction) return ((MalFunction)value).is_macro; + if (value is MalFnTco) return ((MalFnTco)value).is_macro; + } + } + return false; + } + + static MalType macroexpand(MalType ast, Env env) + { + bool expanding; + while (expanding = is_macro_call(ast, env)) + { + MalSymbol name = (MalSymbol) ((MalSeq)ast).items[0]; + List args = ((MalSeq)ast).items.Skip(1).ToList(); + MalType macroFn = env.get(name); + if (macroFn is MalFunction) ast = ((MalFunction)macroFn).function(args); + if (macroFn is MalFnTco) ast = ((MalFnTco)macroFn).fn.function(args); + } + return ast; + } + + static MalType quasiquote(MalType ast) + { + if (ast is MalSeq) + { + MalSeq astSeq = (MalSeq)ast; + if (ast is MalList && astSeq.items.Count > 0) + { + MalType head = astSeq.items[0]; + if (head is MalSymbol && ((MalSymbol)head).value == "unquote") + { + return astSeq.items[1]; + } + } + + List result = new List(); + + // Iterate over each element elt of ast in reverse order + for (int i = astSeq.items.Count - 1; i >= 0; i--) + { + MalType elt = astSeq.items[i]; + + // If elt is a list starting with the "splice-unquote" symbol + if (elt is MalList && ((MalList)elt).items.Count >= 2 && ((MalList)elt).items[0].Equals(new MalSymbol("splice-unquote"))) + { + // replace the current result with a list containing: the "concat" symbol, + // the second element of elt, then the previous result. + result = new List(){ + new MalSymbol("concat"), + ((MalList)elt).items[1], + new MalList(result) + }; + } + else + { + // replace the current result with a list containing: the "cons" symbol, + // the result of calling quasiquote with elt as argument, then the previous result + result = new List() + { + new MalSymbol("cons"), + quasiquote(elt), + new MalList(result) + }; + } + } + + if (ast is MalVector) + { + // when ast is a vector, return a list containing: the "vec" symbol, + // then the result of processing ast as if it were a list not starting with quote + result = new List() { new MalSymbol("vec"), new MalList(result) }; + } + + return new MalList(result); + } + else if (ast is MalHashmap || ast is MalSymbol) + { + // If ast is a map or a symbol, return a list containing: the "quote" symbol, then ast + return new MalList(new List() { new MalSymbol("quote"), ast }); + } + else return ast; // Else return ast unchanged + } + + static string PRINT(MalType input) + { + return printer.pr_str(input, true); + } + + static string rep(string input) + { + MalType read = READ(input); + MalType evaled = EVAL(read, repl_env); + string printed = PRINT(evaled); + return printed; + } + + public static MalType eval_ast(MalType ast, Env env) + { + if (ast is MalSymbol) + { + MalSymbol astSymbol = (MalSymbol)ast; + return env.get(astSymbol); + } + else if (ast is MalList) + { + MalList astList = (MalList)ast; + List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); + return new MalList(evaluated); + } + else if (ast is MalVector) + { + MalVector astVector = (MalVector)ast; + List evaluated = astVector.items.Select(item => EVAL(item, env)).ToList(); + return new MalVector(evaluated); + } + else if (ast is MalHashmap) + { + MalHashmap astHashmap = (MalHashmap)ast; + Dictionary newKVs = new Dictionary(); + foreach (KeyValuePair kv in astHashmap.values) + { + newKVs.Add(EVAL(kv.Key, env), EVAL(kv.Value, env)); + } + return new MalHashmap(newKVs); + } + else + { + return ast; + } + } + + public static Env repl_env = new Env(null); + + static void Main(string[] args) + { + // Load the built-in functions + foreach (var pair in core.ns) { repl_env.set(new MalSymbol(pair.Key), pair.Value); } + + // Define 'eval' + repl_env.data.Add(new MalSymbol("eval"), new MalFunction((IList args) => + { + MalType ast = args[0]; + return EVAL(ast, repl_env); + })); + + // Functions defined on Mal itself: + // - define 'not' + rep("(def! not (fn* (a) (if a false true)))"); + // - define 'load-file' + rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\"))))))"); + 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)))))))"); + + // ARGV + List malArgs = new List(); + malArgs.AddRange(args.Select(arg => new MalString(arg)).ToList()); + repl_env.data.Add(new MalSymbol("*ARGV*"), new MalList(malArgs)); + + // TESTS + // rep("`[unquote 0]"); + + string line = null; + do + { + Console.Write("user> "); + line = Console.ReadLine(); + if (line != null) + { + try + { + Console.WriteLine(rep(line)); + } + catch (Exception ex) + { + + Console.WriteLine(ex.Message); + } + } + } while (line != null); + Console.WriteLine(); + } + } +} diff --git a/impls/cs.2/step8_macros.csproj b/impls/cs.2/step8_macros.csproj new file mode 100644 index 0000000000..baaa3f24cb --- /dev/null +++ b/impls/cs.2/step8_macros.csproj @@ -0,0 +1,9 @@ + + + + exe + net5.0 + mal.step8_macros + + + diff --git a/impls/cs.2/types.cs b/impls/cs.2/types.cs index d46efeb847..ae89486bb9 100644 --- a/impls/cs.2/types.cs +++ b/impls/cs.2/types.cs @@ -151,9 +151,12 @@ public override bool Equals(object other) class MalFunction : MalType { public Func, MalType> function { get; } + public bool is_macro { get; set; } + public MalFunction(Func, MalType> function) { this.function = function; + this.is_macro = false; } } @@ -215,6 +218,7 @@ class MalFnTco : MalType public List @params { get; set; } // 'params' is a reserved word public Env env { get; set; } public MalFunction fn { get; set; } + public bool is_macro { get; set; } public MalFnTco(MalType ast, List @params, Env env, MalFunction fn) { From 20697d39b6cb4f91b2572423c7943763541c4cdb Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Sat, 27 Mar 2021 01:12:46 -0700 Subject: [PATCH 14/20] Step 9 --- impls/cs.2/core.cs | 165 +++++++++++++++ impls/cs.2/env.cs | 2 +- impls/cs.2/printer.cs | 11 +- impls/cs.2/reader.cs | 5 +- impls/cs.2/step9_try.cs | 387 ++++++++++++++++++++++++++++++++++++ impls/cs.2/step9_try.csproj | 9 + impls/cs.2/types.cs | 17 +- 7 files changed, 587 insertions(+), 9 deletions(-) create mode 100644 impls/cs.2/step9_try.cs create mode 100644 impls/cs.2/step9_try.csproj diff --git a/impls/cs.2/core.cs b/impls/cs.2/core.cs index 0aaa97dfb3..89848b7299 100644 --- a/impls/cs.2/core.cs +++ b/impls/cs.2/core.cs @@ -17,6 +17,7 @@ class core )}, {"count", new MalFunction((IList args) => { if (args.Count == 0 || args[0] == MalNil.MAL_NIL) return new MalInteger(0); + if (args[0] is MalHashmap) return new MalInteger( ((MalHashmap)args[0]).values.Count ); else return new MalInteger( ((MalSeq)args[0]).items.Count ); })}, {"+", new MalFunction((IList args) => new MalInteger(((MalInteger)args[0]).value + ((MalInteger)args[1]).value))}, @@ -131,10 +132,13 @@ class core return new MalVector(head.items); })}, + // Step 8 + {"nth", new MalFunction((IList args) => { MalSeq seq = (MalSeq)args[0]; MalInteger index = (MalInteger)args[1]; + if (index.value > seq.items.Count - 1) throw new MalException(new MalString("index out of bounds")); return seq.items[index.value]; })}, @@ -154,6 +158,167 @@ class core return new MalList( seq.items.Skip(1).ToList() ); })}, + // Step 9 + + {"throw", + new MalFunction((IList args) => { + throw new MalException(args[0]); + })}, + + {"apply", + new MalFunction((IList args) => { + MalType fnOrTco = args[0]; + Func, MalType> fn = (fnOrTco is MalFunction) ? ((MalFunction)fnOrTco).function : ((MalFnTco)fnOrTco).fn.function; + List fnArgs = args.Skip(1).ToList(); + if (args[args.Count - 1] is MalSeq) + { + MalSeq lastElem = (MalSeq)args[args.Count - 1]; + fnArgs = args.Skip(1).Take(args.Count - 2).ToList(); // all but first and last + fnArgs.AddRange( lastElem.items ); // add all elements of the last item + } + return fn(fnArgs); + })}, + + {"map", + new MalFunction((IList args) => { + MalType fnOrTco = args[0]; + Func, MalType> fn = (fnOrTco is MalFunction) ? ((MalFunction)fnOrTco).function : ((MalFnTco)fnOrTco).fn.function; + MalSeq xs = (MalSeq)args[1]; + List mapped = xs.items.Select(x => fn( new List(){x} )).ToList(); + return new MalList(mapped); + })}, + + {"symbol", + new MalFunction((IList args) => { + MalString name = (MalString)args[0]; + return new MalSymbol(name.value); + })}, + + {"symbol?", + new MalFunction((IList args) => { + MalType x = args[0]; + return (x is MalSymbol) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE; + })}, + + {"keyword", + new MalFunction((IList args) => { + MalType name = args[0]; + return new MalKeyword(printer.pr_str(name)); + })}, + + {"keyword?", + new MalFunction((IList args) => { + MalType x = args[0]; + return (x is MalKeyword) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE; + })}, + + {"vector", + new MalFunction((IList args) => { + return new MalVector(args); + })}, + + {"vector?", + new MalFunction((IList args) => { + MalType x = args[0]; + return (x is MalVector) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE; + })}, + + {"sequential?", + new MalFunction((IList args) => { + MalType x = args[0]; + return (x is MalSeq) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE; + })}, + + {"hash-map", + new MalFunction((IList args) => { + Dictionary dict = new Dictionary(); + for (int i = 0; i < args.Count; i += 2) + { + MalType key = args[i]; + MalType val = args[i+1]; + if (dict.ContainsKey(key)) dict.Remove(key); + dict.Add(key, val); + } + return new MalHashmap( dict ); + })}, + + {"map?", + new MalFunction((IList args) => { + MalType x = args[0]; + return (x is MalHashmap) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE; + })}, + + {"assoc", + new MalFunction((IList args) => { + MalHashmap baseMap = (MalHashmap)args[0]; + Dictionary dict = new Dictionary(baseMap.values); + for (int i = 1; i < args.Count; i += 2) + { + MalType key = args[i]; + MalType val = args[i+1]; + if (dict.ContainsKey(key)) dict.Remove(key); + dict.Add(key, val); + } + return new MalHashmap( dict ); + })}, + + {"dissoc", + new MalFunction((IList args) => { + MalHashmap baseMap = (MalHashmap)args[0]; + Dictionary dict = new Dictionary(baseMap.values); + for (int i = 1; i < args.Count; i++) + { + MalType key = args[i]; + if (dict.ContainsKey(key)) dict.Remove(key); + } + return new MalHashmap( dict ); + })}, + + {"get", + new MalFunction((IList args) => { + if (args[0] is not MalHashmap) return MalNil.MAL_NIL; + MalHashmap m = (MalHashmap)args[0]; + MalType k = args[1]; + return (m.values.ContainsKey(k)) ? m.values.GetValueOrDefault(k) : MalNil.MAL_NIL; + })}, + + {"contains?", + new MalFunction((IList args) => { + MalHashmap m = (MalHashmap)args[0]; + MalType k = args[1]; + return (m.values.ContainsKey(k)) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE; + })}, + + {"keys", + new MalFunction((IList args) => { + MalHashmap m = (MalHashmap)args[0]; + return new MalList( m.values.Keys.ToList() ); + })}, + + {"vals", + new MalFunction((IList args) => { + MalHashmap m = (MalHashmap)args[0]; + return new MalList( m.values.Values.ToList() ); + })}, + + {"nil?", + new MalFunction((IList args) => { + MalType x = args[0]; + return (x == MalNil.MAL_NIL) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE; + })}, + + {"true?", + new MalFunction((IList args) => { + MalType x = args[0]; + return (x == MalBoolean.MAL_TRUE) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE; + })}, + + {"false?", + new MalFunction((IList args) => { + MalType x = args[0]; + return (x == MalBoolean.MAL_FALSE) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE; + })}, + }; } } \ No newline at end of file diff --git a/impls/cs.2/env.cs b/impls/cs.2/env.cs index bad2dd84ce..17a1a3ecde 100644 --- a/impls/cs.2/env.cs +++ b/impls/cs.2/env.cs @@ -56,7 +56,7 @@ public MalType get(MalSymbol key) } else { - throw new Exception(string.Format("Value for '{0}' not found", key.value)); + throw new MalException(new MalString(string.Format("'{0}' not found", key.value))); } } diff --git a/impls/cs.2/printer.cs b/impls/cs.2/printer.cs index 116668906a..306382ef9d 100644 --- a/impls/cs.2/printer.cs +++ b/impls/cs.2/printer.cs @@ -53,15 +53,15 @@ public static string pr_str(MalType malType, bool print_readably = false) List strings = new List(); foreach (var keyValuePair in hashMap.values) { - strings.Add(pr_str(keyValuePair.Key, true)); - strings.Add(pr_str(keyValuePair.Value, true)); + strings.Add(pr_str(keyValuePair.Key, print_readably)); + strings.Add(pr_str(keyValuePair.Value, print_readably)); } string joined = string.Join(" ", strings); return string.Format("{0}{1}{2}", "{", joined, "}"); } else if (malType is MalKeyword) { - return ((MalKeyword)malType).name; + return string.Format(":{0}", ((MalKeyword)malType).name); } else if (malType is MalFunction) { @@ -84,6 +84,11 @@ public static string pr_str(MalType malType, bool print_readably = false) MalAtom atom = (MalAtom)malType; return string.Format("(atom {0})", pr_str(atom.value, print_readably)); } + else if (malType is MalException) + { + MalException ex = (MalException)malType; + return string.Format(pr_str(ex.cause, print_readably)); + } else { throw new Exception(string.Format("Unknown type to print: {0}", malType.ToString())); diff --git a/impls/cs.2/reader.cs b/impls/cs.2/reader.cs index 441c8f8155..376726dbf8 100644 --- a/impls/cs.2/reader.cs +++ b/impls/cs.2/reader.cs @@ -10,7 +10,7 @@ class Reader static Regex TOKENIZER_INSTANCE = new Regex(TOKENIZER_REGEX); static Dictionary UNESCAPE = new Dictionary() { - {'n', "\n"}, {'"', "\""}, {'\\', "\\"} // TODO add \t and others + {'n', "\n"}, {'"', "\""}, {'\\', "\\"}, {'t', "\t"} }; static Dictionary QUOTING = new Dictionary() { @@ -76,6 +76,7 @@ public static MalType read_str(string input) public static MalType read_form(Reader reader) { string first = reader.peek(); + if (first == null) return null; // signal that we don't want to print back if (first == "^") // expect two other forms { reader.next(); // drop the '^' @@ -179,7 +180,7 @@ static MalType read_atom(Reader reader) string item = reader.next(); if (item.StartsWith(":")) { - return new MalKeyword(item); + return new MalKeyword(item.Substring(1)); } else if (LITERALS.ContainsKey(item)) { diff --git a/impls/cs.2/step9_try.cs b/impls/cs.2/step9_try.cs new file mode 100644 index 0000000000..147982a397 --- /dev/null +++ b/impls/cs.2/step9_try.cs @@ -0,0 +1,387 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace mal +{ + class step9_try + { + + static MalType READ(string input) + { + return Reader.read_str(input); + } + + public static MalType EVAL(MalType ast, Env env) + { + while (true) + { + if (ast is not MalList) + { + return eval_ast(ast, env); + } + else + { + MalList astList = (MalList)ast; + + // Remove any comments from the ast + List nonComments = astList.items.Where(it => !(it is MalSymbol && ((MalSymbol)it).value.StartsWith(";"))).ToList(); + astList = new MalList(nonComments); + + if (astList.items.Count == 0) + { + return astList; + } + else + { + // Macroexpand before special forms + MalType expanded = macroexpand(astList, env); + if (expanded is not MalList) return eval_ast(expanded, env); + else + { + ast = (MalList)expanded; + astList = (MalList)expanded; + } + + MalType first = astList.items.First(); + if (first is MalSymbol) + { + MalSymbol firstSymbol = (MalSymbol)first; + if (firstSymbol.value == "def!") + { + MalSymbol symbol = (MalSymbol)astList.items[1]; + MalType value = EVAL(astList.items[2], env); + env.set(symbol, value); + return value; + } + if (firstSymbol.value == "defmacro!") + { + MalSymbol symbol = (MalSymbol)astList.items[1]; + MalType value = EVAL(astList.items[2], env); + if (value is MalFunction) ((MalFunction)value).is_macro = true; + if (value is MalFnTco) ((MalFnTco)value).is_macro = true; + env.set(symbol, value); + return value; + } + else if (firstSymbol.value == "let*") + { + MalSeq bindings = (MalSeq)astList.items[1]; + MalType expression = astList.items[2]; + Env newEnv = new Env(env); + for (int i = 0; i < bindings.items.Count; i += 2) + { + MalSymbol name = (MalSymbol)bindings.items[i]; + MalType value = EVAL(bindings.items[i + 1], newEnv); + newEnv.data.Add(name, value); + } + env = newEnv; + ast = expression; + continue; + } + else if (firstSymbol.value == "do") + { + int butLast = astList.items.Count - 2; + + // produce side-effects and then just forget + List sideEffects = astList.items.Skip(1).Take(butLast).ToList(); + eval_ast(new MalList(sideEffects), env); + + ast = astList.items.Last(); + continue; + } + else if (firstSymbol.value == "if") + { + MalType test = astList.items[1]; + MalType testResult = EVAL(test, env); + if (testResult == MalNil.MAL_NIL || testResult == MalBoolean.MAL_FALSE) + { + ast = (astList.items.Count > 3) ? astList.items[3] : MalNil.MAL_NIL; + } + else + { + ast = astList.items[2]; + } + continue; + } + else if (firstSymbol.value == "fn*") + { + MalSeq argNames = (MalSeq)astList.items[1]; + MalType funcBody = astList.items[2]; + List argSymbs = new List(); + foreach (MalType arg in argNames.items) { if (arg is MalSymbol) argSymbs.Add((MalSymbol)arg); } + MalFunction fn = new MalFunction( + (IList argValues) => + { + Env funcEnv = new Env(env, argSymbs, argValues); + return EVAL(funcBody, funcEnv); + } + ); + return new MalFnTco(funcBody, argSymbs, env, fn); + } + else if (firstSymbol.value == "quote") + { + return astList.items[1]; + } + else if (firstSymbol.value == "quasiquoteexpand") + { + return quasiquote(astList.items[1]); + } + else if (firstSymbol.value == "quasiquote") + { + ast = quasiquote(astList.items[1]); + continue; + } + else if (firstSymbol.value == "macroexpand") + { + return macroexpand(astList.items[1], env); + } + else if (firstSymbol.value == "try*") + { + try + { + return EVAL(astList.items[1], env); + } + catch (MalException exception) + { + // if a catch block exists, create a MalException and evaluate the expression of the block with it + if (astList.items.Count > 2 && astList.items[2] is MalList) + { + MalList catchArgs = (MalList)astList.items[2]; + MalSymbol exName = (MalSymbol)catchArgs.items[1]; + MalType expr = catchArgs.items[2]; + Env exEnv = new Env(env); + exEnv.set(exName, exception.cause); + return EVAL(expr, exEnv); + } + else throw new MalException(exception.cause); // otherwise just rethrow + } + } + + } + + MalType evaluated = eval_ast(ast, env); + if (evaluated is not MalList) return evaluated; + MalList evaluatedList = (MalList)evaluated; + + // Function application + MalType funcFirst = evaluatedList.items[0]; + if (funcFirst is MalFunction) + { + MalFunction func = (MalFunction)funcFirst; + MalType retVal = func.function(evaluatedList.items.Skip(1).ToList()); + return retVal; + } + else // MalFnTco + { + MalFnTco fnTco = (MalFnTco)funcFirst; + List fnArgs = evaluatedList.items.Skip(1).ToList(); + Env newEnv = new Env(fnTco.env, fnTco.@params, fnArgs); + ast = fnTco.ast; + env = newEnv; + continue; + } + } + } + } + } + + static bool is_macro_call(MalType ast, Env env) + { + if (ast is MalList) + { + MalType head = ((MalList)ast).items[0]; + if (head is MalSymbol && env.find((MalSymbol)head) != null) + { + MalType value = env.get((MalSymbol)head); + if (value is MalFunction) return ((MalFunction)value).is_macro; + if (value is MalFnTco) return ((MalFnTco)value).is_macro; + } + } + return false; + } + + static MalType macroexpand(MalType ast, Env env) + { + bool expanding; + while (expanding = is_macro_call(ast, env)) + { + MalSymbol name = (MalSymbol)((MalSeq)ast).items[0]; + List args = ((MalSeq)ast).items.Skip(1).ToList(); + MalType macroFn = env.get(name); + if (macroFn is MalFunction) ast = ((MalFunction)macroFn).function(args); + if (macroFn is MalFnTco) ast = ((MalFnTco)macroFn).fn.function(args); + } + return ast; + } + + static MalType quasiquote(MalType ast) + { + if (ast is MalSeq) + { + MalSeq astSeq = (MalSeq)ast; + if (ast is MalList && astSeq.items.Count > 0) + { + MalType head = astSeq.items[0]; + if (head is MalSymbol && ((MalSymbol)head).value == "unquote") + { + return astSeq.items[1]; + } + } + + List result = new List(); + + // Iterate over each element elt of ast in reverse order + for (int i = astSeq.items.Count - 1; i >= 0; i--) + { + MalType elt = astSeq.items[i]; + + // If elt is a list starting with the "splice-unquote" symbol + if (elt is MalList && ((MalList)elt).items.Count >= 2 && ((MalList)elt).items[0].Equals(new MalSymbol("splice-unquote"))) + { + // replace the current result with a list containing: the "concat" symbol, + // the second element of elt, then the previous result. + result = new List(){ + new MalSymbol("concat"), + ((MalList)elt).items[1], + new MalList(result) + }; + } + else + { + // replace the current result with a list containing: the "cons" symbol, + // the result of calling quasiquote with elt as argument, then the previous result + result = new List() + { + new MalSymbol("cons"), + quasiquote(elt), + new MalList(result) + }; + } + } + + if (ast is MalVector) + { + // when ast is a vector, return a list containing: the "vec" symbol, + // then the result of processing ast as if it were a list not starting with quote + result = new List() { new MalSymbol("vec"), new MalList(result) }; + } + + return new MalList(result); + } + else if (ast is MalHashmap || ast is MalSymbol) + { + // If ast is a map or a symbol, return a list containing: the "quote" symbol, then ast + return new MalList(new List() { new MalSymbol("quote"), ast }); + } + else return ast; // Else return ast unchanged + } + + static string PRINT(MalType input) + { + return printer.pr_str(input, true); + } + + static string rep(string input) + { + MalType read = READ(input); + MalType evaled; + try + { + evaled = EVAL(read, repl_env); + if (evaled == null) return null; + string printed = PRINT(evaled); + return printed; + } + catch (MalException mex) + { + return string.Format("Exception: {0}", printer.pr_str(mex.cause)); + } + } + + public static MalType eval_ast(MalType ast, Env env) + { + if (ast is MalSymbol) + { + MalSymbol astSymbol = (MalSymbol)ast; + return env.get(astSymbol); + } + else if (ast is MalList) + { + MalList astList = (MalList)ast; + List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); + return new MalList(evaluated); + } + else if (ast is MalVector) + { + MalVector astVector = (MalVector)ast; + List evaluated = astVector.items.Select(item => EVAL(item, env)).ToList(); + return new MalVector(evaluated); + } + else if (ast is MalHashmap) + { + MalHashmap astHashmap = (MalHashmap)ast; + Dictionary newKVs = new Dictionary(); + foreach (KeyValuePair kv in astHashmap.values) + { + newKVs.Add(EVAL(kv.Key, env), EVAL(kv.Value, env)); + } + return new MalHashmap(newKVs); + } + else + { + return ast; + } + } + + public static Env repl_env = new Env(null); + + static void Main(string[] args) + { + // Load the built-in functions + foreach (var pair in core.ns) { repl_env.set(new MalSymbol(pair.Key), pair.Value); } + + // Define 'eval' + repl_env.data.Add(new MalSymbol("eval"), new MalFunction((IList args) => + { + MalType ast = args[0]; + return EVAL(ast, repl_env); + })); + + // Functions defined on Mal itself: + // - define 'not' + rep("(def! not (fn* (a) (if a false true)))"); + // - define 'load-file' + rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\"))))))"); + 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)))))))"); + + // ARGV + List malArgs = new List(); + malArgs.AddRange(args.Select(arg => new MalString(arg)).ToList()); + repl_env.data.Add(new MalSymbol("*ARGV*"), new MalList(malArgs)); + + // TESTS + // rep("`[unquote 0]"); + + string line = null; + do + { + Console.Write("user> "); + line = Console.ReadLine(); + if (line != null) + { + try + { + string repResult = rep(line); + if (repResult != null) Console.WriteLine(repResult); + } + catch (Exception ex) + { + + Console.WriteLine(ex.Message); + } + } + } while (line != null); + Console.WriteLine(); + } + } +} diff --git a/impls/cs.2/step9_try.csproj b/impls/cs.2/step9_try.csproj new file mode 100644 index 0000000000..7a7d2d42dd --- /dev/null +++ b/impls/cs.2/step9_try.csproj @@ -0,0 +1,9 @@ + + + + exe + net5.0 + mal.step9_try + + + diff --git a/impls/cs.2/types.cs b/impls/cs.2/types.cs index ae89486bb9..0dd89a15b8 100644 --- a/impls/cs.2/types.cs +++ b/impls/cs.2/types.cs @@ -3,7 +3,7 @@ namespace mal { - public class MalType { } + public interface MalType { } public class MalSeq : MalType { @@ -142,7 +142,8 @@ public override bool Equals(object other) foreach (var kv in values) { MalType key = kv.Key; - if (values.GetValueOrDefault(key) != otherMap.values.GetValueOrDefault(key)) return false; + MalType val = kv.Value; + if (!val.Equals(otherMap.values.GetValueOrDefault(key))) return false; } return true; } @@ -166,7 +167,7 @@ class MalKeyword : MalType public MalKeyword(string name) { - this.name = name; + this.name = (name.StartsWith(":")) ? name.Substring(1) : name; } public override int GetHashCode() @@ -238,4 +239,14 @@ public MalAtom(MalType value) this.value = value; } } + + class MalException : Exception, MalType + { + public MalType cause { get; } + + public MalException(MalType value): base() + { + this.cause = value; + } + } } \ No newline at end of file From 4ad9dce4a7d25a1b4607f51cc36d7155cc71f7f3 Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Sat, 27 Mar 2021 20:43:31 -0700 Subject: [PATCH 15/20] Fix treating first ARG as filename --- impls/cs.2/step6_file.cs | 9 ++++++++- impls/cs.2/step7_quote.cs | 9 ++++++++- impls/cs.2/step8_macros.cs | 9 ++++++++- impls/cs.2/step9_try.cs | 9 ++++++++- 4 files changed, 32 insertions(+), 4 deletions(-) diff --git a/impls/cs.2/step6_file.cs b/impls/cs.2/step6_file.cs index baa60a47ab..e179a10902 100644 --- a/impls/cs.2/step6_file.cs +++ b/impls/cs.2/step6_file.cs @@ -200,9 +200,16 @@ static void Main(string[] args) // ARGV List malArgs = new List(); - malArgs.AddRange(args.Select(arg => new MalString(arg)).ToList()); + malArgs.AddRange(args.Skip(1).Select(arg => new MalString(arg)).ToList()); repl_env.data.Add(new MalSymbol("*ARGV*"), new MalList(malArgs)); + // if called with arguments, treat first as a script name + if (args.Length > 0) + { + rep("(load-file \"" + args[0] + "\")"); + return; + } + // TESTS // rep("(load-file \"../../tests/computations.mal\")"); diff --git a/impls/cs.2/step7_quote.cs b/impls/cs.2/step7_quote.cs index 916faf06d7..98b6f41a2e 100644 --- a/impls/cs.2/step7_quote.cs +++ b/impls/cs.2/step7_quote.cs @@ -275,9 +275,16 @@ static void Main(string[] args) // ARGV List malArgs = new List(); - malArgs.AddRange(args.Select(arg => new MalString(arg)).ToList()); + malArgs.AddRange(args.Skip(1).Select(arg => new MalString(arg)).ToList()); repl_env.data.Add(new MalSymbol("*ARGV*"), new MalList(malArgs)); + // if called with arguments, treat first as a script name + if (args.Length > 0) + { + rep("(load-file \"" + args[0] + "\")"); + return; + } + // TESTS // rep("`[unquote 0]"); diff --git a/impls/cs.2/step8_macros.cs b/impls/cs.2/step8_macros.cs index dc5b043bd7..28bece5216 100644 --- a/impls/cs.2/step8_macros.cs +++ b/impls/cs.2/step8_macros.cs @@ -326,9 +326,16 @@ static void Main(string[] args) // ARGV List malArgs = new List(); - malArgs.AddRange(args.Select(arg => new MalString(arg)).ToList()); + malArgs.AddRange(args.Skip(1).Select(arg => new MalString(arg)).ToList()); repl_env.data.Add(new MalSymbol("*ARGV*"), new MalList(malArgs)); + // if called with arguments, treat first as a script name + if (args.Length > 0) + { + rep("(load-file \"" + args[0] + "\")"); + return; + } + // TESTS // rep("`[unquote 0]"); diff --git a/impls/cs.2/step9_try.cs b/impls/cs.2/step9_try.cs index 147982a397..fe3d6780e6 100644 --- a/impls/cs.2/step9_try.cs +++ b/impls/cs.2/step9_try.cs @@ -356,9 +356,16 @@ static void Main(string[] args) // ARGV List malArgs = new List(); - malArgs.AddRange(args.Select(arg => new MalString(arg)).ToList()); + malArgs.AddRange(args.Skip(1).Select(arg => new MalString(arg)).ToList()); repl_env.data.Add(new MalSymbol("*ARGV*"), new MalList(malArgs)); + // if called with arguments, treat first as a script name + if (args.Length > 0) + { + rep("(load-file \"" + args[0] + "\")"); + return; + } + // TESTS // rep("`[unquote 0]"); From 5d8ea72b0355d7f43d2e9cf388ef51dd17b55d2f Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Sat, 27 Mar 2021 22:11:33 -0700 Subject: [PATCH 16/20] Step A --- impls/cs.2/core.cs | 95 ++++++++- impls/cs.2/printer.cs | 3 +- impls/cs.2/reader.cs | 16 +- impls/cs.2/stepA_mal.cs | 406 ++++++++++++++++++++++++++++++++++++ impls/cs.2/stepA_mal.csproj | 9 + impls/cs.2/types.cs | 31 ++- 6 files changed, 541 insertions(+), 19 deletions(-) create mode 100644 impls/cs.2/stepA_mal.cs create mode 100644 impls/cs.2/stepA_mal.csproj diff --git a/impls/cs.2/core.cs b/impls/cs.2/core.cs index 89848b7299..a36c0098c1 100644 --- a/impls/cs.2/core.cs +++ b/impls/cs.2/core.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; namespace mal { @@ -139,7 +138,7 @@ class core MalSeq seq = (MalSeq)args[0]; MalInteger index = (MalInteger)args[1]; if (index.value > seq.items.Count - 1) throw new MalException(new MalString("index out of bounds")); - return seq.items[index.value]; + return seq.items[(int)index.value]; })}, {"first", @@ -319,6 +318,98 @@ class core return (x == MalBoolean.MAL_FALSE) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE; })}, + // Step A + {"readline", + new MalFunction((IList args) => { + Console.Write(printer.pr_str(args[0])); + string line = Console.ReadLine(); + return (line == null) ? MalNil.MAL_NIL : new MalString(line); + })}, + + {"time-ms", + new MalFunction((IList args) => { + return new MalInteger(DateTimeOffset.Now.ToUnixTimeMilliseconds()); + })}, + + {"conj", + new MalFunction((IList args) => { + MalSeq coll = (MalSeq)args[0]; + // MalType elem = args[1]; + List items = new List(); + if (coll is MalList) + { + items.AddRange(args.Skip(1).Reverse().ToList()); + items.AddRange(coll.items); + } + else + { + items.AddRange(coll.items); + items.AddRange(args.Skip(1).ToList()); + } + return (coll is MalList) ? new MalList(items) : new MalVector(items); + })}, + + {"string?", + new MalFunction((IList args) => { + return (args[0] is MalString) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE; + })}, + + {"number?", + new MalFunction((IList args) => { + return (args[0] is MalInteger) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE; + })}, + + {"fn?", + new MalFunction((IList args) => { + if (args[0] is MalFunction && !((MalFunction)args[0]).is_macro) return MalBoolean.MAL_TRUE; + if (args[0] is MalFnTco && !((MalFnTco)args[0]).is_macro) return MalBoolean.MAL_TRUE; + else return MalBoolean.MAL_FALSE; + })}, + + {"macro?", + new MalFunction((IList args) => { + if (args[0] is MalFunction && ((MalFunction)args[0]).is_macro) return MalBoolean.MAL_TRUE; + if (args[0] is MalFnTco && ((MalFnTco)args[0]).is_macro) return MalBoolean.MAL_TRUE; + else return MalBoolean.MAL_FALSE; + })}, + + {"seq", + new MalFunction((IList args) => { + if (args[0] is MalList && ((MalList)args[0]).items.Count > 0) return args[0]; + if (args[0] is MalVector && ((MalVector)args[0]).items.Count > 0) return new MalList( ((MalVector)args[0]).items ); + if (args[0] is MalString && ((MalString)args[0]).value.Length > 0) + { + string value = ((MalString)args[0]).value; + List strings = new List(); + strings.AddRange (value.ToCharArray().Select(c => new MalString(c.ToString())).ToList() ); + return new MalList(strings); + } + else return MalNil.MAL_NIL; + })}, + + {"with-meta", + new MalFunction((IList args) => { + MalType arg = args[0]; + MalType meta = args[1]; + if (arg is MalMeta) { + MalMeta orig = (MalMeta)arg; + MalMeta clone = (MalMeta)(orig.Clone()); + clone.meta = meta; + return clone; + } + return arg; + })}, + + {"meta", + new MalFunction((IList args) => { + if (args[0] is MalMeta) + { + MalMeta mm = (MalMeta)args[0]; + if (mm.meta != null) return mm.meta; + } + return MalNil.MAL_NIL; + })}, + }; } } \ No newline at end of file diff --git a/impls/cs.2/printer.cs b/impls/cs.2/printer.cs index 306382ef9d..957f104e44 100644 --- a/impls/cs.2/printer.cs +++ b/impls/cs.2/printer.cs @@ -89,6 +89,7 @@ public static string pr_str(MalType malType, bool print_readably = false) MalException ex = (MalException)malType; return string.Format(pr_str(ex.cause, print_readably)); } + else if (malType == null) return ""; else { throw new Exception(string.Format("Unknown type to print: {0}", malType.ToString())); @@ -96,4 +97,4 @@ public static string pr_str(MalType malType, bool print_readably = false) } } -} \ No newline at end of file +} diff --git a/impls/cs.2/reader.cs b/impls/cs.2/reader.cs index 376726dbf8..48dabd025c 100644 --- a/impls/cs.2/reader.cs +++ b/impls/cs.2/reader.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; namespace mal @@ -77,6 +78,11 @@ public static MalType read_form(Reader reader) { string first = reader.peek(); if (first == null) return null; // signal that we don't want to print back + if (first.StartsWith(";")){ + // drop the token if it's a comment, continue with the next token + reader.next(); + return read_form(reader); + } if (first == "^") // expect two other forms { reader.next(); // drop the '^' @@ -148,7 +154,7 @@ static MalList read_list(Reader reader) } else { - throw new Exception("Expression has unbalanced parenthesis"); + throw new MalException(new MalString("Expression has unbalanced parenthesis")); } } @@ -163,7 +169,7 @@ static MalHashmap read_hashmap(Reader reader) MalList kvs = read_list(reader); if (kvs.items.Count % 2 == 1) { - throw new Exception("Hashmap needs an even number of forms"); + throw new MalException(new MalString("Hashmap needs an even number of forms")); } Dictionary pairs = new Dictionary(); for (int i = 0; i < kvs.items.Count; i += 2) @@ -196,7 +202,7 @@ static MalType read_atom(Reader reader) } else { - throw new Exception("String contains unbalanced quotes"); + throw new MalException(new MalString("String contains unbalanced quotes")); } } try @@ -230,7 +236,7 @@ static string unescape(string input) } else { - throw new Exception("String contains unbalanced escaped characters"); + throw new MalException(new MalString("String contains unbalanced escaped characters")); } } else @@ -241,7 +247,7 @@ static string unescape(string input) } if (escaping) { - throw new Exception("String contains unbalanced escaped characters"); + throw new MalException(new MalString("String contains unbalanced escaped characters")); } return output; } diff --git a/impls/cs.2/stepA_mal.cs b/impls/cs.2/stepA_mal.cs new file mode 100644 index 0000000000..39f7d2ea0d --- /dev/null +++ b/impls/cs.2/stepA_mal.cs @@ -0,0 +1,406 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace mal +{ + class stepA_mal + { + + static MalType READ(string input) + { + return Reader.read_str(input); + } + + public static MalType EVAL(MalType ast, Env env) + { + while (true) + { + if (ast is not MalList) + { + return eval_ast(ast, env); + } + else + { + MalList astList = (MalList)ast; + + // Remove any comments from the ast + List nonComments = astList.items.Where(it => !(it is MalSymbol && ((MalSymbol)it).value.StartsWith(";"))).ToList(); + astList = new MalList(nonComments); + + if (astList.items.Count == 0) + { + return astList; + } + else + { + // Macroexpand before special forms + MalType expanded = macroexpand(astList, env); + if (expanded is not MalList) return eval_ast(expanded, env); + else + { + ast = (MalList)expanded; + astList = (MalList)expanded; + } + + MalType first = astList.items.First(); + if (first is MalSymbol) + { + MalSymbol firstSymbol = (MalSymbol)first; + if (firstSymbol.value == "def!") + { + MalSymbol symbol = (MalSymbol)astList.items[1]; + MalType value = EVAL(astList.items[2], env); + env.set(symbol, value); + return value; + } + if (firstSymbol.value == "defmacro!") + { + MalSymbol symbol = (MalSymbol)astList.items[1]; + MalType value = EVAL(astList.items[2], env); + MalType macroValue; + if (value is MalFunction) + { + macroValue = new MalFunction( ((MalFunction)value).function ); + ((MalFunction)macroValue).is_macro = true; + } + else + { + macroValue = new MalFnTco(value, new List(), env, ((MalFnTco)value).fn); + ((MalFnTco)macroValue).is_macro = true; + } + env.set(symbol, macroValue); + return value; + } + else if (firstSymbol.value == "let*") + { + MalSeq bindings = (MalSeq)astList.items[1]; + MalType expression = astList.items[2]; + Env newEnv = new Env(env); + for (int i = 0; i < bindings.items.Count; i += 2) + { + MalSymbol name = (MalSymbol)bindings.items[i]; + MalType value = EVAL(bindings.items[i + 1], newEnv); + newEnv.data.Add(name, value); + } + env = newEnv; + ast = expression; + continue; + } + else if (firstSymbol.value == "do") + { + int butLast = astList.items.Count - 2; + + // produce side-effects and then just forget + List sideEffects = astList.items.Skip(1).Take(butLast).ToList(); + eval_ast(new MalList(sideEffects), env); + + ast = astList.items.Last(); + continue; + } + else if (firstSymbol.value == "if") + { + MalType test = astList.items[1]; + MalType testResult = EVAL(test, env); + if (testResult == MalNil.MAL_NIL || testResult == MalBoolean.MAL_FALSE) + { + ast = (astList.items.Count > 3) ? astList.items[3] : MalNil.MAL_NIL; + } + else + { + ast = astList.items[2]; + } + continue; + } + else if (firstSymbol.value == "fn*") + { + MalSeq argNames = (MalSeq)astList.items[1]; + MalType funcBody = astList.items[2]; + List argSymbs = new List(); + foreach (MalType arg in argNames.items) { if (arg is MalSymbol) argSymbs.Add((MalSymbol)arg); } + MalFunction fn = new MalFunction( + (IList argValues) => + { + Env funcEnv = new Env(env, argSymbs, argValues); + return EVAL(funcBody, funcEnv); + } + ); + return new MalFnTco(funcBody, argSymbs, env, fn); + } + else if (firstSymbol.value == "quote") + { + return astList.items[1]; + } + else if (firstSymbol.value == "quasiquoteexpand") + { + return quasiquote(astList.items[1]); + } + else if (firstSymbol.value == "quasiquote") + { + ast = quasiquote(astList.items[1]); + continue; + } + else if (firstSymbol.value == "macroexpand") + { + return macroexpand(astList.items[1], env); + } + else if (firstSymbol.value == "try*") + { + try + { + return EVAL(astList.items[1], env); + } + catch (MalException exception) + { + // if a catch block exists, create a MalException and evaluate the expression of the block with it + if (astList.items.Count > 2 && astList.items[2] is MalList) + { + MalList catchArgs = (MalList)astList.items[2]; + MalSymbol exName = (MalSymbol)catchArgs.items[1]; + MalType expr = catchArgs.items[2]; + Env exEnv = new Env(env); + exEnv.set(exName, exception.cause); + return EVAL(expr, exEnv); + } + else throw new MalException(exception.cause); // otherwise just rethrow + } + } + + } + + MalType evaluated = eval_ast(ast, env); + if (evaluated is not MalList) return evaluated; + MalList evaluatedList = (MalList)evaluated; + + // Function application + MalType funcFirst = evaluatedList.items[0]; + if (funcFirst is MalFunction) + { + MalFunction func = (MalFunction)funcFirst; + MalType retVal = func.function(evaluatedList.items.Skip(1).ToList()); + return retVal; + } + else // MalFnTco + { + MalFnTco fnTco = (MalFnTco)funcFirst; + List fnArgs = evaluatedList.items.Skip(1).ToList(); + Env newEnv = new Env(fnTco.env, fnTco.@params, fnArgs); + ast = fnTco.ast; + env = newEnv; + continue; + } + } + } + } + } + + static bool is_macro_call(MalType ast, Env env) + { + if (ast is MalList) + { + MalType head = ((MalList)ast).items[0]; + if (head is MalSymbol && env.find((MalSymbol)head) != null) + { + MalType value = env.get((MalSymbol)head); + if (value is MalFunction) return ((MalFunction)value).is_macro; + if (value is MalFnTco) return ((MalFnTco)value).is_macro; + } + } + return false; + } + + static MalType macroexpand(MalType ast, Env env) + { + bool expanding; + while (expanding = is_macro_call(ast, env)) + { + MalSymbol name = (MalSymbol)((MalSeq)ast).items[0]; + List args = ((MalSeq)ast).items.Skip(1).ToList(); + MalType macroFn = env.get(name); + if (macroFn is MalFunction) ast = ((MalFunction)macroFn).function(args); + if (macroFn is MalFnTco) ast = ((MalFnTco)macroFn).fn.function(args); + } + return ast; + } + + static MalType quasiquote(MalType ast) + { + if (ast is MalSeq) + { + MalSeq astSeq = (MalSeq)ast; + if (ast is MalList && astSeq.items.Count > 0) + { + MalType head = astSeq.items[0]; + if (head is MalSymbol && ((MalSymbol)head).value == "unquote") + { + return astSeq.items[1]; + } + } + + List result = new List(); + + // Iterate over each element elt of ast in reverse order + for (int i = astSeq.items.Count - 1; i >= 0; i--) + { + MalType elt = astSeq.items[i]; + + // If elt is a list starting with the "splice-unquote" symbol + if (elt is MalList && ((MalList)elt).items.Count >= 2 && ((MalList)elt).items[0].Equals(new MalSymbol("splice-unquote"))) + { + // replace the current result with a list containing: the "concat" symbol, + // the second element of elt, then the previous result. + result = new List(){ + new MalSymbol("concat"), + ((MalList)elt).items[1], + new MalList(result) + }; + } + else + { + // replace the current result with a list containing: the "cons" symbol, + // the result of calling quasiquote with elt as argument, then the previous result + result = new List() + { + new MalSymbol("cons"), + quasiquote(elt), + new MalList(result) + }; + } + } + + if (ast is MalVector) + { + // when ast is a vector, return a list containing: the "vec" symbol, + // then the result of processing ast as if it were a list not starting with quote + result = new List() { new MalSymbol("vec"), new MalList(result) }; + } + + return new MalList(result); + } + else if (ast is MalHashmap || ast is MalSymbol) + { + // If ast is a map or a symbol, return a list containing: the "quote" symbol, then ast + return new MalList(new List() { new MalSymbol("quote"), ast }); + } + else return ast; // Else return ast unchanged + } + + static string PRINT(MalType input) + { + return printer.pr_str(input, true); + } + + static string rep(string input) + { + MalType read = READ(input); + MalType evaled; + try + { + evaled = EVAL(read, repl_env); + if (evaled == null) return null; + string printed = PRINT(evaled); + return printed; + } + catch (MalException mex) + { + return string.Format("Exception: {0}", printer.pr_str(mex.cause)); + } + } + + public static MalType eval_ast(MalType ast, Env env) + { + if (ast is MalSymbol) + { + MalSymbol astSymbol = (MalSymbol)ast; + return env.get(astSymbol); + } + else if (ast is MalList) + { + MalList astList = (MalList)ast; + List evaluated = astList.items.Select(item => EVAL(item, env)).ToList(); + return new MalList(evaluated); + } + else if (ast is MalVector) + { + MalVector astVector = (MalVector)ast; + List evaluated = astVector.items.Select(item => EVAL(item, env)).ToList(); + return new MalVector(evaluated); + } + else if (ast is MalHashmap) + { + MalHashmap astHashmap = (MalHashmap)ast; + Dictionary newKVs = new Dictionary(); + foreach (KeyValuePair kv in astHashmap.values) + { + newKVs.Add(EVAL(kv.Key, env), EVAL(kv.Value, env)); + } + return new MalHashmap(newKVs); + } + else + { + return ast; + } + } + + public static Env repl_env = new Env(null); + + static void Main(string[] args) + { + // Load the built-in functions + foreach (var pair in core.ns) { repl_env.set(new MalSymbol(pair.Key), pair.Value); } + + // Define 'eval' + repl_env.data.Add(new MalSymbol("eval"), new MalFunction((IList args) => + { + MalType ast = args[0]; + return EVAL(ast, repl_env); + })); + + // Functions defined on Mal itself: + // - define 'not' + rep("(def! not (fn* (a) (if a false true)))"); + // - define 'load-file' + rep("(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \"\nnil)\"))))))"); + 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* \"C#\")"); + + // ARGV + List malArgs = new List(); + malArgs.AddRange(args.Skip(1).Select(arg => new MalString(arg)).ToList()); + repl_env.data.Add(new MalSymbol("*ARGV*"), new MalList(malArgs)); + + // if called with arguments, treat first as a script name + if (args.Length > 0) + { + rep("(load-file \"" + args[0] + "\")"); + return; + } + + // TESTS + rep("(fn? cond)"); + + rep("(println (str \"Mal [\" *host-language* \"]\"))"); + string line = null; + do + { + Console.Write("user> "); + line = Console.ReadLine(); + if (line != null) + { + try + { + string repResult = rep(line); + if (repResult != null) Console.WriteLine(repResult); + } + catch (Exception ex) + { + + Console.WriteLine(ex.Message); + } + } + } while (line != null); + Console.WriteLine(); + } + } +} diff --git a/impls/cs.2/stepA_mal.csproj b/impls/cs.2/stepA_mal.csproj new file mode 100644 index 0000000000..c7c482bbaf --- /dev/null +++ b/impls/cs.2/stepA_mal.csproj @@ -0,0 +1,9 @@ + + + + exe + net5.0 + mal.stepA_mal + + + diff --git a/impls/cs.2/types.cs b/impls/cs.2/types.cs index 0dd89a15b8..7870d79b19 100644 --- a/impls/cs.2/types.cs +++ b/impls/cs.2/types.cs @@ -5,7 +5,17 @@ namespace mal { public interface MalType { } - public class MalSeq : MalType + public class MalMeta : MalType, ICloneable + { + public MalType meta { get; set; } + + + // Shallow clone: https://stackoverflow.com/a/2023231 + public MalMeta Clone() { return (MalMeta)this.MemberwiseClone(); } + object ICloneable.Clone() { return Clone(); } + } + + public class MalSeq : MalMeta { public IList items { get; } public string openingBracket { get; } @@ -52,10 +62,10 @@ public MalList(IList items) : base(items, "(") } } - class MalInteger : MalType + class MalInteger : MalMeta { - public int value { get; } - public MalInteger(int value) + public long value { get; } + public MalInteger(long value) { this.value = value; } @@ -76,7 +86,7 @@ public override bool Equals(object other) } } - class MalSymbol : MalType + class MalSymbol : MalMeta { public string value { get; } @@ -101,7 +111,7 @@ public override string ToString() } } - class MalString : MalType + class MalString : MalMeta { public string value { get; } @@ -126,7 +136,7 @@ public override bool Equals(object other) } } - class MalHashmap : MalType + class MalHashmap : MalMeta { public Dictionary values { get; } @@ -149,11 +159,10 @@ public override bool Equals(object other) } } - class MalFunction : MalType + class MalFunction : MalMeta { public Func, MalType> function { get; } public bool is_macro { get; set; } - public MalFunction(Func, MalType> function) { this.function = function; @@ -213,7 +222,7 @@ public override string ToString() } } - class MalFnTco : MalType + class MalFnTco : MalMeta { public MalType ast { get; set; } public List @params { get; set; } // 'params' is a reserved word @@ -230,7 +239,7 @@ public MalFnTco(MalType ast, List @params, Env env, MalFunction fn) } } - class MalAtom : MalType + class MalAtom : MalMeta { public MalType value { get; set; } From cafe116c9e1fd458c408ae60ef30261c5d1fe8a6 Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Thu, 7 Apr 2022 00:08:45 -0700 Subject: [PATCH 17/20] Updated to use .NET Core 6 --- Makefile | 2 +- impls/cs.2/Makefile | 2 +- impls/cs.2/run | 2 +- impls/cs.2/step0_repl.csproj | 2 +- impls/cs.2/step1_read_print.csproj | 2 +- impls/cs.2/step2_eval.csproj | 2 +- impls/cs.2/step3_env.csproj | 2 +- impls/cs.2/step4_if_fn_do.csproj | 2 +- impls/cs.2/step5_tco.csproj | 2 +- impls/cs.2/step6_file.csproj | 2 +- impls/cs.2/step7_quote.csproj | 2 +- impls/cs.2/step8_macros.csproj | 2 +- impls/cs.2/step9_try.csproj | 2 +- impls/cs.2/stepA_mal.csproj | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index d22c317fcf..45000002d6 100644 --- a/Makefile +++ b/Makefile @@ -199,7 +199,7 @@ common-lisp_STEP_TO_PROG = impls/common-lisp/$($(1)) cpp_STEP_TO_PROG = impls/cpp/$($(1)) crystal_STEP_TO_PROG = impls/crystal/$($(1)) cs_STEP_TO_PROG = impls/cs/$($(1)).exe -cs.2_STEP_TO_PROG = impls/cs.2/bin/Debug/net5.0/$($(1)).dll +cs.2_STEP_TO_PROG = impls/cs.2/bin/Debug/net6.0/$($(1)).dll d_STEP_TO_PROG = impls/d/$($(1)) dart_STEP_TO_PROG = impls/dart/$($(1)).dart elisp_STEP_TO_PROG = impls/elisp/$($(1)).el diff --git a/impls/cs.2/Makefile b/impls/cs.2/Makefile index f9af55d808..313626d409 100644 --- a/impls/cs.2/Makefile +++ b/impls/cs.2/Makefile @@ -1,4 +1,4 @@ -bin/Debug/net5.0/step%.dll: step%.cs +bin/Debug/net6.0/step%.dll: step%.cs dotnet build ./step$*.csproj clean: diff --git a/impls/cs.2/run b/impls/cs.2/run index 9634198abf..51cd80c749 100755 --- a/impls/cs.2/run +++ b/impls/cs.2/run @@ -1,2 +1,2 @@ #!/bin/bash -exec dotnet $(dirname $0)/bin/Debug/net5.0/${STEP:-stepA_mal}.dll "${@}" +exec dotnet $(dirname $0)/bin/Debug/net6.0/${STEP:-stepA_mal}.dll "${@}" diff --git a/impls/cs.2/step0_repl.csproj b/impls/cs.2/step0_repl.csproj index a91d0a9719..537fe3cae5 100644 --- a/impls/cs.2/step0_repl.csproj +++ b/impls/cs.2/step0_repl.csproj @@ -2,7 +2,7 @@ exe - net5.0 + net6.0 mal.step0_repl diff --git a/impls/cs.2/step1_read_print.csproj b/impls/cs.2/step1_read_print.csproj index 5715cce324..2877f0fef9 100644 --- a/impls/cs.2/step1_read_print.csproj +++ b/impls/cs.2/step1_read_print.csproj @@ -2,7 +2,7 @@ exe - net5.0 + net6.0 mal.step1_read_print diff --git a/impls/cs.2/step2_eval.csproj b/impls/cs.2/step2_eval.csproj index 5235b1d949..2d012622ac 100644 --- a/impls/cs.2/step2_eval.csproj +++ b/impls/cs.2/step2_eval.csproj @@ -2,7 +2,7 @@ exe - net5.0 + net6.0 mal.step2_eval diff --git a/impls/cs.2/step3_env.csproj b/impls/cs.2/step3_env.csproj index 28cc3f23c2..1bb564a14a 100644 --- a/impls/cs.2/step3_env.csproj +++ b/impls/cs.2/step3_env.csproj @@ -2,7 +2,7 @@ exe - net5.0 + net6.0 mal.step3_env diff --git a/impls/cs.2/step4_if_fn_do.csproj b/impls/cs.2/step4_if_fn_do.csproj index f55f5eed7f..816cc36f1e 100644 --- a/impls/cs.2/step4_if_fn_do.csproj +++ b/impls/cs.2/step4_if_fn_do.csproj @@ -2,7 +2,7 @@ exe - net5.0 + net6.0 mal.step4_if_fn_do diff --git a/impls/cs.2/step5_tco.csproj b/impls/cs.2/step5_tco.csproj index 77116216f9..5fb11e61d4 100644 --- a/impls/cs.2/step5_tco.csproj +++ b/impls/cs.2/step5_tco.csproj @@ -2,7 +2,7 @@ exe - net5.0 + net6.0 mal.step5_tco diff --git a/impls/cs.2/step6_file.csproj b/impls/cs.2/step6_file.csproj index c0f7949dc3..528f0d47c1 100644 --- a/impls/cs.2/step6_file.csproj +++ b/impls/cs.2/step6_file.csproj @@ -2,7 +2,7 @@ exe - net5.0 + net6.0 mal.step6_file diff --git a/impls/cs.2/step7_quote.csproj b/impls/cs.2/step7_quote.csproj index 09cfc46bf9..96e176df70 100644 --- a/impls/cs.2/step7_quote.csproj +++ b/impls/cs.2/step7_quote.csproj @@ -2,7 +2,7 @@ exe - net5.0 + net6.0 mal.step7_quote diff --git a/impls/cs.2/step8_macros.csproj b/impls/cs.2/step8_macros.csproj index baaa3f24cb..5155b8b788 100644 --- a/impls/cs.2/step8_macros.csproj +++ b/impls/cs.2/step8_macros.csproj @@ -2,7 +2,7 @@ exe - net5.0 + net6.0 mal.step8_macros diff --git a/impls/cs.2/step9_try.csproj b/impls/cs.2/step9_try.csproj index 7a7d2d42dd..a090bcaedd 100644 --- a/impls/cs.2/step9_try.csproj +++ b/impls/cs.2/step9_try.csproj @@ -2,7 +2,7 @@ exe - net5.0 + net6.0 mal.step9_try diff --git a/impls/cs.2/stepA_mal.csproj b/impls/cs.2/stepA_mal.csproj index c7c482bbaf..bd2943d4ea 100644 --- a/impls/cs.2/stepA_mal.csproj +++ b/impls/cs.2/stepA_mal.csproj @@ -2,7 +2,7 @@ exe - net5.0 + net6.0 mal.stepA_mal From fb62563224358604bd53e50d3504c8540a6db88e Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Sun, 10 Apr 2022 14:44:35 -0700 Subject: [PATCH 18/20] Fix MalException reporting --- impls/cs.2/step1_read_print.cs | 5 +++++ impls/cs.2/step3_env.cs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/impls/cs.2/step1_read_print.cs b/impls/cs.2/step1_read_print.cs index 92e77e5567..7fb71677e7 100644 --- a/impls/cs.2/step1_read_print.cs +++ b/impls/cs.2/step1_read_print.cs @@ -45,6 +45,11 @@ static void Main(string[] args) { Console.WriteLine(rep(line)); } + catch (MalException mex) + { + + Console.WriteLine(mex.cause); + } catch (Exception ex) { diff --git a/impls/cs.2/step3_env.cs b/impls/cs.2/step3_env.cs index 697c5b51d7..4208aec1aa 100644 --- a/impls/cs.2/step3_env.cs +++ b/impls/cs.2/step3_env.cs @@ -149,6 +149,11 @@ static void Main(string[] args) { Console.WriteLine(rep(line)); } + catch (MalException mex) + { + + Console.WriteLine(mex.cause); + } catch (Exception ex) { From 9b169b9acecf7aa27a37cfac0d95c5cca19ea1ed Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Sun, 10 Apr 2022 21:38:04 -0700 Subject: [PATCH 19/20] Added Dockerfile --- impls/cs.2/Dockerfile | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 impls/cs.2/Dockerfile diff --git a/impls/cs.2/Dockerfile b/impls/cs.2/Dockerfile new file mode 100644 index 0000000000..80701f8c9c --- /dev/null +++ b/impls/cs.2/Dockerfile @@ -0,0 +1,35 @@ +FROM ubuntu:focal +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 +########################################################## + +ENV DEBIAN_FRONTEND=noninteractive +ENV DOTNET_CLI_TELEMETRY_OPTOUT=1 + +# Install the .NET Core SDK 6.0 from the Microsoft repo +# See https://docs.microsoft.com/en-us/dotnet/core/install/linux-ubuntu +RUN curl https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -o packages-microsoft-prod.deb ; \ + dpkg -i packages-microsoft-prod.deb && \ + rm packages-microsoft-prod.deb +RUN apt-get update; \ + apt-get install -y apt-transport-https && \ + apt-get update && \ + apt-get install -y dotnet-sdk-6.0 From 1ef2f7a2805a1ae8a4536ec33b2d2651e6271ec5 Mon Sep 17 00:00:00 2001 From: Denis Fuenzalida Date: Thu, 21 Apr 2022 19:57:50 -0700 Subject: [PATCH 20/20] Updated to current master --- IMPLS.yml | 1 + Makefile.impls | 2 +- impls/cs.2/Dockerfile | 2 ++ impls/cs.2/run | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/IMPLS.yml b/IMPLS.yml index f89b099b56..c176843144 100644 --- a/IMPLS.yml +++ b/IMPLS.yml @@ -11,6 +11,7 @@ IMPL: - {IMPL: cpp} - {IMPL: coffee} - {IMPL: cs} + - {IMPL: cs.2} - {IMPL: chuck, NO_SELF_HOST_PERF: 1} # perf OOM - {IMPL: clojure, clojure_MODE: clj} - {IMPL: clojure, clojure_MODE: cljs} diff --git a/Makefile.impls b/Makefile.impls index 6ac35b23e5..d899d3c795 100644 --- a/Makefile.impls +++ b/Makefile.impls @@ -34,7 +34,7 @@ wasm_MODE = wasmtime # Implementation specific settings # -IMPLS = ada ada.2 awk bash basic bbc-basic c c.2 chuck clojure coffee common-lisp cpp crystal cs d dart \ +IMPLS = ada ada.2 awk bash basic bbc-basic c c.2 chuck clojure coffee common-lisp cpp crystal cs cs.2 d dart \ elisp elixir elm erlang es6 factor fantom fennel forth fsharp go groovy gnu-smalltalk \ guile haskell haxe hy io janet java java-truffle js jq julia kotlin livescript logo lua make mal \ matlab miniMAL nasm nim objc objpascal ocaml perl perl6 php picolisp pike plpgsql \ diff --git a/impls/cs.2/Dockerfile b/impls/cs.2/Dockerfile index 80701f8c9c..40aad5a027 100644 --- a/impls/cs.2/Dockerfile +++ b/impls/cs.2/Dockerfile @@ -33,3 +33,5 @@ RUN apt-get update; \ apt-get install -y apt-transport-https && \ apt-get update && \ apt-get install -y dotnet-sdk-6.0 + +ENV HOME /mal \ No newline at end of file diff --git a/impls/cs.2/run b/impls/cs.2/run index 51cd80c749..a6ba3d0d69 100755 --- a/impls/cs.2/run +++ b/impls/cs.2/run @@ -1,2 +1,2 @@ #!/bin/bash -exec dotnet $(dirname $0)/bin/Debug/net6.0/${STEP:-stepA_mal}.dll "${@}" +exec dotnet run --project ${STEP:-stepA_mal}.csproj "${@}" \ No newline at end of file