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:
parent
d86b47c283
commit
688d169286
|
@ -41,6 +41,22 @@ type Location struct {
|
||||||
knownLocation bool
|
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 {
|
type ILocatable interface {
|
||||||
GetLocation() Location
|
GetLocation() Location
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
func MakeRuntimeErrorf(rt *Runtime, msg string, a ...interface{}) IError {
|
||||||
return &Error{
|
return &Error{
|
||||||
msg: fmt.Sprintf(msg, a...),
|
msg: fmt.Sprintf(msg, a...),
|
||||||
|
|
|
@ -197,7 +197,57 @@ tco:
|
||||||
expressions = MakeBlock(list.Rest().(*List).ToSlice())
|
expressions = MakeBlock(list.Rest().(*List).ToSlice())
|
||||||
continue tco // Loop over to execute the new expressions
|
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:
|
// list evaluation rules:
|
||||||
// * The first element of the list has to be an expression which is callable
|
// * The first element of the list has to be an expression which is callable
|
||||||
|
|
|
@ -27,5 +27,7 @@ func Print(rt *Runtime, ast IPrintable) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func PrintError(rt *Runtime, err IError) {
|
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())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,12 +58,17 @@ func (n Node) GetLocation() ast.Location {
|
||||||
return n.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{
|
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 {
|
func MakeSinglePointNode(input *[]string, point int) Node {
|
||||||
return MakeNode(input, point, point)
|
return MakeNode(input, point, point)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue