C#: add stepA_more and core functions.
authorJoel Martin <github@martintribe.org>
Thu, 10 Apr 2014 02:26:35 +0000 (21:26 -0500)
committerJoel Martin <github@martintribe.org>
Thu, 10 Apr 2014 02:26:35 +0000 (21:26 -0500)
cs/Makefile
cs/core.cs
cs/stepA_more.cs [new file with mode: 0644]
cs/types.cs

index e105786..3236bd6 100644 (file)
@@ -3,7 +3,7 @@
 TESTS =
 
 SOURCES = readline.cs types.cs reader.cs printer.cs env.cs core.cs \
-         step8_macros.cs
+         stepA_more.cs
 
 OTHER_SOURCES = getline.cs
 
@@ -11,7 +11,7 @@ OTHER_SOURCES = getline.cs
 
 SRCS = step0_repl.cs step1_read_print.cs step2_eval.cs step3_env.cs \
        step4_if_fn_do.cs step5_tco.cs step6_file.cs step7_quote.cs \
-       step8_macros.cs
+       step8_macros.cs stepA_more.cs
 
 LIB_SRCS = $(filter-out step%,$(OTHER_SOURCES) $(SOURCES))
 
index c5b9e10..f762075 100644 (file)
@@ -3,8 +3,11 @@ using System.Collections.Generic;
 using MalVal = Mal.types.MalVal;
 using MalConstant = Mal.types.MalConstant;
 using MalInteger = Mal.types.MalInteger;
+using MalSymbol = Mal.types.MalSymbol;
 using MalString = Mal.types.MalString;
 using MalList = Mal.types.MalList;
