Exemplo n.º 1
0
        public static void InitEnv(StepFunction step, Env env, string[]?cmdArgs = null)
        {
            env.Set(new Symbol("*ARGV*", NilV), new List((cmdArgs ?? new string[0]).Skip(1).Select(arg => new Str(arg) as MalType).ToLList(), ListType.List, NilV));

            env.Set(new Symbol("eval", NilV), new Fn(args => args switch
            {
                (var Mal, null) => Eval(Mal, env),
                _ => throw new Exception($"'eval' function requires one argument, but got {args.JoinMalTypes(", ")}")
            }, NilV));
Exemplo n.º 2
0
        static Env CreateTopLevelEnv()
        {
            Env env = new Env();

            env.Set(Reader.AddSymbol("+") as Symbol, new Func_Add());
            env.Set(Reader.AddSymbol("-") as Symbol, new Func_Sub());
            env.Set(Reader.AddSymbol("*") as Symbol, new Func_Mul());
            env.Set(Reader.AddSymbol("/") as Symbol, new Func_Div());

            return(env);
        }
Exemplo n.º 3
0
        // Setup the REPL.
        public MalRepl()
        {
            // Load built-in functions into the initial eval environment.
            try
            {
                myEnv = new Env(null);
                // Arithmetic.
                myEnv.Set("+", BuiltInAdd);
                myEnv.Set("*", BuiltInMultiply);
                myEnv.Set("-", BuiltInSubtract);
                myEnv.Set("/", BuiltInDivide);

                // List manipulation.
                myEnv.Set("list", BuiltinList);
                myEnv.Set("list?", BuiltinIsList);
                myEnv.Set("empty?", BuiltinEmpty);
                myEnv.Set("count", BuiltinCount);

                // Functions defined using Mal itself.
                EVAL(READ("(def not (fn (a) (if a false true)))"), myEnv);
            }
            catch (MalEvalError e)
            {
                Console.WriteLine(e.Message);
            }
        }
Exemplo n.º 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));

            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);
            }
#endif
            return(0);
        }
Exemplo n.º 5
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)
                {
                    myEnv.Set(new MalSym(thing.Key), thing.Value);
                }
                // 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);

                // Add some of the test functions for convenience.
                EVAL(READ("(def sumdown (fn (N) (if (> N 0) (+ N (sumdown  (- N 1))) 0)))"), myEnv);
            }
            catch (MalLookupError e)
            {
                Console.WriteLine(e.Message);
            }
            catch (MalEvalError e)
            {
                Console.WriteLine(e.Message);
            }
        }
Exemplo n.º 6
0
        static MalVal EvalDef(MalList astList, Env env)
        {
            // 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");
            }
            MalVal result = EVAL(astList[2], env);

            env.Set(symbol.getName(), result);
            return(result);
        }
Exemplo n.º 7
0
        static MalVal EvalLet(MalList astList, Env env)
        {
            // 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.

            // Confirm the structure of the let* form.
            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.
            Env LetEnv = 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 is the value of the key symbol in Let's environment, E.g. (+ 2 3)
                MalVal val = EVAL(bindingsList[i + 1], LetEnv);
                // This expression can refer to earlier let bindings.

                // Now, store the new value in the environment.
                LetEnv.Set(bindingKey.getName(), val);
                // Note - this can silently redefine built-in functions e.g. if something like '+' is used.
            }

            // Using the populated Let* environment, evaluate and return the result form.
            // The Let* environment itself is discarded, unshadowing symbols in outer environments.
            return(EVAL(astList[2], LetEnv));
        }
Exemplo n.º 8
0
        // Setup the REPL.
        public MalRepl()
        {
            // Load built-in functions into the initial eval environment.
            try
            {
                myEnv = new Env(null);
                // Arithmetic.
                myEnv.Set("+", BuiltInAdd);
                myEnv.Set("*", BuiltInMultiply);
                myEnv.Set("-", BuiltInSubtract);
                myEnv.Set("/", BuiltInDivide);

                // List manipulation.
                myEnv.Set("list", BuiltinList);
                myEnv.Set("list?", BuiltinIsList);
                myEnv.Set("empty?", BuiltinEmpty);
                myEnv.Set("count", BuiltinCount);
            }
            catch (MalEvalError e)
            {
                Console.WriteLine(e.Message);
            }
        }
