serene-golang-implementation/bootstrap/pkg/core/eval.go

324 lines
8.9 KiB
Go
Raw Normal View History

/*
Serene --- Yet an other Lisp
Copyright (c) 2020 Sameer Rahmani <lxsameer@gnu.org>
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 <http://www.gnu.org/licenses/>.
*/
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) {
2020-11-19 19:14:06 +00:00
switch form.GetType() {
case ast.Nil:
2020-11-19 19:14:06 +00:00
return form, nil
case ast.Number:
return form, nil
2020-11-15 21:38:09 +00:00
// 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
2020-11-15 21:38:09 +00:00
case ast.List:
2020-11-19 19:14:06 +00:00
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")
2020-11-19 19:14:06 +00:00
}
// 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:
2020-11-19 19:14:06 +00:00
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}
2020-11-19 19:14:06 +00:00
}
body:
for _, forms := range exprs {
// Evaluating forms one by one
2020-11-19 19:14:06 +00:00
if forms.GetType() != ast.List {
ret, err = evalForm(rt, scope, forms)
break tco // return ret, err
}
2020-11-15 21:38:09 +00:00
list := forms.(*List)
// Empty list evaluates to itself
if list.Count() == 0 {
ret = list
break tco // return &Nil, nil
}
2020-11-19 19:14:06 +00:00
rawFirst := list.First()
sform := ""
2020-11-15 21:38:09 +00:00
// 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()
}
2020-11-19 19:14:06 +00:00
switch sform {
case "def":
ret, err = Def(rt, scope, list.Rest().(*List))
break tco // return
2020-11-19 19:14:06 +00:00
case "fn":
ret, err = Fn(rt, scope, list.Rest().(*List))
break tco // return
2020-11-25 16:18:38 +00:00
// `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.
2020-11-25 13:15:19 +00:00
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
2020-11-25 16:18:38 +00:00
// `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)
2020-11-19 19:14:06 +00:00
if e != nil {
err = e
ret = nil
break tco //return
2020-11-19 19:14:06 +00:00
}
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
2020-11-19 19:14:06 +00:00
}
}
}
}
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)
2020-11-19 19:14:06 +00:00
if err != nil {
return nil, err
}
return v, nil
}