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.
This commit is contained in:
Sameer Rahmani 2020-11-25 19:19:48 +00:00
parent d86b47c283
commit 688d169286
5 changed files with 86 additions and 4 deletions

View File

@ -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
}

View File

@ -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...),

View File

@ -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

View File

@ -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())
}

View File

@ -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)
}