Exemplo n.º 9
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)
                {
                    myEnv.Set(new MalSym(thing.Key), thing.Value);
                }

                // Add functions defined using Mal itself.
                EVAL(READ("(def not (fn (a) (if a false true)))"), myEnv);
                EVAL(READ("(def sumdown (fn (N) (if (> N 0) (+ N (sumdown  (- N 1))) 0)))"), myEnv);
            }
            catch (MalEvalError e)
            {
                Console.WriteLine(e.Message);
            }
        }
Exemplo n.º 10
0
        static Value EVAL(Value arg, Env 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);
                        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");
                            }
                        }

                        return(EVAL(lst.Nth(2), newEnv));
                    }
                    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)
                    {
                        return(cnt == 3 ? Reader.Nil : EVAL(lst.Nth(3), env));
                    }
                    else
                    {
                        return(EVAL(lst.Nth(2), env));
                    }
                }
                else if (val == Reader.Do)
                {
                    if (lst.Count() < 2)
                    {
                        throw new MalException(Reader.Do.Name + " - at least one argument is required");
                    }

                    Value res = null;

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

                    return(res);
                }
                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)
                    {
                        return(EVAL(cls.Body, cls.CreateEnv((ast as List).Rest())));
                    }
                    else
                    {
                        throw new MalException("function is expected");
                    }
                }
            }
