Error Object (#4)

add error expression and error object
This commit is contained in:
Daniel Kang 2019-01-16 12:23:20 -08:00 committed by GitHub
parent b83eac8753
commit 8171d58071
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 477 additions and 152 deletions

View file

@ -1,7 +1,10 @@
vet:
go vet ./...
test: vet
lint:
golint -set_exit_status ./...
test: vet lint
go test -race -cover ./...
fmt:

View file

@ -173,7 +173,19 @@ for i, x in [1, 2, 3] { // array: index and element
for k, v in {k1: 1, k2: 2} { // map: key and value
// ...
}
```
An error object is created using `error` function-like keyword. An error can have any types of value and the underlying value of the error can be accessed using `.value` selector.
```golang
err1 := error("oops") // error with string value
err2 := error(1+2+3) // error with int value
if is_error(err1) { // 'is_error' builtin function
err_val := err1.value // get underlying value
}
```
> [Run in Playground](https://tengolang.com/?s=5eaba4289c9d284d97704dd09cb15f4f03ad05c1)
## Embedding Tengo in Go

View file

@ -173,6 +173,8 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
return equalClosure(t, expected, actual.(*objects.Closure))
case *objects.Undefined:
return true
case *objects.Error:
return Equal(t, expected.Value, actual.(*objects.Error).Value)
default:
panic(fmt.Errorf("type not implemented: %T", expected))
}

View file

@ -0,0 +1,29 @@
package ast
import (
"github.com/d5/tengo/compiler/source"
)
// ErrorExpr represents an error expression
type ErrorExpr struct {
Expr Expr
ErrorPos source.Pos
LParen source.Pos
RParen source.Pos
}
func (e *ErrorExpr) exprNode() {}
// Pos returns the position of first character belonging to the node.
func (e *ErrorExpr) Pos() source.Pos {
return e.ErrorPos
}
// End returns the position of first character immediately after the node.
func (e *ErrorExpr) End() source.Pos {
return e.RParen
}
func (e *ErrorExpr) String() string {
return "error(" + e.Expr.String() + ")"
}

View file

@ -408,6 +408,13 @@ func (c *Compiler) Compile(node ast.Node) error {
}
c.emit(OpCall, len(node.Args))
case *ast.ErrorExpr:
if err := c.Compile(node.Expr); err != nil {
return err
}
c.emit(OpError)
}
return nil

View file

@ -1,68 +0,0 @@
package compiler
// Definition represents an Opcode name and
// the number of operands.
type Definition struct {
Name string
Operands []int
}
var definitions = map[Opcode]*Definition{
OpConstant: {Name: "CONST", Operands: []int{2}},
OpPop: {Name: "POP", Operands: []int{}},
OpTrue: {Name: "TRUE", Operands: []int{}},
OpFalse: {Name: "FALSE", Operands: []int{}},
OpAdd: {Name: "ADD", Operands: []int{}},
OpSub: {Name: "SUB", Operands: []int{}},
OpMul: {Name: "MUL", Operands: []int{}},
OpDiv: {Name: "DIV", Operands: []int{}},
OpRem: {Name: "REM", Operands: []int{}},
OpBAnd: {Name: "AND", Operands: []int{}},
OpBOr: {Name: "OR", Operands: []int{}},
OpBXor: {Name: "XOR", Operands: []int{}},
OpBAndNot: {Name: "ANDN", Operands: []int{}},
OpBShiftLeft: {Name: "SHL", Operands: []int{}},
OpBShiftRight: {Name: "SHR", Operands: []int{}},
OpBComplement: {Name: "NEG", Operands: []int{}},
OpEqual: {Name: "EQL", Operands: []int{}},
OpNotEqual: {Name: "NEQ", Operands: []int{}},
OpGreaterThan: {Name: "GTR", Operands: []int{}},
OpGreaterThanEqual: {Name: "GEQ", Operands: []int{}},
OpMinus: {Name: "NEG", Operands: []int{}},
OpLNot: {Name: "NOT", Operands: []int{}},
OpJumpFalsy: {Name: "JMPF", Operands: []int{2}},
OpAndJump: {Name: "ANDJMP", Operands: []int{2}},
OpOrJump: {Name: "ORJMP", Operands: []int{2}},
OpJump: {Name: "JMP", Operands: []int{2}},
OpNull: {Name: "NULL", Operands: []int{}},
OpGetGlobal: {Name: "GETG", Operands: []int{2}},
OpSetGlobal: {Name: "SETG", Operands: []int{2}},
OpSetSelGlobal: {Name: "SETSG", Operands: []int{2, 1}},
OpArray: {Name: "ARR", Operands: []int{2}},
OpMap: {Name: "MAP", Operands: []int{2}},
OpIndex: {Name: "INDEX", Operands: []int{}},
OpSliceIndex: {Name: "SLICE", Operands: []int{}},
OpCall: {Name: "CALL", Operands: []int{1}},
OpReturn: {Name: "RET", Operands: []int{}},
OpReturnValue: {Name: "RETVAL", Operands: []int{1}},
OpGetLocal: {Name: "GETL", Operands: []int{1}},
OpSetLocal: {Name: "SETL", Operands: []int{1}},
OpDefineLocal: {Name: "DEFL", Operands: []int{1}},
OpSetSelLocal: {Name: "SETSL", Operands: []int{1, 1}},
OpGetBuiltin: {Name: "BUILTIN", Operands: []int{1}},
OpClosure: {Name: "CLOSURE", Operands: []int{2, 1}},
OpGetFree: {Name: "GETF", Operands: []int{1}},
OpSetFree: {Name: "SETF", Operands: []int{1}},
OpSetSelFree: {Name: "SETSF", Operands: []int{1, 1}},
OpIteratorInit: {Name: "ITER", Operands: []int{}},
OpIteratorNext: {Name: "ITNXT", Operands: []int{}},
OpIteratorKey: {Name: "ITKEY", Operands: []int{}},
OpIteratorValue: {Name: "ITVAL", Operands: []int{}},
}
// Lookup returns a Definition of a given opcode.
func Lookup(opcode Opcode) (def *Definition, ok bool) {
def, ok = definitions[opcode]
return
}

View file

@ -6,13 +6,10 @@ import (
// MakeInstruction returns a bytecode for an opcode and the operands.
func MakeInstruction(opcode Opcode, operands ...int) []byte {
def, ok := Lookup(opcode)
if !ok {
return nil
}
numOperands := OpcodeOperands[opcode]
totalLen := 1
for _, w := range def.Operands {
for _, w := range numOperands {
totalLen += w
}
@ -21,7 +18,7 @@ func MakeInstruction(opcode Opcode, operands ...int) []byte {
offset := 1
for i, o := range operands {
width := def.Operands[i]
width := numOperands[i]
switch width {
case 1:
instruction[offset] = byte(o)
@ -43,21 +40,16 @@ func FormatInstructions(b []byte, posOffset int) []string {
i := 0
for i < len(b) {
def, ok := Lookup(Opcode(b[i]))
if !ok {
out = append(out, fmt.Sprintf("error: unknown Opcode %d", b[i]))
continue
}
numOperands := OpcodeOperands[Opcode(b[i])]
operands, read := ReadOperands(numOperands, b[i+1:])
operands, read := ReadOperands(def, b[i+1:])
switch len(def.Operands) {
switch len(numOperands) {
case 0:
out = append(out, fmt.Sprintf("%04d %-7s", posOffset+i, def.Name))
out = append(out, fmt.Sprintf("%04d %-7s", posOffset+i, OpcodeNames[Opcode(b[i])]))
case 1:
out = append(out, fmt.Sprintf("%04d %-7s %-5d", posOffset+i, def.Name, operands[0]))
out = append(out, fmt.Sprintf("%04d %-7s %-5d", posOffset+i, OpcodeNames[Opcode(b[i])], operands[0]))
case 2:
out = append(out, fmt.Sprintf("%04d %-7s %-5d %-5d", posOffset+i, def.Name, operands[0], operands[1]))
out = append(out, fmt.Sprintf("%04d %-7s %-5d %-5d", posOffset+i, OpcodeNames[Opcode(b[i])], operands[0], operands[1]))
}
i += 1 + read

View file

@ -32,8 +32,9 @@ const (
OpOrJump // Logical OR jump
OpJump // Jump
OpNull // Push null
OpArray // Array literal
OpMap // Map literal
OpArray // Array object
OpMap // Map object
OpError // Error object
OpIndex // Index operation
OpSliceIndex // Slice operation
OpCall // Call function
@ -56,3 +57,139 @@ const (
OpIteratorKey // Iterator key
OpIteratorValue // Iterator value
)
// OpcodeNames is opcode names.
var OpcodeNames = [...]string{
OpConstant: "CONST",
OpPop: "POP",
OpTrue: "TRUE",
OpFalse: "FALSE",
OpAdd: "ADD",
OpSub: "SUB",
OpMul: "MUL",
OpDiv: "DIV",
OpRem: "REM",
OpBAnd: "AND",
OpBOr: "OR",
OpBXor: "XOR",
OpBAndNot: "ANDN",
OpBShiftLeft: "SHL",
OpBShiftRight: "SHR",
OpBComplement: "NEG",
OpEqual: "EQL",
OpNotEqual: "NEQ",
OpGreaterThan: "GTR",
OpGreaterThanEqual: "GEQ",
OpMinus: "NEG",
OpLNot: "NOT",
OpJumpFalsy: "JMPF",
OpAndJump: "ANDJMP",
OpOrJump: "ORJMP",
OpJump: "JMP",
OpNull: "NULL",
OpGetGlobal: "GETG",
OpSetGlobal: "SETG",
OpSetSelGlobal: "SETSG",
OpArray: "ARR",
OpMap: "MAP",
OpError: "ERROR",
OpIndex: "INDEX",
OpSliceIndex: "SLICE",
OpCall: "CALL",
OpReturn: "RET",
OpReturnValue: "RETVAL",
OpGetLocal: "GETL",
OpSetLocal: "SETL",
OpDefineLocal: "DEFL",
OpSetSelLocal: "SETSL",
OpGetBuiltin: "BUILTIN",
OpClosure: "CLOSURE",
OpGetFree: "GETF",
OpSetFree: "SETF",
OpSetSelFree: "SETSF",
OpIteratorInit: "ITER",
OpIteratorNext: "ITNXT",
OpIteratorKey: "ITKEY",
OpIteratorValue: "ITVAL",
}
// OpcodeOperands is the number of operands.
var OpcodeOperands = [...][]int{
OpConstant: {2},
OpPop: {},
OpTrue: {},
OpFalse: {},
OpAdd: {},
OpSub: {},
OpMul: {},
OpDiv: {},
OpRem: {},
OpBAnd: {},
OpBOr: {},
OpBXor: {},
OpBAndNot: {},
OpBShiftLeft: {},
OpBShiftRight: {},
OpBComplement: {},
OpEqual: {},
OpNotEqual: {},
OpGreaterThan: {},
OpGreaterThanEqual: {},
OpMinus: {},
OpLNot: {},
OpJumpFalsy: {2},
OpAndJump: {2},
OpOrJump: {2},
OpJump: {2},
OpNull: {},
OpGetGlobal: {2},
OpSetGlobal: {2},
OpSetSelGlobal: {2, 1},
OpArray: {2},
OpMap: {2},
OpError: {},
OpIndex: {},
OpSliceIndex: {},
OpCall: {1},
OpReturn: {},
OpReturnValue: {1},
OpGetLocal: {1},
OpSetLocal: {1},
OpDefineLocal: {1},
OpSetSelLocal: {1, 1},
OpGetBuiltin: {1},
OpClosure: {2, 1},
OpGetFree: {1},
OpSetFree: {1},
OpSetSelFree: {1, 1},
OpIteratorInit: {},
OpIteratorNext: {},
OpIteratorKey: {},
OpIteratorValue: {},
}
// ReadOperands reads operands from the bytecode.
func ReadOperands(numOperands []int, ins []byte) (operands []int, offset int) {
for _, width := range numOperands {
switch width {
case 1:
operands = append(operands, int(ReadUint8(ins[offset:])))
case 2:
operands = append(operands, int(ReadUint16(ins[offset:])))
}
offset += width
}
return
}
// ReadUint16 reads uint16 from the byte slice.
func ReadUint16(b []byte) uint16 {
return uint16(b[1]) | uint16(b[0])<<8
}
// ReadUint8 reads uint8 from the byte slice.
func ReadUint8(b []byte) uint8 {
return uint8(b[0])
}

View file

@ -13,9 +13,8 @@ func TestReadOperands(t *testing.T) {
func assertReadOperand(t *testing.T, opcode compiler.Opcode, operands []int, expectedBytes int) {
inst := compiler.MakeInstruction(opcode, operands...)
def, ok := compiler.Lookup(opcode)
assert.True(t, ok)
operandsRead, read := compiler.ReadOperands(def, inst[1:])
numOperands := compiler.OpcodeOperands[opcode]
operandsRead, read := compiler.ReadOperands(numOperands, inst[1:])
assert.Equal(t, expectedBytes, read)
assert.Equal(t, operands, operandsRead)
}

View file

@ -1,27 +0,0 @@
package compiler
// ReadOperands reads operands from the bytecode.
func ReadOperands(def *Definition, ins []byte) (operands []int, offset int) {
for _, width := range def.Operands {
switch width {
case 1:
operands = append(operands, int(ReadUint8(ins[offset:])))
case 2:
operands = append(operands, int(ReadUint16(ins[offset:])))
}
offset += width
}
return
}
// ReadUint16 reads uint16 from the byte slice.
func ReadUint16(b []byte) uint16 {
return uint16(b[1]) | uint16(b[0])<<8
}
// ReadUint8 reads uint8 from the byte slice.
func ReadUint8(b []byte) uint8 {
return uint8(b[0])
}

View file

@ -355,6 +355,9 @@ func (p *Parser) parseOperand() ast.Expr {
case token.Func: // function literal
return p.parseFuncLit()
case token.Error: // error expression
return p.parseErrorExpr()
}
pos := p.pos
@ -427,7 +430,25 @@ func (p *Parser) parseArrayLit() ast.Expr {
LBrack: lbrack,
RBrack: rbrack,
}
}
func (p *Parser) parseErrorExpr() ast.Expr {
pos := p.pos
p.next()
lparen := p.expect(token.LParen)
value := p.parseExpr()
rparen := p.expect(token.RParen)
expr := &ast.ErrorExpr{
ErrorPos: pos,
Expr: value,
LParen: lparen,
RParen: rparen,
}
return expr
}
func (p *Parser) parseFuncType() *ast.FuncType {
@ -519,7 +540,7 @@ func (p *Parser) parseStmt() (stmt ast.Stmt) {
switch p.token {
case // simple statements
token.Func, token.Ident, token.Int, token.Float, token.Char, token.String, token.True, token.False, token.Undefined, token.LParen, // operands
token.Func, token.Error, token.Ident, token.Int, token.Float, token.Char, token.String, token.True, token.False, token.Undefined, token.LParen, // operands
token.LBrace, token.LBrack, // composite types
token.Add, token.Sub, token.Mul, token.And, token.Xor, token.Not: // unary operators
s := p.parseSimpleStmt(false)

View file

@ -0,0 +1,43 @@
package parser_test
import (
"testing"
"github.com/d5/tengo/compiler/ast"
"github.com/d5/tengo/compiler/token"
)
func TestImport(t *testing.T) {
expect(t, `error(1234)`, func(p pfn) []ast.Stmt {
return stmts(
exprStmt(
errorExpr(p(1, 1), intLit(1234, p(1, 7)), p(1, 6), p(1, 11))))
})
expect(t, `err1 := error("some error")`, func(p pfn) []ast.Stmt {
return stmts(
assignStmt(
exprs(ident("err1", p(1, 1))),
exprs(errorExpr(p(1, 9), stringLit("some error", p(1, 15)), p(1, 14), p(1, 27))),
token.Define, p(1, 6)))
})
expect(t, `return error("some error")`, func(p pfn) []ast.Stmt {
return stmts(
returnStmt(p(1, 1),
errorExpr(p(1, 8), stringLit("some error", p(1, 14)), p(1, 13), p(1, 26))))
})
expect(t, `return error("some" + "error")`, func(p pfn) []ast.Stmt {
return stmts(
returnStmt(p(1, 1),
errorExpr(p(1, 8),
binaryExpr(
stringLit("some", p(1, 14)),
stringLit("error", p(1, 23)),
token.Add, p(1, 21)),
p(1, 13), p(1, 30))))
})
expectError(t, `error()`) // must have a value
}

View file

@ -244,6 +244,10 @@ func sliceExpr(x, low, high ast.Expr, lbrack, rbrack source.Pos) *ast.SliceExpr
return &ast.SliceExpr{Expr: x, Low: low, High: high, LBrack: lbrack, RBrack: rbrack}
}
func errorExpr(pos source.Pos, x ast.Expr, lparen, rparen source.Pos) *ast.ErrorExpr {
return &ast.ErrorExpr{Expr: x, ErrorPos: pos, LParen: lparen, RParen: rparen}
}
func selectorExpr(x, sel ast.Expr) *ast.SelectorExpr {
return &ast.SelectorExpr{Expr: x, Sel: sel}
}
@ -381,6 +385,11 @@ func equalExpr(t *testing.T, expected, actual ast.Expr) bool {
case *ast.SelectorExpr:
return equalExpr(t, expected.Expr, actual.(*ast.SelectorExpr).Expr) &&
equalExpr(t, expected.Sel, actual.(*ast.SelectorExpr).Sel)
case *ast.ErrorExpr:
return equalExpr(t, expected.Expr, actual.(*ast.ErrorExpr).Expr) &&
assert.Equal(t, int(expected.ErrorPos), int(actual.(*ast.ErrorExpr).ErrorPos)) &&
assert.Equal(t, int(expected.LParen), int(actual.(*ast.ErrorExpr).LParen)) &&
assert.Equal(t, int(expected.RParen), int(actual.(*ast.ErrorExpr).RParen))
default:
panic(fmt.Errorf("unknown type: %T", expected))
}

View file

@ -73,6 +73,7 @@ const (
Else
For
Func
Error
If
Return
Switch
@ -146,6 +147,7 @@ var tokens = [...]string{
Else: "else",
For: "for",
Func: "func",
Error: "error",
If: "if",
Return: "return",
Switch: "switch",

View file

@ -0,0 +1,18 @@
package objects
import (
"fmt"
)
func builtinIsError(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, fmt.Errorf("wrong number of arguments (got=%d, want=1)", len(args))
}
switch args[0].(type) {
case *Error:
return TrueValue, nil
}
return FalseValue, nil
}

View file

@ -0,0 +1,18 @@
package objects
import (
"fmt"
)
func builtinIsUndefined(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, fmt.Errorf("wrong number of arguments (got=%d, want=1)", len(args))
}
switch args[0].(type) {
case *Undefined:
return TrueValue, nil
}
return FalseValue, nil
}

View file

@ -44,4 +44,12 @@ var Builtins = []struct {
Name: "char",
Func: builtinChar,
},
{
Name: "is_error",
Func: builtinIsError,
},
{
Name: "is_undefined",
Func: builtinIsUndefined,
},
}

47
objects/error.go Normal file
View file

@ -0,0 +1,47 @@
package objects
import (
"fmt"
"github.com/d5/tengo/compiler/token"
)
// Error represents a string value.
type Error struct {
Value Object
}
// TypeName returns the name of the type.
func (o *Error) TypeName() string {
return "error"
}
func (o *Error) String() string {
if o.Value != nil {
return fmt.Sprintf("error: %s", o.Value.String())
}
return "error"
}
// BinaryOp returns another object that is the result of
// a given binary operator and a right-hand side object.
func (o *Error) BinaryOp(op token.Token, rhs Object) (Object, error) {
return nil, ErrInvalidOperator
}
// IsFalsy returns true if the value of the type is falsy.
func (o *Error) IsFalsy() bool {
return true // error is always false.
}
// Copy returns a copy of the type.
func (o *Error) Copy() Object {
return &Error{Value: o.Value.Copy()}
}
// Equals returns true if the value of the type
// is equal to the value of another object.
func (o *Error) Equals(x Object) bool {
return o == x // pointer equality
}

19
objects/error_test.go Normal file
View file

@ -0,0 +1,19 @@
package objects_test
import (
"testing"
"github.com/d5/tengo/assert"
"github.com/d5/tengo/objects"
)
func TestError_Equals(t *testing.T) {
err1 := &objects.Error{Value: &objects.String{Value: "some error"}}
err2 := err1
assert.True(t, err1.Equals(err2))
assert.True(t, err2.Equals(err1))
err2 = &objects.Error{Value: &objects.String{Value: "some error"}}
assert.False(t, err1.Equals(err2))
assert.False(t, err2.Equals(err1))
}

View file

@ -44,6 +44,8 @@ func TestObject_TypeName(t *testing.T) {
assert.Equal(t, "return-value", o.TypeName())
o = &objects.Undefined{}
assert.Equal(t, "undefined", o.TypeName())
o = &objects.Error{}
assert.Equal(t, "error", o.TypeName())
}
func TestObject_IsFalsy(t *testing.T) {
@ -92,6 +94,8 @@ func TestObject_IsFalsy(t *testing.T) {
assert.False(t, o.IsFalsy())
o = &objects.Undefined{}
assert.True(t, o.IsFalsy())
o = &objects.Error{}
assert.True(t, o.IsFalsy())
}
func TestObject_String(t *testing.T) {
@ -116,6 +120,10 @@ func TestObject_String(t *testing.T) {
assert.Equal(t, "[]", o.String())
o = &objects.Map{Value: nil}
assert.Equal(t, "{}", o.String())
o = &objects.Error{Value: nil}
assert.Equal(t, "error", o.String())
o = &objects.Error{Value: &objects.String{Value: "error 1"}}
assert.Equal(t, `error: "error 1"`, o.String())
o = &objects.StringIterator{}
assert.Equal(t, "<string-iterator>", o.String())
o = &objects.ArrayIterator{}
@ -173,4 +181,7 @@ func TestObject_BinaryOp(t *testing.T) {
o = &objects.Undefined{}
_, err = o.BinaryOp(token.Add, objects.UndefinedValue)
assert.Error(t, err)
o = &objects.Error{}
_, err = o.BinaryOp(token.Add, objects.UndefinedValue)
assert.Error(t, err)
}

View file

@ -1,6 +1,7 @@
package runtime
import (
"errors"
"fmt"
"github.com/d5/tengo/compiler"
@ -562,6 +563,21 @@ func (v *VM) Run() error {
v.stack[v.sp] = &m
v.sp++
case compiler.OpError:
value := v.stack[v.sp-1]
v.sp--
var err objects.Object = &objects.Error{
Value: *value,
}
if v.sp >= StackSize {
return ErrStackOverflow
}
v.stack[v.sp] = &err
v.sp++
case compiler.OpIndex:
index := v.stack[v.sp-1]
left := v.stack[v.sp-2]
@ -625,6 +641,19 @@ func (v *VM) Run() error {
v.stack[v.sp] = &res
v.sp++
case *objects.Error: // err.value
key, ok := (*index).(*objects.String)
if !ok || key.Value != "value" {
return errors.New("invalid selector on error")
}
if v.sp >= StackSize {
return ErrStackOverflow
}
v.stack[v.sp] = &left.Value
v.sp++
default:
return fmt.Errorf("type %s does not support indexing", left.TypeName())
}

View file

@ -30,4 +30,24 @@ func TestBoolean(t *testing.T) {
expect(t, `out = (1 < 2) == false`, false)
expect(t, `out = (1 > 2) == true`, false)
expect(t, `out = (1 > 2) == false`, true)
expectError(t, `5 + true`)
expectError(t, `5 + true; 5`)
expectError(t, `-true`)
expectError(t, `true + false`)
expectError(t, `5; true + false; 5`)
expectError(t, `if (10 > 1) { true + false; }`)
expectError(t, `
if (10 > 1) {
if (10 > 1) {
return true + false;
}
return 1;
}
`)
expectError(t, `if (true + false) { 10 }`)
expectError(t, `10 + (true + false)`)
expectError(t, `(true + false) + 20`)
expectError(t, `!(true + false)`)
}

View file

@ -73,4 +73,10 @@ func TestBuiltinFunction(t *testing.T) {
expect(t, `out = bool({a: 1})`, true) // non-empty maps: true
expect(t, `out = bool({})`, false) // empty maps: false
expect(t, `out = bool(undefined)`, false) // undefined: false
expect(t, `out = is_error(error(1))`, true)
expect(t, `out = is_error(1)`, false)
expect(t, `out = is_undefined(undefined)`, true)
expect(t, `out = is_undefined(error(1))`, false)
}

View file

@ -5,37 +5,17 @@ import (
)
func TestError(t *testing.T) {
expectError(t, `5 + true`)
expect(t, `out = error(1)`, errorObject(1))
expect(t, `out = error(1).value`, 1)
expect(t, `out = error("some error")`, errorObject("some error"))
expect(t, `out = error("some" + " error")`, errorObject("some error"))
expect(t, `out = func() { return error(5) }()`, errorObject(5))
expect(t, `out = error(error("foo"))`, errorObject(errorObject("foo")))
expect(t, `out = error("some error")`, errorObject("some error"))
expect(t, `out = error("some error").value`, "some error")
expect(t, `out = error("some error")["value"]`, "some error")
expectError(t, `5 + true; 5`)
expectError(t, `-true`)
expectError(t, `true + false`)
expectError(t, `5; true + false; 5`)
expectError(t, `if (10 > 1) { true + false; }`)
expectError(t, `
if (10 > 1) {
if (10 > 1) {
return true + false;
}
return 1;
}
`)
expectError(t, `if (true + false) { 10 }`)
expectError(t, `10 + (true + false)`)
expectError(t, `(true + false) + 20`)
expectError(t, `!(true + false)`)
expectError(t, `foobar`)
expectError(t, `"foo" - "bar"`)
expectError(t, `error("error").err`)
expectError(t, `error("error").value_`)
expectError(t, `error([1,2,3])[1]`)
}

View file

@ -41,4 +41,6 @@ func TestString(t *testing.T) {
expectError(t, fmt.Sprintf("%s[%d:]", strStr, -1))
expectError(t, fmt.Sprintf("%s[:%d]", strStr, strLen+1))
expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 2, 1))
expectError(t, `"foo" - "bar"`)
}

View file

@ -82,6 +82,10 @@ func runVMError(t *testing.T, file *ast.File) (ok bool) {
return
}
func errorObject(v interface{}) *objects.Error {
return &objects.Error{Value: toObject(v)}
}
func toObject(v interface{}) objects.Object {
switch v := v.(type) {
case objects.Object:
@ -212,7 +216,7 @@ func traceCompileRun(file *ast.File, symbols map[string]objects.Object) (res map
globalsStr = append(globalsStr, fmt.Sprintf(" %s", l))
}
} else {
globalsStr = append(globalsStr, fmt.Sprintf("[% 3d] %s (%s|%p)", gidx, *g, reflect.TypeOf(*g).Elem().Name(), g))
globalsStr = append(globalsStr, fmt.Sprintf("[% 3d] %s (%s|%p)", gidx, (*g).String(), reflect.TypeOf(*g).Elem().Name(), g))
}
}
trace = append(trace, fmt.Sprintf("\n[Globals]\n\n%s", strings.Join(globalsStr, "\n")))
@ -257,6 +261,8 @@ func objectZeroCopy(o objects.Object) objects.Object {
return &objects.Map{}
case *objects.Undefined:
return &objects.Undefined{}
case *objects.Error:
return &objects.Error{}
case nil:
panic("nil")
default: