Example #1
0
        // Setup the REPL.
        public MalRepl()
        {
            // Load built-in functions into the initial eval environment.
            try
            {
                myEnv = new Env(null);

                // Load core functions.
                foreach (var thing in MalNameSpace)
                {
                    MalSym mSym = new MalSym(thing.Key);

                    if (myEnv.Find(mSym) == null)
                    {
                        myEnv.Set(mSym, thing.Value);
                    }
                    else
                    {
                        // The namespace itself may already have thrown an error but if not.
                        throw new MalInternalError("Attempt to refine symbol: '" + mSym.ToString(true) + "' with '" + thing.Value.ToString(true) + "'");
                    }
                }
                // Load the special eval core function. We have to do it here to access the REPL env.
                myEnv.Set(new MalSym("eval"), new MalFunc(args =>
                {
                    return(EVAL(args[0], myEnv));
                }));

                // some convenient test files
                // "F:\Mal-development\mal-tests\mal-step6-test-01.txt"
                // "F:\Mal-development\mal-tests\mal-code-01.txt"
                // (load-file "F:\Mal-development\mal-tests\mal-code-01.txt")

                // (eval (read-string (slurp "F:\Mal-development\mal-tests\mal-code-01.txt")))

                // Add 'core' functions defined using Mal itself.
                EVAL(READ("(def not (fn (a) (if a false true)))"), myEnv);
                // TODO -number of brackets is wrong and returns nil.

                EVAL(READ("(def load-file (fn (f) (eval (read-string (str \"(do \" (slurp f) \"))\")))))"), myEnv);

                EVAL(READ("(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)))))))"), myEnv);

                EVAL(READ("(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))))))))"), myEnv);
            }
            catch (System.TypeInitializationException e)
            {
                // Typically happens if there is a duplicate symbol in the namespace list
                Console.WriteLine("Unrecoverable error: " + e.InnerException.Message);
            }
            catch (MalLookupError e)
            {
                Console.WriteLine(e.Message);
            }
            catch (MalEvalError e)
            {
                Console.WriteLine(e.Message);
            }
            catch (MalParseError e)
            {
                Console.WriteLine(e.Message);
            }
        }
Example #2
0
 public Env(Env outer)
 {
     this.outer = outer;
 }