+using MalVector = Mal.types.MalVector;
+using MalHashMap = Mal.types.MalHashMap;
 using MalFunction = Mal.types.MalFunction;
 
 namespace Mal {
@@ -13,6 +16,24 @@ namespace Mal {
         static MalConstant True = Mal.types.True;
         static MalConstant False = Mal.types.False;
 
+        // Errors/Exceptions
+        static public MalFunction mal_throw = new MalFunction(
+            a => { throw new Mal.types.MalException(a[0]); });
+
+        // Scalar functions
+        static MalFunction nil_Q = new MalFunction(
+            a => a[0] == Nil ? True : False);
+
+        static MalFunction true_Q = new MalFunction(
+            a => a[0] == True ? True : False);
+
+        static MalFunction false_Q = new MalFunction(
+            a => a[0] == False ? True : False);
+
+        static MalFunction symbol_Q = new MalFunction(
+            a => a[0] is MalSymbol ? True : False);
+
+
         // String functions
         static public MalFunction pr_str = new MalFunction(
             a => new MalString(printer._pr_str_args(a, " ", true)) );
@@ -32,12 +53,66 @@ namespace Mal {
                 return Nil;
             } );
 
-        // Sequence functions
+        // List/Vector functions
         static public MalFunction list_Q = new MalFunction(
             a => a[0].GetType() == typeof(MalList) ? True : False);
 
-        static MalFunction nth = new MalFunction(
-            a => ((MalList)a[0])[ ((MalInteger)a[1]).getValue() ]);
+        static public MalFunction vector_Q = new MalFunction(
+            a => a[0].GetType() == typeof(MalVector) ? True : False);
+
+        // HashMap functions
+        static public MalFunction hash_map_Q = new MalFunction(
+            a => a[0].GetType() == typeof(MalHashMap) ? True : False);
+
+        static MalFunction contains_Q = new MalFunction(
+            a => {
+                string key = ((MalString)a[1]).getValue();
+                var dict = ((MalHashMap)a[0]).getValue();
+                return dict.ContainsKey(key) ? True : False;
+            });
+
+        static MalFunction assoc = new MalFunction(
+            a => {
+                var new_hm = ((MalHashMap)a[0]).copy();
+                return new_hm.assoc_BANG((MalList)a.slice(1));
+            });
+
+        static MalFunction dissoc = new MalFunction(
+            a => {
+                var new_hm = ((MalHashMap)a[0]).copy();
+                return new_hm.dissoc_BANG((MalList)a.slice(1));
+            });
+
+        static MalFunction get = new MalFunction(
+            a => {
+                string key = ((MalString)a[1]).getValue();
+                var dict = ((MalHashMap)a[0]).getValue();
+                return dict.ContainsKey(key) ? dict[key] : Nil;
+            });
+
+        static MalFunction keys = new MalFunction(
+            a => {
+                var dict = ((MalHashMap)a[0]).getValue();
+                MalList key_lst = new MalList();
+                foreach (var key in dict.Keys) {
+                    key_lst.conj_BANG(new MalString(key));
+                }
+                return key_lst;
+            });
+
+        static MalFunction vals = new MalFunction(
+            a => {
+                var dict = ((MalHashMap)a[0]).getValue();
+                MalList val_lst = new MalList();
+                foreach (var val in dict.Values) {
+                    val_lst.conj_BANG(val);
+                }
+                return val_lst;
+            });
+
+        // Sequence functions
+        static public MalFunction sequential_Q = new MalFunction(
+            a => a[0] is MalList ? True : False);
 
         static MalFunction cons = new MalFunction(
             a => {
@@ -58,6 +133,61 @@ namespace Mal {
                 return (MalVal)new MalList(lst);
             });
 
+        static MalFunction nth = new MalFunction(
+            a => ((MalList)a[0])[ ((MalInteger)a[1]).getValue() ]);
+
+        static MalFunction first = new MalFunction(
+            a => ((MalList)a[0])[0]);
+
+        static MalFunction rest = new MalFunction(
+            a => ((MalList)a[0]).rest());
+
+        static MalFunction empty_Q = new MalFunction(
+            a => ((MalList)a[0]).size() == 0 ? True : False);
+
+        static MalFunction count = new MalFunction(
+            a => new MalInteger(((MalList)a[0]).size()));
+
+        static MalFunction conj = new MalFunction(
+            a => {
+                var src_lst = ((MalList)a[0]).getValue();
+                var new_lst = new List<MalVal>();
+                new_lst.AddRange(src_lst);
+                if (a[0] is MalVector) {
+                    for(int i=1; i<a.size(); i++) {
+                        new_lst.Add(a[i]);
+                    }
+                    return new MalVector(new_lst);
+                } else {
+                    for(int i=1; i<a.size(); i++) {
+                        new_lst.Insert(0, a[i]);
+                    }
+                    return new MalList(new_lst);
+                }
+            });
+
+
+        // General list related functions
+        static MalFunction apply = new MalFunction(
+            a => {
+                var f = (MalFunction)a[0];
+                var lst = new List<MalVal>();
+                lst.AddRange(a.slice(1,a.size()-1).getValue());
+                lst.AddRange(((MalList)a[a.size()-1]).getValue());
+                return f.apply(new MalList(lst));
+            });
+
+        static MalFunction map = new MalFunction(
+            a => {
+                MalFunction f = (MalFunction) a[0];
+                var src_lst = ((MalList)a[1]).getValue();
+                var new_lst = new List<MalVal>();
+                for(int i=0; i<src_lst.Count; i++) {
+                    new_lst.Add(f.apply(new MalList(src_lst[i])));
+                }
+                return new MalList(new_lst);
+            });
+
 
 
 
@@ -65,6 +195,11 @@ namespace Mal {
                   new Dictionary<string, MalVal> {
             {"=",  new MalFunction(
                 a => Mal.types._equal_Q(a[0], a[1]) ? True : False)},
+            {"throw", mal_throw},
+            {"nil?", nil_Q},
+            {"true?", true_Q},
+            {"false?", false_Q},
+            {"symbol?", symbol_Q},
             {"pr-str", pr_str},
             {"str", str},
             {"prn", prn},
@@ -80,16 +215,28 @@ namespace Mal {
 
             {"list",  new MalFunction(a => new MalList(a.getValue()))},
             {"list?", list_Q},
+            {"vector",  new MalFunction(a => new MalVector(a.getValue()))},
+            {"vector?", vector_Q},
+            {"hash-map",  new MalFunction(a => new MalHashMap(a))},
+            {"map?", hash_map_Q},
+            {"contains?", contains_Q},
+            {"assoc", assoc},
+            {"dissoc", dissoc},
+            {"get", get},
+            {"keys", keys},
+            {"vals", vals},
 
+            {"sequential?", sequential_Q},
             {"cons", cons},
             {"concat", concat},
             {"nth", nth},
-            {"first", new MalFunction(a => ((MalList)a[0])[0])},
-            {"rest",  new MalFunction(a => ((MalList)a[0]).rest())},
-            {"empty?", new MalFunction(
-                a => ((MalList)a[0]).size() == 0 ? True : False)},
-            {"count", new MalFunction(
-                a => new MalInteger(((MalList)a[0]).size()))},
+            {"first", first},
+            {"rest",  rest},
+            {"empty?", empty_Q},
+            {"count", count},
+            {"conj", conj},
+            {"apply", apply},
+            {"map", map},
         };
     }
 }
diff --git a/cs/stepA_more.cs b/cs/stepA_more.cs
new file mode 100644 (file)
index 0000000..97b1e84
--- /dev/null
@@ -0,0 +1,296 @@
+using System;
+using System.IO;
+using System.Collections;
+using System.Collections.Generic;
+using Mal;
+using MalException = Mal.types.MalException;
+using MalVal = Mal.types.MalVal;
+using MalString = Mal.types.MalString;
+using MalSymbol = Mal.types.MalSymbol;
+using MalInteger = Mal.types.MalInteger;
+using MalList = Mal.types.MalList;
+using MalVector = Mal.types.MalVector;
+using MalHashMap = Mal.types.MalHashMap;
+using MalFunction = Mal.types.MalFunction;
+using Env = Mal.env.Env;
+
+namespace Mal {
+    class stepA_more {
+        // read
+        static MalVal READ(string str) {
+            return reader.read_str(str);
+        }
+
+        // eval
+        public static bool is_pair(MalVal x) {
+            return x is MalList && ((MalList)x).size() > 0;
+        }
+
+        public static MalVal quasiquote(MalVal ast) {
+            if (!is_pair(ast)) {
+                return new MalList(new MalSymbol("quote"), ast);
+            } else {
+                MalVal a0 = ((MalList)ast)[0];
+                if ((a0 is MalSymbol) &&
+                    (((MalSymbol)a0).getName() == "unquote")) {
+                    return ((MalList)ast)[1];
+                } else if (is_pair(a0)) {
+                    MalVal a00 = ((MalList)a0)[0];
+                    if ((a00 is MalSymbol) &&
+                        (((MalSymbol)a00).getName() == "splice-unquote")) {
+                        return new MalList(new MalSymbol("concat"),
+                                           ((MalList)a0)[1],
+                                           quasiquote(((MalList)ast).rest()));
+                    }
+                }
+                return new MalList(new MalSymbol("cons"),
+                                   quasiquote(a0),
+                                   quasiquote(((MalList)ast).rest()));
+            }
+        }
+
+        public static bool is_macro_call(MalVal ast, Env env) {
+            if (ast is MalList) {
+                MalVal a0 = ((MalList)ast)[0];
+                if (a0 is MalSymbol &&
+                    env.find(((MalSymbol)a0).getName()) != null) {
+                    MalVal mac = env.get(((MalSymbol)a0).getName());
+                    if (mac is MalFunction &&
+                        ((MalFunction)mac).isMacro()) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        public static MalVal macroexpand(MalVal ast, Env env) {
+            while (is_macro_call(ast, env)) {
+                MalSymbol a0 = (MalSymbol)((MalList)ast)[0];
+                MalFunction mac = (MalFunction) env.get(a0.getName());
+                ast = mac.apply(((MalList)ast).rest());
+            }
+            return ast;
+        }
+
+        static MalVal eval_ast(MalVal ast, Env env) {
+            if (ast is MalSymbol) {
+                MalSymbol sym = (MalSymbol)ast;
+                return env.get(sym.getName());
+            } else if (ast is MalList) {
+                MalList old_lst = (MalList)ast;
+                MalList new_lst = ast.list_Q() ? new MalList()
+                                            : (MalList)new MalVector();
+                foreach (MalVal mv in old_lst.getValue()) {
+                    new_lst.conj_BANG(EVAL(mv, env));
+                }
+                return new_lst;
+            } else if (ast is MalHashMap) {
+                var new_dict = new Dictionary<string, MalVal>();
+                foreach (var entry in ((MalHashMap)ast).getValue()) {
+                    new_dict.Add(entry.Key, EVAL((MalVal)entry.Value, env));
+                }
+                return new MalHashMap(new_dict);
+            } else {
+                return ast;
+            }
+        }
+
+
+        static MalVal EVAL(MalVal orig_ast, Env env) {
+            MalVal a0, a1, a2, res;
+            MalList el;
+
+            while (true) {
+
+            //System.out.println("EVAL: " + printer._pr_str(orig_ast, true));
+            if (!orig_ast.list_Q()) {
+                return eval_ast(orig_ast, env);
+            }
+
+            // apply list
+            MalVal expanded = macroexpand(orig_ast, env);
+            if (!expanded.list_Q()) { return expanded; } 
+            MalList ast = (MalList) expanded;
+
+            if (ast.size() == 0) { return ast; }
+            a0 = ast[0];
+
+            String a0sym = a0 is MalSymbol ? ((MalSymbol)a0).getName()
+                                           : "__<*fn*>__";
+
+            switch (a0sym) {
+            case "def!":
+                a1 = ast[1];
+                a2 = ast[2];
+                res = EVAL(a2, env);
+                env.set(((MalSymbol)a1).getName(), res);
+                return res;
+            case "let*":
+                a1 = ast[1];
+                a2 = ast[2];
+                MalSymbol key;
+                MalVal val;
+                Env let_env = new Env(env);
+                for(int i=0; i<((MalList)a1).size(); i+=2) {
+                    key = (MalSymbol)((MalList)a1)[i];
+                    val = ((MalList)a1)[i+1];
+                    let_env.set(key.getName(), EVAL(val, let_env));
+                }
+                return EVAL(a2, let_env);
+            case "quote":
+                return ast[1];
+            case "quasiquote":
+                return EVAL(quasiquote(ast[1]), env);
+            case "defmacro!":
+                a1 = ast[1];
+                a2 = ast[2];
+                res = EVAL(a2, env);
+                ((MalFunction)res).setMacro();
+                env.set(((MalSymbol)a1).getName(), res);
+                return res;
+            case "macroexpand":
+                a1 = ast[1];
+                return macroexpand(a1, env);
+            case "try*":
+                try {
+                    return EVAL(ast[1], env);
+                } catch (Exception e) {
+                    if (ast.size() > 2) {
+                        MalVal exc;
+                        a2 = ast[2];
+                        MalVal a20 = ((MalList)a2)[0];
+                        if (((MalSymbol)a20).getName() == "catch*") {
+                            if (e is MalException) {
+                                exc = ((MalException)e).getValue();
+                            } else {
+                                exc = new MalString(e.StackTrace);
+                            }
+                            return EVAL(((MalList)a2)[2],
+                                        new Env(env, ((MalList)a2).slice(1,2),
+                                                new MalList(exc)));
+                        }
+                    }
+                    throw e;
+                }
+            case "do":
+                eval_ast(ast.slice(1, ast.size()-1), env);
+                orig_ast = ast[ast.size()-1];
+                break;
+            case "if":
+                a1 = ast[1];
+                MalVal cond = EVAL(a1, env);
+                if (cond == types.Nil || cond == types.False) {
+                    // eval false slot form
+                    if (ast.size() > 3) {
+                        orig_ast = ast[3];
+                    } else {
+                        return types.Nil;
+                    }
+                } else {
+                    // eval true slot form
+                    orig_ast = ast[2];
+                }
+                break;
+            case "fn*":
+                MalList a1f = (MalList)ast[1];
+                MalVal a2f = ast[2];
+                Env cur_env = env;
+                return new MalFunction(a2f, env, a1f,
+                    args => EVAL(a2f, new Env(cur_env, a1f, args)) );
+            default:
+                el = (MalList)eval_ast(ast, env);
+                var f = (MalFunction)el[0];
+                MalVal fnast = f.getAst();
+                if (fnast != null) {
+                    orig_ast = fnast;
+                    env = f.genEnv(el.rest());
+                } else {
+                    return f.apply(el.rest());
+                }
+                break;
+            }
+
+            }
+        }
+
+        // print
+        static string PRINT(MalVal exp) {
+            return printer._pr_str(exp, true);
+        }
+
+        // REPL
+        static MalVal RE(Env env, string str) {
+            return EVAL(READ(str), env);
+        }
+        public static Env _ref(Env env, string name, MalVal mv) {
+            return env.set(name, mv);
+        }
+
+
+        static void Main(string[] args) {
+            string prompt = "user> ";
+            
+            var repl_env = new env.Env(null);
+            foreach (var entry in core.ns) {
+                _ref(repl_env, entry.Key, entry.Value);
+            }
+            _ref(repl_env, "readline", new MalFunction(
+                a => {
+                    var line = readline.Readline(((MalString)a[0]).getValue());
+                    if (line == null) { return types.Nil; }
+                    else {              return new MalString(line); }
+                }));
+            _ref(repl_env, "read-string", new MalFunction(
+                a => reader.read_str(((MalString)a[0]).getValue())));
+            _ref(repl_env, "eval", new MalFunction(
+                a => EVAL(a[0], repl_env)));
+            _ref(repl_env, "slurp", new MalFunction(
+                a => new MalString(File.ReadAllText(
+                          ((MalString)a[0]).getValue()))));
+
+            RE(repl_env, "(def! not (fn* (a) (if a false true)))");
+            RE(repl_env, "(defmacro! cond (fn* (& xs) (if (> (count xs) 0) (list 'if (first xs) (if (> (count xs) 1) (nth xs 1) (throw \"odd number of forms to cond\")) (cons 'cond (rest (rest xs)))))))");
+            RE(repl_env, "(defmacro! or (fn* (& xs) (if (empty? xs) nil (if (= 1 (count xs)) (first xs) `(let* (or_FIXME ~(first xs)) (if or_FIXME or_FIXME (or ~@(rest xs))))))))");
+            RE(repl_env, "(def! load-file (fn* (f) (eval (read-string (str \"(do \" (slurp f) \")\")))))");
+
+            int fileIdx = 0;
+            if (args.Length > 0 && args[0] == "--raw") {
+                readline.mode = readline.Mode.Raw;
+                fileIdx = 1;
+            }
+            if (args.Length > fileIdx) {
+                for(int i=fileIdx; i<args.Length; i++) {
+                    RE(repl_env, "(load-file \"" + args[i] + "\")");
+                }
+                return;
+            }
+            while (true) {
+                string line;
+                try {
+                    line = readline.Readline(prompt);
+                    if (line == null) { break; }
+                } catch (IOException e) {
+                    Console.WriteLine("IOException: " + e.Message);
+                    break;
+                }
+                try {
+                    Console.WriteLine(PRINT(RE(repl_env, line)));
+                } catch (types.MalContinue) {
+                    continue;
+                } catch (reader.ParseError e) {
+                    Console.WriteLine(e.Message);
+                    continue;
+                } catch (MalException e) {
+                    Console.WriteLine("Error: " +
+                            printer._pr_str(e.getValue(), false));
+                    continue;
+                } catch (Exception e) {
+                    Console.WriteLine("Error: " + e.Message);
+                    Console.WriteLine(e.StackTrace);
+                    continue;
+                }
+            }
+        }
+    }
+}
index cd94f94..642da7a 100644 (file)
@@ -199,9 +199,11 @@ namespace Mal {
             }
 
             public int size() { return value.Count; }
-            public MalVal nth(int idx) { return value[idx]; }
+            public MalVal nth(int idx) {
+                return value.Count > 0 ? value[idx] : Nil;
+            }
             public MalVal this[int idx] {
-                get { return value[idx]; }
+                get { return value.Count > 0 ? value[idx] : Nil; }
             }
             public MalList rest() {
                 if (size() > 0) {
@@ -255,16 +257,12 @@ namespace Mal {
             }
             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);
+                assoc_BANG(lst);
             }
-            */
             public MalHashMap copy() {
-                return (MalHashMap)this.MemberwiseClone();
+                var new_self = (MalHashMap)this.MemberwiseClone();
+                new_self.value = new Dictionary<string, MalVal>(value);
+                return new_self;
             }
 
             public Dictionary<string, MalVal> getValue() { return value; }
@@ -276,15 +274,16 @@ namespace Mal {
                 return "{" + printer.join(value, " ", print_readably) + "}";
             }
 
-            /*
-            public Set _entries() {
-                return value.entrySet();
+            public MalHashMap assoc_BANG(MalList lst) {
+                for (int i=0; i<lst.size(); i+=2) {
+                    value[((MalString)lst[i]).getValue()] = lst[i+1];
+                }
+                return this;
             }
-            */
-            
-            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]);
+
+            public MalHashMap dissoc_BANG(MalList lst) {
+                for (int i=0; i<lst.size(); i++) {
+                    value.Remove(((MalString)lst[i]).getValue());
                 }
                 return this;
             }
@@ -306,6 +305,9 @@ namespace Mal {
                 this.env = env;
                 this.fparams = fparams;
             }
+            public MalFunction copy() {
+                return (MalFunction)this.MemberwiseClone();
+            }
 
             public override string ToString() {
                 if (ast != null) {