Exemplo n.º 11
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)
                {
                    // Console.WriteLine("EVAL: ast is " + astList.ToString(true));
                    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.
                                if (formsToDoCount > 1)
                                {
                                    eval_ast(formsToDo.GetRange(0, formsToDoCount - 2), 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
                                {
                                    // Non-ore 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));
                }
            }
        }
Exemplo n.º 12
0
        // EVALuate the ast in the supplied env. EVAL directly handles special forms
        // and functions (core or otherwise) and passes symbols and sequences to its helper
        // function eval_ast (with which it is mutually recursive).

        // Conceptually, EVAL invokes itself recursively to evaluate  some forms, sometimes in their
        // own new environment (e.g. (let)). In this implementation, Tail Call Optimisation (TCO)
        // is used to avoid recursion if possible. Specifically, the TCO version EVAL has ast and env
        // variables. It sets these to those passed in by the caller and then starts a while(true)
        // loop. If the last act of a pass through EVAL would have been to invoke EVAL recursively and
        // return that value, the TCO version sets the ast and env to whatever would have been
        // passed recursively, and goes back to the beginning of its loop, thus replacing
        // recursion with (stack efficient) iteration. Recursion is still used in cases where
        // the recursive call isn't the last thing EVAL does before returning the value, e.g. in
        // (def ) clause where the returned value must be stored in the env.
        // The downside of TCO is the increased complexity of the EVAL function itself, which
        // is now harder to refactor into helper-functions for the various special forms.
        static MalVal EVAL(MalVal InitialAst, Env IntialEnv)
        {
            // The ast and env to be EVAL'd (and which might be reset in a TCO loop).
            MalVal ast = InitialAst;
            Env    env = IntialEnv;

            // The TCO loop.
            while (true)
            {
                if (ast is MalList astList)
                {
                    // Console.WriteLine("EVAL: ast is " + astList.ToString(true));
                    if (astList.Count() <= 0)
                    {
                        // Empty list, return unchanged.
                        return(ast);
                    }
                    else
                    {
                        // See if we have a special form or something else.
                        string astListHead = astList[0] is MalSym ? ((MalSym)astList[0]).getName() : "__<*fn*>__";

                        switch (astListHead)
                        {
                        // Look for cases where the head of the ast list is a symbol (e.g. let, do, if) associated
                        // with a special form. If it is, we can discard th head symbol, and the remaining elements of the
                        // astList are the forms in the body of the special form. What happens to these depends on the form
                        // that has been encountered,
                        case "def":
                            // Should be something like (def a b). Set() symbol a to be the result of evaluating b
                            // and store the result in the env.
                            // Evaluate all elements of list using eval_ast, retun the final eval'd element.
                            if (astList.Count() != 3 || !(astList[1] is MalSym symbol))
                            {
                                throw new MalEvalError("'def' should be followed by a symbol and a value");
                            }
                            // Can't TCO loop because we must store the result of the EVAL before returning it.
                            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 can 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>). EVAL all forms in the (do ) body in current environment.
                            // Some vars that make meaning clearer.
                            MalList formsInDoBody      = astList.Rest();
                            int     formsInDoBodyCount = formsInDoBody.Count();
                            if (formsInDoBodyCount == 0)
                            {
                                // Empty (do )
                                return(malNil);
                            }
                            else
                            {
                                // Use eval_ast to evaluate the DoBody forms up to but NOT including the last one.
                                if (formsInDoBodyCount > 1)
                                {
                                    // If there is just one DoBody form, this won't get called.
                                    eval_ast(formsInDoBody.GetRange(0, formsInDoBodyCount - 2), env);
                                }

                                // Prep for the TCO by using the last DoBody form as the new ast.
                                ast = formsInDoBody[formsInDoBodyCount - 1];

                                // TCO loop to process the final DoBody form.
                                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 FnParamsList))
                            {
                                // The 2nd element must be an arg list (possibly empty).
                                throw new MalEvalError("Expected arg list but got: '" + astList[1].ToString(true) + "'");
                            }

                            MalVal FnBodyForms;
                            // Make a list of the forms in the funcion body (following the fn name and paramd list).
                            if (astList.Count() == 3)
                            {
                                FnBodyForms = astList[2];
                            }
                            else
                            {
                                // Either I've misunderstood something or the Reference doesn't handle some functions correctly.
                                FnBodyForms = astList.GetRange(2, astList.Count() - 1);
                            }

                            Env cur_env = env;

                            // This is different from C#-Mal but their version doesn't compile. Perhaps it's a C# version thing?
                            return(new MalFunc(FnBodyForms, env, FnParamsList, args => EVAL(FnBodyForms, new Env(cur_env, FnParamsList, args))));

                        case "quote":
                            if (astList.Count() == 2)
                            {
                                // Return the quoted thing.
                                return(astList[1]);
                            }
                            throw new MalEvalError("quote expected 1 arg but had " + (astList.Count() - 1).ToString());

                        case "quasiquote":
                            if (astList.Count() == 2)
                            {
                                // Return the result of processing the quasiquote.
                                ast = ProcessQuasiquote(astList[1]);
                                continue;
                            }
                            throw new MalEvalError("quasiquotequote expected 1 arg but had " + (astList.Count() - 1).ToString());

                        default:
                            // Ast isn't a special form (it mey be a symbol or a function).
                            // eval_ast the ast
                            MalList evaledList = (MalList)eval_ast(ast, env);

                            // Look at the head of the result.
                            MalVal listFirst = evaledList.First();

                            if (listFirst is MalFunc func)
                            {
                                // It's a mal func.
                                if (func.IsCore)
                                {
                                    // Core funcs can be immediately applied and the value returned.
                                    return(func.Apply(evaledList.Rest()));
                                }
                                else
                                {
                                    // Non-core function, i.e. one created by (fn...).
                                    // Use the Fn's ast and env info to set up a new EVAL (via TCO).

                                    // The ast to EVAL is the func's stored ast.
                                    ast = func.ast;

                                    // The new EVAL Env is made as follows...
                                    // The outer env is the func's env
                                    // The 'binds' are the func's params
                                    // The exprs are are the rest of the current ast.
                                    env = new Env(func.env, func.fparams, evaledList.Rest());

                                    // Apply = EVAL the function indirectly via the TCO loop.
                                    continue;
                                }
                            }
                            else
                            {
                                // This is typically the value of a symbol looked up by eval_ast.
                                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));
                }
            }
        }
Exemplo n.º 13
0
        // Setup the REPL.
        public MalRepl()
        {
            // Load built-in functions into the initial eval environment.
            try
            {
                myEnv = new Env(null);

                // Arithmetic.
                myEnv.Set("+", BuiltInAdd);
                myEnv.Set("*", BuiltInMultiply);
                myEnv.Set("-", BuiltInSubtract);
                myEnv.Set("/", BuiltInDivide);

                // List manipulation.
                myEnv.Set("list", BuiltinList);
                myEnv.Set("list?", BuiltinIsList);
                myEnv.Set("empty?", BuiltinEmpty);
                myEnv.Set("count", BuiltinCount);

                // Equality and friends.
                myEnv.Set("=", BuiltinEQ);
                myEnv.Set("<", BuiltinLT);
                myEnv.Set("<=", BuiltinLToE);
                myEnv.Set(">", BuiltinGT);
                myEnv.Set(">=", BuiltinGToE);

                // Printing
                myEnv.Set("pr-str", BuiltinPrStr);
                myEnv.Set("str", BuiltinStr);
                myEnv.Set("prn", BuiltinPrn);
                myEnv.Set("println", BuiltinPrintLn);



                // Functions defined using Mal itself.
                EVAL(READ("(def not (fn (a) (if a false true)))"), myEnv);
            }
            catch (MalEvalError e)
            {
                Console.WriteLine(e.Message);
            }
        }
