コード例 #1
0
        // Name changed from the guide to highlight that the outcome is complicated.
        static MalVal Quasiquote(MalVal ast)
        {
            if (!is_pair(ast))
            {
                // Case 1.
                // If is_pair of ast is false: return a new list containing: a symbol named "quote" and ast.
                MalList qList = new MalList();

                qList.Add(new MalSym("quote"));
                qList.Add(ast);

                //qList.Conj(new MalSym("quote"), ast);
                return(qList);
            }
            else
            {
                MalSequence astSeq = (MalSequence)ast;
                MalVal      a0     = astSeq[0];

                // Case 2:  if the first element of ast is a symbol named "unquote": return the second element of ast.
                if (a0 is MalSym a0Sym && a0Sym.getName() == "unquote")
                {
                    // (qq (uq form)) -> form
                    return(astSeq[1]);
                }
コード例 #2
0
ファイル: Core.cs プロジェクト: KineticLensman/JK-s-Lisp
 //------------------ General utilities -------------------------------------------
 // Generic error check for core fns.
 public static void CheckArgCount(MalList args, int expectedCount, string callerName)
 {
     if (args.Count() != expectedCount)
     {
         throw new MalEvalError("'" + callerName + "' expected " + expectedCount + " arg(s) but got " + args.Count());
     }
 }
コード例 #3
0
ファイル: MalRepl.cs プロジェクト: KineticLensman/JK-s-Lisp
        static MalVal eval_ast(MalVal ast, Env env)
        {
            // TODO - handle malHashMap.
            // 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:
                throw new MalInternalError("Can't evaluate a HashMap yet '" + malHashMap.ToString() + "'");

            default:
                // It's not a symbol or a list.
                return(ast);
            }
        }
コード例 #4
0
ファイル: MalRepl.cs プロジェクト: KineticLensman/JK-s-Lisp
        static MalVal EvalIf(MalList astList, Env env)
        {
            // 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(new MalNil());
                }
            }
            else
            {
                // Eval the 'true' branch.
                return(EVAL(astList[2], env));
            }
        }
コード例 #5
0
        static protected MalNum ProcessNumbers(MalList args, NumericOp op)
        {
            double result = 0;

            // Initiate the calculation with the first arg.
            switch (args[0])
            {
            case MalNum num:
                result = (double)num;
                // Handle the special case of (- 1).
                if (args.Count() == 1 && op == NumericOp.Subtract)
                {
                    double negative = (double)num;
                    return(new MalNum(-negative));
                }
                break;

            default:
                // Should have already detected this but just in case.
                throw new MalInternalError("Non-number while calculating numbers: '" + args[0].ToString(true));
            }
            double divTest = 0;

            // Now apply the op to the remaining args.
            for (var i = 1; i < args.Count(); i++)
            {
                switch (args[i])
                {
                case MalNum num:
                    switch (op)
                    {
                    case NumericOp.Plus:
                        result += (double)num;
                        break;

                    case NumericOp.Multiply:
                        result *= (double)num;
                        break;

                    case NumericOp.Subtract:
                        result -= (double)num;
                        break;

                    case NumericOp.Divide:
                        divTest = (double)num;
                        if (i > 0 && divTest == 0)
                        {
                            throw new MalEvalError("Can't divide by zero");
                        }
                        result /= divTest;
                        break;
                    }
                    break;

                default:
                    throw new MalInternalError("Non-number while calculating numbers: '" + args[i].ToString(true));
                }
            }
            return(new MalNum(result));
        }
コード例 #6
0
 public MalFunc(MalVal ast, Env e, MalList fparams, Func <MalList, MalVal> fn)
 {
     this.ast     = ast;
     this.env     = e;
     this.fparams = fparams;
     this.fn      = fn;
 }
コード例 #7
0
 public MalFunc(MalVal ast, Mal.env.Env env, MalList fparams,
                Func <MalList, MalVal> fn)
 {
     this.fn      = fn;
     this.ast     = ast;
     this.env     = env;
     this.fparams = fparams;
 }
コード例 #8
0
 public MalHashMap dissoc_BANG(MalList lst)
 {
     for (int i = 0; i < lst.size(); i++)
     {
         value.Remove(((MalString)lst[i]).getValue());
     }
     return(this);
 }
コード例 #9
0
 public MalHashMap assoc_BANG(MalList lst)
 {
     for (int i = 0; i < lst.size(); i += 2)
     {
         value[((MalString)lst[i]).getValue()] = lst[i + 1];
     }
     return(this);
 }