Example #3
0
        static Value EVAL(Value arg, Env env)
        {
            for (;;)
            {
                arg = MACROEXPAND(arg, env);

                if (arg is List lst)
                {
                    if (lst.IsEmpty())
                    {
                        return(arg);
                    }

                    Value val = lst.First();

                    if (val == Reader.Def)
                    {
                        if (lst.Count() != 3)
                        {
                            throw new MalException(Reader.Def.Name + " - syntax error");
                        }

                        val = lst.Nth(1);

                        if (val is Symbol sym)
                        {
                            val = EVAL(lst.Nth(2), env);
                            if (val is Closure cls && cls.IsMacro)
                            {
                                cls         = cls.Clone(null) as Closure;
                                cls.IsMacro = false;
                                val         = cls;
                            }
                            env.Set(sym, val);

                            return(val);
                        }
                        else
                        {
                            throw new MalException(Reader.Def.Name + " - symbol expected");
                        }
                    }
                    else if (val == Reader.Let)
                    {
                        if (lst.Count() != 3)
                        {
                            throw new MalException(Reader.Let.Name + " - syntax error");
                        }

                        Env newEnv = new Env(env);

                        Value val_1 = lst.Nth(1);
                        if (val_1 is Sequence syms && syms.Count() % 2 == 0)
                        {
                            int cnt = syms.Count();
                            for (int i = 0; i < cnt; i += 2)
                            {
                                if (syms.Nth(i) is Symbol sym)
                                {
                                    newEnv.Set(sym, EVAL(syms.Nth(i + 1), newEnv));
                                }
                                else
                                {
                                    throw new MalException(Reader.Let.Name + " - bindings - symbol is expected");
                                }
                            }

                            arg = lst.Nth(2);
                            env = newEnv;
                            continue;
                        }
                        else
                        {
                            throw new MalException(Reader.Let.Name + " - bad bindings");
                        }
                    }
                    else if (val == Reader.If)
                    {
                        int cnt = lst.Count();

                        if (cnt != 3 && cnt != 4)
                        {
                            throw new MalException(Reader.If.Name + " - syntax error");
                        }

                        Value tst = EVAL(lst.Nth(1), env);
                        if (tst == null || tst == Reader.Nil || tst == Reader.False)
                        {
                            if (cnt == 3)
                            {
                                return(Reader.Nil);
                            }

                            arg = lst.Nth(3);
                            continue;
                        }
                        else
                        {
                            arg = lst.Nth(2);
                            continue;
                        }
                    }
                    else if (val == Reader.Do)
                    {
                        if (lst.Count() < 2)
                        {
                            throw new MalException(Reader.Do.Name + " - at least one argument is required");
                        }

                        int cnt = lst.Count() - 1;

                        for (int i = 1; i < cnt; i++)
                        {
                            EVAL(lst.Nth(i), env);
                        }

                        arg = lst.Nth(cnt);
                        continue;
                    }
                    else if (val == Reader.Fn)
                    {
                        if (lst.Count() != 3)
                        {
                            throw new MalException(Reader.Fn.Name + " - syntax error");
                        }

                        if (lst.Nth(1) is Sequence seq)
                        {
                            int cnt = seq.Count();
                            for (int i = 0; i < cnt; i++)
                            {
                                if (!(seq.Nth(i) is Symbol))
                                {
                                    throw new MalException(Reader.Fn.Name + " - symbol is expected for argument");
                                }
                            }
                        }
                        else
                        {
                            throw new MalException(Reader.Fn.Name + " - expected sequence of arguments");
                        }

                        if (lst.Nth(2) == null)
                        {
                            throw new MalException(Reader.Fn.Name + " - function body is expected");
                        }

                        return(new Closure(lst.Nth(1) as Sequence, lst.Nth(2), env));
                    }
                    else if (val == Reader.Quote)
                    {
                        if (lst.Count() != 2)
                        {
                            throw new MalException(Reader.Quote.Name + " - syntax error");
                        }

                        return(lst.Nth(1));
                    }
                    else if (val == Reader.Quasiquote)
                    {
                        if (lst.Count() != 2)
                        {
                            throw new MalException(Reader.Quasiquote.Name + " - syntax error");
                        }

                        return(QQuote(lst.Nth(1), env));
                    }
                    else if (val == Reader.Defmacro)
                    {
                        if (lst.Count() != 3)
                        {
                            throw new MalException(Reader.Defmacro.Name + " - syntax error");
                        }

                        val = lst.Nth(1);

                        if (val is Symbol sym)
                        {
                            val = EVAL(lst.Nth(2), env);
                            if (val is Closure cls)
                            {
                                cls         = cls.Clone(null) as Closure;
                                cls.IsMacro = true;
                                env.Set(sym, cls);

                                return(val);
                            }
                            else
                            {
                                throw new MalException(Reader.Defmacro.Name + " - closure is expected");
                            }
                        }
                        else
                        {
                            throw new MalException(Reader.Defmacro.Name + " - symbol expected");
                        }
                    }
                    else if (val == Reader.Macroexpand)
                    {
                        if (lst.Count() != 2)
                        {
                            throw new MalException(Reader.Macroexpand.Name + " - syntax error");
                        }

                        return(MACROEXPAND(lst.Nth(1), env));
                    }
                    else if (val == Reader.Try)
                    {
                        int cnt = lst.Count();
                        if (cnt != 2 && cnt != 3)
                        {
                            throw new MalException(Reader.Try.Name + " - syntax error");
                        }

                        Symbol var = null;
                        Value  exp = null;

                        if (cnt == 3)
                        {
                            if (lst.Nth(2) is List clst && clst.Count() == 3)
                            {
                                if (clst.Nth(1) is Symbol sym)
                                {
                                    var = sym;
                                }
                                else
                                {
                                    throw new MalException(Reader.Catch.Name + " - symbol is expected");
                                }

                                exp = clst.Nth(2);
                            }
                            else
                            {
                                throw new MalException(Reader.Catch.Name + " - syntax error");
                            }
                        }

                        Value res;

                        try
                        {
                            res = EVAL(lst.Nth(1), env);
                        }
                        catch (MalException ex)
                        {
                            if (var != null && exp != null)
                            {
                                Env newEnv = new Env(env);
                                if (ex.Val != null)
                                {
                                    newEnv.Set(var, ex.Val);
                                }
                                else
                                {
                                    newEnv.Set(var, new Str(ex.Message));
                                }
                                res = EVAL(exp, newEnv);
                            }
                            else
                            {
                                throw ex;
                            }
                        }

                        return(res);
                    }
Example #4
0
        static int Main(string[] args)
        {
            Env env = CreateTopLevelEnv();

            List <Value> malArgs = new List <Value>();

            for (int i = 1; i < args.Length; i++)
            {
                malArgs.Add(new Str(args[i]));
            }

            env.Set(Reader.AddSymbol("*ARGV*") as Symbol, new List(malArgs));

            Rep("(try* (load-file \".malrc\"))", env);

            if (args.Length == 0)
            {
                return(Repl(env));
            }

#if NATIVE_LOAD_FILE
            FileInfo fi = new FileInfo(args[0]);
            if (fi.Exists)
            {
                using (Stream stream = fi.OpenRead())
                    LoadStream(stream, env);
            }
            else
            {
                Console.Error.WriteLine("ERROR: unable open file '{0}'", args[0]);
                return(1);
            }
#else
            List loaderArgs = new List(new List <Value>()
            {
                new Str(args[0])
            });
            try
            {
                var val = env.Get(Reader.Load_file);
                if (val is Closure cls)
                {
                    EVAL(cls.Body, cls.CreateEnv(loaderArgs));
                }
                else
                if (val is Func_Native fn)
                {
                    fn.Apply(loaderArgs);
                }
                else
                {
                    throw new MalException("unknown function to evaluate file");
                }
            }
            catch (MalException ex)
            {
                Console.Error.WriteLine("ERROR: {0}", ex.Message);
                return(1);
            }
            catch (MalQuitExc)
            { }
#endif
            return(0);
        }
Example #5
0
        // Evaluate the ast in the supplied environment. This is a bit of a monster but
        // has been left as per the guide to simplify Tail Call Optimisation (TCO)
        static MalVal EVAL(MalVal InitialAst, Env IntialEnv)
        {
            // The ast and env to be EVAL'd. Initially set to those passed to EVAL but
            // may be updated if there is a TCO loop.
            MalVal ast = InitialAst;
            Env    env = IntialEnv;

            // Some eval cases will return out of the function, others keep looping (as per TCO) with
            // an updated ast / Env. The alternative is to call EVAL recursively, which has simpler
            // logic but which can blow up the stack.
            while (true)
            {
                if (ast is MalList astList)
                {
                    if (astList.Count() <= 0)
                    {
                        // Empty list, return unchanged.
                        return(ast);
                    }
                    else
                    {
                        // I used to check astList[0] was a symbol but this dissallows forms whose first el is an anon (fn..) special form.
                        string symbolZero = astList[0] is MalSym ? ((MalSym)astList[0]).getName() : "__<*fn*>__";

                        switch (symbolZero)
                        {
                        case "def":
                            // Evaluate all elements of list using eval_ast, retun the final eval'd element.
                            // Should be something like (def a b). Set() symbol a to be the result of evaluating b.
                            if (astList.Count() != 3 || !(astList[1] is MalSym symbol))
                            {
                                throw new MalEvalError("'def' should be followed by a symbol and a value");
                            }
                            // Must add the result of the EVAL to the environment so can't TCO loop here.
                            MalVal result = EVAL(astList[2], env);
                            env.Set(symbol, result);
                            return(result);

                        case "let":
                            // Let syntax is (let <bindings-list> <result>), e.g. (let (p ( + 2 3) q (+ 2 p)) (+ p q)).
                            // Bindings can refer to earlier bindings in the new let environment or to symbols
                            // in the outer environment. Let defs hide hide same-named symbols in the outer scope.

                            if (astList.Count() != 3)
                            {
                                throw new MalEvalError("'let' should have two arguments (bindings-list and result), but instead had " + (astList.Count() - 1));
                            }
                            // Extract the first parameter - the bindings list. E.g. (p (+ 2 3) q (+ 2 p))
                            if (!(astList[1] is MalList bindingsList))
                            {
                                throw new MalEvalError("'let' should be followed by a non-empty bindings list and a result form");
                            }
                            if (bindingsList.Count() <= 1 || bindingsList.Count() % 2 != 0)
                            {
                                throw new MalEvalError("'let' bindings list should have an even number of entries");
                            }

                            // Create a new Env - the scope of the Let form. It's discarded when done.
                            Env TempLetEnv = new Env(env);

                            // Process each pair of entries in the bindings list.
                            for (int i = 0; i < bindingsList.Count(); i += 2)
                            {
                                // The first element should be a 'key' symbol. E.g. 'p'.
                                if (!(bindingsList[i] is MalSym bindingKey))
                                {
                                    throw new MalEvalError("'let' expected symbol but got: '" + bindingsList[i].ToString(true) + "'");
                                }
                                // The second element (e.g. (+ 2 3)) is the value of the key symbol in Let's environment
                                MalVal val = EVAL(bindingsList[i + 1], TempLetEnv);

                                // Store the new value in the environment.
                                TempLetEnv.Set(bindingKey, val);
                            }

                            // TCO loop instead of EVAL(astList[2], TempLetEnv)
                            ast = astList[2];
                            env = TempLetEnv;
                            continue;

                        case "do":
                            // Something like (do <form> <form>)
                            MalList formsToDo      = astList.Rest();
                            int     formsToDoCount = formsToDo.Count();
                            if (formsToDoCount == 0)
                            {
                                // Empty (do )
                                return(malNil);
                            }
                            else
                            {
                                // EVAL all forms in the (do ) body, up to but not including the last one.
                                eval_ast(formsToDo.GetRange(0, formsToDoCount - 1), env);

                                // EVAL the last form in the (do ) body via TCO loop, keeping the same env.
                                ast = formsToDo[formsToDoCount - 1];

                                // TCO loop.
                                continue;
                            }

                        case "if":
                            // If has the syntax (if <cond> <true-branch> <optional-false-branch>)
                            if (astList.Count() < 3 || astList.Count() > 4)
                            {
                                throw new MalEvalError("'if' should have a condition, true branch and optional false branch");
                            }
                            // Evaluate the Cond part of the if.
                            MalVal cond = EVAL(astList[1], env);
                            if (cond is MalNil || cond is MalFalse)
                            {
                                // Cond is nil or false.
                                if (astList.Count() == 4)
                                {
                                    // Eval the 'false' branch via TCO loop.
                                    ast = astList[3];
                                    continue;
                                }
                                else
                                {
                                    // No 'false' branch.
                                    return(malNil);
                                }
                            }
                            else
                            {
                                // Eval the 'true' branch via TCO loop.
                                ast = astList[2];
                                continue;
                            }

                        case "fn":
                            // e.g.  (def a (fn (i) (* i 2))) or (def b (fn () (prn 1) (prn 2)))
                            if (astList.Count() < 3)
                            {
                                throw new MalEvalError("fn must have an arg list and at least one body form");
                            }
                            if (!(astList[1] is MalList FnArgList))
                            {
                                // The 2nd element must be an arg list (possibly empty).
                                throw new MalEvalError("Expected arg list but got: '" + astList[1].ToString(true) + "'");
                            }

                            MalVal FnBodyExprs;
                            // Unlike the Mal reference, this handles fns that have multiple expr forms.
                            if (astList.Count() == 3)
                            {
                                FnBodyExprs = astList[2];
                            }
                            else
                            {
                                // Either I've misunderstood something or the Reference doesn't handle some functions correctly.
                                FnBodyExprs = astList.GetRange(2, astList.Count() - 1);
                            }

                            Env cur_env = env;

                            // This is different from C#-Mal but their version doesn't work.
                            return(new MalFunc(FnBodyExprs, env, FnArgList, args => EVAL(FnBodyExprs, new Env(cur_env, FnArgList, args))));


                        default:
                            // Ast isn't a special form, so try to evaluate it as a function.
                            MalList evaledList = (MalList)eval_ast(ast, env);

                            MalVal listFirst = evaledList.First();

                            if (listFirst is MalFunc func)
                            {
                                if (func.IsCore)
                                {
                                    // Can't TCO a core function so directly apply func.
                                    return(func.Apply(evaledList.Rest()));
                                }
                                else
                                {
                                    // Core function.
                                    // The ast is the one stored by the func.
                                    ast = func.ast;

                                    // Create a new env using func's env and params as the outer and binds arguments
                                    // and the rest of the current ast as the exprs argument.
                                    env = new Env(func.env, func.fparams, evaledList.Rest());

                                    // and now 'apply' via the TCO loop.
                                    continue;
                                }
                            }
                            else
                            {
                                return(listFirst);
                            }
                        }
                    }
                }
                else
                {
                    // If ast is not a list (e.g. a vector), return the result of calling eval_ast on it.
                    return(eval_ast(ast, env));
                }
            }
        }
Example #6
0
        static Value EVAL(Value arg, Env env)
        {
            for (;;)
            {
                if (arg is List lst)
                {
                    if (lst.IsEmpty())
                    {
                        return(arg);
                    }

                    Value val = lst.First();

                    if (val == Reader.Def)
                    {
                        if (lst.Count() != 3)
                        {
                            throw new MalException(Reader.Def.Name + " - syntax error");
                        }

                        val = lst.Nth(1);

                        if (val is Symbol sym)
                        {
                            val = EVAL(lst.Nth(2), env);
                            env.Set(sym, val);

                            return(val);
                        }
                        else
                        {
                            throw new MalException(Reader.Def.Name + " - symbol expected");
                        }
                    }
                    else if (val == Reader.Let)
                    {
                        if (lst.Count() != 3)
                        {
                            throw new MalException(Reader.Let.Name + " - syntax error");
                        }

                        Env newEnv = new Env(env);

                        Value val_1 = lst.Nth(1);
                        if (val_1 is Sequence syms && syms.Count() % 2 == 0)
                        {
                            int cnt = syms.Count();
                            for (int i = 0; i < cnt; i += 2)
                            {
                                if (syms.Nth(i) is Symbol sym)
                                {
                                    newEnv.Set(sym, EVAL(syms.Nth(i + 1), newEnv));
                                }
                                else
                                {
                                    throw new MalException(Reader.Let.Name + " - bindings - symbol is expected");
                                }
                            }

                            arg = lst.Nth(2);
                            env = newEnv;
                            continue;
                        }
                        else
                        {
                            throw new MalException(Reader.Let.Name + " - bad bindings");
                        }
                    }
                    else if (val == Reader.If)
                    {
                        int cnt = lst.Count();

                        if (cnt != 3 && cnt != 4)
                        {
                            throw new MalException(Reader.If.Name + " - syntax error");
                        }

                        Value tst = EVAL(lst.Nth(1), env);
                        if (tst == null || tst == Reader.Nil || tst == Reader.False)
                        {
                            if (cnt == 3)
                            {
                                return(Reader.Nil);
                            }

                            arg = lst.Nth(3);
                            continue;
                        }
                        else
                        {
                            arg = lst.Nth(2);
                            continue;
                        }
                    }
                    else if (val == Reader.Do)
                    {
                        if (lst.Count() < 2)
                        {
                            throw new MalException(Reader.Do.Name + " - at least one argument is required");
                        }

                        int cnt = lst.Count() - 1;

                        for (int i = 1; i < cnt; i++)
                        {
                            EVAL(lst.Nth(i), env);
                        }

                        arg = lst.Nth(cnt);
                        continue;
                    }
                    else if (val == Reader.Fn)
                    {
                        if (lst.Count() != 3)
                        {
                            throw new MalException(Reader.Fn.Name + " - syntax error");
                        }

                        if (lst.Nth(1) is Sequence seq)
                        {
                            int cnt = seq.Count();
                            for (int i = 0; i < cnt; i++)
                            {
                                if (!(seq.Nth(i) is Symbol))
                                {
                                    throw new MalException(Reader.Fn.Name + " - symbol is expected for argument");
                                }
                            }
                        }
                        else
                        {
                            throw new MalException(Reader.Fn.Name + " - expected sequence of arguments");
                        }

                        if (lst.Nth(2) == null)
                        {
                            throw new MalException(Reader.Fn.Name + " - function body is expected");
                        }

                        return(new Closure(lst.Nth(1) as Sequence, lst.Nth(2), env));
                    }
                    else
                    {
                        Value ast = Eval_ast(arg, env);

                        val = (ast as List).First();

                        if (val is Func_Native fn)
                        {
                            return(fn.Apply((ast as List).Rest()));
                        }
                        else if (val is Closure cls)
                        {
                            arg = cls.Body;
                            env = cls.CreateEnv((ast as List).Rest());
                            continue;
                        }
                        else
                        {
                            throw new MalException("function is expected");
                        }
                    }
                }