parent
b83eac8753
commit
8171d58071
26 changed files with 477 additions and 152 deletions
5
Makefile
5
Makefile
|
@ -1,7 +1,10 @@
|
|||
vet:
|
||||
go vet ./...
|
||||
|
||||
test: vet
|
||||
lint:
|
||||
golint -set_exit_status ./...
|
||||
|
||||
test: vet lint
|
||||
go test -race -cover ./...
|
||||
|
||||
fmt:
|
||||
|
|
12
README.md
12
README.md
|
@ -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
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
29
compiler/ast/error_expr.go
Normal file
29
compiler/ast/error_expr.go
Normal 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() + ")"
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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])
|
||||
}
|
|
@ -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)
|
||||
|
|
43
compiler/parser/parser_error_test.go
Normal file
43
compiler/parser/parser_error_test.go
Normal 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
|
||||
}
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
18
objects/builtin_is_error.go
Normal file
18
objects/builtin_is_error.go
Normal 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
|
||||
}
|
18
objects/builtin_is_undefined.go
Normal file
18
objects/builtin_is_undefined.go
Normal 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
|
||||
}
|
|
@ -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
47
objects/error.go
Normal 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
19
objects/error_test.go
Normal 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))
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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)`)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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]`)
|
||||
}
|
||||
|
|
|
@ -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"`)
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue