From 688d1692866a4025a9dff3a5f8ef0ba923f7c737 Mon Sep 17 00:00:00 2001 From: Sameer Rahmani Date: Wed, 25 Nov 2020 19:19:48 +0000 Subject: [PATCH] Add the `let` special form alongside with locatable errors Add the `let` sform not in the triditional way. In our case the let scope is available to the bindings. Similar to clojure's let and let* in triditional sense. Also add the function `MakeErrorFor` which gets an extra arg, an `IExpr` and returns an error with the location of that expression. --- bootstrap/pkg/ast/ast.go | 16 +++++++++++ bootstrap/pkg/core/errors.go | 9 ++++++ bootstrap/pkg/core/eval.go | 52 ++++++++++++++++++++++++++++++++++- bootstrap/pkg/core/printer.go | 4 ++- bootstrap/pkg/core/types.go | 9 ++++-- 5 files changed, 86 insertions(+), 4 deletions(-) diff --git a/bootstrap/pkg/ast/ast.go b/bootstrap/pkg/ast/ast.go index aea5f78..36d761a 100644 --- a/bootstrap/pkg/ast/ast.go +++ b/bootstrap/pkg/ast/ast.go @@ -41,6 +41,22 @@ type Location struct { knownLocation bool } +func (l *Location) GetStart() int { + return l.start +} + +func (l *Location) GetEnd() int { + return l.end +} + +func (l *Location) GetSource() *[]string { + return l.source +} + +func (l *Location) IsKnownLocaiton() bool { + return l.knownLocation +} + type ILocatable interface { GetLocation() Location } diff --git a/bootstrap/pkg/core/errors.go b/bootstrap/pkg/core/errors.go index 7d446d2..8b9b5d9 100644 --- a/bootstrap/pkg/core/errors.go +++ b/bootstrap/pkg/core/errors.go @@ -49,6 +49,15 @@ func MakeError(rt *Runtime, msg string) IError { } } +func MakeErrorFor(rt *Runtime, e IExpr, msg string) IError { + loc := e.GetLocation() + + return &Error{ + Node: MakeNodeFromLocation(loc), + msg: msg, + } +} + func MakeRuntimeErrorf(rt *Runtime, msg string, a ...interface{}) IError { return &Error{ msg: fmt.Sprintf(msg, a...), diff --git a/bootstrap/pkg/core/eval.go b/bootstrap/pkg/core/eval.go index 4ac1726..ca20e5b 100644 --- a/bootstrap/pkg/core/eval.go +++ b/bootstrap/pkg/core/eval.go @@ -197,7 +197,57 @@ tco: expressions = MakeBlock(list.Rest().(*List).ToSlice()) continue tco // Loop over to execute the new expressions - // case "let": + 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 diff --git a/bootstrap/pkg/core/printer.go b/bootstrap/pkg/core/printer.go index bcdfbda..ae617c9 100644 --- a/bootstrap/pkg/core/printer.go +++ b/bootstrap/pkg/core/printer.go @@ -27,5 +27,7 @@ func Print(rt *Runtime, ast IPrintable) { } func PrintError(rt *Runtime, err IError) { - fmt.Printf("Error: %s\n", err.String()) + loc := err.GetLocation() + fmt.Printf("Error: %s\nAt: %d to %d\n", err.String(), loc.GetStart(), loc.GetEnd()) + } diff --git a/bootstrap/pkg/core/types.go b/bootstrap/pkg/core/types.go index 9b62d50..c0a91e8 100644 --- a/bootstrap/pkg/core/types.go +++ b/bootstrap/pkg/core/types.go @@ -58,12 +58,17 @@ func (n Node) GetLocation() ast.Location { return n.location } -func MakeNode(input *[]string, start int, end int) Node { +// Create a new Node for the given Location `loc` +func MakeNodeFromLocation(loc ast.Location) Node { return Node{ - location: ast.MakeLocation(input, start, end), + location: loc, } } +func MakeNode(input *[]string, start int, end int) Node { + return MakeNodeFromLocation(ast.MakeLocation(input, start, end)) +} + func MakeSinglePointNode(input *[]string, point int) Node { return MakeNode(input, point, point) }