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 ( const (
Nil NodeType = iota Nil NodeType = iota
Nothing Nothing
True
False
Symbol Symbol
Number Number
List List

View File

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

View File

@ -19,9 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package core package core
import ( import (
"errors"
"fmt"
"serene-lang.org/bootstrap/pkg/ast" "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 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 // evaluation of a list it will evaluate all the elements and return the
// evaluated list // 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() { switch form.GetType() {
case ast.Nil: case ast.Nil:
@ -44,14 +41,24 @@ func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, error) {
// * Otherwise throw an error // * Otherwise throw an error
case ast.Symbol: case ast.Symbol:
symbolName := form.(*Symbol).GetName() symbolName := form.(*Symbol).GetName()
expr := scope.Lookup(symbolName)
if expr == nil { switch symbolName {
return nil, fmt.Errorf("can't resolve symbol '%s' in ns '%s'", symbolName, rt.CurrentNS().GetName()) 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 // Evaluate all the elements in the list instead of following the lisp convention
case ast.List: case ast.List:
var result []IExpr var result []IExpr
@ -74,13 +81,13 @@ func evalForm(rt *Runtime, scope IScope, form IExpr) (IExpr, error) {
} }
// Default case // 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) // 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`. // 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. // 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 // 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. // 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 // a functional language we need to avoid unnecessary calls and keep as much
// as possible in a loop. // as possible in a loop.
var ret IExpr var ret IExpr
var err error var err IError
tco: tco:
for { for {
@ -154,7 +161,11 @@ tco:
ret, err = Fn(rt, scope, list.Rest().(*List)) ret, err = Fn(rt, scope, list.Rest().(*List))
break tco // return 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 // * The first element of the list has to be an expression which is callable
// * An empty list evaluates to itself. // * An empty list evaluates to itself.
default: default:
@ -185,7 +196,7 @@ tco:
argList := exprs.(*List).Rest().(*List) 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 { if e != nil {
err = e err = e
ret = nil ret = nil
@ -195,7 +206,7 @@ tco:
expressions = fn.GetBody() expressions = fn.GetBody()
continue tco continue tco
default: 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 ret = nil
break tco break tco
} }
@ -212,7 +223,7 @@ tco:
// concept of Block with blocks from other languages which // concept of Block with blocks from other languages which
// specify by using `{}` or indent or what ever. Blocks in terms // specify by using `{}` or indent or what ever. Blocks in terms
// of Serene are just arrays of expressions and nothing more. // 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 { if forms.Count() == 0 {
// Nothing is literally Nothing // Nothing is literally Nothing
return &Nothing, nil return &Nothing, nil

View File

@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package core package core
import ( import (
"errors"
"fmt" "fmt"
"serene-lang.org/bootstrap/pkg/ast" "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` // MakeFnScope a new scope for the body of a function. It binds the `bindings`
// to the given `values`. // 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)) scope := MakeScope(parent.(*Scope))
// TODO: Implement destructuring // TODO: Implement destructuring
if bindings.Count() > values.Count() { 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() binds := bindings.ToSlice()

View File

@ -19,8 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
package core package core
import ( import (
"errors"
"fmt"
"strings" "strings"
"unicode" "unicode"
) )
@ -88,6 +86,15 @@ func (sp *StringParser) Buffer() *[]string {
// END: IParsable --- // 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 { func contains(s []rune, c rune) bool {
for _, v := range s { for _, v := range s {
if v == c { if v == c {
@ -103,19 +110,20 @@ func isValidForSymbol(char string) bool {
return contains(validChars, c) || unicode.IsLetter(c) || unicode.IsDigit(c) 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) c := parser.peek(false)
var symbol string var symbol string
if c == nil { 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) { if isValidForSymbol(*c) {
parser.next(false) parser.next(false)
symbol = *c symbol = *c
} else { } 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, *c,
parser.GetLocation(), parser.GetLocation(),
) )
@ -141,7 +149,7 @@ func readRawSymbol(parser IParsable) (IExpr, error) {
return MakeSymbol(node, symbol), nil return MakeSymbol(node, symbol), nil
} }
func readNumber(parser IParsable, neg bool) (IExpr, error) { func readNumber(parser IParsable, neg bool) (IExpr, IError) {
isDouble := false isDouble := false
result := "" result := ""
@ -157,7 +165,7 @@ func readNumber(parser IParsable, neg bool) (IExpr, error) {
} }
if *c == "." && isDouble { 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 == "." { 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) c := parser.peek(false)
if c == nil { 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 == "\"" { // if c == "\"" {
@ -218,13 +232,13 @@ func readSymbol(parser IParsable) (IExpr, error) {
return readRawSymbol(parser) return readRawSymbol(parser)
} }
func readList(parser IParsable) (IExpr, error) { func readList(parser IParsable) (IExpr, IError) {
list := []IExpr{} list := []IExpr{}
for { for {
c := parser.peek(true) c := parser.peek(true)
if c == nil { 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 == ")" { if *c == ")" {
parser.next(true) parser.next(true)
@ -243,7 +257,7 @@ func readList(parser IParsable) (IExpr, error) {
return MakeList(list), nil return MakeList(list), nil
} }
func readComment(parser IParsable) (IExpr, error) { func readComment(parser IParsable) (IExpr, IError) {
for { for {
c := parser.next(false) c := parser.next(false)
if c == nil || *c == "\n" { 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) expr, err := readExpr(parser)
if err != nil { if err != nil {
return nil, err return nil, err
@ -265,15 +279,15 @@ func readQuotedExpr(parser IParsable) (IExpr, error) {
}), nil }), nil
} }
func readUnquotedExpr(parser IParsable) (IExpr, error) { func readUnquotedExpr(parser IParsable) (IExpr, IError) {
c := parser.peek(true) c := parser.peek(true)
if c == nil { 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 sym IExpr
var err error var err IError
var expr IExpr var expr IExpr
node := MakeNode(parser.Buffer(), parser.GetLocation(), parser.GetLocation()) node := MakeNode(parser.Buffer(), parser.GetLocation(), parser.GetLocation())
@ -295,7 +309,7 @@ func readUnquotedExpr(parser IParsable) (IExpr, error) {
return MakeList([]IExpr{sym, expr}), nil return MakeList([]IExpr{sym, expr}), nil
} }
func readQuasiquotedExpr(parser IParsable) (IExpr, error) { func readQuasiquotedExpr(parser IParsable) (IExpr, IError) {
expr, err := readExpr(parser) expr, err := readExpr(parser)
if err != nil { if err != nil {
return nil, err return nil, err
@ -308,7 +322,7 @@ func readQuasiquotedExpr(parser IParsable) (IExpr, error) {
}), nil }), nil
} }
func readExpr(parser IParsable) (IExpr, error) { func readExpr(parser IParsable) (IExpr, IError) {
loop: loop:
c := parser.next(true) c := parser.next(true)
@ -347,7 +361,7 @@ loop:
} }
func ParseToAST(input string) (*Block, error) { func ParseToAST(input string) (*Block, IError) {
var ast Block var ast Block
parser := StringParser{ parser := StringParser{

View File

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

View File

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

View File

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