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/.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/Dockerfile b/impls/cs.2/Dockerfile new file mode 100644 index 0000000000..40aad5a027 --- /dev/null +++ b/impls/cs.2/Dockerfile @@ -0,0 +1,37 @@ +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 + +ENV HOME /mal \ No newline at end of file diff --git a/impls/cs.2/Makefile b/impls/cs.2/Makefile new file mode 100644 index 0000000000..313626d409 --- /dev/null +++ b/impls/cs.2/Makefile @@ -0,0 +1,5 @@ +bin/Debug/net6.0/step%.dll: step%.cs + dotnet build ./step$*.csproj + +clean: + \rm -rf bin obj diff --git a/impls/cs.2/core.cs b/impls/cs.2/core.cs new file mode 100644 index 0000000000..a36c0098c1 --- /dev/null +++ b/impls/cs.2/core.cs @@ -0,0 +1,415 @@ +using System; +using System.Collections.Generic; +using System.IO; +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) ? MalBoolean.MAL_TRUE : MalBoolean.MAL_FALSE)}, + {"empty?", new MalFunction((IList args) => + (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); + 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))}, + {"-", 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; + })}, + + // 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; + if (fnOrFnTco is MalFunction) { + newValue = ((MalFunction)fnOrFnTco).function(fnArgs); + } else { + newValue = ((MalFnTco)fnOrFnTco).fn.function(fnArgs); + } + atom.value = newValue; + return newValue; + })}, + + // Step 7 + {"cons", + new MalFunction((IList args) => { + MalType head = args[0]; + MalSeq rest = (MalSeq)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 MalSeq) + { + MalSeq argList = (MalSeq)arg; + newList.AddRange(argList.items); + } + } + return new MalList(newList); + })}, + + {"vec", + new MalFunction((IList args) => { + MalSeq head = (MalSeq)args[0]; + if (head is MalVector) return head; + 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[(int)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() ); + })}, + + // 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; + })}, + + // 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/env.cs b/impls/cs.2/env.cs new file mode 100644 index 0000000000..17a1a3ecde --- /dev/null +++ b/impls/cs.2/env.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace mal +{ + class Env + { + public Env outer { get; } + public Dictionary data { get; } + 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); } + 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 MalException(new MalString(string.Format("'{0}' not found", key.value))); + } + } + + } +} \ No newline at end of file diff --git a/impls/cs.2/printer.cs b/impls/cs.2/printer.cs new file mode 100644 index 0000000000..957f104e44 --- /dev/null +++ b/impls/cs.2/printer.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +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 MalSeq) + { + 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}", malSeq.openingBracket, joined, closing); + } + 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; + StringBuilder output = new StringBuilder(); + foreach (char c in value) + { + output.Append(ESCAPE.GetValueOrDefault(c, c.ToString())); + } + return string.Format("\"{0}\"", output.ToString()); + } + else + { + return ((MalString)malType).value; + } + } + else if (malType is MalHashmap) + { + MalHashmap hashMap = (MalHashmap)malType; + List strings = new List(); + foreach (var keyValuePair in hashMap.values) + { + 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 string.Format(":{0}", ((MalKeyword)malType).name); + } + else if (malType is MalFunction) + { + return "#"; + } + else if (malType is MalFnTco) + { + return "#"; + } + else if (malType is MalBoolean) + { + return ((MalBoolean)malType).value ? "true" : "false"; + } + else if (malType is MalNil) + { + return "nil"; + } + else if (malType is MalAtom) + { + 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 if (malType == null) 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 new file mode 100644 index 0000000000..48dabd025c --- /dev/null +++ b/impls/cs.2/reader.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Linq; +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"}, {'"', "\""}, {'\\', "\\"}, {'t', "\t"} + }; + static Dictionary QUOTING = new Dictionary() + { + {"'", "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; + + Reader(IList tokens) + { + this.tokens = tokens; + this.position = 0; + } + + string next() + { + if (position >= tokens.Count) + { + 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 == 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 '^' + 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 (first == "[") + { + return read_vector(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 + { + MalType 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); + } + else + { + throw new MalException(new MalString("Expression has unbalanced parenthesis")); + } + } + + 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); + if (kvs.items.Count % 2 == 1) + { + 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) + { + 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(":")) + { + return new MalKeyword(item.Substring(1)); + } + else if (LITERALS.ContainsKey(item)) + { + return LITERALS.GetValueOrDefault(item); + } + else 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 MalException(new MalString("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 MalException(new MalString("String contains unbalanced escaped characters")); + } + } + else + { + output += c; + } + + } + if (escaping) + { + throw new MalException(new MalString("String contains unbalanced escaped characters")); + } + return output; + } + } +} diff --git a/impls/cs.2/run b/impls/cs.2/run new file mode 100755 index 0000000000..a6ba3d0d69 --- /dev/null +++ b/impls/cs.2/run @@ -0,0 +1,2 @@ +#!/bin/bash +exec dotnet run --project ${STEP:-stepA_mal}.csproj "${@}" \ No newline at end of file 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..537fe3cae5 --- /dev/null +++ b/impls/cs.2/step0_repl.csproj @@ -0,0 +1,9 @@ + + + + exe + net6.0 + mal.step0_repl + + + diff --git a/impls/cs.2/step1_read_print.cs b/impls/cs.2/step1_read_print.cs new file mode 100644 index 0000000000..7fb71677e7 --- /dev/null +++ b/impls/cs.2/step1_read_print.cs @@ -0,0 +1,63 @@ +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]"); + rep("[+ 1 2]"); + + string line = null; + do + { + Console.Write("user> "); + line = Console.ReadLine(); + if (line != null) + { + try + { + Console.WriteLine(rep(line)); + } + catch (MalException mex) + { + + Console.WriteLine(mex.cause); + } + 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..2877f0fef9 --- /dev/null +++ b/impls/cs.2/step1_read_print.csproj @@ -0,0 +1,9 @@ + + + + exe + net6.0 + mal.step1_read_print + + + diff --git a/impls/cs.2/step2_eval.cs b/impls/cs.2/step2_eval.cs new file mode 100644 index 0000000000..4ae481a852 --- /dev/null +++ b/impls/cs.2/step2_eval.cs @@ -0,0 +1,143 @@ +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 is MalList) + { + 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); + } + 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, 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..2d012622ac --- /dev/null +++ b/impls/cs.2/step2_eval.csproj @@ -0,0 +1,9 @@ + + + + exe + net6.0 + mal.step2_eval + + + diff --git a/impls/cs.2/step3_env.cs b/impls/cs.2/step3_env.cs new file mode 100644 index 0000000000..4208aec1aa --- /dev/null +++ b/impls/cs.2/step3_env.cs @@ -0,0 +1,167 @@ +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*") + { + 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); + } + + } + + // Function application + MalList evaluated = (MalList)(eval_ast(ast, env)); + MalFunction func = (MalFunction)evaluated.items[0]; + MalType retVal = func.function(evaluated.items.Skip(1).ToList()); + return retVal; + } + } + } + + 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) + { + 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))"); + rep("(let* [z 9] z)"); + + string line = null; + do + { + Console.Write("user> "); + line = Console.ReadLine(); + if (line != null) + { + try + { + Console.WriteLine(rep(line)); + } + catch (MalException mex) + { + + Console.WriteLine(mex.cause); + } + 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..1bb564a14a --- /dev/null +++ b/impls/cs.2/step3_env.csproj @@ -0,0 +1,9 @@ + + + + exe + net6.0 + mal.step3_env + + + 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..d3c4eb1568 --- /dev/null +++ b/impls/cs.2/step4_if_fn_do.cs @@ -0,0 +1,181 @@ +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*") + { + 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") + { + 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*") + { + 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); } + return new MalFunction( + (IList argValues) => + { + Env funcEnv = new Env(env, argSymbs, argValues); + return EVAL(funcBody, funcEnv); + } + ); + } + + } + + // Function application + MalList evaluated = (MalList)(eval_ast(ast, env)); + MalFunction func = (MalFunction)evaluated.items[0]; + MalType retVal = func.function(evaluated.items.Skip(1).ToList()); + return retVal; + } + } + } + + 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); } + + // 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..816cc36f1e --- /dev/null +++ b/impls/cs.2/step4_if_fn_do.csproj @@ -0,0 +1,9 @@ + + + + exe + net6.0 + mal.step4_if_fn_do + + + diff --git a/impls/cs.2/step5_tco.cs b/impls/cs.2/step5_tco.cs new file mode 100644 index 0000000000..bee3d490b2 --- /dev/null +++ b/impls/cs.2/step5_tco.cs @@ -0,0 +1,208 @@ +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*") + { + 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 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*") + { + 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); + } + + } + + 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 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); // 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) + { + 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..5fb11e61d4 --- /dev/null +++ b/impls/cs.2/step5_tco.csproj @@ -0,0 +1,9 @@ + + + + exe + net6.0 + mal.step5_tco + + + diff --git a/impls/cs.2/step6_file.cs b/impls/cs.2/step6_file.cs new file mode 100644 index 0000000000..e179a10902 --- /dev/null +++ b/impls/cs.2/step6_file.cs @@ -0,0 +1,237 @@ +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*") + { + 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); + } + + } + + 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 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)\"))))))"); + + // 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("(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..528f0d47c1 --- /dev/null +++ b/impls/cs.2/step6_file.csproj @@ -0,0 +1,9 @@ + + + + exe + net6.0 + mal.step6_file + + + diff --git a/impls/cs.2/step7_quote.cs b/impls/cs.2/step7_quote.cs new file mode 100644 index 0000000000..98b6f41a2e --- /dev/null +++ b/impls/cs.2/step7_quote.cs @@ -0,0 +1,312 @@ +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*") + { + 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; + } + + } + + 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 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)\"))))))"); + + // 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("`[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/step7_quote.csproj b/impls/cs.2/step7_quote.csproj new file mode 100644 index 0000000000..96e176df70 --- /dev/null +++ b/impls/cs.2/step7_quote.csproj @@ -0,0 +1,9 @@ + + + + exe + net6.0 + mal.step7_quote + + + diff --git a/impls/cs.2/step8_macros.cs b/impls/cs.2/step8_macros.cs new file mode 100644 index 0000000000..28bece5216 --- /dev/null +++ b/impls/cs.2/step8_macros.cs @@ -0,0 +1,363 @@ +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.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]"); + + 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..5155b8b788 --- /dev/null +++ b/impls/cs.2/step8_macros.csproj @@ -0,0 +1,9 @@ + + + + exe + net6.0 + mal.step8_macros + + + diff --git a/impls/cs.2/step9_try.cs b/impls/cs.2/step9_try.cs new file mode 100644 index 0000000000..fe3d6780e6 --- /dev/null +++ b/impls/cs.2/step9_try.cs @@ -0,0 +1,394 @@ +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.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]"); + + 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..a090bcaedd --- /dev/null +++ b/impls/cs.2/step9_try.csproj @@ -0,0 +1,9 @@ + + + + exe + net6.0 + mal.step9_try + + + 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..bd2943d4ea --- /dev/null +++ b/impls/cs.2/stepA_mal.csproj @@ -0,0 +1,9 @@ + + + + exe + net6.0 + mal.stepA_mal + + + diff --git a/impls/cs.2/types.cs b/impls/cs.2/types.cs new file mode 100644 index 0000000000..7870d79b19 --- /dev/null +++ b/impls/cs.2/types.cs @@ -0,0 +1,261 @@ +using System; +using System.Collections.Generic; + +namespace mal +{ + public interface 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; } + public MalSeq(IList items, string openingBracket) + { + this.items = items; + this.openingBracket = openingBracket; + } + + public override string ToString() + { + return string.Format("<{0} {1}>", this.GetType().Name, items.ToString()); + } + + public override int GetHashCode() + { + return items.GetHashCode(); + } + + public override bool Equals(object other) + { + 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(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 : MalMeta + { + public long value { get; } + public MalInteger(long 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 : MalMeta + { + public string value { get; } + + 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); + } + + public override string ToString() + { + return string.Format("", value.ToString()); + } + } + + class MalString : MalMeta + { + public string value { get; } + + 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 : MalMeta + { + public Dictionary values { get; } + + public MalHashmap(Dictionary values) { this.values = values; } + + public override int GetHashCode() { return values.GetHashCode(); } + + public override bool Equals(object other) + { + 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; + MalType val = kv.Value; + if (!val.Equals(otherMap.values.GetValueOrDefault(key))) return false; + } + return true; + } + } + + class MalFunction : MalMeta + { + public Func, MalType> function { get; } + public bool is_macro { get; set; } + public MalFunction(Func, MalType> function) + { + this.function = function; + this.is_macro = false; + } + } + + class MalKeyword : MalType + { + public string name { get; } + + public MalKeyword(string name) + { + this.name = (name.StartsWith(":")) ? name.Substring(1) : name; + } + + public override int GetHashCode() + { + return name.GetHashCode(); + } + + 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()); + } + } + + class MalFnTco : MalMeta + { + 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 bool is_macro { get; set; } + + public MalFnTco(MalType ast, List @params, Env env, MalFunction fn) + { + this.ast = ast; + this.@params = @params; + this.env = env; + this.fn = fn; + } + } + + class MalAtom : MalMeta + { + public MalType value { get; set; } + + 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