aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Martin <github@martintribe.org>2014-04-06 22:13:14 -0500
committerJoel Martin <github@martintribe.org>2014-04-06 22:13:14 -0500
commit53beaa0a6ddd9d8a5fb531f97c44a9c7129d1de7 (patch)
treeaad0aa97a9a7f54aed1cc26561aa22cea931f2fe
parentb2ff794a97a80f8acac1914c679222fda5a3355c (diff)
downloadmal-53beaa0a6ddd9d8a5fb531f97c44a9c7129d1de7.tar.gz
mal-53beaa0a6ddd9d8a5fb531f97c44a9c7129d1de7.zip
CS: add step1_read_print
-rw-r--r--Makefile5
-rw-r--r--cs/Makefile33
-rw-r--r--cs/printer.cs44
-rw-r--r--cs/reader.cs126
-rw-r--r--cs/readline.cs24
-rw-r--r--cs/step0_repl.cs5
-rw-r--r--cs/step1_read_print.cs59
-rw-r--r--cs/types.cs210
-rw-r--r--docs/step_notes.txt7
-rwxr-xr-xruntest.py9
10 files changed, 495 insertions, 27 deletions
diff --git a/Makefile b/Makefile
index d38e2d5..bea1604 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/runtest.py b/runtest.py
index 736768a..8a3f26d 100755
--- a/runtest.py
+++ b/runtest.py
@@ -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):