diff options
| author | Joel Martin <github@martintribe.org> | 2014-04-06 22:13:14 -0500 |
|---|---|---|
| committer | Joel Martin <github@martintribe.org> | 2014-04-06 22:13:14 -0500 |
| commit | 53beaa0a6ddd9d8a5fb531f97c44a9c7129d1de7 (patch) | |
| tree | aad0aa97a9a7f54aed1cc26561aa22cea931f2fe | |
| parent | b2ff794a97a80f8acac1914c679222fda5a3355c (diff) | |
| download | mal-53beaa0a6ddd9d8a5fb531f97c44a9c7129d1de7.tar.gz mal-53beaa0a6ddd9d8a5fb531f97c44a9c7129d1de7.zip | |
CS: add step1_read_print
| -rw-r--r-- | Makefile | 5 | ||||
| -rw-r--r-- | cs/Makefile | 33 | ||||
| -rw-r--r-- | cs/printer.cs | 44 | ||||
| -rw-r--r-- | cs/reader.cs | 126 | ||||
| -rw-r--r-- | cs/readline.cs | 24 | ||||
| -rw-r--r-- | cs/step0_repl.cs | 5 | ||||
| -rw-r--r-- | cs/step1_read_print.cs | 59 | ||||
| -rw-r--r-- | cs/types.cs | 210 | ||||
| -rw-r--r-- | docs/step_notes.txt | 7 | ||||
| -rwxr-xr-x | runtest.py | 9 |
10 files changed, 495 insertions, 27 deletions
@@ -8,7 +8,7 @@ MAL_IMPL = js # Settings # -IMPLS = bash c clojure java js make php python ps mal +IMPLS = bash c clojure cs java js make php python ps mal step0 = step0_repl step1 = step1_read_print @@ -33,6 +33,7 @@ STEP_TEST_FILES = $(strip $(wildcard $(1)/tests/$($(2)).mal) $(wildcard tests/$( bash_STEP_TO_PROG = bash/$($(1)).sh c_STEP_TO_PROG = c/$($(1)) clojure_STEP_TO_PROG = clojure/src/$($(1)).clj +cs_STEP_TO_PROG = cs/$($(1)).exe java_STEP_TO_PROG = java/src/main/java/mal/$($(1)).java js_STEP_TO_PROG = js/$($(1)).js make_STEP_TO_PROG = make/$($(1)).mk @@ -45,6 +46,8 @@ mal_STEP_TO_PROG = mal/$($(1)).mal bash_RUNTEST = ../runtest.py $(4) ../$(1) -- bash ../$(2) $(5) c_RUNTEST = ../runtest.py $(4) ../$(1) -- ../$(2) $(5) clojure_RUNTEST = ../runtest.py $(4) ../$(1) -- lein with-profile +$(3) trampoline run $(5) +cs_RUNTEST = ../runtest.py --redirect $(4) ../$(1) -- mono --debug ../$(2) --raw $(5) +#cs_RUNTEST = ../runtest.py --redirect $(4) ../$(1) -- mono --debug ../$(2) $(5) java_RUNTEST = ../runtest.py $(4) ../$(1) -- mvn -quiet exec:java -Dexec.mainClass="mal.$($(3))" -Dexec.args="--raw$(if $(5), $(5),)" js_RUNTEST = ../runtest.py $(4) ../$(1) -- node ../$(2) $(5) make_RUNTEST = ../runtest.py $(4) ../$(1) -- make -f ../$(2) $(5) diff --git a/cs/Makefile b/cs/Makefile index c03e6c9..0028096 100644 --- a/cs/Makefile +++ b/cs/Makefile @@ -2,37 +2,32 @@ TESTS = -SOURCES = readline.h readline.c types.h types.c \ - reader.h reader.c printer.h printer.c \ - env.c core.h core.c interop.h interop.c \ - stepA_more.c +SOURCES = readline.cs types.cs reader.cs printer.cs \ + step0_repl.cs step1_read_print.cs +OTHER_SOURCES = getline.cs ##################### -SRCS = step0_repl.cs -OBJS = $(SRCS:%.cs=%.exe) -BINS = $(OBJS:%.o=%) -OTHER_OBJS = getline.dll +FLAGS = -debug+ + +OBJS = +LIB_SRCS = $(filter-out step%,$(OTHER_SOURCES) $(SOURCES)) ##################### -all: $(BINS) mal.exe +all: mal.exe $(patsubst %.cs,%.exe,$(filter step%,$(SOURCES))) -mal.exe: $(word $(words $(OBJS)),$(OBJS)) +mal.exe: $(patsubst %.cs,%.exe,$(word $(words $(SOURCES)),$(SOURCES))) cp $< $@ -$(OTHER_OBJS): %.dll: %.cs - mcs -target:library -out:$@ $+ - -$(OBJS): %.exe: %.cs $(OTHER_OBJS) - mcs $(foreach lib,$(OTHER_OBJS),-r:$(lib)) $(@:%.exe=%.cs) +mal.dll: $(LIB_SRCS) + mcs $(FLAGS) -target:library $+ -out:$@ -#$(patsubst %.o,%,$(filter step%,$(OBJS))): $(OTHER_OBJS) -#$(BINS): %: %.o -# gcc $+ -o $@ $(LDFLAGS) +%.exe: %.cs mal.dll + mcs $(FLAGS) -r:mal.dll $< clean: - rm -f $(OBJS) $(OTHER_OBJS) mal.exe + rm -f *.dll *.exe *.mbd .PHONY: stats tests $(TESTS) diff --git a/cs/printer.cs b/cs/printer.cs new file mode 100644 index 0000000..7ee5c03 --- /dev/null +++ b/cs/printer.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using Mal; +using MalVal = Mal.types.MalVal; +using MalList = Mal.types.MalList; + +namespace Mal { + public class printer { + public static string join(List<MalVal> value, + string delim, bool print_readably) { + List<string> strs = new List<string>(); + foreach (MalVal mv in value) { + strs.Add(mv.ToString(print_readably)); + } + return String.Join(delim, strs.ToArray()); + } + + public static string join(Dictionary<string,MalVal> value, + string delim, bool print_readably) { + List<string> strs = new List<string>(); + foreach (KeyValuePair<string, MalVal> entry in value) { + if (print_readably) { + strs.Add("\"" + entry.Key.ToString() + "\""); + } else { + strs.Add(entry.Key.ToString()); + } + strs.Add(entry.Value.ToString(print_readably)); + } + return String.Join(delim, strs.ToArray()); + } + + public static string _pr_str(MalVal mv, bool print_readably) { + return mv.ToString(print_readably); + } + + /* + public static string _pr_str_args(MalList args, String sep, + bool print_readably) { + return join(args.getList(), sep, print_readably); + } + */ + + } +} diff --git a/cs/reader.cs b/cs/reader.cs new file mode 100644 index 0000000..5e38136 --- /dev/null +++ b/cs/reader.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using Mal; +using MalVal = Mal.types.MalVal; +using MalList = Mal.types.MalList; +using MalVector = Mal.types.MalVector; +using MalHashMap = Mal.types.MalHashMap; +using MalThrowable = Mal.types.MalThrowable; +using MalContinue = Mal.types.MalContinue; + +namespace Mal { + public class reader { + public class ParseError : MalThrowable { + public ParseError(string msg) : base(msg) { } + } + + public class Reader { + List<string> tokens; + int position; + public Reader(List<string> t) { + tokens = t; + position = 0; + } + + public string peek() { + if (position >= tokens.Count) { + return null; + } else { + return tokens[position]; + } + } + public string next() { + return tokens[position++]; + } + } + + public static List<string> tokenize(string str) { + List<string> tokens = new List<string>(); + string pattern = @"[\s ,]*(~@|[\[\]{}()'`~@]|""(?:[\\].|[^\\""])*""|;.*|[^\s \[\]{}()'""`~@,;]*)"; + Regex regex = new Regex(pattern); + foreach (Match match in regex.Matches(str)) { + string token = match.Groups[1].Value; + if ((token != null) && !(token == "") && !(token[0] == ';')) { + //Console.WriteLine("match: ^" + match.Groups[1] + "$"); + tokens.Add(token); + } + } + return tokens; + } + + public static MalVal read_atom(Reader rdr) { + string token = rdr.next(); + string pattern = @"(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^""(.*)""$|(^[^""]*$)"; + Regex regex = new Regex(pattern); + Match match = regex.Match(token); + //Console.WriteLine("token: ^" + token + "$"); + if (!match.Success) { + throw new ParseError("unrecognized token '" + token + "'"); + } + if (match.Groups[1].Value != String.Empty) { + return new Mal.types.MalInteger(int.Parse(match.Groups[1].Value)); + } else if (match.Groups[3].Value != String.Empty) { + return Mal.types.Nil; + } else if (match.Groups[4].Value != String.Empty) { + return Mal.types.True; + } else if (match.Groups[5].Value != String.Empty) { + return Mal.types.False; + } else if (match.Groups[6].Value != String.Empty) { + //return new MalString(StringEscapeUtils.unescapeJson(match.Groups[6])); + return new Mal.types.MalString(match.Groups[6].Value); + } else if (match.Groups[7].Value != String.Empty) { + return new Mal.types.MalSymbol(match.Groups[7].Value); + } else { + throw new ParseError("unrecognized '" + match.Groups[0] + "'"); + } + } + + public static MalVal read_list(Reader rdr, MalList lst, char start, char end) { + string token = rdr.next(); + if (token[0] != start) { + throw new ParseError("expected '" + start + "'"); + } + + while ((token = rdr.peek()) != null && token[0] != end) { + lst.conj_BANG(read_form(rdr)); + } + + if (token == null) { + throw new ParseError("expected '" + end + "', got EOF"); + } + rdr.next(); + + return lst; + } + + public static MalVal read_hash_map(Reader rdr) { + MalList lst = (MalList)read_list(rdr, new MalList(), '{', '}'); + return new MalHashMap(lst); + } + + + public static MalVal read_form(Reader rdr) { + string token = rdr.peek(); + if (token == null) { throw new MalContinue(); } + MalVal form = null; + + switch (token[0]) { + case '(': form = read_list(rdr, new MalList(), '(' , ')'); break; + case ')': throw new ParseError("unexpected ')'"); + case '[': form = read_list(rdr, new MalVector(), '[' , ']'); break; + case ']': throw new ParseError("unexpected ']'"); + case '{': form = read_hash_map(rdr); break; + case '}': throw new ParseError("unexpected '}'"); + default: form = read_atom(rdr); break; + } + return form; + } + + + public static MalVal read_str(string str) { + return read_form(new Reader(tokenize(str))); + } + } +} diff --git a/cs/readline.cs b/cs/readline.cs new file mode 100644 index 0000000..968e6f8 --- /dev/null +++ b/cs/readline.cs @@ -0,0 +1,24 @@ +using System; +using Mono.Terminal; // LineEditor (getline.cs) + +namespace Mal { + public class readline { + public enum Mode { Terminal, Raw }; + public static Mode mode = Mode.Terminal; + + static LineEditor lineedit = null; + + public static string Readline(string prompt) { + if (mode == Mode.Terminal) { + if (lineedit == null) { + lineedit = new LineEditor("Mal"); + } + return lineedit.Edit(prompt, ""); + } else { + Console.Write(prompt); + Console.Out.Flush(); + return Console.ReadLine(); + } + } + } +} diff --git a/cs/step0_repl.cs b/cs/step0_repl.cs index 665aab6..de7a6de 100644 --- a/cs/step0_repl.cs +++ b/cs/step0_repl.cs @@ -1,6 +1,6 @@ using System; using System.IO; -using Mono.Terminal; +using Mal; namespace Mal { class step0_repl { @@ -26,12 +26,11 @@ namespace Mal { static void Main(string[] args) { string prompt = "user> "; - LineEditor lineedit = new LineEditor("Mal"); while (true) { string line; try { - line = lineedit.Edit(prompt, ""); + line = Mal.readline.Readline(prompt); if (line == null) { break; } } catch (IOException e) { Console.WriteLine("IOException: " + e.Message); diff --git a/cs/step1_read_print.cs b/cs/step1_read_print.cs new file mode 100644 index 0000000..d06071c --- /dev/null +++ b/cs/step1_read_print.cs @@ -0,0 +1,59 @@ +using System; +using System.IO; +using Mal; +using MalVal = Mal.types.MalVal; + +namespace Mal { + class step1_repl { + // read + static MalVal READ(string str) { + return reader.read_str(str); + } + + // eval + static MalVal EVAL(MalVal ast, string env) { + return ast; + } + + // print + static string PRINT(MalVal exp) { + return printer._pr_str(exp, true); + } + + // REPL + static MalVal RE(string env, string str) { + return EVAL(READ(str), env); + } + + static void Main(string[] args) { + string prompt = "user> "; + + if (args.Length > 0 && args[0] == "--raw") { + Mal.readline.mode = Mal.readline.Mode.Raw; + } + + while (true) { + string line; + try { + line = Mal.readline.Readline(prompt); + if (line == null) { break; } + } catch (IOException e) { + Console.WriteLine("IOException: " + e.Message); + break; + } + try { + Console.WriteLine(PRINT(RE(null, line))); + } catch (Mal.types.MalContinue) { + continue; + } catch (Mal.types.MalError e) { + Console.WriteLine("Error: " + e.Message); + continue; + } catch (Mal.reader.ParseError e) { + Console.WriteLine(e.Message); + continue; + + } + } + } + } +} diff --git a/cs/types.cs b/cs/types.cs new file mode 100644 index 0000000..1f2e797 --- /dev/null +++ b/cs/types.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using Mal; + +namespace Mal { + public class types { + public class MalThrowable : Exception { + public MalThrowable() : base() { } + public MalThrowable(string msg) : base(msg) { } + } + public class MalError : MalThrowable { + public MalError(string msg) :base(msg) { } + } + public class MalContinue : MalThrowable { } + + + public abstract class MalVal { + // Default is just to call regular toString() + public abstract string ToString(bool print_readably); + } + + public class MalConstant : MalVal { + string value; + public MalConstant(string name) { value = name; } + public MalConstant copy() { return this; } + + public override string ToString() { + return value; + } + public override string ToString(bool print_readably) { + return value; + } + } + + static public MalConstant Nil = new MalConstant("nil"); + static public MalConstant True = new MalConstant("true"); + static public MalConstant False = new MalConstant("false"); + + public class MalInteger : MalVal { + int value; + public MalInteger(int v) { value = v; } + public MalInteger copy() { return this; } + + public int getValue() { return value; } + public override string ToString() { + return value.ToString(); + } + public override string ToString(bool print_readably) { + return value.ToString(); + } + public MalInteger add(MalInteger other) { + return new MalInteger(value + other.getValue()); + } + public MalInteger subtract(MalInteger other) { + return new MalInteger(value - other.getValue()); + } + public MalInteger multiply(MalInteger other) { + return new MalInteger(value * other.getValue()); + } + public MalInteger divide(MalInteger other) { + return new MalInteger(value / other.getValue()); + } + public MalConstant lt(MalInteger other) { + return (value < other.getValue()) ? True : False; + } + public MalConstant lte(MalInteger other) { + return (value <= other.getValue()) ? True : False; + } + public MalConstant gt(MalInteger other) { + return (value > other.getValue()) ? True : False; + } + public MalConstant gte(MalInteger other) { + return (value >= other.getValue()) ? True : False; + } + } + + public class MalSymbol : MalVal { + string value; + public MalSymbol(string v) { value = v; } + public MalSymbol copy() { return this; } + + public string getName() { return value; } + public override string ToString() { + return value; + } + public override string ToString(bool print_readably) { + return value; + } + } + + public class MalString : MalVal { + string value; + public MalString(string v) { value = v; } + public MalString copy() { return this; } + + public string getValue() { return value; } + public override string ToString() { + return "\"" + value + "\""; + } + public override string ToString(bool print_readably) { + if (print_readably) { + //return "\"" + printer.escapeString(value) + "\""; + return "\"" + value + "\""; + } else { + return value; + } + } + } + + + + public class MalList : MalVal { + public string start = "(", end = ")"; + List<MalVal> value; + public MalList() { + value = new List<MalVal>(); + } + public MalList(List<MalVal> val) { + value = val; + } + public MalList(params MalVal[] mvs) { + conj_BANG(mvs); + } + + public MalList copy() { + return (MalList)this.MemberwiseClone(); + } + + public List<MalVal> getValue() { return value; } + + public override string ToString() { + return start + printer.join(value, " ", true) + end; + } + public override string ToString(bool print_readably) { + return start + printer.join(value, " ", print_readably) + end; + } + + public MalList conj_BANG(params MalVal[] mvs) { + for (int i = 0; i < mvs.Length; i++) { + value.Add(mvs[i]); + } + return this; + } + } + + public class MalVector : MalList { + // Same implementation except for instantiation methods + public MalVector() :base() { + start = "["; + end = "]"; + } + public MalVector(List<MalVal> val) + :base(val) { + start = "["; + end = "]"; + } + /* + public MalVector(MalVal[] mvs) : base(mvs.ToArray()) { + start = "["; + end = "]"; + } + */ + public new MalVector copy() { + return (MalVector)this.MemberwiseClone(); + } + + } + + public class MalHashMap : MalVal { + Dictionary<string, MalVal> value; + public MalHashMap(Dictionary<string, MalVal> val) { + value = val; + } + public MalHashMap(MalList lst) { + value = new Dictionary<String, MalVal>(); + assoc_BANG(lst.getValue().ToArray()); + } + /* + public MalHashMap(params MalVal[] mvs) { + value = new Dictionary<String, MalVal>(); + assoc_BANG(mvs); + } + */ + public MalHashMap copy() { + return (MalHashMap)this.MemberwiseClone(); + } + + public override string ToString() { + return "{" + printer.join(value, " ", true) + "}"; + } + public override string ToString(Boolean print_readably) { + return "{" + printer.join(value, " ", print_readably) + "}"; + } + + /* + public Set _entries() { + return value.entrySet(); + } + */ + + public MalHashMap assoc_BANG(params MalVal[] mvs) { + for (int i=0; i<mvs.Length; i+=2) { + value.Add(((MalString)mvs[i]).getValue(), mvs[i+1]); + } + return this; + } + } + + } +} diff --git a/docs/step_notes.txt b/docs/step_notes.txt index b5b1c6f..c596a33 100644 --- a/docs/step_notes.txt +++ b/docs/step_notes.txt @@ -17,9 +17,6 @@ Step Notes: - types module: - add boxed types if no language equivalent: - nil, true, false, symbol, integer, string, list - - pr_str: - - stringify boxed types to their Mal representations - - list/array is recursive - reader module: - stateful reader object - alternative: mutate token list @@ -36,6 +33,10 @@ Step Notes: - read_atom - return scalar boxed type: - nil, true, false, symbol, integer, string + - printer module: + - _pr_str: + - stringify boxed types to their Mal representations + - list/array is recursive - repl loop - catch errors, print them and continue - impls without exception handling will need to have a global @@ -18,6 +18,8 @@ parser.add_argument('--start-timeout', default=10, type=int, help="default timeout for initial prompt") parser.add_argument('--test-timeout', default=20, type=int, help="default timeout for each individual test action") +parser.add_argument('--redirect', action='store_true', + help="Run implementation in bash and redirect output to /dev/null") parser.add_argument('test_file', type=argparse.FileType('r'), help="a test file formatted as with mal test data") @@ -30,7 +32,12 @@ test_data = args.test_file.read().split('\n') if args.rundir: os.chdir(args.rundir) -p = spawn(args.mal_cmd[0], args.mal_cmd[1:]) +if args.redirect: + # Redirect to try and force raw mode (no ASCII codes) + p = spawn('/bin/bash -c "' + " ".join(args.mal_cmd) + ' |tee /dev/null"') +else: + p = spawn(args.mal_cmd[0], args.mal_cmd[1:]) + test_idx = 0 def read_test(data): |