コード例 #10
0
ファイル: MalRepl.cs プロジェクト: KineticLensman/JK-s-Lisp
        static MalVal EvalDo(MalList astList, Env env)
        {
            // Evaluate all elements of list using eval_ast, retun the final eval'd element.
            // I peeked at the reference to get this right although in my defence I added error checking
            MalList el = (MalList)eval_ast(astList.Rest(), env);

            return((el.Count() > 0) ? el[el.Count() - 1] : new MalNil());
        }
コード例 #11
0
ファイル: MalRepl.cs プロジェクト: KineticLensman/JK-s-Lisp
 static MalVal EvalFnDef(MalList astList, Env env)
 {
     // return new function closure. The body of that closure does the following
     //   create a new env using Env (closed over from outer scope) as the outer param, the first param (second el of ast from outer scope) as the binds param
     //   and the params to the closure as the exprs parameter.
     // Call EVAL on the second parameter (third list el of ast from outer scope_) using the new environment
     // Use the result as the return value of the closure.
     throw new MalInternalError("'fn' not implemented yet");
 }
コード例 #12
0
ファイル: MalRepl.cs プロジェクト: KineticLensman/JK-s-Lisp
        // 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);
            }
        }
コード例 #13
0
            public MalList Conj(params MalVal[] mvs)
            {
                // TODO. Is this the solution to the macro problem?
                Console.WriteLine("mvs: " + mvs.Length);
                MalList newList = new MalList();

                for (var i = 0; i < mvs.Length; i++)
                {
                    newList.Add(mvs[i]);
                }
                return(newList);
            }
コード例 #14
0
ファイル: MalRepl.cs プロジェクト: KineticLensman/JK-s-Lisp
        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);
        }
コード例 #15
0
        public void ReadForm_ReadsAList()
        {
            var input = "(+ 1 1)";

            var reader = Reader.ReadStr(input);

            var result = new MalList();

            result.Add(new MalAtom("+"));
            result.Add(new MalAtom("1"));
            result.Add(new MalAtom("1"));

            Reader.ReadForm(reader).Should().BeEquivalentTo(result);
        }
コード例 #16
0
ファイル: MalRepl.cs プロジェクト: KineticLensman/JK-s-Lisp
        static MalVal eval_ast(MalVal ast, Dictionary <string, MalFunc> replEnv)
        {
            // TODO - handle vectors
            // Switch on the type of the ast.
            switch ((Object)ast)
            {
            case MalSym malSym:
                // Lookup the symbol in the environment and return the value or raise an error.
                string malsymstring = malSym.ToString(true);
                Console.WriteLine("eval_ast - searching for symbol: '" + malsymstring + "'");

                if (replEnv.TryGetValue(malsymstring, out MalFunc func))
                {
                    Console.WriteLine("eval_ast - found function '" + func.ToString(true) + "'");
                    return(func);
                }
                else
                {
                    throw new MalEvalError("Undefined symbol '" + malSym.ToString(true) + "'");
                }

            case MalList malList:
                MalList derivedList = new MalList();
                Console.WriteLine("eval_ast - handling MalList");
                for (int i = 0; i < malList.Count(); i++)
                {
                    derivedList.Add(EVAL(malList.Get(i), replEnv));
                }
                return(derivedList);

            case MalVector malVec:
                MalVector derivedVec = new MalVector();
                Console.WriteLine("eval_ast - handling MalVector");
                // TODO - return a new list that is the result of calling EVAL on each of the members of the list.
                for (int i = 0; i < malVec.Count(); i++)
                {
                    derivedVec.Add(EVAL(malVec.Get(i), replEnv));
                }
                return(derivedVec);

            case MalHashMap malHashMap:
                throw new MalEvalError("INTERNAL - can't evaluate a HashMap yet '" + malHashMap.ToString() + "'");

            default:
                // It's not a symbol or a list.
                return(ast);
            }
        }
コード例 #17
0
ファイル: MalRepl.cs プロジェクト: KineticLensman/JK-s-Lisp
        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));
        }
コード例 #18
0
ファイル: MalRepl.cs プロジェクト: KineticLensman/JK-s-Lisp
        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.");
            }
        }