Exemplo n.º 14
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 TCO in step 5.
        // I've taken off the '*' and '!' chars from the special forms.
        static MalVal EVAL(MalVal ast, Env env)
        {
            if (ast is MalList astList)
            {
                if (astList.Count() <= 0)
                {
                    // Empty list, return unchanged.
                    return(ast);
                }
                else
                {
                    // ast is a non-empty list. [0] should be a special form or a function name.
                    if (!(astList[0] is MalSym sym))
                    {
                        // Something like ([ 1 2]) perhaps.
                        throw new MalEvalError("Expected function name or special form but got '" + astList[0] + "'");
                    }

                    switch (sym.getName())
                    {
                    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");
                        }
                        MalVal result = EVAL(astList[2], env);
                        env.Set(symbol.getName(), 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.getName(), val);
                        }

                        // Using the populated Let environment, evaluate and return the result form.
                        return(EVAL(astList[2], TempLetEnv));

                    case "do":
                        // Evaluate all elements of list using eval_ast, retun the final eval'd element.
                        MalList el = (MalList)eval_ast(astList.Rest(), env);

                        return((el.Count() > 0) ? el[el.Count() - 1] : malNil);

                    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. Eval the 'false' branch, if any.
                            if (astList.Count() == 4)
                            {
                                return(EVAL(astList[3], env));
                            }
                            else
                            {
                                return(malNil);
                            }
                        }
                        else
                        {
                            // Eval the 'true' branch.
                            return(EVAL(astList[2], env));
                        }

                    case "fn":
                        // e.g.  (def a (fn (i) (* i 2)))
                        if (!(astList[1] is MalList a1f))
                        {
                            throw new MalEvalError("Expected arg list but got: '" + astList[1].ToString(true) + "'");
                        }
                        // Note - these functions can create Mal runtime errors - e.g. if undefined symbols are used.
                        MalVal a2f     = astList[2];
                        Env    cur_env = env;
                        return(new MalFunc(a2f, env, a1f, args => EVAL(a2f, new Env(cur_env, a1f, args))));

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

                        MalVal listFirst = evaledList.First();

                        if (listFirst is MalFunc func)
                        {
                            // Ast *is* a function, so call it.
                            return(func.Apply(evaledList.Rest()));
                        }
                        else
                        {
                            throw new MalEvalError("Can't use '" + listFirst.ToString(true) + "' as a function.");
                        }
                    }
                }
            }
            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));
            }
        }
Exemplo n.º 15
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);
                    }
Exemplo n.º 16
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
                    {
                        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)
                        {
                            if (cls.IsMacro)
                            {
                                arg = EVAL(cls.Body, cls.CreateEnv((ast as List).Rest()));
                            }
                            else
                            {
                                arg = cls.Body;
                                env = cls.CreateEnv((ast as List).Rest());
                            }
                            continue;
                        }
                        else
                        {
                            throw new MalException("function is expected");
                        }
                    }
                }
Exemplo n.º 17
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);

                // Establish a gensym mechanism
                EVAL(READ("(def *gensym-counter* (atom 0))"), myEnv);
                EVAL(READ("(def gensym (fn [] (symbol (str \"G__\" (swap *gensym-counter* (fn [x] (+ 1 x)))))))"), myEnv);

                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 (condvar (gensym)) `(let (~condvar ~(first xs)) (if ~condvar ~condvar (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);
            }
        }
Exemplo n.º 18
0
        static Value EVAL(Value arg, Env 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);
                        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");
                            }
                        }

                        return(EVAL(lst.Nth(2), newEnv));
                    }
                    else
                    {
                        throw new MalException(Reader.Let.Name + " - bad bindings");
                    }
                }
                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
                    {
                        throw new MalException("function is expected");
                    }
                }
            }