/* Serene --- Yet an other Lisp Copyright (c) 2020 Sameer Rahmani This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package core import ( "serene-lang.org/bootstrap/pkg/ast" ) // evalForm evaluates the given expression `form` by a slightly different // evaluation rules. For example if `form` is a list instead of the formal // evaluation of a list it will evaluate all the elements and return the // evaluated list func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, IError) { switch form.GetType() { case ast.Nil: return form, nil case ast.Number: return form, nil // Symbol evaluation rules: // * If it's a NS qualified symbol (NSQS), Look it up in the external symbol table of // the current namespace. // * If it's not a NSQS Look up the name in the current scope. // * Otherwise throw an error case ast.Symbol: symbolName := form.(*Symbol).GetName() switch symbolName { case "true": return &True, nil case "false": return &False, nil case "nil": return &Nil, nil default: expr := scope.Lookup(symbolName) if expr == nil { return nil, MakeRuntimeErrorf(rt, "can't resolve symbol '%s' in ns '%s'", symbolName, rt.CurrentNS().GetName()) } return expr.Value, nil } // Evaluate all the elements in the list instead of following the lisp convention case ast.List: var result []IExpr lst := form.(*List) for { if lst.Count() > 0 { expr, err := EvalForms(rt, scope, lst.First()) if err != nil { return nil, err } result = append(result, expr) lst = lst.Rest().(*List) } else { break } } return MakeList(result), nil } // Default case return nil, MakeError(rt, "not implemented") } // EvalForms evaluates the given expr `expressions` (it can be a list, block, symbol or anything else) // with the given runtime `rt` and the scope `scope`. func EvalForms(rt *Runtime, scope IScope, expressions IExpr) (IExpr, IError) { // EvalForms is the main and the most important evaluation function on Serene. // It's a long loooooooooooong function. Why? Well, Because we don't want to // waste call stack spots in order to have a well organized code. // In order to avoid stackoverflows and implement TCO ( which is a must for // a functional language we need to avoid unnecessary calls and keep as much // as possible in a loop. var ret IExpr var err IError tco: for { // The TCO loop is there to take advantage or the fact that // in order to call a function or a block we simply can change // the value of the `expressions` and `scope` var exprs []IExpr // Block evaluation rules: // * If empty, return Nothing // * Otherwise evaluate the expressions in the block one by one // and return the last result if expressions.GetType() == ast.Block { if expressions.(*Block).Count() == 0 { return &Nothing, nil } exprs = expressions.(*Block).ToSlice() } else { exprs = []IExpr{expressions} } body: for _, forms := range exprs { // Evaluating forms one by one if forms.GetType() != ast.List { ret, err = evalForm(rt, scope, forms) break tco // return ret, err } list := forms.(*List) // Empty list evaluates to itself if list.Count() == 0 { ret = list break tco // return &Nil, nil } rawFirst := list.First() sform := "" // Handling special forms by looking up the first // element of the list. If it is a symbol, Grab // the name and check it for build it forms. // // Note: If we don't care about recursion in any // case we can simply extract it to a function // for example in `def` since we are going to // evaluate the value separately, we don't care // about recursion because we're going to handle // it wen we're evaluating the value. But in the // case of let it's a different story. if rawFirst.GetType() == ast.Symbol { sform = rawFirst.(*Symbol).GetName() } switch sform { // `quote` evaluation rules: // * Only takes one argument // * Returns the argument without evaluating it case "quote": // Including the `quote` itself if list.Count() != 2 { return nil, MakeErrorFor(rt, list, "'quote' quote only accepts one argument.") } return list.Rest().First(), nil // `def` evaluation rules // * The first argument has to be a symbol. // * The second argument has to be evaluated and be used as // the value. // * Defines a global binding in the current namespace using // the symbol name binded to the value case "def": ret, err = Def(rt, scope, list.Rest().(*List)) break tco // return // `fn` evaluation rules: // * It needs at least a collection of arguments // * Defines an anonymous function. case "fn": ret, err = Fn(rt, scope, list.Rest().(*List)) break tco // return // `if` evaluation rules: // * It has to get only 3 arguments: PRED THEN ELSE // * Evaluate only the PRED expression if the result // is not `nil` or `false` evaluates THEN otherwise // evaluate the ELSE expression and return the result. case "if": args := list.Rest().(*List) if args.Count() != 3 { return nil, MakeError(rt, "'if' needs exactly 3 aruments") } pred, err := EvalForms(rt, scope, args.First()) result := pred.GetType() if err != nil { return nil, err } if result != ast.False && result != ast.Nil { // Truthy clause expressions = args.Rest().First() } else { // Falsy clause expressions = args.Rest().Rest().First() } continue tco // Loop over to execute the new expressions // `do` evaluation rules: // * Evaluate the body as a new block in the TCO loop // and return the result of the last expression case "do": expressions = MakeBlock(list.Rest().(*List).ToSlice()) continue tco // Loop over to execute the new expressions case "let": if list.Count() < 2 { return nil, MakeError(rt, "'let' needs at list 1 aruments") } letScope := MakeScope(scope.(*Scope)) // Since we're using IColl for the bindings, we can use either lists // or vectors or even hashmaps for bindings var bindings IColl bindings = list.Rest().First().(IColl) body := list.Rest().Rest().(*List).ToSlice() if bindings.Count()%2 != 0 { return nil, MakeError(rt, "'let' bindings has to have even number of forms.") } for { // We're reducing over bindings here if bindings.Count() == 0 { break } name := bindings.First() expr := bindings.Rest().First() // TODO: We need to destruct the bindings here and remove this check // for the symbol type if name.GetType() != ast.Symbol { err := MakeErrorFor(rt, name, "'let' doesn't support desbbtructuring yet, use a symbol.") return nil, err } // You might be wondering why we're using `EvalForms` here to evaluate // the exprs in bindings, what about TCO ? // Well, It's called TAIL call optimization for a reason. Exprs in the // bindings are not tail calls evaluatedExpr, e := EvalForms(rt, letScope, expr) if e != nil { return nil, e } letScope.Insert(name.String(), evaluatedExpr, false) bindings = bindings.Rest().Rest().(IColl) } expressions = MakeBlock(body) scope = letScope continue tco // list evaluation rules: // * The first element of the list has to be an expression which is callable // * An empty list evaluates to itself. default: // Evaluating all the elements of the list exprs, e := evalForm(rt, scope, list) if e != nil { err = e ret = nil break tco //return } f := exprs.(*List).First() switch f.GetType() { case ast.Fn: // If the first element of the evaluated list is a function // create a scope for it by creating the binding to the given // parameters in the new scope and set the parent of it to // the scope which the function defined in and then set the // `expressions` to the body of function and loop again fn := f.(*Function) if e != nil { err = e ret = nil break body //return } argList := exprs.(*List).Rest().(*List) scope, e = MakeFnScope(rt, fn.GetScope(), fn.GetParams(), argList) if e != nil { err = e ret = nil break body //return } expressions = fn.GetBody() continue tco default: err = MakeError(rt, "don't know how to execute anything beside function") ret = nil break tco } } } } return ret, err } // Eval the given `Block` of code with the given runtime `rt`. // The Important part here is that any expression that we need // to Eval has to be wrapped in a Block. Don't confused the // concept of Block with blocks from other languages which // specify by using `{}` or indent or what ever. Blocks in terms // of Serene are just arrays of expressions and nothing more. func Eval(rt *Runtime, forms *Block) (IExpr, IError) { if forms.Count() == 0 { // Nothing is literally Nothing return &Nothing, nil } v, err := EvalForms(rt, rt.CurrentNS().GetRootScope(), forms) if err != nil { return nil, err } return v, nil }