コード例 #19
0
ファイル: Types.cs プロジェクト: KineticLensman/JK-s-Lisp
            // Return the tail of the list.
            public MalList Rest()
            {
                if (MyElements.Count > 0)
                {
                    // We return a MalList, not a List<MalVal>.
                    MalList newList = new MalList();

                    foreach (var element in MyElements.GetRange(1, MyElements.Count - 1))
                    {
                        newList.Add(element);
                    }
                    return(newList);
                }
                else
                {
                    return(new MalList());
                }
            }
コード例 #20
0
ファイル: Types.cs プロジェクト: KineticLensman/JK-s-Lisp
            // Analagous to the C# equivalent, shallow copy of a MalList.
            public MalList GetRange(int first, int last)
            {
                if (first > last)
                {
                    throw new MalInternalError("GetRange - first '" + first + "' must be less than or equal to '" + last + "'");
                }
                MalList newList = new MalList();

                for (var i = 0; i < MyElements.Count; i++)
                {
                    if (i >= first && i <= last)
                    {
                        newList.Add(MyElements[i]);
                    }
                }

                return(newList);
            }
コード例 #21
0
ファイル: MalRepl.cs プロジェクト: KineticLensman/JK-s-Lisp
        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));
            }
        }
コード例 #22
0
ファイル: Types.cs プロジェクト: KineticLensman/JK-s-Lisp
            // Non-destructively remove a target item from a supplied list.
            // This has CL-like semantics, do only removes the first target encountered.
            public MalSequence Remove(MalVal target)
            {
                // Create a list to which the non-target items will be copied.
                MalSequence RemoveSeq;
                bool        removedP = false;

                // Instantiate a sequence of the same type.
                if (this is MalList)
                {
                    RemoveSeq = new MalList();
                }
                else if (this is MalVector)
                {
                    RemoveSeq = new MalVector();
                }
                else
                {
                    throw new MalEvalError("remove expected list or vector but got '" + this.ToString() + "'");
                }
                for (var i = 0; i < MyElements.Count; i++)
                {
                    if (removedP)
                    {
                        // target already removed so always okay to keep this one.
                        RemoveSeq.Add(MyElements[i]);
                    }
                    else
                    {
                        if (EQ(MyElements[i], target) == malTrue)
                        {
                            // This is the target to remove, so don't copy it.
                            removedP = true;
                        }
                        else
                        {
                            RemoveSeq.Add(MyElements[i]);
                        }
                    }
                }
                return(RemoveSeq);
            }
コード例 #23
0
        public void ReadForm_ReadsNestedLists()
        {
            var input = "(+ 1 (+ 2 3) 4)";

            var reader = Reader.ReadStr(input);

            var innerList = new MalList();

            innerList.Add(new MalAtom("+"));
            innerList.Add(new MalAtom("2"));
            innerList.Add(new MalAtom("3"));

            var outerList = new MalList();

            outerList.Add(new MalAtom("+"));
            outerList.Add(new MalAtom("1"));
            outerList.Add(innerList);
            outerList.Add(new MalAtom("4"));

            Reader.ReadForm(reader).Should().BeEquivalentTo(outerList);
        }
コード例 #24
0
ファイル: Env.cs プロジェクト: KineticLensman/JK-s-Lisp
        public Env(Env outer, MalList binds, MalList exprs)
        {
            // A key-MalVal dictionary.
            data = new Dictionary <string, MalVal>();

            // The scope we are created in. Null is taken to mean the REPL itself.
            this.outer = outer;

            if (binds.Count() != exprs.Count())
            {
                throw new MalEvalError("Incorrect number of fn arguments?");
            }
            // Bind (set) each element (symbol) to the respective elements of the exprs list
            for (var i = 0; i < binds.Count(); i++)
            {
                if (binds[i] is MalSym symbol)
                {
                    Set(symbol, exprs[i]);
                }
            }
        }
