Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions evaluator/assign.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
15 changes: 14 additions & 1 deletion evaluator/compound.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
4 changes: 2 additions & 2 deletions evaluator/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions evaluator/for.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 4 additions & 0 deletions evaluator/for_in.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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:
Expand Down
12 changes: 9 additions & 3 deletions evaluator/method.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions evaluator/number.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 2 additions & 0 deletions evaluator/while.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions library/modules/math.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions object/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

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

Expand Down
2 changes: 1 addition & 1 deletion object/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
33 changes: 28 additions & 5 deletions scanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,21 +197,44 @@ 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()

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
Expand Down
Loading