diff --git a/evaluator/assign.go b/evaluator/assign.go index 2061687..40e6b35 100644 --- a/evaluator/assign.go +++ b/evaluator/assign.go @@ -22,9 +22,7 @@ func evaluateAssign(node *ast.Assign, scope *object.Scope) object.Object { return evaluatePropertyAssignment(assignment, value, scope) } - object.NewError("%d:%d:%s: runtime error: cannot assign variable to a %T", node.Token.Line, node.Token.Column, node.Token.File, node.Name) - - return nil + return object.NewError("%d:%d:%s: runtime error: cannot assign variable to a %T", node.Token.Line, node.Token.Column, node.Token.File, node.Name) } func evaluateIdentifierAssignment(node *ast.Identifier, value object.Object, scope *object.Scope) object.Object { @@ -44,7 +42,11 @@ func evaluateIndexAssignment(node *ast.Index, assignmentValue object.Object, sco switch obj := left.(type) { case *object.List: - idx := int(index.(*object.Number).Int64()) + numIdx, ok := index.(*object.Number) + if !ok { + return object.NewError("%d:%d:%s: runtime error: list index must be a number, got %s", node.Token.Line, node.Token.Column, node.Token.File, index.Type()) + } + idx := int(numIdx.Int64()) elements := obj.Elements if idx < 0 { diff --git a/evaluator/compound.go b/evaluator/compound.go index 93f29a9..6d125c2 100644 --- a/evaluator/compound.go +++ b/evaluator/compound.go @@ -15,7 +15,20 @@ func evaluateCompound(node *ast.Compound, scope *object.Scope) object.Object { value := evaluateInfix(infix, scope) - scope.Environment.Set(node.Left.(*ast.Identifier).Value, value) + if isError(value) { + return value + } + + switch target := node.Left.(type) { + case *ast.Identifier: + scope.Environment.Set(target.Value, value) + case *ast.Index: + return evaluateIndexAssignment(target, value, scope) + case *ast.Property: + return evaluatePropertyAssignment(target, value, scope) + default: + return newError("%d:%d:%s: runtime error: invalid compound assignment target: %T", node.Token.Line, node.Token.Column, node.Token.File, node.Left) + } return nil } diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index c33f0d5..b426575 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -111,10 +111,10 @@ func isError(obj object.Object) bool { return false } -// isTerminator determines if the referenced object is an error, break, or continue. +// isTerminator determines if the referenced object is an error, break, continue, or return. func isTerminator(obj object.Object) bool { if obj != nil { - return obj.Type() == object.ERROR || obj.Type() == object.BREAK || obj.Type() == object.CONTINUE + return obj.Type() == object.ERROR || obj.Type() == object.BREAK || obj.Type() == object.CONTINUE || obj.Type() == object.RETURN } return false diff --git a/evaluator/for.go b/evaluator/for.go index 9a68cbb..579800e 100644 --- a/evaluator/for.go +++ b/evaluator/for.go @@ -39,6 +39,8 @@ func evaluateFor(node *ast.For, scope *object.Scope) object.Object { switch val := err.(type) { case *object.Error: return val + case *object.Return: + return val case *object.Continue: // case *object.Break: diff --git a/evaluator/for_in.go b/evaluator/for_in.go index 0b44d4b..ac19b85 100644 --- a/evaluator/for_in.go +++ b/evaluator/for_in.go @@ -41,6 +41,8 @@ func evaluateForIn(node *ast.ForIn, scope *object.Scope) object.Object { switch val := block.(type) { case *object.Error: return val + case *object.Return: + return val case *object.Continue: // case *object.Break: @@ -61,6 +63,8 @@ func evaluateForIn(node *ast.ForIn, scope *object.Scope) object.Object { switch val := block.(type) { case *object.Error: return val + case *object.Return: + return val case *object.Continue: // case *object.Break: diff --git a/evaluator/method.go b/evaluator/method.go index 5899258..9c4e86c 100644 --- a/evaluator/method.go +++ b/evaluator/method.go @@ -74,10 +74,16 @@ func evaluateInstanceMethod(node *ast.Method, receiver *object.Instance, name st } } - // if we dont have a method, check for a trait + // if we dont have a method, check for a trait up the superclass chain if method == nil { - for _, trait := range receiver.Class.Traits { - method, ok = trait.Environment.Get(name) + for c := receiver.Class; c != nil; c = c.Super { + for _, trait := range c.Traits { + method, ok = trait.Environment.Get(name) + + if ok { + break + } + } if ok { break diff --git a/evaluator/number.go b/evaluator/number.go index 9a5f26d..57d1912 100644 --- a/evaluator/number.go +++ b/evaluator/number.go @@ -24,8 +24,14 @@ func evaluateNumberInfix(node *ast.Infix, left object.Object, right object.Objec case "*": return leftNum.Mul(rightNum) case "/": + if rightNum.IsZero() { + return newError("%d:%d:%s: runtime error: division by zero", node.Token.Line, node.Token.Column, node.Token.File) + } return leftNum.Div(rightNum) case "%": + if rightNum.IsZero() { + return newError("%d:%d:%s: runtime error: division by zero", node.Token.Line, node.Token.Column, node.Token.File) + } return leftNum.Mod(rightNum) case "<": return toBooleanValue(leftNum.LessThan(rightNum)) diff --git a/evaluator/while.go b/evaluator/while.go index 74eccdd..f7a402a 100644 --- a/evaluator/while.go +++ b/evaluator/while.go @@ -20,6 +20,8 @@ func evaluateWhile(node *ast.While, scope *object.Scope) object.Object { switch val := evaluated.(type) { case *object.Error: return val + case *object.Return: + return val case *object.Continue: // case *object.Break: diff --git a/library/modules/math.go b/library/modules/math.go index eda9b40..fc473e5 100644 --- a/library/modules/math.go +++ b/library/modules/math.go @@ -135,7 +135,7 @@ func mathTan(scope *object.Scope, tok token.Token, args ...object.Object) object // mathMax returns the largest number of the referenced numbers. func mathMax(scope *object.Scope, tok token.Token, args ...object.Object) object.Object { if len(args) < 2 { - panic("math.max requires at least two arguments") + return object.NewError("%d:%d:%s: runtime error: math.max requires at least two arguments", tok.Line, tok.Column, tok.File) } if args[0].Type() != object.NUMBER { @@ -159,7 +159,7 @@ func mathMax(scope *object.Scope, tok token.Token, args ...object.Object) object // mathMin returns the smallest number of the referenced numbers. func mathMin(scope *object.Scope, tok token.Token, args ...object.Object) object.Object { if len(args) < 2 { - panic("math.min requires at least two arguments") + return object.NewError("%d:%d:%s: runtime error: math.min requires at least two arguments", tok.Line, tok.Column, tok.File) } if args[0].Type() != object.NUMBER { diff --git a/object/list.go b/object/list.go index 1c7db78..b0d42af 100644 --- a/object/list.go +++ b/object/list.go @@ -62,6 +62,9 @@ func (list *List) Method(method string, args []Object) (Object, bool) { // Object methods func (list *List) first(args []Object) (Object, bool) { + if len(list.Elements) == 0 { + return &Null{}, true + } return list.Elements[0], true } @@ -80,6 +83,9 @@ func (list *List) join(args []Object) (Object, bool) { func (list *List) last(args []Object) (Object, bool) { length := len(list.Elements) + if length == 0 { + return &Null{}, true + } return list.Elements[length-1], true } diff --git a/object/string.go b/object/string.go index 54f1088..3947515 100644 --- a/object/string.go +++ b/object/string.go @@ -84,7 +84,7 @@ func (str *String) find(args []Object) (Object, bool) { found := re.FindStringSubmatch(args[0].(*String).Value) if len(found) > 0 { - return &String{Value: found[1]}, true + return &String{Value: found[0]}, true } return &String{}, true diff --git a/scanner/scanner.go b/scanner/scanner.go index 73b4a08..ffb6fe4 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -197,11 +197,10 @@ func (scanner *Scanner) ScanToken() token.Token { return scannedToken } -// scanString consumes characters until it hits either the closing or end of -// file. If we run to the end of the file without a closing ", we report an -// error. +// scanString consumes characters until it hits either the closing quote or end +// of file, processing backslash escape sequences along the way. func (scanner *Scanner) scanString(closing rune) string { - position := scanner.position + 1 + var result []rune for { scanner.readCharacter() @@ -209,9 +208,33 @@ func (scanner *Scanner) scanString(closing rune) string { if scanner.character == closing || scanner.isAtEnd() { break } + + if scanner.character == '\\' { + scanner.readCharacter() + switch scanner.character { + case 'n': + result = append(result, '\n') + case 't': + result = append(result, '\t') + case 'r': + result = append(result, '\r') + case '\\': + result = append(result, '\\') + case '"': + result = append(result, '"') + case '\'': + result = append(result, '\'') + default: + result = append(result, '\\') + result = append(result, scanner.character) + } + continue + } + + result = append(result, scanner.character) } - return string(scanner.source[position:scanner.position]) + return string(result) } // scanNumber consumes all digits for the integer part of the literal, and then