Add IError interface to represent system errors

This commit is contained in:
Sameer Rahmani 2020-11-24 18:27:48 +00:00
parent 57714accfc
commit d92150f0c3
8 changed files with 77 additions and 50 deletions

View File

@ -25,6 +25,8 @@ type NodeType int
const (
Nil NodeType = iota
Nothing
True
False
Symbol
Number
List

View File

@ -41,12 +41,11 @@ func rep(rt *Runtime, line string) {
fmt.Print("#############\n\n")
}
result, err := Eval(rt, ast)
if err != nil {
result, e := Eval(rt, ast)
if e != nil {
fmt.Printf("Error: %s\n", err)
return
}
Print(rt, result)
}

View File

@ -19,9 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"errors"
"fmt"
"serene-lang.org/bootstrap/pkg/ast"
)
@ -29,7 +26,7 @@ import (
// 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, error) {
func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, IError) {
switch form.GetType() {
case ast.Nil:
@ -44,14 +41,24 @@ func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, error) {
// * Otherwise throw an error
case ast.Symbol:
symbolName := form.(*Symbol).GetName()
expr := scope.Lookup(symbolName)
if expr == nil {
return nil, fmt.Errorf("can't resolve symbol '%s' in ns '%s'", symbolName, rt.CurrentNS().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
}
return expr.Value, nil
// Evaluate all the elements in the list instead of following the lisp convention
case ast.List:
var result []IExpr
@ -74,13 +81,13 @@ func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, error) {
}
// Default case
return nil, errors.New("not implemented")
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, error) {
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.
@ -88,7 +95,7 @@ func EvalForms(rt *Runtime, scope IScope, expressions IExpr) (IExpr, error) {
// a functional language we need to avoid unnecessary calls and keep as much
// as possible in a loop.
var ret IExpr
var err error
var err IError
tco:
for {
@ -154,7 +161,11 @@ tco:
ret, err = Fn(rt, scope, list.Rest().(*List))
break tco // return
// List evaluation rules:
// case "if":
// ret, err = If(rt, scope, list.Rest().(*List))
// break tco // return
// list evaluation rules:
// * The first element of the list has to be an expression which is callable
// * An empty list evaluates to itself.
default:
@ -185,7 +196,7 @@ tco:
argList := exprs.(*List).Rest().(*List)
scope, e = MakeFnScope(fn.GetScope(), fn.GetParams(), argList)
scope, e = MakeFnScope(rt, fn.GetScope(), fn.GetParams(), argList)
if e != nil {
err = e
ret = nil
@ -195,7 +206,7 @@ tco:
expressions = fn.GetBody()
continue tco
default:
err = errors.New("don't know how to execute anything beside function")
err = MakeError(rt, "don't know how to execute anything beside function")
ret = nil
break tco
}
@ -212,7 +223,7 @@ tco:
// 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, error) {
func Eval(rt *Runtime, forms *Block) (IExpr, IError) {
if forms.Count() == 0 {
// Nothing is literally Nothing
return &Nothing, nil

View File

@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"errors"
"fmt"
"serene-lang.org/bootstrap/pkg/ast"
@ -89,13 +88,13 @@ func MakeFunction(scope IScope, params IColl, body *Block) *Function {
// MakeFnScope a new scope for the body of a function. It binds the `bindings`
// to the given `values`.
func MakeFnScope(parent IScope, bindings IColl, values IColl) (*Scope, error) {
func MakeFnScope(rt *Runtime, parent IScope, bindings IColl, values IColl) (*Scope, IError) {
scope := MakeScope(parent.(*Scope))
// TODO: Implement destructuring
if bindings.Count() > values.Count() {
return nil, errors.New("'binding' and 'valuse' size don't match")
return nil, MakeError(rt, "'binding' and 'valuse' size don't match")
}
binds := bindings.ToSlice()

View File

@ -19,8 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"errors"
"fmt"
"strings"
"unicode"
)
@ -88,6 +86,15 @@ func (sp *StringParser) Buffer() *[]string {
// END: IParsable ---
func makeErrorAtPoint(p IParsable, msg string, a ...interface{}) IError {
n := MakeSinglePointNode(p.Buffer(), p.GetLocation())
return MakeParsetimeErrorf(n, msg, a)
}
func makeErrorFromError(parser IParsable, e error) IError {
return makeErrorAtPoint(parser, "%w", e)
}
func contains(s []rune, c rune) bool {
for _, v := range s {
if v == c {
@ -103,19 +110,20 @@ func isValidForSymbol(char string) bool {
return contains(validChars, c) || unicode.IsLetter(c) || unicode.IsDigit(c)
}
func readRawSymbol(parser IParsable) (IExpr, error) {
func readRawSymbol(parser IParsable) (IExpr, IError) {
c := parser.peek(false)
var symbol string
if c == nil {
return nil, errors.New("unexpected enf of file while parsing a symbol")
return nil, makeErrorAtPoint(parser, "unexpected enf of file while parsing a symbol")
}
if isValidForSymbol(*c) {
parser.next(false)
symbol = *c
} else {
return nil, fmt.Errorf("unexpected character: got '%s', expected a symbol at %d",
return nil, makeErrorAtPoint(parser,
"unexpected character: got '%s', expected a symbol at %d",
*c,
parser.GetLocation(),
)
@ -141,7 +149,7 @@ func readRawSymbol(parser IParsable) (IExpr, error) {
return MakeSymbol(node, symbol), nil
}
func readNumber(parser IParsable, neg bool) (IExpr, error) {
func readNumber(parser IParsable, neg bool) (IExpr, IError) {
isDouble := false
result := ""
@ -157,7 +165,7 @@ func readNumber(parser IParsable, neg bool) (IExpr, error) {
}
if *c == "." && isDouble {
return nil, errors.New("a double with more that one '.' ???")
return nil, makeErrorAtPoint(parser, "a double with more that one '.' ???")
}
if *c == "." {
@ -177,14 +185,20 @@ func readNumber(parser IParsable, neg bool) (IExpr, error) {
}
}
return MakeNumberFromStr(result, isDouble)
value, err := MakeNumberFromStr(result, isDouble)
if err != nil {
return nil, makeErrorFromError(parser, err)
}
return value, nil
}
func readSymbol(parser IParsable) (IExpr, error) {
func readSymbol(parser IParsable) (IExpr, IError) {
c := parser.peek(false)
if c == nil {
return nil, errors.New("unexpected end of file while scanning a symbol")
return nil, makeErrorAtPoint(parser, "unexpected end of file while scanning a symbol")
}
// if c == "\"" {
@ -218,13 +232,13 @@ func readSymbol(parser IParsable) (IExpr, error) {
return readRawSymbol(parser)
}
func readList(parser IParsable) (IExpr, error) {
func readList(parser IParsable) (IExpr, IError) {
list := []IExpr{}
for {
c := parser.peek(true)
if c == nil {
return nil, errors.New("reaching the end of file while reading a list")
return nil, makeErrorAtPoint(parser, "reaching the end of file while reading a list")
}
if *c == ")" {
parser.next(true)
@ -243,7 +257,7 @@ func readList(parser IParsable) (IExpr, error) {
return MakeList(list), nil
}
func readComment(parser IParsable) (IExpr, error) {
func readComment(parser IParsable) (IExpr, IError) {
for {
c := parser.next(false)
if c == nil || *c == "\n" {
@ -252,7 +266,7 @@ func readComment(parser IParsable) (IExpr, error) {
}
}
func readQuotedExpr(parser IParsable) (IExpr, error) {
func readQuotedExpr(parser IParsable) (IExpr, IError) {
expr, err := readExpr(parser)
if err != nil {
return nil, err
@ -265,15 +279,15 @@ func readQuotedExpr(parser IParsable) (IExpr, error) {
}), nil
}
func readUnquotedExpr(parser IParsable) (IExpr, error) {
func readUnquotedExpr(parser IParsable) (IExpr, IError) {
c := parser.peek(true)
if c == nil {
return nil, errors.New("end of file while reading an unquoted expression")
return nil, makeErrorAtPoint(parser, "end of file while reading an unquoted expression")
}
var sym IExpr
var err error
var err IError
var expr IExpr
node := MakeNode(parser.Buffer(), parser.GetLocation(), parser.GetLocation())
@ -295,7 +309,7 @@ func readUnquotedExpr(parser IParsable) (IExpr, error) {
return MakeList([]IExpr{sym, expr}), nil
}
func readQuasiquotedExpr(parser IParsable) (IExpr, error) {
func readQuasiquotedExpr(parser IParsable) (IExpr, IError) {
expr, err := readExpr(parser)
if err != nil {
return nil, err
@ -308,7 +322,7 @@ func readQuasiquotedExpr(parser IParsable) (IExpr, error) {
}), nil
}
func readExpr(parser IParsable) (IExpr, error) {
func readExpr(parser IParsable) (IExpr, IError) {
loop:
c := parser.next(true)
@ -347,7 +361,7 @@ loop:
}
func ParseToAST(input string) (*Block, error) {
func ParseToAST(input string) (*Block, IError) {
var ast Block
parser := StringParser{

View File

@ -18,6 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
func ReadString(input string) (*Block, error) {
func ReadString(input string) (*Block, IError) {
return ParseToAST(input)
}

View File

@ -19,15 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package core
import (
"errors"
"serene-lang.org/bootstrap/pkg/ast"
)
// Def defines a global binding in the current namespace. The first
// arguments in `args` has to be a symbol ( none ns qualified ) and
// the second param should be the value of the binding
func Def(rt *Runtime, scope IScope, args *List) (IExpr, error) {
func Def(rt *Runtime, scope IScope, args *List) (IExpr, IError) {
// TODO: Add support for docstrings and meta
@ -36,7 +34,7 @@ func Def(rt *Runtime, scope IScope, args *List) (IExpr, error) {
name := args.First()
if name.GetType() != ast.Symbol {
return nil, errors.New("the first argument of 'def' has to be a symbol")
return nil, MakeError(rt, "the first argument of 'def' has to be a symbol")
}
sym := name.(*Symbol)
@ -53,15 +51,15 @@ func Def(rt *Runtime, scope IScope, args *List) (IExpr, error) {
return sym, nil
}
return nil, errors.New("'def' form need at least 2 arguments")
return nil, MakeError(rt, "'def' form need at least 2 arguments")
}
// Fn defines a function inside the given scope `scope` with the given `args`.
// `args` contains the arugment list, docstring and body of the function.
func Fn(rt *Runtime, scope IScope, args *List) (IExpr, error) {
func Fn(rt *Runtime, scope IScope, args *List) (IExpr, IError) {
if args.Count() < 1 {
return nil, errors.New("'fn' needs at least an arguments list")
return nil, MakeError(rt, "'fn' needs at least an arguments list")
}
var params IColl

View File

@ -63,3 +63,7 @@ func MakeNode(input *[]string, start int, end int) Node {
location: ast.MakeLocation(input, start, end),
}
}
func MakeSinglePointNode(input *[]string, point int) Node {
return MakeNode(input, point, point)
}