Example #1
0
 public MalAtom(MalVal atom)
 {
     if (atom == null)
     {
         throw new MalInternalError("Attempt to create null Atom");
     }
     if (((object)atom is MalNil) || ((object)atom is MalTrue) || ((object)atom is MalNil))
     {
         throw new MalEvalError("'" + atom.ToString(true) + "' cannot be an Atom");
     }
     myAtom = atom;
 }
Example #2
0
 // The converse of read_str - return a string rep of a MAL object.
 public static string pr_str(MalVal ast, bool printReadably)
 {
     // Mal Guide says switch on the ast type, but we use virtuals instead.
     if (ast != null)
     {
         return(ast.ToString(printReadably));
     }
     else
     {
         // The MAL guide doesn't do this check but it stops comment lines from crashing.
         return("");
     }
 }
Example #3
0
        static MalVal EvalMalFunc(MalVal ast, Env env)
        {
            // Try to evaluate an ast as a MalFunc.
            MalList evaledList = (MalList)eval_ast(ast, env);

            MalVal  listFirst = evaledList.First();
            MalList listRest  = evaledList.Rest();

            if (listFirst is MalFunc func)
            {
                // Ast *is* a function, so call it.
                return(func.Apply(listRest));
            }
            else
            {
                throw new MalEvalError("Can't use '" + listFirst.ToString(true) + "' as a function.");
            }
        }
Example #4
0
        static MalVal EVAL(MalVal ast, Dictionary <string, MalFunc> replEnv)
        {
            switch ((Object)ast)
            {
            case MalList mList:
                // Ast is a list.
                // TODO - should this also do vectors and hashmaps?
                if (mList.Count() <= 0)
                {
                    // Empty list, return unchanged.
                    Console.WriteLine("EVAL - empty list: " + Printer.pr_str(mList, true));
                    return(ast);
                }
                else
                {
                    // ast is a non-empty list, so evaluate it.
                    Console.WriteLine("EVAL - non-empty list: " + Printer.pr_str(mList, true));

                    // Evaluate the List.
                    MalList evaledList = (MalList)eval_ast(ast, replEnv);

                    MalVal  listFirst = evaledList.First();
                    MalList listRest  = evaledList.Rest();

                    switch ((Object)listFirst)
                    {
                    case MalFunc func:
                        Console.WriteLine("EVAL - List head is: '" + Printer.pr_str(listFirst, true) + "'. Rest elements: ");
                        // Take the first item of the evaluated list and call it as function using the rest of the evaluated list as its arguments

                        return(func.Apply(listRest));

                    //return null;
                    default:
                        throw new MalEvalError("Can't use '" + listFirst.ToString(true) + "' as a function.");
                    }
                }

            default:
                // If ast is not a list (e.g. a vector), return the result of calling eval_ast on it.
                return(eval_ast(ast, replEnv));
            }
        }
Example #5
0
 public override string ToString(bool printReadably)
 {
     return("<Atom>:" + myAtom.ToString(printReadably));
 }
Example #6
0
 public MalMalError(MalVal exceptionVal) : base(exceptionVal.ToString(true))
 {
     myException = exceptionVal;
 }
Example #7
0
        // EVAL is handled by two functions - EVAL which decides whether or not the ast is a special
        // form or not and eval_ast which evaluates the remaining symbols, lists, etc.
        // EVAL is tail-call optimised, as per Mal guide step 5

        static MalVal eval_ast(MalVal ast, Env env)
        {
            // Switch on the type of the ast.
            switch ((Object)ast)
            {
            case MalSym malSym:
                return(env.Get(malSym));

            case MalList malList:
                // Return a new list that is the result of calling EVAL on each of the members of the list.
                MalList derivedList = new MalList();
                for (int i = 0; i < malList.Count(); i++)
                {
                    derivedList.Add(EVAL(malList.Get(i), env));
                }
                return(derivedList);

            case MalVector malVec:
                // Return a new vector that is the result of calling EVAL on each of the members of the vector.
                MalVector derivedVec = new MalVector();
                for (int i = 0; i < malVec.Count(); i++)
                {
                    derivedVec.Add(EVAL(malVec.Get(i), env));
                }
                return(derivedVec);

            case MalHashMap malHashMap:
                // Return a new hash-map which consists of key-value pairs where the key is a key from the hash-map
                // and the value is the result of calling EVAL on the corresponding value.
                if (malHashMap.Count() % 2 != 0)
                {
                    throw new MalEvalError("Hashmap requires an even number of elements forming key value pairs '" + malHashMap.ToString(true) + "'");
                }

                MalHashMap derivedHashMap = new MalHashMap();
                // Work through successive key value pairs.
                // Note - C#-Mal creates a dictionary, loads it and then passes the result to a Hashmap c'tor that takes a Dict.
                for (int i = 0; i < malHashMap.Count(); i += 2)
                {
                    MalVal key = malHashMap.Get(i);
                    if (key is MalString || key is MalKeyword)
                    {
                        derivedHashMap.Add(key);
                        derivedHashMap.Add(EVAL(malHashMap.Get(i + 1), env));
                    }
                    else
                    {
                        throw new MalEvalError("Expecting a keyword or string as a HashMap key but got '" + key.ToString(true) + "'");
                    }
                }
                return(derivedHashMap);

            default:
                // It's not a symbol or a list.
                return(ast);
            }
        }
Example #8
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));
            }
        }