コード例 #25
0
ファイル: MalRepl.cs プロジェクト: KineticLensman/JK-s-Lisp
        // Name changed from the guide to highlight that the outcome is complicated.
        // TODO logic here is broken - see the above for the right if nesting.
        static MalVal ProcessQuasiquote(MalVal ast)
        {
            if (!is_pair(ast))
            {
                // Case 1.
                // If is_pair of ast is false: return a new list containing: a symbol named "quote" and ast.
                MalList qList = new MalList();

                qList.Add(new MalSym("quote"));
                qList.Add(ast);
                return(qList);
            }
            else
            {
                MalList astList = (MalList)ast;
                MalVal  a0      = astList[0];

                // Case 2:  if the first element of ast is a symbol named "unquote": return the second element of ast.
                if (a0 is MalSym a0Sym && a0Sym.getName() == "unquote")
                {
                    return(astList[1]);
                }
コード例 #26
0
ファイル: Core.cs プロジェクト: KineticLensman/JK-s-Lisp
        //------------------ Utilities for Numbers and numeric ops -------------------------------------------
        // C#-Mal only allows integers and thus avoids a lot of complexity. I allow ints and floats
        // although the implementation is more complex.
        // Also unlike C#-Mal, numeric args can have arg lists of variable  length, e.g. (+ 1 2 3).

        // Decide whether to use int or float arithmetic and check for non-numerics. Applies
        // float contaigon if an expression has a mix of ints and floats.
        protected static bool AllIntegers(MalList args)
        {
            bool allInts = true;

            for (var index = 0; index < args.Count(); index++)
            {
                switch (args[index])
                {
                case MalInt i:
                    break;

                case MalFloat f:
                    allInts = false;
                    // Don't return at this point in case there are lurking non-numbers.
                    break;

                default:
                    throw new MalEvalError("non-number in numeric expression: '" + args[index].ToString(true));
                }
            }
            return(allInts);
        }
コード例 #27
0
ファイル: Core.cs プロジェクト: KineticLensman/JK-s-Lisp
        // Mixing floats and integers introduces loads of special cases. I decided to
        // move the things that are type-dependent into helper functions to keep the
        // numeric Builtins themselves cleaner.
        static protected MalInt ProcessIntegers(MalList args, NumericOp op)
        {
            int result  = (int)(MalInt)args[0];
            int divTest = 0;

            // Handle the special case of (- 1).
            if (args.Count() == 1 && op == NumericOp.Subtract)
            {
                return(new MalInt(-result));
            }
            for (var i = 1; i < args.Count(); i++)
            {
                switch (op)
                {
                case NumericOp.Plus:
                    result += (int)(MalInt)args[i];
                    break;

                case NumericOp.Multiply:
                    result *= (int)(MalInt)args[i];
                    break;

                case NumericOp.Subtract:
                    result -= (int)(MalInt)args[i];
                    break;

                case NumericOp.Divide:
                    divTest = (int)(MalInt)args[i];
                    if (i > 0 && divTest == 0)
                    {
                        throw new MalEvalError("Can't divide by zero");
                    }
                    result /= divTest;
                    break;
                }
            }
            return(new MalInt(result));
        }
コード例 #28
0
ファイル: Types.cs プロジェクト: KineticLensman/JK-s-Lisp
            // Call the stored func, passing it a list containing args, if any.
            public MalVal Apply(MalList args)
            {
//                Console.Write("applying function built-in = " + myCreatedByFn);
                return(fn(args));
            }
コード例 #29
0
ファイル: types.cs プロジェクト: csellsword/mal
 public MalHashMap(MalList lst) {
     value = new Dictionary<String, MalVal>();
     assoc_BANG(lst);
 }
コード例 #30
0
ファイル: types.cs プロジェクト: csellsword/mal
 public MalHashMap assoc_BANG(MalList lst) {
     for (int i=0; i<lst.size(); i+=2) {
         value[((MalString)lst[i]).getValue()] = lst[i+1];
     }
     return this;
 }
コード例 #31
0
 public Mal.env.Env genEnv(MalList args)
 {
     return(new Mal.env.Env(env, fparams, args));
 }
コード例 #32
0
 public MalVal apply(MalList args)
 {
     return(fn(args));
 }
コード例 #33
0
ファイル: types.cs プロジェクト: csellsword/mal
 public MalHashMap dissoc_BANG(MalList lst) {
     for (int i=0; i<lst.size(); i++) {
         value.Remove(((MalString)lst[i]).getValue());
     }
     return this;
 }
コード例 #34
0
ファイル: types.cs プロジェクト: csellsword/mal
 public MalFunc(MalVal ast, Mal.env.Env env, MalList fparams,
                    Func<MalList, MalVal> fn) {
     this.fn = fn;
     this.ast = ast;
     this.env = env;
     this.fparams = fparams;
 }
コード例 #35
0
ファイル: types.cs プロジェクト: csellsword/mal
 public MalVal apply(MalList args) {
     return fn(args);
 }
コード例 #36
0
ファイル: types.cs プロジェクト: csellsword/mal
 public Mal.env.Env genEnv(MalList args) {
     return new Mal.env.Env(env, fparams, args);
 }