diff --git a/assert/assert.go b/assert/assert.go index ae4ff4e..be93ffc 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -33,7 +33,7 @@ func Error(t *testing.T, err error, msg ...interface{}) bool { // Nil asserts v is nil. func Nil(t *testing.T, v interface{}, msg ...interface{}) bool { - if v == nil { + if isNil(v) { return true } @@ -60,7 +60,7 @@ func False(t *testing.T, v bool, msg ...interface{}) bool { // NotNil asserts v is not nil. func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool { - if v != nil { + if !isNil(v) { return true } @@ -78,7 +78,7 @@ func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool // Equal asserts expected and actual are equal. func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool { - if expected == nil { + if isNil(expected) { return Nil(t, actual, "expected nil, but got not nil") } if !NotNil(t, actual, "expected not nil, but got nil") { @@ -175,6 +175,13 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool if !expected.Equals(actual.(objects.Object)) { return failExpectedActual(t, expected, actual, msg...) } + case *source.FileSet: + return equalFileSet(t, expected, actual.(*source.FileSet), msg...) + case *source.File: + return Equal(t, expected.Name, actual.(*source.File).Name, msg...) && + Equal(t, expected.Base, actual.(*source.File).Base, msg...) && + Equal(t, expected.Size, actual.(*source.File).Size, msg...) && + True(t, equalIntSlice(expected.Lines, actual.(*source.File).Lines), msg...) case error: if expected != actual.(error) { return failExpectedActual(t, expected, actual, msg...) @@ -245,8 +252,6 @@ func equalSymbol(a, b *compiler.Symbol) bool { } func equalObjectSlice(t *testing.T, expected, actual []objects.Object, msg ...interface{}) bool { - // TODO: this test does not differentiate nil vs empty slice - if !Equal(t, len(expected), len(actual), msg...) { return false } @@ -260,6 +265,20 @@ func equalObjectSlice(t *testing.T, expected, actual []objects.Object, msg ...in return true } +func equalFileSet(t *testing.T, expected, actual *source.FileSet, msg ...interface{}) bool { + if !Equal(t, len(expected.Files), len(actual.Files), msg...) { + return false + } + for i, f := range expected.Files { + if !Equal(t, f, actual.Files[i], msg...) { + return false + } + } + + return Equal(t, expected.Base, actual.Base) && + Equal(t, expected.LastFile, actual.LastFile) +} + func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object, msg ...interface{}) bool { if !Equal(t, len(expected), len(actual), msg...) { return false @@ -303,3 +322,17 @@ func equalClosure(t *testing.T, expected, actual objects.Object, msg ...interfac return true } + +func isNil(v interface{}) bool { + if v == nil { + return true + } + + value := reflect.ValueOf(v) + kind := value.Kind() + if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() { + return true + } + + return false +} diff --git a/cmd/bench/main.go b/cmd/bench/main.go index 730ce6b..c131f38 100644 --- a/cmd/bench/main.go +++ b/cmd/bench/main.go @@ -175,7 +175,7 @@ func runBench(input []byte) (parseTime time.Duration, compileTime time.Duration, func parse(input []byte) (time.Duration, *ast.File, error) { fileSet := source.NewFileSet() - inputFile := fileSet.AddFile("test", -1, len(input)) + inputFile := fileSet.AddFile("bench", -1, len(input)) start := time.Now() @@ -193,7 +193,7 @@ func compileFile(file *ast.File) (time.Duration, *compiler.Bytecode, error) { start := time.Now() - c := compiler.NewCompiler(symTable, nil, nil, nil) + c := compiler.NewCompiler(file.InputFile, symTable, nil, nil, nil) if err := c.Compile(file); err != nil { return time.Since(start), nil, err } @@ -206,7 +206,7 @@ func runVM(bytecode *compiler.Bytecode) (time.Duration, objects.Object, error) { start := time.Now() - v := runtime.NewVM(bytecode, globals) + v := runtime.NewVM(bytecode, globals, nil) if err := v.Run(); err != nil { return time.Since(start), nil, err } diff --git a/cmd/tengo/main.go b/cmd/tengo/main.go index 81e3faf..4a6e12e 100644 --- a/cmd/tengo/main.go +++ b/cmd/tengo/main.go @@ -148,7 +148,7 @@ func compileAndRun(data []byte, inputFile string) (err error) { return } - machine := runtime.NewVM(bytecode, nil) + machine := runtime.NewVM(bytecode, nil, nil) err = machine.Run() if err != nil { @@ -165,7 +165,7 @@ func runCompiled(data []byte) (err error) { return } - machine := runtime.NewVM(bytecode, nil) + machine := runtime.NewVM(bytecode, nil, nil) err = machine.Run() if err != nil { @@ -198,7 +198,8 @@ func runREPL(in io.Reader, out io.Writer) { line := stdin.Text() - file, err := parser.ParseFile(fileSet.AddFile("test", -1, len(line)), []byte(line), nil) + srcFile := fileSet.AddFile("repl", -1, len(line)) + file, err := parser.ParseFile(srcFile, []byte(line), nil) if err != nil { _, _ = fmt.Fprintf(out, "error: %s\n", err.Error()) continue @@ -206,7 +207,7 @@ func runREPL(in io.Reader, out io.Writer) { file = addPrints(file) - c := compiler.NewCompiler(symbolTable, constants, nil, nil) + c := compiler.NewCompiler(srcFile, symbolTable, constants, nil, nil) if err := c.Compile(file); err != nil { _, _ = fmt.Fprintf(out, "Compilation error:\n %s\n", err.Error()) continue @@ -214,7 +215,7 @@ func runREPL(in io.Reader, out io.Writer) { bytecode := c.Bytecode() - machine := runtime.NewVM(bytecode, globals) + machine := runtime.NewVM(bytecode, globals, nil) if err != nil { _, _ = fmt.Fprintf(out, "VM error:\n %s\n", err.Error()) continue @@ -230,14 +231,15 @@ func runREPL(in io.Reader, out io.Writer) { func compileSrc(src []byte, filename string) (*compiler.Bytecode, error) { fileSet := source.NewFileSet() + srcFile := fileSet.AddFile(filename, -1, len(src)) - p := parser.NewParser(fileSet.AddFile(filename, -1, len(src)), src, nil) + p := parser.NewParser(srcFile, src, nil) file, err := p.ParseFile() if err != nil { return nil, err } - c := compiler.NewCompiler(nil, nil, nil, nil) + c := compiler.NewCompiler(srcFile, nil, nil, nil, nil) if err := c.Compile(file); err != nil { return nil, err } diff --git a/compiler/ast/file.go b/compiler/ast/file.go index 7ea18c5..fc18b2d 100644 --- a/compiler/ast/file.go +++ b/compiler/ast/file.go @@ -14,12 +14,12 @@ type File struct { // Pos returns the position of first character belonging to the node. func (n *File) Pos() source.Pos { - return source.Pos(n.InputFile.Base()) + return source.Pos(n.InputFile.Base) } // End returns the position of first character immediately after the node. func (n *File) End() source.Pos { - return source.Pos(n.InputFile.Base() + n.InputFile.Size()) + return source.Pos(n.InputFile.Base + n.InputFile.Size) } func (n *File) String() string { diff --git a/compiler/bytecode.go b/compiler/bytecode.go index e0416b5..4252773 100644 --- a/compiler/bytecode.go +++ b/compiler/bytecode.go @@ -6,12 +6,14 @@ import ( "io" "reflect" + "github.com/d5/tengo/compiler/source" "github.com/d5/tengo/objects" ) // Bytecode is a compiled instructions and constants. type Bytecode struct { - Instructions []byte + FileSet *source.FileSet + MainFunction *objects.CompiledFunction Constants []objects.Object } @@ -19,7 +21,13 @@ type Bytecode struct { func (b *Bytecode) Decode(r io.Reader) error { dec := gob.NewDecoder(r) - if err := dec.Decode(&b.Instructions); err != nil { + if err := dec.Decode(&b.FileSet); err != nil { + return err + } + // TODO: files in b.FileSet.File does not have their 'set' field properly set to b.FileSet + // as it's private field and not serialized by gob encoder/decoder. + + if err := dec.Decode(&b.MainFunction); err != nil { return err } @@ -39,7 +47,11 @@ func (b *Bytecode) Decode(r io.Reader) error { func (b *Bytecode) Encode(w io.Writer) error { enc := gob.NewEncoder(w) - if err := enc.Encode(b.Instructions); err != nil { + if err := enc.Encode(b.FileSet); err != nil { + return err + } + + if err := enc.Encode(b.MainFunction); err != nil { return err } @@ -50,7 +62,7 @@ func (b *Bytecode) Encode(w io.Writer) error { // FormatInstructions returns human readable string representations of // compiled instructions. func (b *Bytecode) FormatInstructions() []string { - return FormatInstructions(b.Instructions, 0) + return FormatInstructions(b.MainFunction.Instructions, 0) } // FormatConstants returns human readable string representations of @@ -94,21 +106,29 @@ func cleanupObjects(o objects.Object) objects.Object { } func init() { - gob.Register(&objects.Int{}) - gob.Register(&objects.Float{}) - gob.Register(&objects.String{}) - gob.Register(&objects.Bool{}) - gob.Register(&objects.Char{}) + gob.Register(&source.FileSet{}) + gob.Register(&source.File{}) gob.Register(&objects.Array{}) - gob.Register(&objects.ImmutableArray{}) - gob.Register(&objects.Map{}) - gob.Register(&objects.ImmutableMap{}) - gob.Register(&objects.CompiledFunction{}) - gob.Register(&objects.Undefined{}) - gob.Register(&objects.Error{}) - gob.Register(&objects.Bytes{}) - gob.Register(&objects.StringIterator{}) - gob.Register(&objects.MapIterator{}) gob.Register(&objects.ArrayIterator{}) + gob.Register(&objects.Bool{}) + gob.Register(&objects.Break{}) + gob.Register(&objects.BuiltinFunction{}) + gob.Register(&objects.Bytes{}) + gob.Register(&objects.Char{}) + gob.Register(&objects.Closure{}) + gob.Register(&objects.CompiledFunction{}) + gob.Register(&objects.Continue{}) + gob.Register(&objects.Error{}) + gob.Register(&objects.Float{}) + gob.Register(&objects.ImmutableArray{}) + gob.Register(&objects.ImmutableMap{}) + gob.Register(&objects.Int{}) + gob.Register(&objects.Map{}) + gob.Register(&objects.MapIterator{}) + gob.Register(&objects.ReturnValue{}) + gob.Register(&objects.String{}) + gob.Register(&objects.StringIterator{}) gob.Register(&objects.Time{}) + gob.Register(&objects.Undefined{}) + gob.Register(&objects.UserFunction{}) } diff --git a/compiler/bytecode_test.go b/compiler/bytecode_test.go index 60eebb4..c9a9c87 100644 --- a/compiler/bytecode_test.go +++ b/compiler/bytecode_test.go @@ -7,11 +7,17 @@ import ( "github.com/d5/tengo/assert" "github.com/d5/tengo/compiler" + "github.com/d5/tengo/compiler/source" "github.com/d5/tengo/objects" ) +type srcfile struct { + name string + size int +} + func TestBytecode(t *testing.T) { - testBytecodeSerialization(t, &compiler.Bytecode{}) + testBytecodeSerialization(t, bytecode(concat(), objectsArray())) testBytecodeSerialization(t, bytecode( concat(), objectsArray( @@ -48,7 +54,7 @@ func TestBytecode(t *testing.T) { &objects.String{Value: "bar"}, objects.UndefinedValue))) - testBytecodeSerialization(t, bytecode( + testBytecodeSerialization(t, bytecodeFileSet( concat( compiler.MakeInstruction(compiler.OpConstant, 0), compiler.MakeInstruction(compiler.OpSetGlobal, 0), @@ -82,7 +88,24 @@ func TestBytecode(t *testing.T) { compiler.MakeInstruction(compiler.OpSetLocal, 0), compiler.MakeInstruction(compiler.OpGetLocal, 0), compiler.MakeInstruction(compiler.OpClosure, 5, 1), - compiler.MakeInstruction(compiler.OpReturnValue))))) + compiler.MakeInstruction(compiler.OpReturnValue))), + fileSet(srcfile{name: "file1", size: 100}, srcfile{name: "file2", size: 200}))) +} + +func fileSet(files ...srcfile) *source.FileSet { + fileSet := source.NewFileSet() + for _, f := range files { + fileSet.AddFile(f.name, -1, f.size) + } + return fileSet +} + +func bytecodeFileSet(instructions []byte, constants []objects.Object, fileSet *source.FileSet) *compiler.Bytecode { + return &compiler.Bytecode{ + FileSet: fileSet, + MainFunction: &objects.CompiledFunction{Instructions: instructions}, + Constants: constants, + } } func testBytecodeSerialization(t *testing.T, b *compiler.Bytecode) { @@ -94,6 +117,7 @@ func testBytecodeSerialization(t *testing.T, b *compiler.Bytecode) { err = r.Decode(bytes.NewReader(buf.Bytes())) assert.NoError(t, err) - assert.Equal(t, b.Instructions, r.Instructions) + assert.Equal(t, b.FileSet, r.FileSet) + assert.Equal(t, b.MainFunction, r.MainFunction) assert.Equal(t, b.Constants, r.Constants) } diff --git a/compiler/compilation_scope.go b/compiler/compilation_scope.go index b7ee7b2..dd198ae 100644 --- a/compiler/compilation_scope.go +++ b/compiler/compilation_scope.go @@ -1,9 +1,12 @@ package compiler +import "github.com/d5/tengo/compiler/source" + // CompilationScope represents a compiled instructions // and the last two instructions that were emitted. type CompilationScope struct { instructions []byte lastInstructions [2]EmittedInstruction symbolInit map[string]bool + sourceMap map[int]source.Pos } diff --git a/compiler/compiler.go b/compiler/compiler.go index 13967e7..141ea8f 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -6,13 +6,15 @@ import ( "reflect" "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/stdlib" + "github.com/d5/tengo/compiler/source" "github.com/d5/tengo/compiler/token" "github.com/d5/tengo/objects" + "github.com/d5/tengo/stdlib" ) // Compiler compiles the AST into a bytecode. type Compiler struct { + file *source.File parent *Compiler moduleName string constants []objects.Object @@ -20,7 +22,7 @@ type Compiler struct { scopes []CompilationScope scopeIndex int moduleLoader ModuleLoader - stdModules map[string]*objects.ImmutableMap + builtinModules map[string]bool compiledModules map[string]*objects.CompiledFunction loops []*Loop loopIndex int @@ -34,9 +36,10 @@ type Compiler struct { // a new symbol table and use the default builtin functions. Likewise, standard // modules can be explicitly provided if user wants to add or remove some modules. // By default, Compile will use all the standard modules otherwise. -func NewCompiler(symbolTable *SymbolTable, constants []objects.Object, stdModules map[string]*objects.ImmutableMap, trace io.Writer) *Compiler { +func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []objects.Object, builtinModules map[string]bool, trace io.Writer) *Compiler { mainScope := CompilationScope{ - instructions: make([]byte, 0), + symbolInit: make(map[string]bool), + sourceMap: make(map[int]source.Pos), } // symbol table @@ -48,19 +51,23 @@ func NewCompiler(symbolTable *SymbolTable, constants []objects.Object, stdModule } } - // standard modules - if stdModules == nil { - stdModules = stdlib.Modules + // builtin modules + if builtinModules == nil { + builtinModules = make(map[string]bool) + for name := range stdlib.Modules { + builtinModules[name] = true + } } return &Compiler{ + file: file, symbolTable: symbolTable, constants: constants, scopes: []CompilationScope{mainScope}, scopeIndex: 0, loopIndex: -1, trace: trace, - stdModules: stdModules, + builtinModules: builtinModules, compiledModules: make(map[string]*objects.CompiledFunction), } } @@ -87,7 +94,7 @@ func (c *Compiler) Compile(node ast.Node) error { if err := c.Compile(node.Expr); err != nil { return err } - c.emit(OpPop) + c.emit(node, OpPop) case *ast.IncDecStmt: op := token.AddAssign @@ -95,7 +102,7 @@ func (c *Compiler) Compile(node ast.Node) error { op = token.SubAssign } - return c.compileAssign([]ast.Expr{node.Expr}, []ast.Expr{&ast.IntLit{Value: 1}}, op) + return c.compileAssign(node, []ast.Expr{node.Expr}, []ast.Expr{&ast.IntLit{Value: 1}}, op) case *ast.ParenExpr: if err := c.Compile(node.Expr); err != nil { @@ -116,7 +123,7 @@ func (c *Compiler) Compile(node ast.Node) error { return err } - c.emit(OpGreaterThan) + c.emit(node, OpGreaterThan) return nil } else if node.Token == token.LessEq { @@ -127,7 +134,7 @@ func (c *Compiler) Compile(node ast.Node) error { return err } - c.emit(OpGreaterThanEqual) + c.emit(node, OpGreaterThanEqual) return nil } @@ -141,60 +148,60 @@ func (c *Compiler) Compile(node ast.Node) error { switch node.Token { case token.Add: - c.emit(OpAdd) + c.emit(node, OpAdd) case token.Sub: - c.emit(OpSub) + c.emit(node, OpSub) case token.Mul: - c.emit(OpMul) + c.emit(node, OpMul) case token.Quo: - c.emit(OpDiv) + c.emit(node, OpDiv) case token.Rem: - c.emit(OpRem) + c.emit(node, OpRem) case token.Greater: - c.emit(OpGreaterThan) + c.emit(node, OpGreaterThan) case token.GreaterEq: - c.emit(OpGreaterThanEqual) + c.emit(node, OpGreaterThanEqual) case token.Equal: - c.emit(OpEqual) + c.emit(node, OpEqual) case token.NotEqual: - c.emit(OpNotEqual) + c.emit(node, OpNotEqual) case token.And: - c.emit(OpBAnd) + c.emit(node, OpBAnd) case token.Or: - c.emit(OpBOr) + c.emit(node, OpBOr) case token.Xor: - c.emit(OpBXor) + c.emit(node, OpBXor) case token.AndNot: - c.emit(OpBAndNot) + c.emit(node, OpBAndNot) case token.Shl: - c.emit(OpBShiftLeft) + c.emit(node, OpBShiftLeft) case token.Shr: - c.emit(OpBShiftRight) + c.emit(node, OpBShiftRight) default: - return fmt.Errorf("unknown operator: %s", node.Token.String()) + return c.errorf(node, "invalid binary operator: %s", node.Token.String()) } case *ast.IntLit: - c.emit(OpConstant, c.addConstant(&objects.Int{Value: node.Value})) + c.emit(node, OpConstant, c.addConstant(&objects.Int{Value: node.Value})) case *ast.FloatLit: - c.emit(OpConstant, c.addConstant(&objects.Float{Value: node.Value})) + c.emit(node, OpConstant, c.addConstant(&objects.Float{Value: node.Value})) case *ast.BoolLit: if node.Value { - c.emit(OpTrue) + c.emit(node, OpTrue) } else { - c.emit(OpFalse) + c.emit(node, OpFalse) } case *ast.StringLit: - c.emit(OpConstant, c.addConstant(&objects.String{Value: node.Value})) + c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.Value})) case *ast.CharLit: - c.emit(OpConstant, c.addConstant(&objects.Char{Value: node.Value})) + c.emit(node, OpConstant, c.addConstant(&objects.Char{Value: node.Value})) case *ast.UndefinedLit: - c.emit(OpNull) + c.emit(node, OpNull) case *ast.UnaryExpr: if err := c.Compile(node.Expr); err != nil { @@ -203,15 +210,15 @@ func (c *Compiler) Compile(node ast.Node) error { switch node.Token { case token.Not: - c.emit(OpLNot) + c.emit(node, OpLNot) case token.Sub: - c.emit(OpMinus) + c.emit(node, OpMinus) case token.Xor: - c.emit(OpBComplement) + c.emit(node, OpBComplement) case token.Add: // do nothing? default: - return fmt.Errorf("unknown operator: %s", node.Token.String()) + return c.errorf(node, "invalid unary operator: %s", node.Token.String()) } case *ast.IfStmt: @@ -232,7 +239,7 @@ func (c *Compiler) Compile(node ast.Node) error { } // first jump placeholder - jumpPos1 := c.emit(OpJumpFalsy, 0) + jumpPos1 := c.emit(node, OpJumpFalsy, 0) if err := c.Compile(node.Body); err != nil { return err @@ -240,7 +247,7 @@ func (c *Compiler) Compile(node ast.Node) error { if node.Else != nil { // second jump placeholder - jumpPos2 := c.emit(OpJump, 0) + jumpPos2 := c.emit(node, OpJump, 0) // update first jump offset curPos := len(c.currentInstructions()) @@ -269,19 +276,19 @@ func (c *Compiler) Compile(node ast.Node) error { if node.Token == token.Break { curLoop := c.currentLoop() if curLoop == nil { - return fmt.Errorf("break statement outside loop") + return c.errorf(node, "break not allowed outside loop") } - pos := c.emit(OpJump, 0) + pos := c.emit(node, OpJump, 0) curLoop.Breaks = append(curLoop.Breaks, pos) } else if node.Token == token.Continue { curLoop := c.currentLoop() if curLoop == nil { - return fmt.Errorf("continue statement outside loop") + return c.errorf(node, "continue not allowed outside loop") } - pos := c.emit(OpJump, 0) + pos := c.emit(node, OpJump, 0) curLoop.Continues = append(curLoop.Continues, pos) } else { - return fmt.Errorf("unknown branch statement: %s", node.Token.String()) + panic(fmt.Errorf("invalid branch statement: %s", node.Token.String())) } case *ast.BlockStmt: @@ -292,25 +299,25 @@ func (c *Compiler) Compile(node ast.Node) error { } case *ast.AssignStmt: - if err := c.compileAssign(node.LHS, node.RHS, node.Token); err != nil { + if err := c.compileAssign(node, node.LHS, node.RHS, node.Token); err != nil { return err } case *ast.Ident: symbol, _, ok := c.symbolTable.Resolve(node.Name) if !ok { - return fmt.Errorf("undefined variable: %s", node.Name) + return c.errorf(node, "unresolved reference '%s'", node.Name) } switch symbol.Scope { case ScopeGlobal: - c.emit(OpGetGlobal, symbol.Index) + c.emit(node, OpGetGlobal, symbol.Index) case ScopeLocal: - c.emit(OpGetLocal, symbol.Index) + c.emit(node, OpGetLocal, symbol.Index) case ScopeBuiltin: - c.emit(OpGetBuiltin, symbol.Index) + c.emit(node, OpGetBuiltin, symbol.Index) case ScopeFree: - c.emit(OpGetFree, symbol.Index) + c.emit(node, OpGetFree, symbol.Index) } case *ast.ArrayLit: @@ -320,12 +327,12 @@ func (c *Compiler) Compile(node ast.Node) error { } } - c.emit(OpArray, len(node.Elements)) + c.emit(node, OpArray, len(node.Elements)) case *ast.MapLit: for _, elt := range node.Elements { // key - c.emit(OpConstant, c.addConstant(&objects.String{Value: elt.Key})) + c.emit(node, OpConstant, c.addConstant(&objects.String{Value: elt.Key})) // value if err := c.Compile(elt.Value); err != nil { @@ -333,7 +340,7 @@ func (c *Compiler) Compile(node ast.Node) error { } } - c.emit(OpMap, len(node.Elements)*2) + c.emit(node, OpMap, len(node.Elements)*2) case *ast.SelectorExpr: // selector on RHS side if err := c.Compile(node.Expr); err != nil { @@ -344,7 +351,7 @@ func (c *Compiler) Compile(node ast.Node) error { return err } - c.emit(OpIndex) + c.emit(node, OpIndex) case *ast.IndexExpr: if err := c.Compile(node.Expr); err != nil { @@ -355,7 +362,7 @@ func (c *Compiler) Compile(node ast.Node) error { return err } - c.emit(OpIndex) + c.emit(node, OpIndex) case *ast.SliceExpr: if err := c.Compile(node.Expr); err != nil { @@ -367,7 +374,7 @@ func (c *Compiler) Compile(node ast.Node) error { return err } } else { - c.emit(OpNull) + c.emit(node, OpNull) } if node.High != nil { @@ -375,10 +382,10 @@ func (c *Compiler) Compile(node ast.Node) error { return err } } else { - c.emit(OpNull) + c.emit(node, OpNull) } - c.emit(OpSliceIndex) + c.emit(node, OpSliceIndex) case *ast.FuncLit: c.enterScope() @@ -396,12 +403,12 @@ func (c *Compiler) Compile(node ast.Node) error { // add OpReturn if function returns nothing if !c.lastInstructionIs(OpReturnValue) && !c.lastInstructionIs(OpReturn) { - c.emit(OpReturn) + c.emit(node, OpReturn) } freeSymbols := c.symbolTable.FreeSymbols() numLocals := c.symbolTable.MaxSymbols() - instructions := c.leaveScope() + instructions, sourceMap := c.leaveScope() for _, s := range freeSymbols { switch s.Scope { @@ -444,15 +451,15 @@ func (c *Compiler) Compile(node ast.Node) error { // 0009 SETL 0 // - c.emit(OpNull) - c.emit(OpDefineLocal, s.Index) + c.emit(node, OpNull) + c.emit(node, OpDefineLocal, s.Index) s.LocalAssigned = true } - c.emit(OpGetLocal, s.Index) + c.emit(node, OpGetLocal, s.Index) case ScopeFree: - c.emit(OpGetFree, s.Index) + c.emit(node, OpGetFree, s.Index) } } @@ -460,28 +467,29 @@ func (c *Compiler) Compile(node ast.Node) error { Instructions: instructions, NumLocals: numLocals, NumParameters: len(node.Type.Params.List), + SourceMap: sourceMap, } if len(freeSymbols) > 0 { - c.emit(OpClosure, c.addConstant(compiledFunction), len(freeSymbols)) + c.emit(node, OpClosure, c.addConstant(compiledFunction), len(freeSymbols)) } else { - c.emit(OpConstant, c.addConstant(compiledFunction)) + c.emit(node, OpConstant, c.addConstant(compiledFunction)) } case *ast.ReturnStmt: if c.symbolTable.Parent(true) == nil { // outside the function - return fmt.Errorf("return statement outside function") + return c.errorf(node, "return not allowed outside function") } if node.Result == nil { - c.emit(OpReturn) + c.emit(node, OpReturn) } else { if err := c.Compile(node.Result); err != nil { return err } - c.emit(OpReturnValue) + c.emit(node, OpReturnValue) } case *ast.CallExpr: @@ -495,28 +503,26 @@ func (c *Compiler) Compile(node ast.Node) error { } } - c.emit(OpCall, len(node.Args)) + c.emit(node, OpCall, len(node.Args)) case *ast.ImportExpr: - stdMod, ok := c.stdModules[node.ModuleName] - if ok { - // standard modules contain only globals with no code. - // so no need to compile anything - c.emit(OpConstant, c.addConstant(stdMod)) + if c.builtinModules[node.ModuleName] { + c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.ModuleName})) + c.emit(node, OpGetBuiltinModule) } else { - userMod, err := c.compileModule(node.ModuleName) + userMod, err := c.compileModule(node) if err != nil { return err } - c.emit(OpConstant, c.addConstant(userMod)) - c.emit(OpCall, 0) + c.emit(node, OpConstant, c.addConstant(userMod)) + c.emit(node, OpCall, 0) } case *ast.ExportStmt: // export statement must be in top-level scope if c.scopeIndex != 0 { - return fmt.Errorf("cannot use 'export' inside function") + return c.errorf(node, "export not allowed inside function") } // export statement is simply ignore when compiling non-module code @@ -528,22 +534,22 @@ func (c *Compiler) Compile(node ast.Node) error { return err } - c.emit(OpImmutable) - c.emit(OpReturnValue) + c.emit(node, OpImmutable) + c.emit(node, OpReturnValue) case *ast.ErrorExpr: if err := c.Compile(node.Expr); err != nil { return err } - c.emit(OpError) + c.emit(node, OpError) case *ast.ImmutableExpr: if err := c.Compile(node.Expr); err != nil { return err } - c.emit(OpImmutable) + c.emit(node, OpImmutable) case *ast.CondExpr: if err := c.Compile(node.Cond); err != nil { @@ -551,14 +557,14 @@ func (c *Compiler) Compile(node ast.Node) error { } // first jump placeholder - jumpPos1 := c.emit(OpJumpFalsy, 0) + jumpPos1 := c.emit(node, OpJumpFalsy, 0) if err := c.Compile(node.True); err != nil { return err } // second jump placeholder - jumpPos2 := c.emit(OpJump, 0) + jumpPos2 := c.emit(node, OpJump, 0) // update first jump offset curPos := len(c.currentInstructions()) @@ -579,8 +585,12 @@ func (c *Compiler) Compile(node ast.Node) error { // Bytecode returns a compiled bytecode. func (c *Compiler) Bytecode() *Bytecode { return &Bytecode{ - Instructions: c.currentInstructions(), - Constants: c.constants, + FileSet: c.file.Set(), + MainFunction: &objects.CompiledFunction{ + Instructions: c.currentInstructions(), + SourceMap: c.currentSourceMap(), + }, + Constants: c.constants, } } @@ -591,8 +601,8 @@ func (c *Compiler) SetModuleLoader(moduleLoader ModuleLoader) { c.moduleLoader = moduleLoader } -func (c *Compiler) fork(moduleName string, symbolTable *SymbolTable) *Compiler { - child := NewCompiler(symbolTable, nil, c.stdModules, c.trace) +func (c *Compiler) fork(file *source.File, moduleName string, symbolTable *SymbolTable) *Compiler { + child := NewCompiler(file, symbolTable, nil, c.builtinModules, c.trace) child.moduleName = moduleName // name of the module to compile child.parent = c // parent to set to current compiler child.moduleLoader = c.moduleLoader // share module loader @@ -600,6 +610,14 @@ func (c *Compiler) fork(moduleName string, symbolTable *SymbolTable) *Compiler { return child } +func (c *Compiler) errorf(node ast.Node, format string, args ...interface{}) error { + return &Error{ + fileSet: c.file.Set(), + node: node, + error: fmt.Errorf(format, args...), + } +} + func (c *Compiler) addConstant(o objects.Object) int { if c.parent != nil { // module compilers will use their parent's constants array @@ -666,9 +684,15 @@ func (c *Compiler) changeOperand(opPos int, operand ...int) { c.replaceInstruction(opPos, inst) } -func (c *Compiler) emit(opcode Opcode, operands ...int) int { +func (c *Compiler) emit(node ast.Node, opcode Opcode, operands ...int) int { + filePos := source.NoPos + if node != nil { + filePos = node.Pos() + } + inst := MakeInstruction(opcode, operands...) pos := c.addInstruction(inst) + c.scopes[c.scopeIndex].sourceMap[pos] = filePos c.setLastInstruction(opcode, pos) if c.trace != nil { diff --git a/compiler/compiler_assign.go b/compiler/compiler_assign.go index 50e6f42..0e086c8 100644 --- a/compiler/compiler_assign.go +++ b/compiler/compiler_assign.go @@ -1,50 +1,37 @@ package compiler import ( - "errors" "fmt" "github.com/d5/tengo/compiler/ast" "github.com/d5/tengo/compiler/token" ) -func (c *Compiler) compileAssign(lhs, rhs []ast.Expr, op token.Token) error { +func (c *Compiler) compileAssign(node ast.Node, lhs, rhs []ast.Expr, op token.Token) error { numLHS, numRHS := len(lhs), len(rhs) - if numLHS < numRHS { - // # of LHS must be >= # of RHS - return fmt.Errorf("assigntment count error: %d < %d", numLHS, numRHS) + if numLHS > 1 || numRHS > 1 { + return c.errorf(node, "tuple assignment not allowed") } - if numLHS > 1 { - // TODO: until we fully implement the tuple assignment - return fmt.Errorf("tuple assignment not implemented") - } - //if numLHS > 1 && op != token.Assign && op != token.Define { - // return fmt.Errorf("invalid operator for tuple assignment: %s", op.String()) - //} // resolve and compile left-hand side - ident, selectors, err := resolveAssignLHS(lhs[0]) - if err != nil { - return err - } - + ident, selectors := resolveAssignLHS(lhs[0]) numSel := len(selectors) if op == token.Define && numSel > 0 { // using selector on new variable does not make sense - return errors.New("cannot use selector with ':='") + return c.errorf(node, "operator ':=' not allowed with selector") } symbol, depth, exists := c.symbolTable.Resolve(ident) if op == token.Define { if depth == 0 && exists { - return fmt.Errorf("'%s' redeclared in this block", ident) + return c.errorf(node, "'%s' redeclared in this block", ident) } symbol = c.symbolTable.Define(ident) } else { if !exists { - return fmt.Errorf("unresolved reference '%s'", ident) + return c.errorf(node, "unresolved reference '%s'", ident) } } @@ -64,27 +51,27 @@ func (c *Compiler) compileAssign(lhs, rhs []ast.Expr, op token.Token) error { switch op { case token.AddAssign: - c.emit(OpAdd) + c.emit(node, OpAdd) case token.SubAssign: - c.emit(OpSub) + c.emit(node, OpSub) case token.MulAssign: - c.emit(OpMul) + c.emit(node, OpMul) case token.QuoAssign: - c.emit(OpDiv) + c.emit(node, OpDiv) case token.RemAssign: - c.emit(OpRem) + c.emit(node, OpRem) case token.AndAssign: - c.emit(OpBAnd) + c.emit(node, OpBAnd) case token.OrAssign: - c.emit(OpBOr) + c.emit(node, OpBOr) case token.AndNotAssign: - c.emit(OpBAndNot) + c.emit(node, OpBAndNot) case token.XorAssign: - c.emit(OpBXor) + c.emit(node, OpBXor) case token.ShlAssign: - c.emit(OpBShiftLeft) + c.emit(node, OpBShiftLeft) case token.ShrAssign: - c.emit(OpBShiftRight) + c.emit(node, OpBShiftRight) } // compile selector expressions (right to left) @@ -97,18 +84,18 @@ func (c *Compiler) compileAssign(lhs, rhs []ast.Expr, op token.Token) error { switch symbol.Scope { case ScopeGlobal: if numSel > 0 { - c.emit(OpSetSelGlobal, symbol.Index, numSel) + c.emit(node, OpSetSelGlobal, symbol.Index, numSel) } else { - c.emit(OpSetGlobal, symbol.Index) + c.emit(node, OpSetGlobal, symbol.Index) } case ScopeLocal: if numSel > 0 { - c.emit(OpSetSelLocal, symbol.Index, numSel) + c.emit(node, OpSetSelLocal, symbol.Index, numSel) } else { if op == token.Define && !symbol.LocalAssigned { - c.emit(OpDefineLocal, symbol.Index) + c.emit(node, OpDefineLocal, symbol.Index) } else { - c.emit(OpSetLocal, symbol.Index) + c.emit(node, OpSetLocal, symbol.Index) } } @@ -116,39 +103,30 @@ func (c *Compiler) compileAssign(lhs, rhs []ast.Expr, op token.Token) error { symbol.LocalAssigned = true case ScopeFree: if numSel > 0 { - c.emit(OpSetSelFree, symbol.Index, numSel) + c.emit(node, OpSetSelFree, symbol.Index, numSel) } else { - c.emit(OpSetFree, symbol.Index) + c.emit(node, OpSetFree, symbol.Index) } default: - return fmt.Errorf("invalid assignment variable scope: %s", symbol.Scope) + panic(fmt.Errorf("invalid assignment variable scope: %s", symbol.Scope)) } return nil } -func resolveAssignLHS(expr ast.Expr) (name string, selectors []ast.Expr, err error) { +func resolveAssignLHS(expr ast.Expr) (name string, selectors []ast.Expr) { switch term := expr.(type) { case *ast.SelectorExpr: - name, selectors, err = resolveAssignLHS(term.Expr) - if err != nil { - return - } - + name, selectors = resolveAssignLHS(term.Expr) selectors = append(selectors, term.Sel) - return case *ast.IndexExpr: - name, selectors, err = resolveAssignLHS(term.Expr) - if err != nil { - return - } - + name, selectors = resolveAssignLHS(term.Expr) selectors = append(selectors, term.Index) case *ast.Ident: - return term.Name, nil, nil + name = term.Name } return diff --git a/compiler/compiler_error_report_test.go b/compiler/compiler_error_report_test.go new file mode 100644 index 0000000..27191c5 --- /dev/null +++ b/compiler/compiler_error_report_test.go @@ -0,0 +1,17 @@ +package compiler_test + +import "testing" + +func TestCompilerErrorReport(t *testing.T) { + expectError(t, `import("user1")`, "test:1:1: module file read error: open user1.tengo: no such file or directory") + + expectError(t, `a = 1`, "test:1:1: unresolved reference 'a'") + expectError(t, `a, b := 1, 2`, "test:1:1: tuple assignment not allowed") + expectError(t, `a.b := 1`, "not allowed with selector") + expectError(t, `a:=1; a:=3`, "test:1:7: 'a' redeclared in this block") + + expectError(t, `return 5`, "test:1:1: return not allowed outside function") + expectError(t, `func() { break }`, "test:1:10: break not allowed outside loop") + expectError(t, `func() { continue }`, "test:1:10: continue not allowed outside loop") + expectError(t, `func() { export 5 }`, "test:1:10: export not allowed inside function") +} diff --git a/compiler/compiler_for.go b/compiler/compiler_for.go index 059330e..e7b7b5f 100644 --- a/compiler/compiler_for.go +++ b/compiler/compiler_for.go @@ -27,7 +27,7 @@ func (c *Compiler) compileForStmt(stmt *ast.ForStmt) error { return err } // condition jump position - postCondPos = c.emit(OpJumpFalsy, 0) + postCondPos = c.emit(stmt, OpJumpFalsy, 0) } // enter loop @@ -52,7 +52,7 @@ func (c *Compiler) compileForStmt(stmt *ast.ForStmt) error { } // back to condition - c.emit(OpJump, preCondPos) + c.emit(stmt, OpJump, preCondPos) // post-statement position postStmtPos := len(c.currentInstructions()) @@ -94,11 +94,11 @@ func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error { if err := c.Compile(stmt.Iterable); err != nil { return err } - c.emit(OpIteratorInit) + c.emit(stmt, OpIteratorInit) if itSymbol.Scope == ScopeGlobal { - c.emit(OpSetGlobal, itSymbol.Index) + c.emit(stmt, OpSetGlobal, itSymbol.Index) } else { - c.emit(OpDefineLocal, itSymbol.Index) + c.emit(stmt, OpDefineLocal, itSymbol.Index) } // pre-condition position @@ -107,14 +107,14 @@ func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error { // condition // :it.HasMore() if itSymbol.Scope == ScopeGlobal { - c.emit(OpGetGlobal, itSymbol.Index) + c.emit(stmt, OpGetGlobal, itSymbol.Index) } else { - c.emit(OpGetLocal, itSymbol.Index) + c.emit(stmt, OpGetLocal, itSymbol.Index) } - c.emit(OpIteratorNext) + c.emit(stmt, OpIteratorNext) // condition jump position - postCondPos := c.emit(OpJumpFalsy, 0) + postCondPos := c.emit(stmt, OpJumpFalsy, 0) // enter loop loop := c.enterLoop() @@ -123,15 +123,15 @@ func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error { if stmt.Key.Name != "_" { keySymbol := c.symbolTable.Define(stmt.Key.Name) if itSymbol.Scope == ScopeGlobal { - c.emit(OpGetGlobal, itSymbol.Index) + c.emit(stmt, OpGetGlobal, itSymbol.Index) } else { - c.emit(OpGetLocal, itSymbol.Index) + c.emit(stmt, OpGetLocal, itSymbol.Index) } - c.emit(OpIteratorKey) + c.emit(stmt, OpIteratorKey) if keySymbol.Scope == ScopeGlobal { - c.emit(OpSetGlobal, keySymbol.Index) + c.emit(stmt, OpSetGlobal, keySymbol.Index) } else { - c.emit(OpDefineLocal, keySymbol.Index) + c.emit(stmt, OpDefineLocal, keySymbol.Index) } } @@ -139,15 +139,15 @@ func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error { if stmt.Value.Name != "_" { valueSymbol := c.symbolTable.Define(stmt.Value.Name) if itSymbol.Scope == ScopeGlobal { - c.emit(OpGetGlobal, itSymbol.Index) + c.emit(stmt, OpGetGlobal, itSymbol.Index) } else { - c.emit(OpGetLocal, itSymbol.Index) + c.emit(stmt, OpGetLocal, itSymbol.Index) } - c.emit(OpIteratorValue) + c.emit(stmt, OpIteratorValue) if valueSymbol.Scope == ScopeGlobal { - c.emit(OpSetGlobal, valueSymbol.Index) + c.emit(stmt, OpSetGlobal, valueSymbol.Index) } else { - c.emit(OpDefineLocal, valueSymbol.Index) + c.emit(stmt, OpDefineLocal, valueSymbol.Index) } } @@ -163,7 +163,7 @@ func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error { postBodyPos := len(c.currentInstructions()) // back to condition - c.emit(OpJump, preCondPos) + c.emit(stmt, OpJump, preCondPos) // post-statement position postStmtPos := len(c.currentInstructions()) diff --git a/compiler/compiler_logical.go b/compiler/compiler_logical.go index ba12507..68c9675 100644 --- a/compiler/compiler_logical.go +++ b/compiler/compiler_logical.go @@ -14,9 +14,9 @@ func (c *Compiler) compileLogical(node *ast.BinaryExpr) error { // jump position var jumpPos int if node.Token == token.LAnd { - jumpPos = c.emit(OpAndJump, 0) + jumpPos = c.emit(node, OpAndJump, 0) } else { - jumpPos = c.emit(OpOrJump, 0) + jumpPos = c.emit(node, OpOrJump, 0) } // right side term diff --git a/compiler/compiler_module.go b/compiler/compiler_module.go index 5930c32..8f63abb 100644 --- a/compiler/compiler_module.go +++ b/compiler/compiler_module.go @@ -1,25 +1,22 @@ package compiler import ( - "fmt" "io/ioutil" "strings" + "github.com/d5/tengo/compiler/ast" "github.com/d5/tengo/compiler/parser" - "github.com/d5/tengo/compiler/source" "github.com/d5/tengo/objects" ) -var ( - fileSet = source.NewFileSet() -) - -func (c *Compiler) compileModule(moduleName string) (*objects.CompiledFunction, error) { - compiledModule, exists := c.loadCompiledModule(moduleName) +func (c *Compiler) compileModule(expr *ast.ImportExpr) (*objects.CompiledFunction, error) { + compiledModule, exists := c.loadCompiledModule(expr.ModuleName) if exists { return compiledModule, nil } + moduleName := expr.ModuleName + // read module source from loader var moduleSrc []byte if c.moduleLoader == nil { @@ -28,17 +25,17 @@ func (c *Compiler) compileModule(moduleName string) (*objects.CompiledFunction, moduleName += ".tengo" } - if err := c.checkCyclicImports(moduleName); err != nil { + if err := c.checkCyclicImports(expr, moduleName); err != nil { return nil, err } var err error moduleSrc, err = ioutil.ReadFile(moduleName) if err != nil { - return nil, err + return nil, c.errorf(expr, "module file read error: %s", err.Error()) } } else { - if err := c.checkCyclicImports(moduleName); err != nil { + if err := c.checkCyclicImports(expr, moduleName); err != nil { return nil, err } @@ -59,18 +56,19 @@ func (c *Compiler) compileModule(moduleName string) (*objects.CompiledFunction, return compiledModule, nil } -func (c *Compiler) checkCyclicImports(moduleName string) error { +func (c *Compiler) checkCyclicImports(node ast.Node, moduleName string) error { if c.moduleName == moduleName { - return fmt.Errorf("cyclic module import: %s", moduleName) + return c.errorf(node, "cyclic module import: %s", moduleName) } else if c.parent != nil { - return c.parent.checkCyclicImports(moduleName) + return c.parent.checkCyclicImports(node, moduleName) } return nil } func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.CompiledFunction, error) { - p := parser.NewParser(fileSet.AddFile(moduleName, -1, len(src)), src, nil) + modFile := c.file.Set().AddFile(moduleName, -1, len(src)) + p := parser.NewParser(modFile, src, nil) file, err := p.ParseFile() if err != nil { return nil, err @@ -90,20 +88,20 @@ func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.Comp symbolTable = symbolTable.Fork(false) // compile module - moduleCompiler := c.fork(moduleName, symbolTable) + moduleCompiler := c.fork(modFile, moduleName, symbolTable) if err := moduleCompiler.Compile(file); err != nil { return nil, err } // add OpReturn (== export undefined) if export is missing if !moduleCompiler.lastInstructionIs(OpReturnValue) { - moduleCompiler.emit(OpReturn) + moduleCompiler.emit(nil, OpReturn) } - return &objects.CompiledFunction{ - Instructions: moduleCompiler.Bytecode().Instructions, - NumLocals: symbolTable.MaxSymbols(), - }, nil + compiledFunc := moduleCompiler.Bytecode().MainFunction + compiledFunc.NumLocals = symbolTable.MaxSymbols() + + return compiledFunc, nil } func (c *Compiler) loadCompiledModule(moduleName string) (mod *objects.CompiledFunction, ok bool) { diff --git a/compiler/compiler_scopes.go b/compiler/compiler_scopes.go index fdc68b0..b63f915 100644 --- a/compiler/compiler_scopes.go +++ b/compiler/compiler_scopes.go @@ -1,13 +1,19 @@ package compiler +import "github.com/d5/tengo/compiler/source" + func (c *Compiler) currentInstructions() []byte { return c.scopes[c.scopeIndex].instructions } +func (c *Compiler) currentSourceMap() map[int]source.Pos { + return c.scopes[c.scopeIndex].sourceMap +} + func (c *Compiler) enterScope() { scope := CompilationScope{ - instructions: make([]byte, 0), - symbolInit: make(map[string]bool), + symbolInit: make(map[string]bool), + sourceMap: make(map[int]source.Pos), } c.scopes = append(c.scopes, scope) @@ -20,8 +26,9 @@ func (c *Compiler) enterScope() { } } -func (c *Compiler) leaveScope() []byte { - instructions := c.currentInstructions() +func (c *Compiler) leaveScope() (instructions []byte, sourceMap map[int]source.Pos) { + instructions = c.currentInstructions() + sourceMap = c.currentSourceMap() c.scopes = c.scopes[:len(c.scopes)-1] c.scopeIndex-- @@ -32,5 +39,5 @@ func (c *Compiler) leaveScope() []byte { c.printTrace("SCOPL", c.scopeIndex) } - return instructions + return } diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 362dc87..41ad49e 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -874,11 +874,11 @@ func() { intObject(1), intObject(1)))) - expectError(t, `import("user1")`) // unknown module name + expectError(t, `import("user1")`, "no such file or directory") // unknown module name } func concat(instructions ...[]byte) []byte { - concat := make([]byte, 0) + var concat []byte for _, i := range instructions { concat = append(concat, i...) } @@ -888,7 +888,8 @@ func concat(instructions ...[]byte) []byte { func bytecode(instructions []byte, constants []objects.Object) *compiler.Bytecode { return &compiler.Bytecode{ - Instructions: instructions, + FileSet: source.NewFileSet(), + MainFunction: &objects.CompiledFunction{Instructions: instructions}, Constants: constants, } } @@ -913,7 +914,7 @@ func expect(t *testing.T, input string, expected *compiler.Bytecode) (ok bool) { return } -func expectError(t *testing.T, input string) (ok bool) { +func expectError(t *testing.T, input, expected string) (ok bool) { _, trace, err := traceCompile(input, nil) defer func() { @@ -924,16 +925,21 @@ func expectError(t *testing.T, input string) (ok bool) { } }() - ok = assert.Error(t, err) + if !assert.Error(t, err) { + return + } + + if !assert.True(t, strings.Contains(err.Error(), expected), "expected error string: %s, got: %s", expected, err.Error()) { + return + } + + ok = true return } func equalBytecode(t *testing.T, expected, actual *compiler.Bytecode) bool { - expectedInstructions := strings.Join(compiler.FormatInstructions(expected.Instructions, 0), "\n") - actualInstructions := strings.Join(compiler.FormatInstructions(actual.Instructions, 0), "\n") - - return assert.Equal(t, expectedInstructions, actualInstructions) && + return assert.Equal(t, expected.MainFunction, actual.MainFunction) && equalConstants(t, expected.Constants, actual.Constants) } @@ -975,7 +981,7 @@ func traceCompile(input string, symbols map[string]objects.Object) (res *compile } tr := &tracer{} - c := compiler.NewCompiler(symTable, nil, nil, tr) + c := compiler.NewCompiler(file, symTable, nil, nil, tr) parsed, err := p.ParseFile() if err != nil { return diff --git a/compiler/error.go b/compiler/error.go new file mode 100644 index 0000000..b0697f9 --- /dev/null +++ b/compiler/error.go @@ -0,0 +1,20 @@ +package compiler + +import ( + "fmt" + + "github.com/d5/tengo/compiler/ast" + "github.com/d5/tengo/compiler/source" +) + +// Error represents a compiler error. +type Error struct { + fileSet *source.FileSet + node ast.Node + error error +} + +func (e *Error) Error() string { + filePos := e.fileSet.Position(e.node.Pos()) + return fmt.Sprintf("%s: %s", filePos, e.error.Error()) +} diff --git a/compiler/opcodes.go b/compiler/opcodes.go index e8f06b8..7a79511 100644 --- a/compiler/opcodes.go +++ b/compiler/opcodes.go @@ -1,7 +1,7 @@ package compiler // Opcode represents a single byte operation code. -type Opcode byte +type Opcode = byte // List of opcodes const ( @@ -53,6 +53,7 @@ const ( OpSetFree // Set free variables OpSetSelFree // Set free variables using selectors OpGetBuiltin // Get builtin function + OpGetBuiltinModule // Get builtin module OpClosure // Push closure OpIteratorInit // Iterator init OpIteratorNext // Iterator next @@ -107,6 +108,7 @@ var OpcodeNames = [...]string{ OpDefineLocal: "DEFL", OpSetSelLocal: "SETSL", OpGetBuiltin: "BUILTIN", + OpGetBuiltinModule: "BLTMOD", OpClosure: "CLOSURE", OpGetFree: "GETF", OpSetFree: "SETF", @@ -164,6 +166,7 @@ var OpcodeOperands = [...][]int{ OpDefineLocal: {1}, OpSetSelLocal: {1, 1}, OpGetBuiltin: {1}, + OpGetBuiltinModule: {}, OpClosure: {2, 1}, OpGetFree: {1}, OpSetFree: {1}, diff --git a/compiler/parser/parse_source.go b/compiler/parser/parse_source.go index 3a62e15..5d242db 100644 --- a/compiler/parser/parse_source.go +++ b/compiler/parser/parse_source.go @@ -8,9 +8,9 @@ import ( ) // ParseSource parses source code 'src' and builds an AST. -func ParseSource(src []byte, trace io.Writer) (res *ast.File, err error) { +func ParseSource(filename string, src []byte, trace io.Writer) (res *ast.File, err error) { fileSet := source.NewFileSet() - file := fileSet.AddFile("", -1, len(src)) + file := fileSet.AddFile(filename, -1, len(src)) return ParseFile(file, src, trace) } diff --git a/compiler/parser/parser.go b/compiler/parser/parser.go index 5af2666..93f04f7 100644 --- a/compiler/parser/parser.go +++ b/compiler/parser/parser.go @@ -1158,8 +1158,8 @@ func (p *Parser) printTrace(a ...interface{}) { } func (p *Parser) safePos(pos source.Pos) source.Pos { - fileBase := p.file.Base() - fileSize := p.file.Size() + fileBase := p.file.Base + fileSize := p.file.Size if int(pos) < fileBase || int(pos) > fileBase+fileSize { return source.Pos(fileBase + fileSize) diff --git a/compiler/parser/parser_test.go b/compiler/parser/parser_test.go index 2e975eb..356a396 100644 --- a/compiler/parser/parser_test.go +++ b/compiler/parser/parser_test.go @@ -36,7 +36,7 @@ func (o *tracer) Write(p []byte) (n int, err error) { func expect(t *testing.T, input string, fn expectedFn) (ok bool) { testFileSet := source.NewFileSet() - testFile := testFileSet.AddFile("", -1, len(input)) + testFile := testFileSet.AddFile("test", -1, len(input)) defer func() { if !ok { @@ -76,7 +76,7 @@ func expect(t *testing.T, input string, fn expectedFn) (ok bool) { func expectError(t *testing.T, input string) (ok bool) { testFileSet := source.NewFileSet() - testFile := testFileSet.AddFile("", -1, len(input)) + testFile := testFileSet.AddFile("test", -1, len(input)) defer func() { if !ok { @@ -102,12 +102,12 @@ func expectString(t *testing.T, input, expected string) (ok bool) { if !ok { // print trace tr := &tracer{} - _, _ = parser.ParseSource([]byte(input), tr) + _, _ = parser.ParseSource("test", []byte(input), tr) t.Logf("Trace:\n%s", strings.Join(tr.out, "")) } }() - actual, err := parser.ParseSource([]byte(input), nil) + actual, err := parser.ParseSource("test", []byte(input), nil) if !assert.NoError(t, err) { return } diff --git a/compiler/scanner/scanner.go b/compiler/scanner/scanner.go index 11e71b9..387cd8e 100644 --- a/compiler/scanner/scanner.go +++ b/compiler/scanner/scanner.go @@ -38,8 +38,8 @@ type Scanner struct { // NewScanner creates a Scanner. func NewScanner(file *source.File, src []byte, errorHandler ErrorHandler, mode Mode) *Scanner { - if file.Size() != len(src) { - panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size(), len(src))) + if file.Size != len(src) { + panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size, len(src))) } s := &Scanner{ diff --git a/compiler/scanner/scanner_test.go b/compiler/scanner/scanner_test.go index c975386..d0e495c 100644 --- a/compiler/scanner/scanner_test.go +++ b/compiler/scanner/scanner_test.go @@ -213,7 +213,7 @@ func TestStripCR(t *testing.T) { } func scanExpect(t *testing.T, input string, mode scanner.Mode, expected ...scanResult) bool { - testFile := testFileSet.AddFile("", testFileSet.Base(), len(input)) + testFile := testFileSet.AddFile("test", -1, len(input)) s := scanner.NewScanner( testFile, diff --git a/compiler/source/file.go b/compiler/source/file.go index 754742b..9e51c9a 100644 --- a/compiler/source/file.go +++ b/compiler/source/file.go @@ -1,50 +1,34 @@ package source -import ( - "sync" -) - // File represents a source file. type File struct { - set *FileSet - name string // file name as provided to AddFile - base int // Pos value range for this file is [base...base+size] - size int // file size as provided to AddFile - mutex sync.Mutex - lines []int // lines contains the offset of the first character for each line (the first entry is always 0) + // File set for the file + set *FileSet + // File name as provided to AddFile + Name string + // Pos value range for this file is [base...base+size] + Base int + // File size as provided to AddFile + Size int + // Lines contains the offset of the first character for each line (the first entry is always 0) + Lines []int } -// Name returns the file name. -func (f *File) Name() string { - return f.name -} - -// Base returns the base position of the file. -func (f *File) Base() int { - return f.base -} - -// Size returns the size of the file. -func (f *File) Size() int { - return f.size +// Set returns FileSet. +func (f *File) Set() *FileSet { + return f.set } // LineCount returns the current number of lines. func (f *File) LineCount() int { - f.mutex.Lock() - n := len(f.lines) - f.mutex.Unlock() - - return n + return len(f.Lines) } // AddLine adds a new line. func (f *File) AddLine(offset int) { - f.mutex.Lock() - if i := len(f.lines); (i == 0 || f.lines[i-1] < offset) && offset < f.size { - f.lines = append(f.lines, offset) + if i := len(f.Lines); (i == 0 || f.Lines[i-1] < offset) && offset < f.Size { + f.Lines = append(f.Lines, offset) } - f.mutex.Unlock() } // LineStart returns the position of the first character in the line. @@ -53,38 +37,35 @@ func (f *File) LineStart(line int) Pos { panic("illegal line number (line numbering starts at 1)") } - f.mutex.Lock() - defer f.mutex.Unlock() - - if line > len(f.lines) { + if line > len(f.Lines) { panic("illegal line number") } - return Pos(f.base + f.lines[line-1]) + return Pos(f.Base + f.Lines[line-1]) } // FileSetPos returns the position in the file set. func (f *File) FileSetPos(offset int) Pos { - if offset > f.size { + if offset > f.Size { panic("illegal file offset") } - return Pos(f.base + offset) + return Pos(f.Base + offset) } // Offset translates the file set position into the file offset. func (f *File) Offset(p Pos) int { - if int(p) < f.base || int(p) > f.base+f.size { + if int(p) < f.Base || int(p) > f.Base+f.Size { panic("illegal Pos value") } - return int(p) - f.base + return int(p) - f.Base } // Position translates the file set position into the file position. func (f *File) Position(p Pos) (pos FilePos) { if p != NoPos { - if int(p) < f.base || int(p) > f.base+f.size { + if int(p) < f.Base || int(p) > f.Base+f.Size { panic("illegal Pos value") } @@ -95,7 +76,7 @@ func (f *File) Position(p Pos) (pos FilePos) { } func (f *File) position(p Pos) (pos FilePos) { - offset := int(p) - f.base + offset := int(p) - f.Base pos.Offset = offset pos.Filename, pos.Line, pos.Column = f.unpack(offset) @@ -103,12 +84,9 @@ func (f *File) position(p Pos) (pos FilePos) { } func (f *File) unpack(offset int) (filename string, line, column int) { - f.mutex.Lock() - defer f.mutex.Unlock() - - filename = f.name - if i := searchInts(f.lines, offset); i >= 0 { - line, column = i+1, offset-f.lines[i]+1 + filename = f.Name + if i := searchInts(f.Lines, offset); i >= 0 { + line, column = i+1, offset-f.Lines[i]+1 } return diff --git a/compiler/source/file_set.go b/compiler/source/file_set.go index 45dc529..da34236 100644 --- a/compiler/source/file_set.go +++ b/compiler/source/file_set.go @@ -2,51 +2,37 @@ package source import ( "sort" - "sync" ) // FileSet represents a set of source files. type FileSet struct { - mutex sync.RWMutex // protects the file set - base int // base offset for the next file - files []*File // list of files in the order added to the set - last *File // cache of last file looked up + Base int // base offset for the next file + Files []*File // list of files in the order added to the set + LastFile *File // cache of last file looked up } // NewFileSet creates a new file set. func NewFileSet() *FileSet { return &FileSet{ - base: 1, // 0 == NoPos + Base: 1, // 0 == NoPos } } -// Base returns the current base position of the file set. -func (s *FileSet) Base() int { - s.mutex.RLock() - b := s.base - s.mutex.RUnlock() - - return b -} - // AddFile adds a new file in the file set. func (s *FileSet) AddFile(filename string, base, size int) *File { - s.mutex.Lock() - defer s.mutex.Unlock() - if base < 0 { - base = s.base + base = s.Base } - if base < s.base || size < 0 { + if base < s.Base || size < 0 { panic("illegal base or size") } f := &File{ set: s, - name: filename, - base: base, - size: size, - lines: []int{0}, + Name: filename, + Base: base, + Size: size, + Lines: []int{0}, } base += size + 1 // +1 because EOF also has a position @@ -55,9 +41,9 @@ func (s *FileSet) AddFile(filename string, base, size int) *File { } // add the file to the file set - s.base = base - s.files = append(s.files, f) - s.last = f + s.Base = base + s.Files = append(s.Files, f) + s.LastFile = f return f } @@ -86,32 +72,25 @@ func (s *FileSet) Position(p Pos) (pos FilePos) { } func (s *FileSet) file(p Pos) *File { - s.mutex.RLock() - // common case: p is in last file - if f := s.last; f != nil && f.base <= int(p) && int(p) <= f.base+f.size { - s.mutex.RUnlock() + if f := s.LastFile; f != nil && f.Base <= int(p) && int(p) <= f.Base+f.Size { return f } // p is not in last file - search all files - if i := searchFiles(s.files, int(p)); i >= 0 { - f := s.files[i] + if i := searchFiles(s.Files, int(p)); i >= 0 { + f := s.Files[i] // f.base <= int(p) by definition of searchFiles - if int(p) <= f.base+f.size { - s.mutex.RUnlock() - s.mutex.Lock() - s.last = f // race is ok - s.last is only a cache - s.mutex.Unlock() + if int(p) <= f.Base+f.Size { + s.LastFile = f // race is ok - s.last is only a cache return f } } - s.mutex.RUnlock() return nil } func searchFiles(a []*File, x int) int { - return sort.Search(len(a), func(i int) bool { return a[i].base > x }) - 1 + return sort.Search(len(a), func(i int) bool { return a[i].Base > x }) - 1 } diff --git a/compiler/stdlib/func_typedefs.go b/compiler/stdlib/func_typedefs.go deleted file mode 100644 index d1640e9..0000000 --- a/compiler/stdlib/func_typedefs.go +++ /dev/null @@ -1,930 +0,0 @@ -package stdlib - -import ( - "github.com/d5/tengo/objects" -) - -// FuncAR transform a function of 'func()' signature -// into a user function object. -func FuncAR(fn func()) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 0 { - return nil, objects.ErrWrongNumArguments - } - - fn() - - return objects.UndefinedValue, nil - }, - } -} - -// FuncARI transform a function of 'func() int' signature -// into a user function object. -func FuncARI(fn func() int) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 0 { - return nil, objects.ErrWrongNumArguments - } - - return &objects.Int{Value: int64(fn())}, nil - }, - } -} - -// FuncARI64 transform a function of 'func() int64' signature -// into a user function object. -func FuncARI64(fn func() int64) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 0 { - return nil, objects.ErrWrongNumArguments - } - - return &objects.Int{Value: fn()}, nil - }, - } -} - -// FuncAI64RI64 transform a function of 'func(int64) int64' signature -// into a user function object. -func FuncAI64RI64(fn func(int64) int64) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - i1, ok := objects.ToInt64(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return &objects.Int{Value: fn(i1)}, nil - }, - } -} - -// FuncAI64R transform a function of 'func(int64)' signature -// into a user function object. -func FuncAI64R(fn func(int64)) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - i1, ok := objects.ToInt64(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - fn(i1) - - return objects.UndefinedValue, nil - }, - } -} - -// FuncARB transform a function of 'func() bool' signature -// into a user function object. -func FuncARB(fn func() bool) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 0 { - return nil, objects.ErrWrongNumArguments - } - - if fn() { - return objects.TrueValue, nil - } - - return objects.FalseValue, nil - }, - } -} - -// FuncARE transform a function of 'func() error' signature -// into a user function object. -func FuncARE(fn func() error) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 0 { - return nil, objects.ErrWrongNumArguments - } - - return wrapError(fn()), nil - }, - } -} - -// FuncARS transform a function of 'func() string' signature -// into a user function object. -func FuncARS(fn func() string) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 0 { - return nil, objects.ErrWrongNumArguments - } - - return &objects.String{Value: fn()}, nil - }, - } -} - -// FuncARSE transform a function of 'func() (string, error)' signature -// into a user function object. -func FuncARSE(fn func() (string, error)) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 0 { - return nil, objects.ErrWrongNumArguments - } - - res, err := fn() - if err != nil { - return wrapError(err), nil - } - - return &objects.String{Value: res}, nil - }, - } -} - -// FuncARYE transform a function of 'func() ([]byte, error)' signature -// into a user function object. -func FuncARYE(fn func() ([]byte, error)) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 0 { - return nil, objects.ErrWrongNumArguments - } - - res, err := fn() - if err != nil { - return wrapError(err), nil - } - - return &objects.Bytes{Value: res}, nil - }, - } -} - -// FuncARF transform a function of 'func() float64' signature -// into a user function object. -func FuncARF(fn func() float64) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 0 { - return nil, objects.ErrWrongNumArguments - } - - return &objects.Float{Value: fn()}, nil - }, - } -} - -// FuncARSs transform a function of 'func() []string' signature -// into a user function object. -func FuncARSs(fn func() []string) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 0 { - return nil, objects.ErrWrongNumArguments - } - - arr := &objects.Array{} - for _, osArg := range fn() { - arr.Value = append(arr.Value, &objects.String{Value: osArg}) - } - - return arr, nil - }, - } -} - -// FuncARIsE transform a function of 'func() ([]int, error)' signature -// into a user function object. -func FuncARIsE(fn func() ([]int, error)) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 0 { - return nil, objects.ErrWrongNumArguments - } - - res, err := fn() - if err != nil { - return wrapError(err), nil - } - - arr := &objects.Array{} - for _, v := range res { - arr.Value = append(arr.Value, &objects.Int{Value: int64(v)}) - } - - return arr, nil - }, - } -} - -// FuncAIRIs transform a function of 'func(int) []int' signature -// into a user function object. -func FuncAIRIs(fn func(int) []int) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - i1, ok := objects.ToInt(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - res := fn(i1) - - arr := &objects.Array{} - for _, v := range res { - arr.Value = append(arr.Value, &objects.Int{Value: int64(v)}) - } - - return arr, nil - }, - } -} - -// FuncAFRF transform a function of 'func(float64) float64' signature -// into a user function object. -func FuncAFRF(fn func(float64) float64) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - f1, ok := objects.ToFloat64(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return &objects.Float{Value: fn(f1)}, nil - }, - } -} - -// FuncAIR transform a function of 'func(int)' signature -// into a user function object. -func FuncAIR(fn func(int)) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - i1, ok := objects.ToInt(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - fn(i1) - - return objects.UndefinedValue, nil - }, - } -} - -// FuncAIRF transform a function of 'func(int) float64' signature -// into a user function object. -func FuncAIRF(fn func(int) float64) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - i1, ok := objects.ToInt(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return &objects.Float{Value: fn(i1)}, nil - }, - } -} - -// FuncAFRI transform a function of 'func(float64) int' signature -// into a user function object. -func FuncAFRI(fn func(float64) int) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - f1, ok := objects.ToFloat64(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return &objects.Int{Value: int64(fn(f1))}, nil - }, - } -} - -// FuncAFFRF transform a function of 'func(float64, float64) float64' signature -// into a user function object. -func FuncAFFRF(fn func(float64, float64) float64) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 2 { - return nil, objects.ErrWrongNumArguments - } - - f1, ok := objects.ToFloat64(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - f2, ok := objects.ToFloat64(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return &objects.Float{Value: fn(f1, f2)}, nil - }, - } -} - -// FuncAIFRF transform a function of 'func(int, float64) float64' signature -// into a user function object. -func FuncAIFRF(fn func(int, float64) float64) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 2 { - return nil, objects.ErrWrongNumArguments - } - - i1, ok := objects.ToInt(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - f2, ok := objects.ToFloat64(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return &objects.Float{Value: fn(i1, f2)}, nil - }, - } -} - -// FuncAFIRF transform a function of 'func(float64, int) float64' signature -// into a user function object. -func FuncAFIRF(fn func(float64, int) float64) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 2 { - return nil, objects.ErrWrongNumArguments - } - - f1, ok := objects.ToFloat64(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - i2, ok := objects.ToInt(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return &objects.Float{Value: fn(f1, i2)}, nil - }, - } -} - -// FuncAFIRB transform a function of 'func(float64, int) bool' signature -// into a user function object. -func FuncAFIRB(fn func(float64, int) bool) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 2 { - return nil, objects.ErrWrongNumArguments - } - - f1, ok := objects.ToFloat64(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - i2, ok := objects.ToInt(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - if fn(f1, i2) { - return objects.TrueValue, nil - } - - return objects.FalseValue, nil - }, - } -} - -// FuncAFRB transform a function of 'func(float64) bool' signature -// into a user function object. -func FuncAFRB(fn func(float64) bool) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - f1, ok := objects.ToFloat64(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - if fn(f1) { - return objects.TrueValue, nil - } - - return objects.FalseValue, nil - }, - } -} - -// FuncASRS transform a function of 'func(string) string' signature into a user function object. -// User function will return 'true' if underlying native function returns nil. -func FuncASRS(fn func(string) string) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (objects.Object, error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return &objects.String{Value: fn(s1)}, nil - }, - } -} - -// FuncASRSs transform a function of 'func(string) []string' signature into a user function object. -func FuncASRSs(fn func(string) []string) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (objects.Object, error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - res := fn(s1) - - arr := &objects.Array{} - for _, osArg := range res { - arr.Value = append(arr.Value, &objects.String{Value: osArg}) - } - - return arr, nil - }, - } -} - -// FuncASRSE transform a function of 'func(string) (string, error)' signature into a user function object. -// User function will return 'true' if underlying native function returns nil. -func FuncASRSE(fn func(string) (string, error)) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (objects.Object, error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - res, err := fn(s1) - if err != nil { - return wrapError(err), nil - } - - return &objects.String{Value: res}, nil - }, - } -} - -// FuncASRE transform a function of 'func(string) error' signature into a user function object. -// User function will return 'true' if underlying native function returns nil. -func FuncASRE(fn func(string) error) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (objects.Object, error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return wrapError(fn(s1)), nil - }, - } -} - -// FuncASSRE transform a function of 'func(string, string) error' signature into a user function object. -// User function will return 'true' if underlying native function returns nil. -func FuncASSRE(fn func(string, string) error) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (objects.Object, error) { - if len(args) != 2 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - s2, ok := objects.ToString(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return wrapError(fn(s1, s2)), nil - }, - } -} - -// FuncASSRSs transform a function of 'func(string, string) []string' signature into a user function object. -func FuncASSRSs(fn func(string, string) []string) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (objects.Object, error) { - if len(args) != 2 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - s2, ok := objects.ToString(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - arr := &objects.Array{} - for _, res := range fn(s1, s2) { - arr.Value = append(arr.Value, &objects.String{Value: res}) - } - - return arr, nil - }, - } -} - -// FuncASSIRSs transform a function of 'func(string, string, int) []string' signature into a user function object. -func FuncASSIRSs(fn func(string, string, int) []string) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (objects.Object, error) { - if len(args) != 3 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - s2, ok := objects.ToString(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - i3, ok := objects.ToInt(args[2]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - arr := &objects.Array{} - for _, res := range fn(s1, s2, i3) { - arr.Value = append(arr.Value, &objects.String{Value: res}) - } - - return arr, nil - }, - } -} - -// FuncASSRI transform a function of 'func(string, string) int' signature into a user function object. -func FuncASSRI(fn func(string, string) int) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (objects.Object, error) { - if len(args) != 2 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - s2, ok := objects.ToString(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return &objects.Int{Value: int64(fn(s1, s2))}, nil - }, - } -} - -// FuncASSRS transform a function of 'func(string, string) string' signature into a user function object. -func FuncASSRS(fn func(string, string) string) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (objects.Object, error) { - if len(args) != 2 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - s2, ok := objects.ToString(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return &objects.String{Value: fn(s1, s2)}, nil - }, - } -} - -// FuncASSRB transform a function of 'func(string, string) bool' signature into a user function object. -func FuncASSRB(fn func(string, string) bool) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (objects.Object, error) { - if len(args) != 2 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - s2, ok := objects.ToString(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - if fn(s1, s2) { - return objects.TrueValue, nil - } - - return objects.FalseValue, nil - }, - } -} - -// FuncASsSRS transform a function of 'func([]string, string) string' signature into a user function object. -func FuncASsSRS(fn func([]string, string) string) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (objects.Object, error) { - if len(args) != 2 { - return nil, objects.ErrWrongNumArguments - } - - var ss1 []string - arr, ok := args[0].(*objects.Array) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - for _, a := range arr.Value { - as, ok := objects.ToString(a) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - ss1 = append(ss1, as) - } - - s2, ok := objects.ToString(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return &objects.String{Value: fn(ss1, s2)}, nil - }, - } -} - -// FuncASI64RE transform a function of 'func(string, int64) error' signature -// into a user function object. -func FuncASI64RE(fn func(string, int64) error) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 2 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - i2, ok := objects.ToInt64(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return wrapError(fn(s1, i2)), nil - }, - } -} - -// FuncAIIRE transform a function of 'func(int, int) error' signature -// into a user function object. -func FuncAIIRE(fn func(int, int) error) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 2 { - return nil, objects.ErrWrongNumArguments - } - - i1, ok := objects.ToInt(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - i2, ok := objects.ToInt(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return wrapError(fn(i1, i2)), nil - }, - } -} - -// FuncASIRS transform a function of 'func(string, int) string' signature -// into a user function object. -func FuncASIRS(fn func(string, int) string) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 2 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - i2, ok := objects.ToInt(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return &objects.String{Value: fn(s1, i2)}, nil - }, - } -} - -// FuncASIIRE transform a function of 'func(string, int, int) error' signature -// into a user function object. -func FuncASIIRE(fn func(string, int, int) error) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 3 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - i2, ok := objects.ToInt(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - i3, ok := objects.ToInt(args[2]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return wrapError(fn(s1, i2, i3)), nil - }, - } -} - -// FuncAYRIE transform a function of 'func([]byte) (int, error)' signature -// into a user function object. -func FuncAYRIE(fn func([]byte) (int, error)) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - y1, ok := objects.ToByteSlice(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - res, err := fn(y1) - if err != nil { - return wrapError(err), nil - } - - return &objects.Int{Value: int64(res)}, nil - }, - } -} - -// FuncASRIE transform a function of 'func(string) (int, error)' signature -// into a user function object. -func FuncASRIE(fn func(string) (int, error)) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - res, err := fn(s1) - if err != nil { - return wrapError(err), nil - } - - return &objects.Int{Value: int64(res)}, nil - }, - } -} - -// FuncAIRSsE transform a function of 'func(int) ([]string, error)' signature -// into a user function object. -func FuncAIRSsE(fn func(int) ([]string, error)) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - i1, ok := objects.ToInt(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - res, err := fn(i1) - if err != nil { - return wrapError(err), nil - } - - arr := &objects.Array{} - for _, r := range res { - arr.Value = append(arr.Value, &objects.String{Value: r}) - } - - return arr, nil - }, - } -} - -// FuncAIRS transform a function of 'func(int) string' signature -// into a user function object. -func FuncAIRS(fn func(int) string) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - i1, ok := objects.ToInt(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return &objects.String{Value: fn(i1)}, nil - }, - } -} diff --git a/compiler/stdlib/math.go b/compiler/stdlib/math.go deleted file mode 100644 index 5c519d7..0000000 --- a/compiler/stdlib/math.go +++ /dev/null @@ -1,74 +0,0 @@ -package stdlib - -import ( - "math" - - "github.com/d5/tengo/objects" -) - -var mathModule = map[string]objects.Object{ - "e": &objects.Float{Value: math.E}, - "pi": &objects.Float{Value: math.Pi}, - "phi": &objects.Float{Value: math.Phi}, - "sqrt2": &objects.Float{Value: math.Sqrt2}, - "sqrtE": &objects.Float{Value: math.SqrtE}, - "sqrtPi": &objects.Float{Value: math.SqrtPi}, - "sqrtPhi": &objects.Float{Value: math.SqrtPhi}, - "ln2": &objects.Float{Value: math.Ln2}, - "log2E": &objects.Float{Value: math.Log2E}, - "ln10": &objects.Float{Value: math.Ln10}, - "log10E": &objects.Float{Value: math.Log10E}, - "abs": FuncAFRF(math.Abs), - "acos": FuncAFRF(math.Acos), - "acosh": FuncAFRF(math.Acosh), - "asin": FuncAFRF(math.Asin), - "asinh": FuncAFRF(math.Asinh), - "atan": FuncAFRF(math.Atan), - "atan2": FuncAFFRF(math.Atan2), - "atanh": FuncAFRF(math.Atanh), - "cbrt": FuncAFRF(math.Cbrt), - "ceil": FuncAFRF(math.Ceil), - "copysign": FuncAFFRF(math.Copysign), - "cos": FuncAFRF(math.Cos), - "cosh": FuncAFRF(math.Cosh), - "dim": FuncAFFRF(math.Dim), - "erf": FuncAFRF(math.Erf), - "erfc": FuncAFRF(math.Erfc), - "exp": FuncAFRF(math.Exp), - "exp2": FuncAFRF(math.Exp2), - "expm1": FuncAFRF(math.Expm1), - "floor": FuncAFRF(math.Floor), - "gamma": FuncAFRF(math.Gamma), - "hypot": FuncAFFRF(math.Hypot), - "ilogb": FuncAFRI(math.Ilogb), - "inf": FuncAIRF(math.Inf), - "is_inf": FuncAFIRB(math.IsInf), - "is_nan": FuncAFRB(math.IsNaN), - "j0": FuncAFRF(math.J0), - "j1": FuncAFRF(math.J1), - "jn": FuncAIFRF(math.Jn), - "ldexp": FuncAFIRF(math.Ldexp), - "log": FuncAFRF(math.Log), - "log10": FuncAFRF(math.Log10), - "log1p": FuncAFRF(math.Log1p), - "log2": FuncAFRF(math.Log2), - "logb": FuncAFRF(math.Logb), - "max": FuncAFFRF(math.Max), - "min": FuncAFFRF(math.Min), - "mod": FuncAFFRF(math.Mod), - "nan": FuncARF(math.NaN), - "nextafter": FuncAFFRF(math.Nextafter), - "pow": FuncAFFRF(math.Pow), - "pow10": FuncAIRF(math.Pow10), - "remainder": FuncAFFRF(math.Remainder), - "signbit": FuncAFRB(math.Signbit), - "sin": FuncAFRF(math.Sin), - "sinh": FuncAFRF(math.Sinh), - "sqrt": FuncAFRF(math.Sqrt), - "tan": FuncAFRF(math.Tan), - "tanh": FuncAFRF(math.Tanh), - "trunc": FuncAFRF(math.Trunc), - "y0": FuncAFRF(math.Y0), - "y1": FuncAFRF(math.Y1), - "yn": FuncAIFRF(math.Yn), -} diff --git a/compiler/stdlib/os.go b/compiler/stdlib/os.go deleted file mode 100644 index d2c44b7..0000000 --- a/compiler/stdlib/os.go +++ /dev/null @@ -1,346 +0,0 @@ -package stdlib - -import ( - "io" - "io/ioutil" - "os" - "os/exec" - - "github.com/d5/tengo/objects" -) - -var osModule = map[string]objects.Object{ - "o_rdonly": &objects.Int{Value: int64(os.O_RDONLY)}, - "o_wronly": &objects.Int{Value: int64(os.O_WRONLY)}, - "o_rdwr": &objects.Int{Value: int64(os.O_RDWR)}, - "o_append": &objects.Int{Value: int64(os.O_APPEND)}, - "o_create": &objects.Int{Value: int64(os.O_CREATE)}, - "o_excl": &objects.Int{Value: int64(os.O_EXCL)}, - "o_sync": &objects.Int{Value: int64(os.O_SYNC)}, - "o_trunc": &objects.Int{Value: int64(os.O_TRUNC)}, - "mode_dir": &objects.Int{Value: int64(os.ModeDir)}, - "mode_append": &objects.Int{Value: int64(os.ModeAppend)}, - "mode_exclusive": &objects.Int{Value: int64(os.ModeExclusive)}, - "mode_temporary": &objects.Int{Value: int64(os.ModeTemporary)}, - "mode_symlink": &objects.Int{Value: int64(os.ModeSymlink)}, - "mode_device": &objects.Int{Value: int64(os.ModeDevice)}, - "mode_named_pipe": &objects.Int{Value: int64(os.ModeNamedPipe)}, - "mode_socket": &objects.Int{Value: int64(os.ModeSocket)}, - "mode_setuid": &objects.Int{Value: int64(os.ModeSetuid)}, - "mode_setgui": &objects.Int{Value: int64(os.ModeSetgid)}, - "mode_char_device": &objects.Int{Value: int64(os.ModeCharDevice)}, - "mode_sticky": &objects.Int{Value: int64(os.ModeSticky)}, - "mode_type": &objects.Int{Value: int64(os.ModeType)}, - "mode_perm": &objects.Int{Value: int64(os.ModePerm)}, - "path_separator": &objects.Char{Value: os.PathSeparator}, - "path_list_separator": &objects.Char{Value: os.PathListSeparator}, - "dev_null": &objects.String{Value: os.DevNull}, - "seek_set": &objects.Int{Value: int64(io.SeekStart)}, - "seek_cur": &objects.Int{Value: int64(io.SeekCurrent)}, - "seek_end": &objects.Int{Value: int64(io.SeekEnd)}, - "args": &objects.UserFunction{Value: osArgs}, // args() => array(string) - "chdir": FuncASRE(os.Chdir), // chdir(dir string) => error - "chmod": osFuncASFmRE(os.Chmod), // chmod(name string, mode int) => error - "chown": FuncASIIRE(os.Chown), // chown(name string, uid int, gid int) => error - "clearenv": FuncAR(os.Clearenv), // clearenv() - "environ": FuncARSs(os.Environ), // environ() => array(string) - "exit": FuncAIR(os.Exit), // exit(code int) - "expand_env": FuncASRS(os.ExpandEnv), // expand_env(s string) => string - "getegid": FuncARI(os.Getegid), // getegid() => int - "getenv": FuncASRS(os.Getenv), // getenv(s string) => string - "geteuid": FuncARI(os.Geteuid), // geteuid() => int - "getgid": FuncARI(os.Getgid), // getgid() => int - "getgroups": FuncARIsE(os.Getgroups), // getgroups() => array(string)/error - "getpagesize": FuncARI(os.Getpagesize), // getpagesize() => int - "getpid": FuncARI(os.Getpid), // getpid() => int - "getppid": FuncARI(os.Getppid), // getppid() => int - "getuid": FuncARI(os.Getuid), // getuid() => int - "getwd": FuncARSE(os.Getwd), // getwd() => string/error - "hostname": FuncARSE(os.Hostname), // hostname() => string/error - "lchown": FuncASIIRE(os.Lchown), // lchown(name string, uid int, gid int) => error - "link": FuncASSRE(os.Link), // link(oldname string, newname string) => error - "lookup_env": &objects.UserFunction{Value: osLookupEnv}, // lookup_env(key string) => string/false - "mkdir": osFuncASFmRE(os.Mkdir), // mkdir(name string, perm int) => error - "mkdir_all": osFuncASFmRE(os.MkdirAll), // mkdir_all(name string, perm int) => error - "readlink": FuncASRSE(os.Readlink), // readlink(name string) => string/error - "remove": FuncASRE(os.Remove), // remove(name string) => error - "remove_all": FuncASRE(os.RemoveAll), // remove_all(name string) => error - "rename": FuncASSRE(os.Rename), // rename(oldpath string, newpath string) => error - "setenv": FuncASSRE(os.Setenv), // setenv(key string, value string) => error - "symlink": FuncASSRE(os.Symlink), // symlink(oldname string newname string) => error - "temp_dir": FuncARS(os.TempDir), // temp_dir() => string - "truncate": FuncASI64RE(os.Truncate), // truncate(name string, size int) => error - "unsetenv": FuncASRE(os.Unsetenv), // unsetenv(key string) => error - "create": &objects.UserFunction{Value: osCreate}, // create(name string) => imap(file)/error - "open": &objects.UserFunction{Value: osOpen}, // open(name string) => imap(file)/error - "open_file": &objects.UserFunction{Value: osOpenFile}, // open_file(name string, flag int, perm int) => imap(file)/error - "find_process": &objects.UserFunction{Value: osFindProcess}, // find_process(pid int) => imap(process)/error - "start_process": &objects.UserFunction{Value: osStartProcess}, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error - "exec_look_path": FuncASRSE(exec.LookPath), // exec_look_path(file) => string/error - "exec": &objects.UserFunction{Value: osExec}, // exec(name, args...) => command - "stat": &objects.UserFunction{Value: osStat}, // stat(name) => imap(fileinfo)/error - "read_file": &objects.UserFunction{Value: osReadFile}, // readfile(name) => array(byte)/error -} - -func osReadFile(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - fname, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - bytes, err := ioutil.ReadFile(fname) - if err != nil { - return wrapError(err), nil - } - - return &objects.Bytes{Value: bytes}, nil -} - -func osStat(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - fname, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - stat, err := os.Stat(fname) - if err != nil { - return wrapError(err), nil - } - - fstat := &objects.ImmutableMap{ - Value: map[string]objects.Object{ - "name": &objects.String{Value: stat.Name()}, - "mtime": &objects.Time{Value: stat.ModTime()}, - "size": &objects.Int{Value: stat.Size()}, - "mode": &objects.Int{Value: int64(stat.Mode())}, - }, - } - - if stat.IsDir() { - fstat.Value["directory"] = objects.TrueValue - } else { - fstat.Value["directory"] = objects.FalseValue - } - - return fstat, nil -} - -func osCreate(args ...objects.Object) (objects.Object, error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - res, err := os.Create(s1) - if err != nil { - return wrapError(err), nil - } - - return makeOSFile(res), nil -} - -func osOpen(args ...objects.Object) (objects.Object, error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - res, err := os.Open(s1) - if err != nil { - return wrapError(err), nil - } - - return makeOSFile(res), nil -} - -func osOpenFile(args ...objects.Object) (objects.Object, error) { - if len(args) != 3 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - i2, ok := objects.ToInt(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - i3, ok := objects.ToInt(args[2]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - res, err := os.OpenFile(s1, i2, os.FileMode(i3)) - if err != nil { - return wrapError(err), nil - } - - return makeOSFile(res), nil -} - -func osArgs(args ...objects.Object) (objects.Object, error) { - if len(args) != 0 { - return nil, objects.ErrWrongNumArguments - } - - arr := &objects.Array{} - for _, osArg := range os.Args { - arr.Value = append(arr.Value, &objects.String{Value: osArg}) - } - - return arr, nil -} - -func osFuncASFmRE(fn func(string, os.FileMode) error) *objects.UserFunction { - return &objects.UserFunction{ - Value: func(args ...objects.Object) (objects.Object, error) { - if len(args) != 2 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - i2, ok := objects.ToInt64(args[1]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - return wrapError(fn(s1, os.FileMode(i2))), nil - }, - } -} - -func osLookupEnv(args ...objects.Object) (objects.Object, error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - s1, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - res, ok := os.LookupEnv(s1) - if !ok { - return objects.FalseValue, nil - } - - return &objects.String{Value: res}, nil -} - -func osExec(args ...objects.Object) (objects.Object, error) { - if len(args) == 0 { - return nil, objects.ErrWrongNumArguments - } - - name, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - var execArgs []string - for _, arg := range args[1:] { - execArg, ok := objects.ToString(arg) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - execArgs = append(execArgs, execArg) - } - - return makeOSExecCommand(exec.Command(name, execArgs...)), nil -} - -func osFindProcess(args ...objects.Object) (objects.Object, error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - i1, ok := objects.ToInt(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - proc, err := os.FindProcess(i1) - if err != nil { - return wrapError(err), nil - } - - return makeOSProcess(proc), nil -} - -func osStartProcess(args ...objects.Object) (objects.Object, error) { - if len(args) != 4 { - return nil, objects.ErrWrongNumArguments - } - - name, ok := objects.ToString(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - argv, err := stringArray(args[1]) - if err != nil { - return nil, err - } - - dir, ok := objects.ToString(args[2]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - env, err := stringArray(args[3]) - if err != nil { - return nil, err - } - - proc, err := os.StartProcess(name, argv, &os.ProcAttr{ - Dir: dir, - Env: env, - }) - if err != nil { - return wrapError(err), nil - } - - return makeOSProcess(proc), nil -} - -func stringArray(o objects.Object) ([]string, error) { - arr, ok := o.(*objects.Array) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - var sarr []string - for _, elem := range arr.Value { - str, ok := elem.(*objects.String) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - sarr = append(sarr, str.Value) - } - - return sarr, nil -} diff --git a/compiler/stdlib/rand.go b/compiler/stdlib/rand.go deleted file mode 100644 index 4ce70ff..0000000 --- a/compiler/stdlib/rand.go +++ /dev/null @@ -1,87 +0,0 @@ -package stdlib - -import ( - "math/rand" - - "github.com/d5/tengo/objects" -) - -var randModule = map[string]objects.Object{ - "int": FuncARI64(rand.Int63), - "float": FuncARF(rand.Float64), - "intn": FuncAI64RI64(rand.Int63n), - "exp_float": FuncARF(rand.ExpFloat64), - "norm_float": FuncARF(rand.NormFloat64), - "perm": FuncAIRIs(rand.Perm), - "seed": FuncAI64R(rand.Seed), - "read": &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - y1, ok := args[0].(*objects.Bytes) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - res, err := rand.Read(y1.Value) - if err != nil { - ret = wrapError(err) - return - } - - return &objects.Int{Value: int64(res)}, nil - }, - }, - "rand": &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - i1, ok := objects.ToInt64(args[0]) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - src := rand.NewSource(i1) - - return randRand(rand.New(src)), nil - }, - }, -} - -func randRand(r *rand.Rand) *objects.ImmutableMap { - return &objects.ImmutableMap{ - Value: map[string]objects.Object{ - "int": FuncARI64(r.Int63), - "float": FuncARF(r.Float64), - "intn": FuncAI64RI64(r.Int63n), - "exp_float": FuncARF(r.ExpFloat64), - "norm_float": FuncARF(r.NormFloat64), - "perm": FuncAIRIs(r.Perm), - "seed": FuncAI64R(r.Seed), - "read": &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - return nil, objects.ErrWrongNumArguments - } - - y1, ok := args[0].(*objects.Bytes) - if !ok { - return nil, objects.ErrInvalidTypeConversion - } - - res, err := r.Read(y1.Value) - if err != nil { - ret = wrapError(err) - return - } - - return &objects.Int{Value: int64(res)}, nil - }, - }, - }, - } -} diff --git a/compiler/stdlib/stdlib.go b/compiler/stdlib/stdlib.go deleted file mode 100644 index 7c3068b..0000000 --- a/compiler/stdlib/stdlib.go +++ /dev/null @@ -1,12 +0,0 @@ -package stdlib - -import "github.com/d5/tengo/objects" - -// Modules contain the standard modules. -var Modules = map[string]*objects.ImmutableMap{ - "math": {Value: mathModule}, - "os": {Value: osModule}, - "text": {Value: textModule}, - "times": {Value: timesModule}, - "rand": {Value: randModule}, -} diff --git a/compiler/stdlib/text.go b/compiler/stdlib/text.go deleted file mode 100644 index b68b43b..0000000 --- a/compiler/stdlib/text.go +++ /dev/null @@ -1,469 +0,0 @@ -package stdlib - -import ( - "regexp" - "strconv" - "strings" - - "github.com/d5/tengo/objects" -) - -var textModule = map[string]objects.Object{ - "re_match": &objects.UserFunction{Value: textREMatch}, // re_match(pattern, text) => bool/error - "re_find": &objects.UserFunction{Value: textREFind}, // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined - "re_replace": &objects.UserFunction{Value: textREReplace}, // re_replace(pattern, text, repl) => string/error - "re_split": &objects.UserFunction{Value: textRESplit}, // re_split(pattern, text, count) => [string]/error - "re_compile": &objects.UserFunction{Value: textRECompile}, // re_compile(pattern) => Regexp/error - "compare": FuncASSRI(strings.Compare), // compare(a, b) => int - "contains": FuncASSRB(strings.Contains), // contains(s, substr) => bool - "contains_any": FuncASSRB(strings.ContainsAny), // contains_any(s, chars) => bool - "count": FuncASSRI(strings.Count), // count(s, substr) => int - "equal_fold": FuncASSRB(strings.EqualFold), // "equal_fold(s, t) => bool - "fields": FuncASRSs(strings.Fields), // fields(s) => [string] - "has_prefix": FuncASSRB(strings.HasPrefix), // has_prefix(s, prefix) => bool - "has_suffix": FuncASSRB(strings.HasSuffix), // has_suffix(s, suffix) => bool - "index": FuncASSRI(strings.Index), // index(s, substr) => int - "index_any": FuncASSRI(strings.IndexAny), // index_any(s, chars) => int - "join": FuncASsSRS(strings.Join), // join(arr, sep) => string - "last_index": FuncASSRI(strings.LastIndex), // last_index(s, substr) => int - "last_index_any": FuncASSRI(strings.LastIndexAny), // last_index_any(s, chars) => int - "repeat": FuncASIRS(strings.Repeat), // repeat(s, count) => string - "replace": &objects.UserFunction{Value: textReplace}, // replace(s, old, new, n) => string - "split": FuncASSRSs(strings.Split), // split(s, sep) => [string] - "split_after": FuncASSRSs(strings.SplitAfter), // split_after(s, sep) => [string] - "split_after_n": FuncASSIRSs(strings.SplitAfterN), // split_after_n(s, sep, n) => [string] - "split_n": FuncASSIRSs(strings.SplitN), // split_n(s, sep, n) => [string] - "title": FuncASRS(strings.Title), // title(s) => string - "to_lower": FuncASRS(strings.ToLower), // to_lower(s) => string - "to_title": FuncASRS(strings.ToTitle), // to_title(s) => string - "to_upper": FuncASRS(strings.ToUpper), // to_upper(s) => string - "trim_left": FuncASSRS(strings.TrimLeft), // trim_left(s, cutset) => string - "trim_prefix": FuncASSRS(strings.TrimPrefix), // trim_prefix(s, prefix) => string - "trim_right": FuncASSRS(strings.TrimRight), // trim_right(s, cutset) => string - "trim_space": FuncASRS(strings.TrimSpace), // trim_space(s) => string - "trim_suffix": FuncASSRS(strings.TrimSuffix), // trim_suffix(s, suffix) => string - "atoi": FuncASRIE(strconv.Atoi), // atoi(str) => int/error - "format_bool": &objects.UserFunction{Value: textFormatBool}, // format_bool(b) => string - "format_float": &objects.UserFunction{Value: textFormatFloat}, // format_float(f, fmt, prec, bits) => string - "format_int": &objects.UserFunction{Value: textFormatInt}, // format_int(i, base) => string - "itoa": FuncAIRS(strconv.Itoa), // itoa(i) => string - "parse_bool": &objects.UserFunction{Value: textParseBool}, // parse_bool(str) => bool/error - "parse_float": &objects.UserFunction{Value: textParseFloat}, // parse_float(str, bits) => float/error - "parse_int": &objects.UserFunction{Value: textParseInt}, // parse_int(str, base, bits) => int/error - "quote": FuncASRS(strconv.Quote), // quote(str) => string - "unquote": FuncASRSE(strconv.Unquote), // unquote(str) => string/error -} - -func textREMatch(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 2 { - err = objects.ErrWrongNumArguments - return - } - - s1, ok := objects.ToString(args[0]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - s2, ok := objects.ToString(args[1]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - matched, err := regexp.MatchString(s1, s2) - if err != nil { - ret = wrapError(err) - return - } - - if matched { - ret = objects.TrueValue - } else { - ret = objects.FalseValue - } - - return -} - -func textREFind(args ...objects.Object) (ret objects.Object, err error) { - numArgs := len(args) - if numArgs != 2 && numArgs != 3 { - err = objects.ErrWrongNumArguments - return - } - - s1, ok := objects.ToString(args[0]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - re, err := regexp.Compile(s1) - if err != nil { - ret = wrapError(err) - return - } - - s2, ok := objects.ToString(args[1]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - if numArgs < 3 { - m := re.FindStringSubmatchIndex(s2) - if m == nil { - ret = objects.UndefinedValue - return - } - - arr := &objects.Array{} - for i := 0; i < len(m); i += 2 { - arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ - "text": &objects.String{Value: s2[m[i]:m[i+1]]}, - "begin": &objects.Int{Value: int64(m[i])}, - "end": &objects.Int{Value: int64(m[i+1])}, - }}) - } - - ret = &objects.Array{Value: []objects.Object{arr}} - - return - } - - i3, ok := objects.ToInt(args[2]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - m := re.FindAllStringSubmatchIndex(s2, i3) - if m == nil { - ret = objects.UndefinedValue - return - } - - arr := &objects.Array{} - for _, m := range m { - subMatch := &objects.Array{} - for i := 0; i < len(m); i += 2 { - subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ - "text": &objects.String{Value: s2[m[i]:m[i+1]]}, - "begin": &objects.Int{Value: int64(m[i])}, - "end": &objects.Int{Value: int64(m[i+1])}, - }}) - } - - arr.Value = append(arr.Value, subMatch) - } - - ret = arr - - return -} - -func textREReplace(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 3 { - err = objects.ErrWrongNumArguments - return - } - - s1, ok := objects.ToString(args[0]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - s2, ok := objects.ToString(args[1]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - s3, ok := objects.ToString(args[2]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - re, err := regexp.Compile(s1) - if err != nil { - ret = wrapError(err) - } else { - ret = &objects.String{Value: re.ReplaceAllString(s2, s3)} - } - - return -} - -func textRESplit(args ...objects.Object) (ret objects.Object, err error) { - numArgs := len(args) - if numArgs != 2 && numArgs != 3 { - err = objects.ErrWrongNumArguments - return - } - - s1, ok := objects.ToString(args[0]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - s2, ok := objects.ToString(args[1]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - var i3 = -1 - if numArgs > 2 { - i3, ok = objects.ToInt(args[2]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - } - - re, err := regexp.Compile(s1) - if err != nil { - ret = wrapError(err) - return - } - - arr := &objects.Array{} - for _, s := range re.Split(s2, i3) { - arr.Value = append(arr.Value, &objects.String{Value: s}) - } - - ret = arr - - return -} - -func textRECompile(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - err = objects.ErrWrongNumArguments - return - } - - s1, ok := objects.ToString(args[0]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - re, err := regexp.Compile(s1) - if err != nil { - ret = wrapError(err) - } else { - ret = makeTextRegexp(re) - } - - return -} - -func textReplace(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 4 { - err = objects.ErrWrongNumArguments - return - } - - s1, ok := objects.ToString(args[0]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - s2, ok := objects.ToString(args[1]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - s3, ok := objects.ToString(args[2]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - i4, ok := objects.ToInt(args[3]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - ret = &objects.String{Value: strings.Replace(s1, s2, s3, i4)} - - return -} - -func textFormatBool(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - err = objects.ErrWrongNumArguments - return - } - - b1, ok := args[0].(*objects.Bool) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - if b1 == objects.TrueValue { - ret = &objects.String{Value: "true"} - } else { - ret = &objects.String{Value: "false"} - } - - return -} - -func textFormatFloat(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 4 { - err = objects.ErrWrongNumArguments - return - } - - f1, ok := args[0].(*objects.Float) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - s2, ok := objects.ToString(args[1]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - i3, ok := objects.ToInt(args[2]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - i4, ok := objects.ToInt(args[3]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - ret = &objects.String{Value: strconv.FormatFloat(f1.Value, s2[0], i3, i4)} - - return -} - -func textFormatInt(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 2 { - err = objects.ErrWrongNumArguments - return - } - - i1, ok := args[0].(*objects.Int) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - i2, ok := objects.ToInt(args[1]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - ret = &objects.String{Value: strconv.FormatInt(i1.Value, i2)} - - return -} - -func textParseBool(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 1 { - err = objects.ErrWrongNumArguments - return - } - - s1, ok := args[0].(*objects.String) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - parsed, err := strconv.ParseBool(s1.Value) - if err != nil { - ret = wrapError(err) - return - } - - if parsed { - ret = objects.TrueValue - } else { - ret = objects.FalseValue - } - - return -} - -func textParseFloat(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 2 { - err = objects.ErrWrongNumArguments - return - } - - s1, ok := args[0].(*objects.String) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - i2, ok := objects.ToInt(args[1]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - parsed, err := strconv.ParseFloat(s1.Value, i2) - if err != nil { - ret = wrapError(err) - return - } - - ret = &objects.Float{Value: parsed} - - return -} - -func textParseInt(args ...objects.Object) (ret objects.Object, err error) { - if len(args) != 3 { - err = objects.ErrWrongNumArguments - return - } - - s1, ok := args[0].(*objects.String) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - i2, ok := objects.ToInt(args[1]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - i3, ok := objects.ToInt(args[2]) - if !ok { - err = objects.ErrInvalidTypeConversion - return - } - - parsed, err := strconv.ParseInt(s1.Value, i2, i3) - if err != nil { - ret = wrapError(err) - return - } - - ret = &objects.Int{Value: parsed} - - return -} diff --git a/objects/array.go b/objects/array.go index 7f3cd0a..1e917c5 100644 --- a/objects/array.go +++ b/objects/array.go @@ -107,7 +107,7 @@ func (o *Array) IndexGet(index Object) (res Object, err error) { func (o *Array) IndexSet(index, value Object) (err error) { intIdx, ok := ToInt(index) if !ok { - err = ErrInvalidTypeConversion + err = ErrInvalidIndexType return } diff --git a/objects/builtin_append.go b/objects/builtin_append.go index c4f8c27..9fb14b8 100644 --- a/objects/builtin_append.go +++ b/objects/builtin_append.go @@ -1,10 +1,6 @@ package objects -import ( - "fmt" -) - -// append(src, items...) +// append(arr, items...) func builtinAppend(args ...Object) (Object, error) { if len(args) < 2 { return nil, ErrWrongNumArguments @@ -13,7 +9,13 @@ func builtinAppend(args ...Object) (Object, error) { switch arg := args[0].(type) { case *Array: return &Array{Value: append(arg.Value, args[1:]...)}, nil + case *ImmutableArray: + return &Array{Value: append(arg.Value, args[1:]...)}, nil default: - return nil, fmt.Errorf("unsupported type for 'append' function: %s", arg.TypeName()) + return nil, ErrInvalidArgumentType{ + Name: "first", + Expected: "array", + Found: arg.TypeName(), + } } } diff --git a/objects/builtin_function.go b/objects/builtin_function.go index 56fed40..1d02161 100644 --- a/objects/builtin_function.go +++ b/objects/builtin_function.go @@ -6,12 +6,13 @@ import ( // BuiltinFunction represents a builtin function. type BuiltinFunction struct { + Name string Value CallableFunc } // TypeName returns the name of the type. func (o *BuiltinFunction) TypeName() string { - return "builtin-function" + return "builtin-function:" + o.Name } func (o *BuiltinFunction) String() string { diff --git a/objects/builtin_json.go b/objects/builtin_json.go index 60bc548..c0810f7 100644 --- a/objects/builtin_json.go +++ b/objects/builtin_json.go @@ -4,6 +4,7 @@ import ( "encoding/json" ) +// to_json(v object) => bytes func builtinToJSON(args ...Object) (Object, error) { if len(args) != 1 { return nil, ErrWrongNumArguments @@ -11,12 +12,13 @@ func builtinToJSON(args ...Object) (Object, error) { res, err := json.Marshal(objectToInterface(args[0])) if err != nil { - return nil, err + return &Error{Value: &String{Value: err.Error()}}, nil } return &Bytes{Value: res}, nil } +// from_json(data string/bytes) => object func builtinFromJSON(args ...Object) (Object, error) { if len(args) != 1 { return nil, ErrWrongNumArguments @@ -28,15 +30,19 @@ func builtinFromJSON(args ...Object) (Object, error) { case *Bytes: err := json.Unmarshal(o.Value, &target) if err != nil { - return nil, err + return &Error{Value: &String{Value: err.Error()}}, nil } case *String: err := json.Unmarshal([]byte(o.Value), &target) if err != nil { - return nil, err + return &Error{Value: &String{Value: err.Error()}}, nil } default: - return nil, ErrInvalidTypeConversion + return nil, ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes/string", + Found: args[0].TypeName(), + } } res, err := FromInterface(target) diff --git a/objects/builtin_len.go b/objects/builtin_len.go index f749363..39fbedd 100644 --- a/objects/builtin_len.go +++ b/objects/builtin_len.go @@ -1,9 +1,6 @@ package objects -import ( - "fmt" -) - +// len(obj object) => int func builtinLen(args ...Object) (Object, error) { if len(args) != 1 { return nil, ErrWrongNumArguments @@ -23,6 +20,10 @@ func builtinLen(args ...Object) (Object, error) { case *ImmutableMap: return &Int{Value: int64(len(arg.Value))}, nil default: - return nil, fmt.Errorf("unsupported type for 'len' function: %s", arg.TypeName()) + return nil, ErrInvalidArgumentType{ + Name: "first", + Expected: "array/string/bytes/map", + Found: arg.TypeName(), + } } } diff --git a/objects/builtin_print.go b/objects/builtin_print.go index 22be787..c5fe36d 100644 --- a/objects/builtin_print.go +++ b/objects/builtin_print.go @@ -26,7 +26,11 @@ func builtinPrintf(args ...Object) (Object, error) { format, ok := args[0].(*String) if !ok { - return nil, ErrInvalidTypeConversion + return nil, ErrInvalidArgumentType{ + Name: "format", + Expected: "string", + Found: args[0].TypeName(), + } } if numArgs == 1 { fmt.Print(format) @@ -52,7 +56,11 @@ func builtinSprintf(args ...Object) (Object, error) { format, ok := args[0].(*String) if !ok { - return nil, ErrInvalidTypeConversion + return nil, ErrInvalidArgumentType{ + Name: "format", + Expected: "string", + Found: args[0].TypeName(), + } } if numArgs == 1 { return format, nil // okay to return 'format' directly as String is immutable diff --git a/objects/compiled_function.go b/objects/compiled_function.go index a112d1d..d20f237 100644 --- a/objects/compiled_function.go +++ b/objects/compiled_function.go @@ -1,14 +1,16 @@ package objects import ( + "github.com/d5/tengo/compiler/source" "github.com/d5/tengo/compiler/token" ) // CompiledFunction represents a compiled function. type CompiledFunction struct { Instructions []byte - NumLocals int + NumLocals int // number of local variables (including function parameters) NumParameters int + SourceMap map[int]source.Pos } // TypeName returns the name of the type. diff --git a/objects/conversion.go b/objects/conversion.go index 5cdb8b9..3c17546 100644 --- a/objects/conversion.go +++ b/objects/conversion.go @@ -245,5 +245,5 @@ func FromInterface(v interface{}) (Object, error) { return v, nil } - return nil, fmt.Errorf("unsupported value type: %T", v) + return nil, fmt.Errorf("cannot convert to object: %T", v) } diff --git a/objects/errors.go b/objects/errors.go index 53e7d1c..e401231 100644 --- a/objects/errors.go +++ b/objects/errors.go @@ -1,24 +1,32 @@ package objects -import "errors" - -// ErrNotIndexable means the type is not indexable. -var ErrNotIndexable = errors.New("type is not indexable") - -// ErrNotIndexAssignable means the type is not index-assignable. -var ErrNotIndexAssignable = errors.New("type is not index-assignable") +import ( + "errors" + "fmt" +) // ErrIndexOutOfBounds is an error where a given index is out of the bounds. var ErrIndexOutOfBounds = errors.New("index out of bounds") -// ErrInvalidIndexType means the type is not supported as an index. +// ErrInvalidIndexType represents an invalid index type. var ErrInvalidIndexType = errors.New("invalid index type") +// ErrInvalidIndexValueType represents an invalid index value type. +var ErrInvalidIndexValueType = errors.New("invalid index value type") + // ErrInvalidOperator represents an error for invalid operator usage. var ErrInvalidOperator = errors.New("invalid operator") // ErrWrongNumArguments represents a wrong number of arguments error. var ErrWrongNumArguments = errors.New("wrong number of arguments") -// ErrInvalidTypeConversion represents an invalid type conversion error. -var ErrInvalidTypeConversion = errors.New("invalid type conversion") +// ErrInvalidArgumentType represents an invalid argument value type error. +type ErrInvalidArgumentType struct { + Name string + Expected string + Found string +} + +func (e ErrInvalidArgumentType) Error() string { + return fmt.Sprintf("invalid type for argument '%s': expected %s, found %s", e.Name, e.Expected, e.Found) +} diff --git a/objects/immutable_map.go b/objects/immutable_map.go index 38ae670..8f58701 100644 --- a/objects/immutable_map.go +++ b/objects/immutable_map.go @@ -51,7 +51,7 @@ func (o *ImmutableMap) IsFalsy() bool { func (o *ImmutableMap) IndexGet(index Object) (res Object, err error) { strIdx, ok := ToString(index) if !ok { - err = ErrInvalidTypeConversion + err = ErrInvalidIndexType return } diff --git a/objects/map.go b/objects/map.go index 44d6801..c42ffe9 100644 --- a/objects/map.go +++ b/objects/map.go @@ -94,7 +94,7 @@ func (o *Map) IndexGet(index Object) (res Object, err error) { func (o *Map) IndexSet(index, value Object) (err error) { strIdx, ok := ToString(index) if !ok { - err = ErrInvalidTypeConversion + err = ErrInvalidIndexType return } diff --git a/objects/object_test.go b/objects/object_test.go index 88ca669..ad854de 100644 --- a/objects/object_test.go +++ b/objects/object_test.go @@ -34,8 +34,10 @@ func TestObject_TypeName(t *testing.T) { assert.Equal(t, "break", o.TypeName()) o = &objects.Continue{} assert.Equal(t, "continue", o.TypeName()) - o = &objects.BuiltinFunction{} - assert.Equal(t, "builtin-function", o.TypeName()) + o = &objects.BuiltinFunction{Name: "fn"} + assert.Equal(t, "builtin-function:fn", o.TypeName()) + o = &objects.UserFunction{Name: "fn"} + assert.Equal(t, "user-function:fn", o.TypeName()) o = &objects.Closure{} assert.Equal(t, "closure", o.TypeName()) o = &objects.CompiledFunction{} diff --git a/objects/user_function.go b/objects/user_function.go index 74b818c..1d9bb4f 100644 --- a/objects/user_function.go +++ b/objects/user_function.go @@ -6,12 +6,13 @@ import ( // UserFunction represents a user function. type UserFunction struct { + Name string Value CallableFunc } // TypeName returns the name of the type. func (o *UserFunction) TypeName() string { - return "user-function" + return "user-function:" + o.Name } func (o *UserFunction) String() string { diff --git a/runtime/vm.go b/runtime/vm.go index f0b85d9..9d1e9db 100644 --- a/runtime/vm.go +++ b/runtime/vm.go @@ -1,13 +1,14 @@ package runtime import ( - "errors" "fmt" "sync/atomic" "github.com/d5/tengo/compiler" + "github.com/d5/tengo/compiler/source" "github.com/d5/tengo/compiler/token" "github.com/d5/tengo/objects" + "github.com/d5/tengo/stdlib" ) const ( @@ -30,42 +31,50 @@ var ( // VM is a virtual machine that executes the bytecode compiled by Compiler. type VM struct { - constants []objects.Object - stack []*objects.Object - sp int - globals []*objects.Object - frames []Frame - framesIndex int - curFrame *Frame - curInsts []byte - curIPLimit int - ip int - aborting int64 + constants []objects.Object + stack []*objects.Object + sp int + globals []*objects.Object + fileSet *source.FileSet + frames []Frame + framesIndex int + curFrame *Frame + curInsts []byte + curIPLimit int + ip int + aborting int64 + builtinModules map[string]*objects.Object } // NewVM creates a VM. -func NewVM(bytecode *compiler.Bytecode, globals []*objects.Object) *VM { +func NewVM(bytecode *compiler.Bytecode, globals []*objects.Object, builtinModules map[string]*objects.Object) *VM { if globals == nil { globals = make([]*objects.Object, GlobalsSize) } + if builtinModules == nil { + builtinModules = stdlib.Modules + } + frames := make([]Frame, MaxFrames) - frames[0].fn = &objects.CompiledFunction{Instructions: bytecode.Instructions} + frames[0].fn = bytecode.MainFunction frames[0].freeVars = nil frames[0].ip = -1 frames[0].basePointer = 0 return &VM{ - constants: bytecode.Constants, - stack: make([]*objects.Object, StackSize), - sp: 0, - globals: globals, - frames: frames, - framesIndex: 1, - curFrame: &(frames[0]), - curInsts: frames[0].fn.Instructions, - curIPLimit: len(frames[0].fn.Instructions) - 1, - ip: -1, + constants: bytecode.Constants, + stack: make([]*objects.Object, StackSize), + sp: 0, + globals: globals, + fileSet: bytecode.FileSet, + frames: frames, + framesIndex: 1, + curFrame: &(frames[0]), + curInsts: frames[0].fn.Instructions, + curIPLimit: len(frames[0].fn.Instructions) - 1, + ip: -1, + builtinModules: builtinModules, } } @@ -85,10 +94,11 @@ func (v *VM) Run() error { v.ip = -1 atomic.StoreInt64(&v.aborting, 0) +mainloop: for v.ip < v.curIPLimit && (atomic.LoadInt64(&v.aborting) == 0) { v.ip++ - switch compiler.Opcode(v.curInsts[v.ip]) { + switch v.curInsts[v.ip] { case compiler.OpConstant: cidx := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 v.ip += 2 @@ -115,7 +125,13 @@ func (v *VM) Run() error { res, err := (*left).BinaryOp(token.Add, *right) if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if err == objects.ErrInvalidOperator { + return fmt.Errorf("%s: invalid operation: %s + %s", + filePos, (*left).TypeName(), (*right).TypeName()) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } if v.sp >= StackSize { @@ -132,7 +148,13 @@ func (v *VM) Run() error { res, err := (*left).BinaryOp(token.Sub, *right) if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if err == objects.ErrInvalidOperator { + return fmt.Errorf("%s: invalid operation: %s - %s", + filePos, (*left).TypeName(), (*right).TypeName()) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } if v.sp >= StackSize { @@ -149,7 +171,13 @@ func (v *VM) Run() error { res, err := (*left).BinaryOp(token.Mul, *right) if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if err == objects.ErrInvalidOperator { + return fmt.Errorf("%s: invalid operation: %s * %s", + filePos, (*left).TypeName(), (*right).TypeName()) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } if v.sp >= StackSize { @@ -166,7 +194,13 @@ func (v *VM) Run() error { res, err := (*left).BinaryOp(token.Quo, *right) if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if err == objects.ErrInvalidOperator { + return fmt.Errorf("%s: invalid operation: %s / %s", + filePos, (*left).TypeName(), (*right).TypeName()) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } if v.sp >= StackSize { @@ -183,7 +217,13 @@ func (v *VM) Run() error { res, err := (*left).BinaryOp(token.Rem, *right) if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if err == objects.ErrInvalidOperator { + return fmt.Errorf("%s: invalid operation: %s %% %s", + filePos, (*left).TypeName(), (*right).TypeName()) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } if v.sp >= StackSize { @@ -200,7 +240,13 @@ func (v *VM) Run() error { res, err := (*left).BinaryOp(token.And, *right) if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if err == objects.ErrInvalidOperator { + return fmt.Errorf("%s: invalid operation: %s & %s", + filePos, (*left).TypeName(), (*right).TypeName()) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } if v.sp >= StackSize { @@ -217,7 +263,13 @@ func (v *VM) Run() error { res, err := (*left).BinaryOp(token.Or, *right) if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if err == objects.ErrInvalidOperator { + return fmt.Errorf("%s: invalid operation: %s | %s", + filePos, (*left).TypeName(), (*right).TypeName()) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } if v.sp >= StackSize { @@ -234,7 +286,13 @@ func (v *VM) Run() error { res, err := (*left).BinaryOp(token.Xor, *right) if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if err == objects.ErrInvalidOperator { + return fmt.Errorf("%s: invalid operation: %s ^ %s", + filePos, (*left).TypeName(), (*right).TypeName()) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } if v.sp >= StackSize { @@ -251,7 +309,13 @@ func (v *VM) Run() error { res, err := (*left).BinaryOp(token.AndNot, *right) if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if err == objects.ErrInvalidOperator { + return fmt.Errorf("%s: invalid operation: %s &^ %s", + filePos, (*left).TypeName(), (*right).TypeName()) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } if v.sp >= StackSize { @@ -268,7 +332,13 @@ func (v *VM) Run() error { res, err := (*left).BinaryOp(token.Shl, *right) if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if err == objects.ErrInvalidOperator { + return fmt.Errorf("%s: invalid operation: %s << %s", + filePos, (*left).TypeName(), (*right).TypeName()) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } if v.sp >= StackSize { @@ -285,7 +355,13 @@ func (v *VM) Run() error { res, err := (*left).BinaryOp(token.Shr, *right) if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if err == objects.ErrInvalidOperator { + return fmt.Errorf("%s: invalid operation: %s >> %s", + filePos, (*left).TypeName(), (*right).TypeName()) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } if v.sp >= StackSize { @@ -334,7 +410,13 @@ func (v *VM) Run() error { res, err := (*left).BinaryOp(token.Greater, *right) if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if err == objects.ErrInvalidOperator { + return fmt.Errorf("%s: invalid operation: %s > %s", + filePos, (*left).TypeName(), (*right).TypeName()) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } if v.sp >= StackSize { @@ -351,7 +433,13 @@ func (v *VM) Run() error { res, err := (*left).BinaryOp(token.GreaterEq, *right) if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + if err == objects.ErrInvalidOperator { + return fmt.Errorf("%s: invalid operation: %s >= %s", + filePos, (*left).TypeName(), (*right).TypeName()) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } if v.sp >= StackSize { @@ -410,7 +498,8 @@ func (v *VM) Run() error { v.stack[v.sp] = &res v.sp++ default: - return fmt.Errorf("invalid operation on %s", (*operand).TypeName()) + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + return fmt.Errorf("%s: invalid operation: ^%s", filePos, (*operand).TypeName()) } case compiler.OpMinus: @@ -437,7 +526,8 @@ func (v *VM) Run() error { v.stack[v.sp] = &res v.sp++ default: - return fmt.Errorf("invalid operation on %s", (*operand).TypeName()) + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + return fmt.Errorf("%s: invalid operation: -%s", filePos, (*operand).TypeName()) } case compiler.OpJumpFalsy: @@ -496,7 +586,8 @@ func (v *VM) Run() error { v.sp -= numSelectors + 1 if err := indexAssign(v.globals[globalIndex], val, selectors); err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-3]) + return fmt.Errorf("%s: %s", filePos, err.Error()) } case compiler.OpGetGlobal: @@ -586,7 +677,13 @@ func (v *VM) Run() error { case objects.Indexable: val, err := left.IndexGet(*index) if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + + if err == objects.ErrInvalidIndexType { + return fmt.Errorf("%s: invalid index type: %s", filePos, (*index).TypeName()) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } if val == nil { val = objects.UndefinedValue @@ -602,7 +699,8 @@ func (v *VM) Run() error { case *objects.Error: // err.value key, ok := (*index).(*objects.String) if !ok || key.Value != "value" { - return errors.New("invalid selector on error") + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + return fmt.Errorf("%s: invalid index on error", filePos) } if v.sp >= StackSize { @@ -613,7 +711,8 @@ func (v *VM) Run() error { v.sp++ default: - return objects.ErrNotIndexable + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + return fmt.Errorf("%s: not indexable: %s", filePos, left.TypeName()) } case compiler.OpSliceIndex: @@ -627,7 +726,8 @@ func (v *VM) Run() error { if low, ok := (*low).(*objects.Int); ok { lowIdx = low.Value } else { - return fmt.Errorf("non-integer slice index: %s", low.TypeName()) + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + return fmt.Errorf("%s: invalid slice index type: %s", filePos, low.TypeName()) } } @@ -640,11 +740,13 @@ func (v *VM) Run() error { } else if high, ok := (*high).(*objects.Int); ok { highIdx = high.Value } else { - return fmt.Errorf("non-integer slice index: %s", high.TypeName()) + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + return fmt.Errorf("%s: invalid slice index type: %s", filePos, high.TypeName()) } if lowIdx > highIdx { - return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx) + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + return fmt.Errorf("%s: invalid slice index: %d > %d", filePos, lowIdx, highIdx) } if lowIdx < 0 { @@ -675,11 +777,13 @@ func (v *VM) Run() error { } else if high, ok := (*high).(*objects.Int); ok { highIdx = high.Value } else { - return fmt.Errorf("non-integer slice index: %s", high.TypeName()) + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + return fmt.Errorf("%s: invalid slice index type: %s", filePos, high.TypeName()) } if lowIdx > highIdx { - return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx) + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + return fmt.Errorf("%s: invalid slice index: %d > %d", filePos, lowIdx, highIdx) } if lowIdx < 0 { @@ -711,11 +815,13 @@ func (v *VM) Run() error { } else if high, ok := (*high).(*objects.Int); ok { highIdx = high.Value } else { - return fmt.Errorf("non-integer slice index: %s", high.TypeName()) + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + return fmt.Errorf("%s: invalid slice index type: %s", filePos, high.TypeName()) } if lowIdx > highIdx { - return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx) + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + return fmt.Errorf("%s: invalid slice index: %d > %d", filePos, lowIdx, highIdx) } if lowIdx < 0 { @@ -747,11 +853,13 @@ func (v *VM) Run() error { } else if high, ok := (*high).(*objects.Int); ok { highIdx = high.Value } else { - return fmt.Errorf("non-integer slice index: %s", high.TypeName()) + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + return fmt.Errorf("%s: invalid slice index type: %s", filePos, high.TypeName()) } if lowIdx > highIdx { - return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx) + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + return fmt.Errorf("%s: invalid slice index: %d > %d", filePos, lowIdx, highIdx) } if lowIdx < 0 { @@ -780,17 +888,75 @@ func (v *VM) Run() error { numArgs := int(v.curInsts[v.ip+1]) v.ip++ - callee := *v.stack[v.sp-1-numArgs] + value := *v.stack[v.sp-1-numArgs] - switch callee := callee.(type) { + switch callee := value.(type) { case *objects.Closure: - if err := v.callFunction(callee.Fn, callee.Free, numArgs); err != nil { - return err + if numArgs != callee.Fn.NumParameters { + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-1]) + return fmt.Errorf("%s: wrong number of arguments: want=%d, got=%d", + filePos, callee.Fn.NumParameters, numArgs) } + + // test if it's tail-call + if callee.Fn == v.curFrame.fn { // recursion + nextOp := v.curInsts[v.ip+1] + if nextOp == compiler.OpReturnValue || + (nextOp == compiler.OpPop && compiler.OpReturn == v.curInsts[v.ip+2]) { + for p := 0; p < numArgs; p++ { + v.stack[v.curFrame.basePointer+p] = v.stack[v.sp-numArgs+p] + } + v.sp -= numArgs + 1 + v.ip = -1 // reset IP to beginning of the frame + continue mainloop + } + } + + // update call frame + v.curFrame.ip = v.ip // store current ip before call + v.curFrame = &(v.frames[v.framesIndex]) + v.curFrame.fn = callee.Fn + v.curFrame.freeVars = callee.Free + v.curFrame.basePointer = v.sp - numArgs + v.curInsts = callee.Fn.Instructions + v.ip = -1 + v.curIPLimit = len(v.curInsts) - 1 + v.framesIndex++ + v.sp = v.sp - numArgs + callee.Fn.NumLocals + case *objects.CompiledFunction: - if err := v.callFunction(callee, nil, numArgs); err != nil { - return err + if numArgs != callee.NumParameters { + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-1]) + return fmt.Errorf("%s: wrong number of arguments: want=%d, got=%d", + filePos, callee.NumParameters, numArgs) } + + // test if it's tail-call + if callee == v.curFrame.fn { // recursion + nextOp := v.curInsts[v.ip+1] + if nextOp == compiler.OpReturnValue || + (nextOp == compiler.OpPop && compiler.OpReturn == v.curInsts[v.ip+2]) { + for p := 0; p < numArgs; p++ { + v.stack[v.curFrame.basePointer+p] = v.stack[v.sp-numArgs+p] + } + v.sp -= numArgs + 1 + v.ip = -1 // reset IP to beginning of the frame + continue mainloop + } + } + + // update call frame + v.curFrame.ip = v.ip // store current ip before call + v.curFrame = &(v.frames[v.framesIndex]) + v.curFrame.fn = callee + v.curFrame.freeVars = nil + v.curFrame.basePointer = v.sp - numArgs + v.curInsts = callee.Instructions + v.ip = -1 + v.curIPLimit = len(v.curInsts) - 1 + v.framesIndex++ + v.sp = v.sp - numArgs + callee.NumLocals + case objects.Callable: var args []objects.Object for _, arg := range v.stack[v.sp-numArgs : v.sp] { @@ -802,7 +968,19 @@ func (v *VM) Run() error { // runtime error if err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-1]) + + if err == objects.ErrWrongNumArguments { + return fmt.Errorf("%s: wrong number of arguments in call to '%s'", + filePos, value.TypeName()) + } + + if err, ok := err.(objects.ErrInvalidArgumentType); ok { + return fmt.Errorf("%s: invalid type for argument '%s' in call to '%s': expected %s, found %s", + filePos, err.Name, value.TypeName(), err.Expected, err.Found) + } + + return fmt.Errorf("%s: %s", filePos, err.Error()) } // nil return -> undefined @@ -816,8 +994,10 @@ func (v *VM) Run() error { v.stack[v.sp] = &ret v.sp++ + default: - return fmt.Errorf("calling non-function: %s", callee.TypeName()) + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-1]) + return fmt.Errorf("%s: not callable: %s", filePos, callee.TypeName()) } case compiler.OpReturnValue: @@ -834,9 +1014,10 @@ func (v *VM) Run() error { //v.sp = lastFrame.basePointer - 1 v.sp = lastFrame.basePointer - if v.sp-1 >= StackSize { - return ErrStackOverflow - } + // skip stack overflow check because (newSP) <= (oldSP) + //if v.sp-1 >= StackSize { + // return ErrStackOverflow + //} v.stack[v.sp-1] = retVal //v.sp++ @@ -852,9 +1033,10 @@ func (v *VM) Run() error { //v.sp = lastFrame.basePointer - 1 v.sp = lastFrame.basePointer - if v.sp-1 >= StackSize { - return ErrStackOverflow - } + // skip stack overflow check because (newSP) <= (oldSP) + //if v.sp-1 >= StackSize { + // return ErrStackOverflow + //} v.stack[v.sp-1] = undefinedPtr //v.sp++ @@ -898,7 +1080,8 @@ func (v *VM) Run() error { sp := v.curFrame.basePointer + localIndex if err := indexAssign(v.stack[sp], val, selectors); err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-2]) + return fmt.Errorf("%s: %s", filePos, err.Error()) } case compiler.OpGetLocal: @@ -925,15 +1108,54 @@ func (v *VM) Run() error { v.stack[v.sp] = &builtinFuncs[builtinIndex] v.sp++ + case compiler.OpGetBuiltinModule: + val := v.stack[v.sp-1] + v.sp-- + + moduleName := (*val).(*objects.String).Value + + module, ok := v.builtinModules[moduleName] + if !ok { + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-3]) + return fmt.Errorf("%s: module '%s' not found", filePos, moduleName) + } + + if v.sp >= StackSize { + return ErrStackOverflow + } + + v.stack[v.sp] = module + v.sp++ + case compiler.OpClosure: constIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 numFree := int(v.curInsts[v.ip+3]) v.ip += 3 - if err := v.pushClosure(constIndex, numFree); err != nil { - return err + fn, ok := v.constants[constIndex].(*objects.CompiledFunction) + if !ok { + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-3]) + return fmt.Errorf("%s: not function: %s", filePos, fn.TypeName()) } + free := make([]*objects.Object, numFree) + for i := 0; i < numFree; i++ { + free[i] = v.stack[v.sp-numFree+i] + } + v.sp -= numFree + + if v.sp >= StackSize { + return ErrStackOverflow + } + + var cl objects.Object = &objects.Closure{ + Fn: fn, + Free: free, + } + + v.stack[v.sp] = &cl + v.sp++ + case compiler.OpGetFree: freeIndex := int(v.curInsts[v.ip+1]) v.ip++ @@ -958,7 +1180,8 @@ func (v *VM) Run() error { v.sp -= numSelectors + 1 if err := indexAssign(v.curFrame.freeVars[freeIndex], val, selectors); err != nil { - return err + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-2]) + return fmt.Errorf("%s: %s", filePos, err.Error()) } case compiler.OpSetFree: @@ -978,7 +1201,8 @@ func (v *VM) Run() error { iterable, ok := (*dst).(objects.Iterable) if !ok { - return fmt.Errorf("non-iterable type: %s", (*dst).TypeName()) + filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip]) + return fmt.Errorf("%s: not iterable: %s", filePos, (*dst).TypeName()) } iterator = iterable.Iterate() @@ -1034,13 +1258,13 @@ func (v *VM) Run() error { v.sp++ default: - return fmt.Errorf("unknown opcode: %d", v.curInsts[v.ip]) + panic(fmt.Errorf("unknown opcode: %d", v.curInsts[v.ip])) } } // check if stack still has some objects left if v.sp > 0 && atomic.LoadInt64(&v.aborting) == 0 { - return fmt.Errorf("non empty stack after execution: %d", v.sp) + panic(fmt.Errorf("non empty stack after execution: %d", v.sp)) } return nil @@ -1056,141 +1280,21 @@ func (v *VM) FrameInfo() (frameIndex, ip int) { return v.framesIndex - 1, v.ip } -func (v *VM) pushClosure(constIndex, numFree int) error { - c := v.constants[constIndex] - - fn, ok := c.(*objects.CompiledFunction) - if !ok { - return fmt.Errorf("not a function: %s", fn.TypeName()) - } - - free := make([]*objects.Object, numFree) - for i := 0; i < numFree; i++ { - free[i] = v.stack[v.sp-numFree+i] - } - v.sp -= numFree - - if v.sp >= StackSize { - return ErrStackOverflow - } - - var cl objects.Object = &objects.Closure{ - Fn: fn, - Free: free, - } - - v.stack[v.sp] = &cl - v.sp++ - - return nil -} - -func (v *VM) callFunction(fn *objects.CompiledFunction, freeVars []*objects.Object, numArgs int) error { - if numArgs != fn.NumParameters { - return fmt.Errorf("wrong number of arguments: want=%d, got=%d", - fn.NumParameters, numArgs) - } - - // check if this is a tail-call (recursive call right before return) - if fn == v.curFrame.fn { // recursion - nextOp := compiler.Opcode(v.curInsts[v.ip+1]) - if nextOp == compiler.OpReturnValue || // tail call - (nextOp == compiler.OpPop && - compiler.OpReturn == compiler.Opcode(v.curInsts[v.ip+2])) { - - // stack before tail-call - // - // |--------| - // | | <- SP current - // |--------| - // | *ARG2 | for next function (tail-call) - // |--------| - // | *ARG1 | for next function (tail-call) - // |--------| - // | FUNC | function itself - // |--------| - // | LOCAL3 | for current function - // |--------| - // | LOCAL2 | for current function - // |--------| - // | ARG2 | for current function - // |--------| - // | ARG1 | <- BP for current function - // |--------| - - for p := 0; p < numArgs; p++ { - v.stack[v.curFrame.basePointer+p] = v.stack[v.sp-numArgs+p] - } - v.sp -= numArgs + 1 - v.ip = -1 // reset IP to beginning of the frame - - // stack after tail-call - // - // |--------| - // | | - // |--------| - // | *ARG2 | - // |--------| - // | *ARG1 | - // |--------| - // | FUNC | <- SP current - // |--------| - // | LOCAL3 | for current function - // |--------| - // | LOCAL2 | for current function - // |--------| - // | *ARG2 | (copied) - // |--------| - // | *ARG1 | <- BP (copied) - // |--------| - - return nil - } - } - - // store current ip before call - v.curFrame.ip = v.ip - - // update call frame - v.curFrame = &(v.frames[v.framesIndex]) - v.curFrame.fn = fn - v.curFrame.freeVars = freeVars - v.curFrame.basePointer = v.sp - numArgs - v.curInsts = fn.Instructions - v.ip = -1 - v.curIPLimit = len(v.curInsts) - 1 - v.framesIndex++ - - v.sp = v.sp - numArgs + fn.NumLocals - - // stack after the function call - // - // |--------| - // | | <- SP after function call - // |--------| - // | LOCAL4 | (BP+3) - // |--------| - // | LOCAL3 | (BP+2) <- SP before function call - // |--------| - // | ARG2 | (BP+1) - // |--------| - // | ARG1 | (BP+0) <- BP - // |--------| - - return nil -} - func indexAssign(dst, src *objects.Object, selectors []*objects.Object) error { numSel := len(selectors) for sidx := numSel - 1; sidx > 0; sidx-- { indexable, ok := (*dst).(objects.Indexable) if !ok { - return objects.ErrNotIndexable + return fmt.Errorf("not indexable: %s", (*dst).TypeName()) } next, err := indexable.IndexGet(*selectors[sidx]) if err != nil { + if err == objects.ErrInvalidIndexType { + return fmt.Errorf("invalid index type: %s", (*selectors[sidx]).TypeName()) + } + return err } @@ -1199,15 +1303,26 @@ func indexAssign(dst, src *objects.Object, selectors []*objects.Object) error { indexAssignable, ok := (*dst).(objects.IndexAssignable) if !ok { - return objects.ErrNotIndexAssignable + return fmt.Errorf("not index-assignable: %s", (*dst).TypeName()) } - return indexAssignable.IndexSet(*selectors[0], *src) + if err := indexAssignable.IndexSet(*selectors[0], *src); err != nil { + if err == objects.ErrInvalidIndexValueType { + return fmt.Errorf("invaid index value type: %s", (*src).TypeName()) + } + + return err + } + + return nil } func init() { builtinFuncs = make([]objects.Object, len(objects.Builtins)) for i, b := range objects.Builtins { - builtinFuncs[i] = &objects.BuiltinFunction{Value: b.Func} + builtinFuncs[i] = &objects.BuiltinFunction{ + Name: b.Name, + Value: b.Func, + } } } diff --git a/runtime/vm_array_test.go b/runtime/vm_array_test.go index a793382..56a59ca 100644 --- a/runtime/vm_array_test.go +++ b/runtime/vm_array_test.go @@ -15,7 +15,7 @@ func TestArray(t *testing.T) { expect(t, `func () { a1 := [1, 2, 3]; a2 := a1; a1[0] = 5; out = a2 }()`, ARR{5, 2, 3}) // array index set - expectError(t, `a1 := [1, 2, 3]; a1[3] = 5`) + expectError(t, `a1 := [1, 2, 3]; a1[3] = 5`, "index out of bounds") // index operator arr := ARR{1, 2, 3, 4, 5, 6} @@ -48,8 +48,8 @@ func TestArray(t *testing.T) { expect(t, fmt.Sprintf("out = %s[:%d]", arrStr, arrLen+1), arr) expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, 2, 2), ARR{}) - expectError(t, fmt.Sprintf("out = %s[:%d]", arrStr, -1)) - expectError(t, fmt.Sprintf("out = %s[%d:]", arrStr, arrLen+1)) - expectError(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, 0, -1)) - expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 2, 1)) + expectError(t, fmt.Sprintf("%s[:%d]", arrStr, -1), "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:]", arrStr, arrLen+1), "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 0, -1), "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 2, 1), "invalid slice index") } diff --git a/runtime/vm_assignment_test.go b/runtime/vm_assignment_test.go index cb475cc..4a122fc 100644 --- a/runtime/vm_assignment_test.go +++ b/runtime/vm_assignment_test.go @@ -35,8 +35,8 @@ func() { }() }()`, 4) - expectError(t, `a := 1; a := 2`) // redeclared in the same scope - expectError(t, `func() { a := 1; a := 2 }()`) // redeclared in the same scope + expectError(t, `a := 1; a := 2`, "redeclared") // redeclared in the same scope + expectError(t, `func() { a := 1; a := 2 }()`, "redeclared") // redeclared in the same scope expect(t, `a := 1; a += 2; out = a`, 3) expect(t, `a := 1; a += 4 - 2;; out = a`, 3) @@ -47,11 +47,11 @@ func() { expect(t, `a := 10; a /= 2;; out = a`, 5) expect(t, `a := 10; a /= 5 - 3;; out = a`, 5) - // +=, -=, *=, /= operator does not define new variable - expectError(t, `a += 4`) - expectError(t, `a -= 4`) - expectError(t, `a *= 4`) - expectError(t, `a /= 4`) + // composite assignment operator does not define new variable + expectError(t, `a += 4`, "unresolved reference") + expectError(t, `a -= 4`, "unresolved reference") + expectError(t, `a *= 4`, "unresolved reference") + expectError(t, `a /= 4`, "unresolved reference") expect(t, ` f1 := func() { @@ -193,8 +193,8 @@ out = func() { `, 136) // assigning different type value - expect(t, `a := 1; a = "foo"; out = a`, "foo") // global - expect(t, `func() { a := 1; a = "foo"; out = a }()`, "foo") // local + expect(t, `a := 1; a = "foo"; out = a`, "foo") // global + expect(t, `func() { a := 1; a = "foo"; out = a }()`, "foo") // local expect(t, ` out = func() { a := 5 @@ -250,7 +250,7 @@ a := { f: [9, 8] } } -a.x.e = "bar"`) +a.x.e = "bar"`, "not index-assignable") // multi-variables //expect(t, `a, b = 1, 2; out = a + b`, 3) diff --git a/runtime/vm_boolean_test.go b/runtime/vm_boolean_test.go index 2c61a62..0a6468b 100644 --- a/runtime/vm_boolean_test.go +++ b/runtime/vm_boolean_test.go @@ -31,23 +31,25 @@ func TestBoolean(t *testing.T) { 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, `5 + true`, "invalid operation") + expectError(t, `5 + true; 5`, "invalid operation") + expectError(t, `-true`, "invalid operation") + expectError(t, `true + false`, "invalid operation") + expectError(t, `5; true + false; 5`, "invalid operation") + expectError(t, `if (10 > 1) { true + false; }`, "invalid operation") expectError(t, ` -if (10 > 1) { +func() { if (10 > 1) { - return true + false; - } + 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)`) + return 1; + } +}() +`, "invalid operation") + expectError(t, `if (true + false) { 10 }`, "invalid operation") + expectError(t, `10 + (true + false)`, "invalid operation") + expectError(t, `(true + false) + 20`, "invalid operation") + expectError(t, `!(true + false)`, "invalid operation") } diff --git a/runtime/vm_builtin_test.go b/runtime/vm_builtin_test.go index 841f789..2dad8f8 100644 --- a/runtime/vm_builtin_test.go +++ b/runtime/vm_builtin_test.go @@ -18,11 +18,11 @@ func TestBuiltinFunction(t *testing.T) { expect(t, `out = len(immutable([1, 2, 3]))`, 3) expect(t, `out = len(immutable({}))`, 0) expect(t, `out = len(immutable({a:1, b:2}))`, 2) - expectError(t, `len(1)`) - expectError(t, `len("one", "two")`) + expectError(t, `len(1)`, "invalid type for argument") + expectError(t, `len("one", "two")`, "wrong number of arguments") expect(t, `out = copy(1)`, 1) - expectError(t, `out = copy(1, 2)`) + expectError(t, `copy(1, 2)`, "wrong number of arguments") expect(t, `out = append([1, 2, 3], 4)`, ARR{1, 2, 3, 4}) expect(t, `out = append([1, 2, 3], 4, 5, 6)`, ARR{1, 2, 3, 4, 5, 6}) @@ -157,8 +157,8 @@ func TestBuiltinFunction(t *testing.T) { expect(t, `out = sprintf("foo %d %v %s", 1, 2, "bar")`, "foo 1 2 bar") expect(t, `out = sprintf("foo %v", [1, "bar", true])`, "foo [1 bar true]") expect(t, `out = sprintf("foo %v %d", [1, "bar", true], 19)`, "foo [1 bar true] 19") - expectError(t, `sprintf(1)`) // format has to be String - expectError(t, `sprintf('c')`) // format has to be String + expectError(t, `sprintf(1)`, "invalid type for argument") // format has to be String + expectError(t, `sprintf('c')`, "invalid type for argument") // format has to be String // type_name expect(t, `out = type_name(1)`, "int") @@ -192,5 +192,4 @@ func TestBuiltinFunction(t *testing.T) { expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a)`, true) // function expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a(5))`, true) // closure expectWithSymbols(t, `out = is_callable(x)`, true, SYM{"x": &StringArray{Value: []string{"foo", "bar"}}}) // user object - } diff --git a/runtime/vm_error_report_test.go b/runtime/vm_error_report_test.go new file mode 100644 index 0000000..49bfc32 --- /dev/null +++ b/runtime/vm_error_report_test.go @@ -0,0 +1,48 @@ +package runtime_test + +import "testing" + +func TestVMErrorInfo(t *testing.T) { + expectError(t, `a := 5 +a + "boo"`, + "test:2:1: invalid operation: int + string") + + expectError(t, `a := 5 +b := a(5)`, + "test:2:6: not callable: int") + + expectError(t, `a := 5 +b := {} +b.x.y = 10`, + "test:3:1: not index-assignable: undefined") + + expectError(t, ` +a := func() { + b := 5 + b += "foo" +} +a()`, + "test:4:2: invalid operation: int + string") + + expectErrorWithUserModules(t, `a := 5 + a + import("mod1")`, map[string]string{ + "mod1": `export "foo"`, + }, "test:2:2: invalid operation: int + string") + + expectErrorWithUserModules(t, `a := import("mod1")()`, map[string]string{ + "mod1": ` +export func() { + b := 5 + return b + "foo" +}`, + }, "mod1:4:9: invalid operation: int + string") + + expectErrorWithUserModules(t, `a := import("mod1")()`, map[string]string{ + "mod1": `export import("mod2")()`, + "mod2": ` +export func() { + b := 5 + return b + "foo" +}`, + }, "mod2:4:9: invalid operation: int + string") +} diff --git a/runtime/vm_error_test.go b/runtime/vm_error_test.go index b40a38f..3120e82 100644 --- a/runtime/vm_error_test.go +++ b/runtime/vm_error_test.go @@ -15,7 +15,7 @@ func TestError(t *testing.T) { expect(t, `out = error("some error").value`, "some error") expect(t, `out = error("some error")["value"]`, "some error") - expectError(t, `error("error").err`) - expectError(t, `error("error").value_`) - expectError(t, `error([1,2,3])[1]`) + expectError(t, `error("error").err`, "invalid index on error") + expectError(t, `error("error").value_`, "invalid index on error") + expectError(t, `error([1,2,3])[1]`, "invalid index on error") } diff --git a/runtime/vm_function_test.go b/runtime/vm_function_test.go index a0edee6..775943c 100644 --- a/runtime/vm_function_test.go +++ b/runtime/vm_function_test.go @@ -135,9 +135,9 @@ func TestFunction(t *testing.T) { out = outer() + g `, 50) - expectError(t, `func() { return 1; }(1)`) - expectError(t, `func(a) { return a; }()`) - expectError(t, `func(a, b) { return a + b; }(1)`) + expectError(t, `func() { return 1; }(1)`, "wrong number of arguments") + expectError(t, `func(a) { return a; }()`, "wrong number of arguments") + expectError(t, `func(a, b) { return a + b; }(1)`, "wrong number of arguments") expect(t, ` f1 := func(a) { @@ -218,7 +218,7 @@ out = func() { return sum(5) }()`, 15) - expectError(t, `return 5`) + expectError(t, `return 5`, "return not allowed outside function") // closure and block scopes expect(t, ` diff --git a/runtime/vm_immutable_test.go b/runtime/vm_immutable_test.go index 73fed92..5156865 100644 --- a/runtime/vm_immutable_test.go +++ b/runtime/vm_immutable_test.go @@ -14,11 +14,11 @@ func TestImmutable(t *testing.T) { expect(t, `a := immutable(1); a = 5; out = a`, 5) // array - expectError(t, `a := immutable([1, 2, 3]); a[1] = 5`) - expectError(t, `a := immutable(["foo", [1,2,3]]); a[1] = "bar"`) + expectError(t, `a := immutable([1, 2, 3]); a[1] = 5`, "not index-assignable") + expectError(t, `a := immutable(["foo", [1,2,3]]); a[1] = "bar"`, "not index-assignable") expect(t, `a := immutable(["foo", [1,2,3]]); a[1][1] = "bar"; out = a`, IARR{"foo", ARR{1, "bar", 3}}) - expectError(t, `a := immutable(["foo", immutable([1,2,3])]); a[1][1] = "bar"`) - expectError(t, `a := ["foo", immutable([1,2,3])]; a[1][1] = "bar"`) + expectError(t, `a := immutable(["foo", immutable([1,2,3])]); a[1][1] = "bar"`, "not index-assignable") + expectError(t, `a := ["foo", immutable([1,2,3])]; a[1][1] = "bar"`, "not index-assignable") expect(t, `a := immutable([1,2,3]); b := copy(a); b[1] = 5; out = b`, ARR{1, 5, 3}) expect(t, `a := immutable([1,2,3]); b := copy(a); b[1] = 5; out = a`, IARR{1, 2, 3}) expect(t, `out = immutable([1,2,3]) == [1,2,3]`, true) @@ -33,11 +33,11 @@ func TestImmutable(t *testing.T) { expect(t, `a := immutable([1, 2, 3]); out = a[5]`, objects.UndefinedValue) // map - expectError(t, `a := immutable({b: 1, c: 2}); a.b = 5`) - expectError(t, `a := immutable({b: 1, c: 2}); a["b"] = "bar"`) + expectError(t, `a := immutable({b: 1, c: 2}); a.b = 5`, "not index-assignable") + expectError(t, `a := immutable({b: 1, c: 2}); a["b"] = "bar"`, "not index-assignable") expect(t, `a := immutable({b: 1, c: [1,2,3]}); a.c[1] = "bar"; out = a`, IMAP{"b": 1, "c": ARR{1, "bar", 3}}) - expectError(t, `a := immutable({b: 1, c: immutable([1,2,3])}); a.c[1] = "bar"`) - expectError(t, `a := {b: 1, c: immutable([1,2,3])}; a.c[1] = "bar"`) + expectError(t, `a := immutable({b: 1, c: immutable([1,2,3])}); a.c[1] = "bar"`, "not index-assignable") + expectError(t, `a := {b: 1, c: immutable([1,2,3])}; a.c[1] = "bar"`, "not index-assignable") expect(t, `out = immutable({a:1,b:2}) == {a:1,b:2}`, true) expect(t, `out = immutable({a:1,b:2}) == immutable({a:1,b:2})`, true) expect(t, `out = {a:1,b:2} == immutable({a:1,b:2})`, true) @@ -50,5 +50,5 @@ func TestImmutable(t *testing.T) { expect(t, `a := immutable({a:1,b:2}); out = a.c`, objects.UndefinedValue) expect(t, `a := immutable({b: 5, c: "foo"}); out = a.b`, 5) - expectError(t, `a := immutable({b: 5, c: "foo"}); a.b = 10`) + expectError(t, `a := immutable({b: 5, c: "foo"}); a.b = 10`, "not index-assignable") } diff --git a/runtime/vm_inc_dec_test.go b/runtime/vm_inc_dec_test.go index c83c1bb..94fc80a 100644 --- a/runtime/vm_inc_dec_test.go +++ b/runtime/vm_inc_dec_test.go @@ -13,10 +13,10 @@ func TestIncDec(t *testing.T) { // this seems strange but it works because 'a += b' is // translated into 'a = a + b' and string type takes other types for + operator. expect(t, `a := "foo"; a++; out = a`, "foo1") - expectError(t, `a := "foo"; a--`) + expectError(t, `a := "foo"; a--`, "invalid operation") - expectError(t, `a++`) // not declared - expectError(t, `a--`) // not declared + expectError(t, `a++`, "unresolved reference") // not declared + expectError(t, `a--`, "unresolved reference") // not declared //expectError(t, `a := 0; b := a++`) // inc-dec is statement not expression <- parser error - expectError(t, `4++`) + expectError(t, `4++`, "unresolved reference") } diff --git a/runtime/vm_indexable_test.go b/runtime/vm_indexable_test.go index a838e92..e774190 100644 --- a/runtime/vm_indexable_test.go +++ b/runtime/vm_indexable_test.go @@ -51,7 +51,7 @@ func (o *StringDict) IndexSet(index, value objects.Object) error { strVal, ok := objects.ToString(value) if !ok { - return objects.ErrInvalidTypeConversion + return objects.ErrInvalidIndexValueType } o.Value[strings.ToLower(strIdx.Value)] = strVal @@ -95,7 +95,7 @@ func (o *StringCircle) IndexSet(index, value objects.Object) error { strVal, ok := objects.ToString(value) if !ok { - return objects.ErrInvalidTypeConversion + return objects.ErrInvalidIndexValueType } o.Value[r] = strVal @@ -184,7 +184,7 @@ func (o *StringArray) IndexGet(index objects.Object) (objects.Object, error) { func (o *StringArray) IndexSet(index, value objects.Object) error { strVal, ok := objects.ToString(value) if !ok { - return objects.ErrInvalidTypeConversion + return objects.ErrInvalidIndexValueType } intIdx, ok := index.(*objects.Int) @@ -207,7 +207,11 @@ func (o *StringArray) Call(args ...objects.Object) (ret objects.Object, err erro s1, ok := objects.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidTypeConversion + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } } for i, v := range o.Value { @@ -224,7 +228,7 @@ func TestIndexable(t *testing.T) { expectWithSymbols(t, `out = dict["a"]`, "foo", SYM{"dict": dict()}) expectWithSymbols(t, `out = dict["B"]`, "bar", SYM{"dict": dict()}) expectWithSymbols(t, `out = dict["x"]`, objects.UndefinedValue, SYM{"dict": dict()}) - expectErrorWithSymbols(t, `out = dict[0]`, SYM{"dict": dict()}) + expectErrorWithSymbols(t, `dict[0]`, SYM{"dict": dict()}, "invalid index type") strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} } expectWithSymbols(t, `out = cir[0]`, "one", SYM{"cir": strCir()}) @@ -232,7 +236,7 @@ func TestIndexable(t *testing.T) { expectWithSymbols(t, `out = cir[-1]`, "three", SYM{"cir": strCir()}) expectWithSymbols(t, `out = cir[-2]`, "two", SYM{"cir": strCir()}) expectWithSymbols(t, `out = cir[3]`, "one", SYM{"cir": strCir()}) - expectErrorWithSymbols(t, `out = cir["a"]`, SYM{"cir": strCir()}) + expectErrorWithSymbols(t, `cir["a"]`, SYM{"cir": strCir()}, "invalid index type") strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} } expectWithSymbols(t, `out = arr["one"]`, 0, SYM{"arr": strArr()}) @@ -240,7 +244,7 @@ func TestIndexable(t *testing.T) { expectWithSymbols(t, `out = arr["four"]`, objects.UndefinedValue, SYM{"arr": strArr()}) expectWithSymbols(t, `out = arr[0]`, "one", SYM{"arr": strArr()}) expectWithSymbols(t, `out = arr[1]`, "two", SYM{"arr": strArr()}) - expectErrorWithSymbols(t, `out = arr[-1]`, SYM{"arr": strArr()}) + expectErrorWithSymbols(t, `arr[-1]`, SYM{"arr": strArr()}, "index out of bounds") } func TestIndexAssignable(t *testing.T) { @@ -248,17 +252,17 @@ func TestIndexAssignable(t *testing.T) { expectWithSymbols(t, `dict["a"] = "1984"; out = dict["a"]`, "1984", SYM{"dict": dict()}) expectWithSymbols(t, `dict["c"] = "1984"; out = dict["c"]`, "1984", SYM{"dict": dict()}) expectWithSymbols(t, `dict["c"] = 1984; out = dict["C"]`, "1984", SYM{"dict": dict()}) - expectErrorWithSymbols(t, `dict[0] = "1984"`, SYM{"dict": dict()}) + expectErrorWithSymbols(t, `dict[0] = "1984"`, SYM{"dict": dict()}, "invalid index type") strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} } expectWithSymbols(t, `cir[0] = "ONE"; out = cir[0]`, "ONE", SYM{"cir": strCir()}) expectWithSymbols(t, `cir[1] = "TWO"; out = cir[1]`, "TWO", SYM{"cir": strCir()}) expectWithSymbols(t, `cir[-1] = "THREE"; out = cir[2]`, "THREE", SYM{"cir": strCir()}) expectWithSymbols(t, `cir[0] = "ONE"; out = cir[3]`, "ONE", SYM{"cir": strCir()}) - expectErrorWithSymbols(t, `cir["a"] = "ONE"`, SYM{"cir": strCir()}) + expectErrorWithSymbols(t, `cir["a"] = "ONE"`, SYM{"cir": strCir()}, "invalid index type") strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} } expectWithSymbols(t, `arr[0] = "ONE"; out = arr[0]`, "ONE", SYM{"arr": strArr()}) expectWithSymbols(t, `arr[1] = "TWO"; out = arr[1]`, "TWO", SYM{"arr": strArr()}) - expectErrorWithSymbols(t, `arr["one"] = "ONE"`, SYM{"arr": strArr()}) + expectErrorWithSymbols(t, `arr["one"] = "ONE"`, SYM{"arr": strArr()}, "invalid index type") } diff --git a/runtime/vm_module_test.go b/runtime/vm_module_test.go index 2a55d53..c826fd5 100644 --- a/runtime/vm_module_test.go +++ b/runtime/vm_module_test.go @@ -86,10 +86,10 @@ func TestUserModules(t *testing.T) { // export value is immutable expectErrorWithUserModules(t, `m1 := import("mod1"); m1.a = 5`, map[string]string{ "mod1": `export {a: 1, b: 2}`, - }) + }, "not index-assignable") expectErrorWithUserModules(t, `m1 := import("mod1"); m1[1] = 5`, map[string]string{ "mod1": `export [1, 2, 3]`, - }) + }, "not index-assignable") // code after export statement will not be executed expectWithUserModules(t, `out = import("mod1")`, 10, map[string]string{ @@ -160,27 +160,27 @@ export func() { expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ "mod1": `import("mod2")`, "mod2": `import("mod1")`, - }) + }, "mod2:1:1: cyclic module import") // (main) -> mod1 -> mod2 -> mod3 -> mod1 expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ "mod1": `import("mod2")`, "mod2": `import("mod3")`, "mod3": `import("mod1")`, - }) + }, "mod3:1:1: cyclic module import") // (main) -> mod1 -> mod2 -> mod3 -> mod2 expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ "mod1": `import("mod2")`, "mod2": `import("mod3")`, "mod3": `import("mod2")`, - }) + }, "mod3:1:1: cyclic module import") // unknown modules expectErrorWithUserModules(t, `import("mod0")`, map[string]string{ "mod1": `a := 5`, - }) + }, "module 'mod0' not found") expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ "mod1": `import("mod2")`, - }) + }, "module 'mod2' not found") // module is immutable but its variables is not necessarily immutable. expectWithUserModules(t, `m1 := import("mod1"); m1.a.b = 5; out = m1.a.b`, 5, map[string]string{ @@ -198,15 +198,15 @@ export func() { // 'export' must be in the top-level expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ "mod1": `func() { export 5 }()`, - }) + }, "mod1:1:10: export not allowed inside function") expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ "mod1": `func() { func() { export 5 }() }()`, - }) + }, "mod1:1:19: export not allowed inside function") // module cannot access outer scope - expectErrorWithUserModules(t, `a := 5; import("mod")`, map[string]string{ + expectErrorWithUserModules(t, `a := 5; import("mod1")`, map[string]string{ "mod1": `export a`, - }) + }, "mod1:1:8: unresolved reference 'a'") } func TestModuleBlockScopes(t *testing.T) { diff --git a/runtime/vm_selector_test.go b/runtime/vm_selector_test.go index 1212d20..c024282 100644 --- a/runtime/vm_selector_test.go +++ b/runtime/vm_selector_test.go @@ -21,7 +21,7 @@ a := { } out = a.b.c`, 4) - expectError(t, ` + expect(t, ` a := { b: { c: 4, @@ -29,9 +29,9 @@ a := { }, c: "foo bar" } -out = a.x.c`) +b := a.x.c`, objects.UndefinedValue) - expectError(t, ` + expect(t, ` a := { b: { c: 4, @@ -39,7 +39,7 @@ a := { }, c: "foo bar" } -out = a.x.y`) +b := a.x.y`, objects.UndefinedValue) expect(t, `a := {b: 1, c: "foo"}; a.b = 2; out = a.b`, 2) expect(t, `a := {b: 1, c: "foo"}; a.c = 2; out = a.c`, 2) // type not checked on sub-field @@ -81,10 +81,10 @@ func() { }() `, 9) - expectError(t, `a := {b: {c: 1}}; a.d.c = 2`) - expectError(t, `a := [1, 2, 3]; a.b = 2`) - expectError(t, `a := "foo"; a.b = 2`) - expectError(t, `func() { a := {b: {c: 1}}; a.d.c = 2 }()`) - expectError(t, `func() { a := [1, 2, 3]; a.b = 2 }()`) - expectError(t, `func() { a := "foo"; a.b = 2 }()`) + expectError(t, `a := {b: {c: 1}}; a.d.c = 2`, "not index-assignable") + expectError(t, `a := [1, 2, 3]; a.b = 2`, "invalid index type") + expectError(t, `a := "foo"; a.b = 2`, "not index-assignable") + expectError(t, `func() { a := {b: {c: 1}}; a.d.c = 2 }()`, "not index-assignable") + expectError(t, `func() { a := [1, 2, 3]; a.b = 2 }()`, "invalid index type") + expectError(t, `func() { a := "foo"; a.b = 2 }()`, "not index-assignable") } diff --git a/runtime/vm_string_test.go b/runtime/vm_string_test.go index 7b13a4f..6140634 100644 --- a/runtime/vm_string_test.go +++ b/runtime/vm_string_test.go @@ -48,10 +48,10 @@ func TestString(t *testing.T) { expect(t, fmt.Sprintf("out = %s[:%d]", strStr, strLen+1), str) expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, 2, 2), "") - expectError(t, fmt.Sprintf("out = %s[:%d]", strStr, -1)) - expectError(t, fmt.Sprintf("out = %s[%d:]", strStr, strLen+1)) - expectError(t, fmt.Sprintf("out = %s[%d:%d]", strStr, 0, -1)) - expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 2, 1)) + expectError(t, fmt.Sprintf("%s[:%d]", strStr, -1), "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:]", strStr, strLen+1), "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 0, -1), "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 2, 1), "invalid slice index") // string concatenation with other types expect(t, `out = "foo" + 1`, "foo1") @@ -68,7 +68,7 @@ func TestString(t *testing.T) { // also works with "+=" operator expect(t, `out = "foo"; out += 1.5`, "foo1.5") // string concats works only when string is LHS - expectError(t, `1 + "foo"`) + expectError(t, `1 + "foo"`, "invalid operation") - expectError(t, `"foo" - "bar"`) + expectError(t, `"foo" - "bar"`, "invalid operation") } diff --git a/runtime/vm_tail_call_test.go b/runtime/vm_tail_call_test.go index 522b357..449231a 100644 --- a/runtime/vm_tail_call_test.go +++ b/runtime/vm_tail_call_test.go @@ -78,6 +78,19 @@ iter := func(n, max) { return iter(n+1, max) } out = iter(0, 9999) +`, 9999) + expect(t, ` +c := 0 +iter := func(n, max) { + if n == max { + return + } + + c++ + iter(n+1, max) +} +iter(0, 9999) +out = c `, 9999) } diff --git a/runtime/vm_test.go b/runtime/vm_test.go index 5f7e8f8..5524b46 100644 --- a/runtime/vm_test.go +++ b/runtime/vm_test.go @@ -52,11 +52,16 @@ func expectWithUserModules(t *testing.T, input string, expected interface{}, use runVM(t, file, expected, nil, userModules) } -func expectError(t *testing.T, input string) { - expectErrorWithUserModules(t, input, nil) +func expectError(t *testing.T, input, expected string) { + expected = strings.TrimSpace(expected) + if expected == "" { + panic("expected must not be empty") + } + + expectErrorWithUserModules(t, input, nil, expected) } -func expectErrorWithUserModules(t *testing.T, input string, userModules map[string]string) { +func expectErrorWithUserModules(t *testing.T, input string, userModules map[string]string, expected string) { // parse program := parse(t, input) if program == nil { @@ -64,10 +69,14 @@ func expectErrorWithUserModules(t *testing.T, input string, userModules map[stri } // compiler/VM - runVMError(t, program, nil, userModules) + _, trace, err := traceCompileRun(program, nil, userModules) + if !assert.Error(t, err) || + !assert.True(t, strings.Contains(err.Error(), expected), "expected error string: %s, got: %s", expected, err.Error()) { + t.Log("\n" + strings.Join(trace, "\n")) + } } -func expectErrorWithSymbols(t *testing.T, input string, symbols map[string]objects.Object) { +func expectErrorWithSymbols(t *testing.T, input string, symbols map[string]objects.Object, expected string) { // parse program := parse(t, input) if program == nil { @@ -75,7 +84,11 @@ func expectErrorWithSymbols(t *testing.T, input string, symbols map[string]objec } // compiler/VM - runVMError(t, program, symbols, nil) + _, trace, err := traceCompileRun(program, symbols, nil) + if !assert.Error(t, err) || + !assert.True(t, strings.Contains(err.Error(), expected), "expected error string: %s, got: %s", expected, err.Error()) { + t.Log("\n" + strings.Join(trace, "\n")) + } } func runVM(t *testing.T, file *ast.File, expected interface{}, symbols map[string]objects.Object, userModules map[string]string) (ok bool) { @@ -103,21 +116,6 @@ func runVM(t *testing.T, file *ast.File, expected interface{}, symbols map[strin return } -// TODO: should differentiate compile-time error, runtime error, and, error object returned -func runVMError(t *testing.T, file *ast.File, symbols map[string]objects.Object, userModules map[string]string) (ok bool) { - _, trace, err := traceCompileRun(file, symbols, userModules) - - defer func() { - if !ok { - t.Log("\n" + strings.Join(trace, "\n")) - } - }() - - ok = assert.Error(t, err) - - return -} - func errorObject(v interface{}) *objects.Error { return &objects.Error{Value: toObject(v)} } @@ -230,7 +228,7 @@ func traceCompileRun(file *ast.File, symbols map[string]objects.Object, userModu } tr := &tracer{} - c := compiler.NewCompiler(symTable, nil, nil, tr) + c := compiler.NewCompiler(file.InputFile, symTable, nil, nil, tr) c.SetModuleLoader(func(moduleName string) ([]byte, error) { if src, ok := userModules[moduleName]; ok { return []byte(src), nil @@ -248,7 +246,7 @@ func traceCompileRun(file *ast.File, symbols map[string]objects.Object, userModu trace = append(trace, fmt.Sprintf("\n[Compiled Constants]\n\n%s", strings.Join(bytecode.FormatConstants(), "\n"))) trace = append(trace, fmt.Sprintf("\n[Compiled Instructions]\n\n%s\n", strings.Join(bytecode.FormatInstructions(), "\n"))) - v = runtime.NewVM(bytecode, globals) + v = runtime.NewVM(bytecode, globals, nil) err = v.Run() { @@ -296,7 +294,7 @@ func formatGlobals(globals []*objects.Object) (formatted []string) { func parse(t *testing.T, input string) *ast.File { testFileSet := source.NewFileSet() - testFile := testFileSet.AddFile("", -1, len(input)) + testFile := testFileSet.AddFile("test", -1, len(input)) file, err := parser.ParseFile(testFile, []byte(input), nil) if !assert.NoError(t, err) { diff --git a/script/script.go b/script/script.go index 6a56f45..0b81027 100644 --- a/script/script.go +++ b/script/script.go @@ -7,9 +7,9 @@ import ( "github.com/d5/tengo/compiler" "github.com/d5/tengo/compiler/parser" "github.com/d5/tengo/compiler/source" - "github.com/d5/tengo/compiler/stdlib" "github.com/d5/tengo/objects" "github.com/d5/tengo/runtime" + "github.com/d5/tengo/stdlib" ) // Script can simplify compilation and execution of embedded scripts. @@ -87,14 +87,15 @@ func (s *Script) Compile() (*Compiled, error) { } fileSet := source.NewFileSet() + srcFile := fileSet.AddFile("(main)", -1, len(s.input)) - p := parser.NewParser(fileSet.AddFile("", -1, len(s.input)), s.input, nil) + p := parser.NewParser(srcFile, s.input, nil) file, err := p.ParseFile() if err != nil { return nil, fmt.Errorf("parse error: %s", err.Error()) } - c := compiler.NewCompiler(symbolTable, nil, stdModules, nil) + c := compiler.NewCompiler(srcFile, symbolTable, nil, stdModules, nil) if s.userModuleLoader != nil { c.SetModuleLoader(s.userModuleLoader) @@ -106,7 +107,7 @@ func (s *Script) Compile() (*Compiled, error) { return &Compiled{ symbolTable: symbolTable, - machine: runtime.NewVM(c.Bytecode(), globals), + machine: runtime.NewVM(c.Bytecode(), globals, nil), }, nil } @@ -135,7 +136,7 @@ func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error) return } -func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules map[string]*objects.ImmutableMap, globals []*objects.Object, err error) { +func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules map[string]bool, globals []*objects.Object, err error) { var names []string for name := range s.variables { names = append(names, name) @@ -148,10 +149,10 @@ func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules ma } } - stdModules = make(map[string]*objects.ImmutableMap) - for name, mod := range stdlib.Modules { + stdModules = make(map[string]bool) + for name := range stdlib.Modules { if !s.removedStdModules[name] { - stdModules[name] = mod + stdModules[name] = true } } diff --git a/compiler/stdlib/errors.go b/stdlib/errors.go similarity index 100% rename from compiler/stdlib/errors.go rename to stdlib/errors.go diff --git a/stdlib/func_typedefs.go b/stdlib/func_typedefs.go new file mode 100644 index 0000000..a85619f --- /dev/null +++ b/stdlib/func_typedefs.go @@ -0,0 +1,1057 @@ +package stdlib + +import ( + "fmt" + + "github.com/d5/tengo/objects" +) + +// FuncAR transform a function of 'func()' signature +// into CallableFunc type. +func FuncAR(fn func()) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + fn() + + return objects.UndefinedValue, nil + } +} + +// FuncARI transform a function of 'func() int' signature +// into CallableFunc type. +func FuncARI(fn func() int) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return &objects.Int{Value: int64(fn())}, nil + } +} + +// FuncARI64 transform a function of 'func() int64' signature +// into CallableFunc type. +func FuncARI64(fn func() int64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return &objects.Int{Value: fn()}, nil + } +} + +// FuncAI64RI64 transform a function of 'func(int64) int64' signature +// into CallableFunc type. +func FuncAI64RI64(fn func(int64) int64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Int{Value: fn(i1)}, nil + } +} + +// FuncAI64R transform a function of 'func(int64)' signature +// into CallableFunc type. +func FuncAI64R(fn func(int64)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + fn(i1) + + return objects.UndefinedValue, nil + } +} + +// FuncARB transform a function of 'func() bool' signature +// into CallableFunc type. +func FuncARB(fn func() bool) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + if fn() { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil + } +} + +// FuncARE transform a function of 'func() error' signature +// into CallableFunc type. +func FuncARE(fn func() error) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return wrapError(fn()), nil + } +} + +// FuncARS transform a function of 'func() string' signature +// into CallableFunc type. +func FuncARS(fn func() string) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return &objects.String{Value: fn()}, nil + } +} + +// FuncARSE transform a function of 'func() (string, error)' signature +// into CallableFunc type. +func FuncARSE(fn func() (string, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + res, err := fn() + if err != nil { + return wrapError(err), nil + } + + return &objects.String{Value: res}, nil + } +} + +// FuncARYE transform a function of 'func() ([]byte, error)' signature +// into CallableFunc type. +func FuncARYE(fn func() ([]byte, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + res, err := fn() + if err != nil { + return wrapError(err), nil + } + + return &objects.Bytes{Value: res}, nil + } +} + +// FuncARF transform a function of 'func() float64' signature +// into CallableFunc type. +func FuncARF(fn func() float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return &objects.Float{Value: fn()}, nil + } +} + +// FuncARSs transform a function of 'func() []string' signature +// into CallableFunc type. +func FuncARSs(fn func() []string) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + arr := &objects.Array{} + for _, osArg := range fn() { + arr.Value = append(arr.Value, &objects.String{Value: osArg}) + } + + return arr, nil + } +} + +// FuncARIsE transform a function of 'func() ([]int, error)' signature +// into CallableFunc type. +func FuncARIsE(fn func() ([]int, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + res, err := fn() + if err != nil { + return wrapError(err), nil + } + + arr := &objects.Array{} + for _, v := range res { + arr.Value = append(arr.Value, &objects.Int{Value: int64(v)}) + } + + return arr, nil + } +} + +// FuncAIRIs transform a function of 'func(int) []int' signature +// into CallableFunc type. +func FuncAIRIs(fn func(int) []int) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + res := fn(i1) + + arr := &objects.Array{} + for _, v := range res { + arr.Value = append(arr.Value, &objects.Int{Value: int64(v)}) + } + + return arr, nil + } +} + +// FuncAFRF transform a function of 'func(float64) float64' signature +// into CallableFunc type. +func FuncAFRF(fn func(float64) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Float{Value: fn(f1)}, nil + } +} + +// FuncAIR transform a function of 'func(int)' signature +// into CallableFunc type. +func FuncAIR(fn func(int)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + fn(i1) + + return objects.UndefinedValue, nil + } +} + +// FuncAIRF transform a function of 'func(int) float64' signature +// into CallableFunc type. +func FuncAIRF(fn func(int) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Float{Value: fn(i1)}, nil + } +} + +// FuncAFRI transform a function of 'func(float64) int' signature +// into CallableFunc type. +func FuncAFRI(fn func(float64) int) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Int{Value: int64(fn(f1))}, nil + } +} + +// FuncAFFRF transform a function of 'func(float64, float64) float64' signature +// into CallableFunc type. +func FuncAFFRF(fn func(float64, float64) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + f2, ok := objects.ToFloat64(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "float(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.Float{Value: fn(f1, f2)}, nil + } +} + +// FuncAIFRF transform a function of 'func(int, float64) float64' signature +// into CallableFunc type. +func FuncAIFRF(fn func(int, float64) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + f2, ok := objects.ToFloat64(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "float(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.Float{Value: fn(i1, f2)}, nil + } +} + +// FuncAFIRF transform a function of 'func(float64, int) float64' signature +// into CallableFunc type. +func FuncAFIRF(fn func(float64, int) float64) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.Float{Value: fn(f1, i2)}, nil + } +} + +// FuncAFIRB transform a function of 'func(float64, int) bool' signature +// into CallableFunc type. +func FuncAFIRB(fn func(float64, int) bool) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + if fn(f1, i2) { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil + } +} + +// FuncAFRB transform a function of 'func(float64) bool' signature +// into CallableFunc type. +func FuncAFRB(fn func(float64) bool) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + f1, ok := objects.ToFloat64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float(compatible)", + Found: args[0].TypeName(), + } + } + + if fn(f1) { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil + } +} + +// FuncASRS transform a function of 'func(string) string' signature into CallableFunc type. +// User function will return 'true' if underlying native function returns nil. +func FuncASRS(fn func(string) string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.String{Value: fn(s1)}, nil + } +} + +// FuncASRSs transform a function of 'func(string) []string' signature into CallableFunc type. +func FuncASRSs(fn func(string) []string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res := fn(s1) + + arr := &objects.Array{} + for _, osArg := range res { + arr.Value = append(arr.Value, &objects.String{Value: osArg}) + } + + return arr, nil + } +} + +// FuncASRSE transform a function of 'func(string) (string, error)' signature into CallableFunc type. +// User function will return 'true' if underlying native function returns nil. +func FuncASRSE(fn func(string) (string, error)) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := fn(s1) + if err != nil { + return wrapError(err), nil + } + + return &objects.String{Value: res}, nil + } +} + +// FuncASRE transform a function of 'func(string) error' signature into CallableFunc type. +// User function will return 'true' if underlying native function returns nil. +func FuncASRE(fn func(string) error) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + return wrapError(fn(s1)), nil + } +} + +// FuncASSRE transform a function of 'func(string, string) error' signature into CallableFunc type. +// User function will return 'true' if underlying native function returns nil. +func FuncASSRE(fn func(string, string) error) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + return wrapError(fn(s1, s2)), nil + } +} + +// FuncASSRSs transform a function of 'func(string, string) []string' signature into CallableFunc type. +func FuncASSRSs(fn func(string, string) []string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + arr := &objects.Array{} + for _, res := range fn(s1, s2) { + arr.Value = append(arr.Value, &objects.String{Value: res}) + } + + return arr, nil + } +} + +// FuncASSIRSs transform a function of 'func(string, string, int) []string' signature into CallableFunc type. +func FuncASSIRSs(fn func(string, string, int) []string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 3 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + } + + arr := &objects.Array{} + for _, res := range fn(s1, s2, i3) { + arr.Value = append(arr.Value, &objects.String{Value: res}) + } + + return arr, nil + } +} + +// FuncASSRI transform a function of 'func(string, string) int' signature into CallableFunc type. +func FuncASSRI(fn func(string, string) int) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.Int{Value: int64(fn(s1, s2))}, nil + } +} + +// FuncASSRS transform a function of 'func(string, string) string' signature into CallableFunc type. +func FuncASSRS(fn func(string, string) string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.String{Value: fn(s1, s2)}, nil + } +} + +// FuncASSRB transform a function of 'func(string, string) bool' signature into CallableFunc type. +func FuncASSRB(fn func(string, string) bool) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + if fn(s1, s2) { + return objects.TrueValue, nil + } + + return objects.FalseValue, nil + } +} + +// FuncASsSRS transform a function of 'func([]string, string) string' signature into CallableFunc type. +func FuncASsSRS(fn func([]string, string) string) objects.CallableFunc { + return func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + var ss1 []string + switch arg0 := args[0].(type) { + case *objects.Array: + for idx, a := range arg0.Value { + as, ok := objects.ToString(a) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: fmt.Sprintf("first[%d]", idx), + Expected: "string(compatible)", + Found: a.TypeName(), + } + } + ss1 = append(ss1, as) + } + case *objects.ImmutableArray: + for idx, a := range arg0.Value { + as, ok := objects.ToString(a) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: fmt.Sprintf("first[%d]", idx), + Expected: "string(compatible)", + Found: a.TypeName(), + } + } + ss1 = append(ss1, as) + } + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "array", + Found: args[0].TypeName(), + } + } + + s2, ok := objects.ToString(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.String{Value: fn(ss1, s2)}, nil + } +} + +// FuncASI64RE transform a function of 'func(string, int64) error' signature +// into CallableFunc type. +func FuncASI64RE(fn func(string, int64) error) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt64(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + return wrapError(fn(s1, i2)), nil + } +} + +// FuncAIIRE transform a function of 'func(int, int) error' signature +// into CallableFunc type. +func FuncAIIRE(fn func(int, int) error) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + return wrapError(fn(i1, i2)), nil + } +} + +// FuncASIRS transform a function of 'func(string, int) string' signature +// into CallableFunc type. +func FuncASIRS(fn func(string, int) string) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + return &objects.String{Value: fn(s1, i2)}, nil + } +} + +// FuncASIIRE transform a function of 'func(string, int, int) error' signature +// into CallableFunc type. +func FuncASIIRE(fn func(string, int, int) error) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 3 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + } + + return wrapError(fn(s1, i2, i3)), nil + } +} + +// FuncAYRIE transform a function of 'func([]byte) (int, error)' signature +// into CallableFunc type. +func FuncAYRIE(fn func([]byte) (int, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + y1, ok := objects.ToByteSlice(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := fn(y1) + if err != nil { + return wrapError(err), nil + } + + return &objects.Int{Value: int64(res)}, nil + } +} + +// FuncASRIE transform a function of 'func(string) (int, error)' signature +// into CallableFunc type. +func FuncASRIE(fn func(string) (int, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := fn(s1) + if err != nil { + return wrapError(err), nil + } + + return &objects.Int{Value: int64(res)}, nil + } +} + +// FuncAIRSsE transform a function of 'func(int) ([]string, error)' signature +// into CallableFunc type. +func FuncAIRSsE(fn func(int) ([]string, error)) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := fn(i1) + if err != nil { + return wrapError(err), nil + } + + arr := &objects.Array{} + for _, r := range res { + arr.Value = append(arr.Value, &objects.String{Value: r}) + } + + return arr, nil + } +} + +// FuncAIRS transform a function of 'func(int) string' signature +// into CallableFunc type. +func FuncAIRS(fn func(int) string) objects.CallableFunc { + return func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + return &objects.String{Value: fn(i1)}, nil + } +} diff --git a/compiler/stdlib/func_typedefs_test.go b/stdlib/func_typedefs_test.go similarity index 71% rename from compiler/stdlib/func_typedefs_test.go rename to stdlib/func_typedefs_test.go index 3128c01..c78b03f 100644 --- a/compiler/stdlib/func_typedefs_test.go +++ b/stdlib/func_typedefs_test.go @@ -7,215 +7,216 @@ import ( "testing" "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler/stdlib" "github.com/d5/tengo/objects" + "github.com/d5/tengo/stdlib" ) func TestFuncAIR(t *testing.T) { uf := stdlib.FuncAIR(func(int) {}) - ret, err := uf.Call(&objects.Int{Value: 10}) + ret, err := funcCall(uf, &objects.Int{Value: 10}) assert.NoError(t, err) assert.Equal(t, objects.UndefinedValue, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAR(t *testing.T) { uf := stdlib.FuncAR(func() {}) - ret, err := uf.Call() + ret, err := funcCall(uf) assert.NoError(t, err) assert.Equal(t, objects.UndefinedValue, ret) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncARI(t *testing.T) { uf := stdlib.FuncARI(func() int { return 10 }) - ret, err := uf.Call() + ret, err := funcCall(uf) assert.NoError(t, err) assert.Equal(t, &objects.Int{Value: 10}, ret) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncARE(t *testing.T) { uf := stdlib.FuncARE(func() error { return nil }) - ret, err := uf.Call() + ret, err := funcCall(uf) assert.NoError(t, err) assert.Equal(t, objects.TrueValue, ret) uf = stdlib.FuncARE(func() error { return errors.New("some error") }) - ret, err = uf.Call() + ret, err = funcCall(uf) assert.NoError(t, err) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncARIsE(t *testing.T) { uf := stdlib.FuncARIsE(func() ([]int, error) { return []int{1, 2, 3}, nil }) - ret, err := uf.Call() + ret, err := funcCall(uf) assert.NoError(t, err) assert.Equal(t, array(&objects.Int{Value: 1}, &objects.Int{Value: 2}, &objects.Int{Value: 3}), ret) uf = stdlib.FuncARIsE(func() ([]int, error) { return nil, errors.New("some error") }) - ret, err = uf.Call() + ret, err = funcCall(uf) assert.NoError(t, err) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncARS(t *testing.T) { uf := stdlib.FuncARS(func() string { return "foo" }) - ret, err := uf.Call() + ret, err := funcCall(uf) assert.NoError(t, err) assert.Equal(t, &objects.String{Value: "foo"}, ret) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncARSE(t *testing.T) { uf := stdlib.FuncARSE(func() (string, error) { return "foo", nil }) - ret, err := uf.Call() + ret, err := funcCall(uf) assert.NoError(t, err) assert.Equal(t, &objects.String{Value: "foo"}, ret) uf = stdlib.FuncARSE(func() (string, error) { return "", errors.New("some error") }) - ret, err = uf.Call() + ret, err = funcCall(uf) assert.NoError(t, err) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncARSs(t *testing.T) { uf := stdlib.FuncARSs(func() []string { return []string{"foo", "bar"} }) - ret, err := uf.Call() + ret, err := funcCall(uf) assert.NoError(t, err) assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASRE(t *testing.T) { uf := stdlib.FuncASRE(func(a string) error { return nil }) - ret, err := uf.Call(&objects.String{Value: "foo"}) + ret, err := funcCall(uf, &objects.String{Value: "foo"}) assert.NoError(t, err) assert.Equal(t, objects.TrueValue, ret) uf = stdlib.FuncASRE(func(a string) error { return errors.New("some error") }) - ret, err = uf.Call(&objects.String{Value: "foo"}) + ret, err = funcCall(uf, &objects.String{Value: "foo"}) assert.NoError(t, err) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASRS(t *testing.T) { uf := stdlib.FuncASRS(func(a string) string { return a }) - ret, err := uf.Call(&objects.String{Value: "foo"}) + ret, err := funcCall(uf, &objects.String{Value: "foo"}) assert.NoError(t, err) assert.Equal(t, &objects.String{Value: "foo"}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASRSs(t *testing.T) { uf := stdlib.FuncASRSs(func(a string) []string { return []string{a} }) - ret, err := uf.Call(&objects.String{Value: "foo"}) + ret, err := funcCall(uf, &objects.String{Value: "foo"}) assert.NoError(t, err) assert.Equal(t, array(&objects.String{Value: "foo"}), ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASI64RE(t *testing.T) { uf := stdlib.FuncASI64RE(func(a string, b int64) error { return nil }) - ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5}) + ret, err := funcCall(uf, &objects.String{Value: "foo"}, &objects.Int{Value: 5}) assert.NoError(t, err) assert.Equal(t, objects.TrueValue, ret) uf = stdlib.FuncASI64RE(func(a string, b int64) error { return errors.New("some error") }) - ret, err = uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5}) + ret, err = funcCall(uf, &objects.String{Value: "foo"}, &objects.Int{Value: 5}) assert.NoError(t, err) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAIIRE(t *testing.T) { uf := stdlib.FuncAIIRE(func(a, b int) error { return nil }) - ret, err := uf.Call(&objects.Int{Value: 5}, &objects.Int{Value: 7}) + ret, err := funcCall(uf, &objects.Int{Value: 5}, &objects.Int{Value: 7}) assert.NoError(t, err) assert.Equal(t, objects.TrueValue, ret) uf = stdlib.FuncAIIRE(func(a, b int) error { return errors.New("some error") }) - ret, err = uf.Call(&objects.Int{Value: 5}, &objects.Int{Value: 7}) + ret, err = funcCall(uf, &objects.Int{Value: 5}, &objects.Int{Value: 7}) assert.NoError(t, err) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASIIRE(t *testing.T) { uf := stdlib.FuncASIIRE(func(a string, b, c int) error { return nil }) - ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5}, &objects.Int{Value: 7}) + ret, err := funcCall(uf, &objects.String{Value: "foo"}, &objects.Int{Value: 5}, &objects.Int{Value: 7}) assert.NoError(t, err) assert.Equal(t, objects.TrueValue, ret) uf = stdlib.FuncASIIRE(func(a string, b, c int) error { return errors.New("some error") }) - ret, err = uf.Call(&objects.String{Value: "foo"}, &objects.Int{Value: 5}, &objects.Int{Value: 7}) + ret, err = funcCall(uf, &objects.String{Value: "foo"}, &objects.Int{Value: 5}, &objects.Int{Value: 7}) assert.NoError(t, err) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASRSE(t *testing.T) { uf := stdlib.FuncASRSE(func(a string) (string, error) { return a, nil }) - ret, err := uf.Call(&objects.String{Value: "foo"}) + ret, err := funcCall(uf, &objects.String{Value: "foo"}) assert.NoError(t, err) assert.Equal(t, &objects.String{Value: "foo"}, ret) uf = stdlib.FuncASRSE(func(a string) (string, error) { return a, errors.New("some error") }) - ret, err = uf.Call(&objects.String{Value: "foo"}) + ret, err = funcCall(uf, &objects.String{Value: "foo"}) assert.NoError(t, err) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASSRE(t *testing.T) { uf := stdlib.FuncASSRE(func(a, b string) error { return nil }) - ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}) + ret, err := funcCall(uf, &objects.String{Value: "foo"}, &objects.String{Value: "bar"}) assert.NoError(t, err) + assert.Equal(t, objects.TrueValue, ret) uf = stdlib.FuncASSRE(func(a, b string) error { return errors.New("some error") }) - ret, err = uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}) + ret, err = funcCall(uf, &objects.String{Value: "foo"}, &objects.String{Value: "bar"}) assert.NoError(t, err) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = uf.Call(&objects.String{Value: "foo"}) + _, err = funcCall(uf, &objects.String{Value: "foo"}) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASsRS(t *testing.T) { uf := stdlib.FuncASsSRS(func(a []string, b string) string { return strings.Join(a, b) }) - ret, err := uf.Call(array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), &objects.String{Value: " "}) + ret, err := funcCall(uf, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), &objects.String{Value: " "}) assert.NoError(t, err) assert.Equal(t, &objects.String{Value: "foo bar"}, ret) - _, err = uf.Call(&objects.String{Value: "foo"}) + _, err = funcCall(uf, &objects.String{Value: "foo"}) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncARF(t *testing.T) { uf := stdlib.FuncARF(func() float64 { return 10.0 }) - ret, err := uf.Call() + ret, err := funcCall(uf) assert.NoError(t, err) assert.Equal(t, &objects.Float{Value: 10.0}, ret) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAFRF(t *testing.T) { uf := stdlib.FuncAFRF(func(a float64) float64 { return a }) - ret, err := uf.Call(&objects.Float{Value: 10.0}) + ret, err := funcCall(uf, &objects.Float{Value: 10.0}) assert.NoError(t, err) assert.Equal(t, &objects.Float{Value: 10.0}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = uf.Call(objects.TrueValue, objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } @@ -223,12 +224,12 @@ func TestFuncAIRF(t *testing.T) { uf := stdlib.FuncAIRF(func(a int) float64 { return float64(a) }) - ret, err := uf.Call(&objects.Int{Value: 10.0}) + ret, err := funcCall(uf, &objects.Int{Value: 10.0}) assert.NoError(t, err) assert.Equal(t, &objects.Float{Value: 10.0}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = uf.Call(objects.TrueValue, objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } @@ -236,12 +237,12 @@ func TestFuncAFRI(t *testing.T) { uf := stdlib.FuncAFRI(func(a float64) int { return int(a) }) - ret, err := uf.Call(&objects.Float{Value: 10.5}) + ret, err := funcCall(uf, &objects.Float{Value: 10.5}) assert.NoError(t, err) assert.Equal(t, &objects.Int{Value: 10}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = uf.Call(objects.TrueValue, objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } @@ -249,12 +250,12 @@ func TestFuncAFRB(t *testing.T) { uf := stdlib.FuncAFRB(func(a float64) bool { return a > 0.0 }) - ret, err := uf.Call(&objects.Float{Value: 0.1}) + ret, err := funcCall(uf, &objects.Float{Value: 0.1}) assert.NoError(t, err) assert.Equal(t, objects.TrueValue, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = uf.Call(objects.TrueValue, objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } @@ -262,23 +263,23 @@ func TestFuncAFFRF(t *testing.T) { uf := stdlib.FuncAFFRF(func(a, b float64) float64 { return a + b }) - ret, err := uf.Call(&objects.Float{Value: 10.0}, &objects.Float{Value: 20.0}) + ret, err := funcCall(uf, &objects.Float{Value: 10.0}, &objects.Float{Value: 20.0}) assert.NoError(t, err) assert.Equal(t, &objects.Float{Value: 30.0}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASIRS(t *testing.T) { uf := stdlib.FuncASIRS(func(a string, b int) string { return strings.Repeat(a, b) }) - ret, err := uf.Call(&objects.String{Value: "ab"}, &objects.Int{Value: 2}) + ret, err := funcCall(uf, &objects.String{Value: "ab"}, &objects.Int{Value: 2}) assert.NoError(t, err) assert.Equal(t, &objects.String{Value: "abab"}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } @@ -286,12 +287,12 @@ func TestFuncAIFRF(t *testing.T) { uf := stdlib.FuncAIFRF(func(a int, b float64) float64 { return float64(a) + b }) - ret, err := uf.Call(&objects.Int{Value: 10}, &objects.Float{Value: 20.0}) + ret, err := funcCall(uf, &objects.Int{Value: 10}, &objects.Float{Value: 20.0}) assert.NoError(t, err) assert.Equal(t, &objects.Float{Value: 30.0}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } @@ -299,12 +300,12 @@ func TestFuncAFIRF(t *testing.T) { uf := stdlib.FuncAFIRF(func(a float64, b int) float64 { return a + float64(b) }) - ret, err := uf.Call(&objects.Float{Value: 10.0}, &objects.Int{Value: 20}) + ret, err := funcCall(uf, &objects.Float{Value: 10.0}, &objects.Int{Value: 20}) assert.NoError(t, err) assert.Equal(t, &objects.Float{Value: 30.0}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } @@ -312,12 +313,12 @@ func TestFuncAFIRB(t *testing.T) { uf := stdlib.FuncAFIRB(func(a float64, b int) bool { return a < float64(b) }) - ret, err := uf.Call(&objects.Float{Value: 10.0}, &objects.Int{Value: 20}) + ret, err := funcCall(uf, &objects.Float{Value: 10.0}, &objects.Int{Value: 20}) assert.NoError(t, err) assert.Equal(t, objects.TrueValue, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } @@ -325,43 +326,43 @@ func TestFuncAIRSsE(t *testing.T) { uf := stdlib.FuncAIRSsE(func(a int) ([]string, error) { return []string{"foo", "bar"}, nil }) - ret, err := uf.Call(&objects.Int{Value: 10}) + ret, err := funcCall(uf, &objects.Int{Value: 10}) assert.NoError(t, err) assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret) uf = stdlib.FuncAIRSsE(func(a int) ([]string, error) { return nil, errors.New("some error") }) - ret, err = uf.Call(&objects.Int{Value: 10}) + ret, err = funcCall(uf, &objects.Int{Value: 10}) assert.NoError(t, err) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASSRSs(t *testing.T) { uf := stdlib.FuncASSRSs(func(a, b string) []string { return []string{a, b} }) - ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}) + ret, err := funcCall(uf, &objects.String{Value: "foo"}, &objects.String{Value: "bar"}) assert.NoError(t, err) assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASSIRSs(t *testing.T) { uf := stdlib.FuncASSIRSs(func(a, b string, c int) []string { return []string{a, b, strconv.Itoa(c)} }) - ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}, &objects.Int{Value: 5}) + ret, err := funcCall(uf, &objects.String{Value: "foo"}, &objects.String{Value: "bar"}, &objects.Int{Value: 5}) assert.NoError(t, err) assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}, &objects.String{Value: "5"}), ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncARB(t *testing.T) { uf := stdlib.FuncARB(func() bool { return true }) - ret, err := uf.Call() + ret, err := funcCall(uf) assert.NoError(t, err) assert.Equal(t, objects.TrueValue, ret) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } @@ -369,126 +370,131 @@ func TestFuncARYE(t *testing.T) { uf := stdlib.FuncARYE(func() ([]byte, error) { return []byte("foo bar"), nil }) - ret, err := uf.Call() + ret, err := funcCall(uf) assert.NoError(t, err) assert.Equal(t, &objects.Bytes{Value: []byte("foo bar")}, ret) uf = stdlib.FuncARYE(func() ([]byte, error) { return nil, errors.New("some error") }) - ret, err = uf.Call() + ret, err = funcCall(uf) assert.NoError(t, err) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = uf.Call(objects.TrueValue) + _, err = funcCall(uf, objects.TrueValue) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASRIE(t *testing.T) { uf := stdlib.FuncASRIE(func(a string) (int, error) { return 5, nil }) - ret, err := uf.Call(&objects.String{Value: "foo"}) + ret, err := funcCall(uf, &objects.String{Value: "foo"}) assert.NoError(t, err) assert.Equal(t, &objects.Int{Value: 5}, ret) uf = stdlib.FuncASRIE(func(a string) (int, error) { return 0, errors.New("some error") }) - ret, err = uf.Call(&objects.String{Value: "foo"}) + ret, err = funcCall(uf, &objects.String{Value: "foo"}) assert.NoError(t, err) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAYRIE(t *testing.T) { uf := stdlib.FuncAYRIE(func(a []byte) (int, error) { return 5, nil }) - ret, err := uf.Call(&objects.Bytes{Value: []byte("foo")}) + ret, err := funcCall(uf, &objects.Bytes{Value: []byte("foo")}) assert.NoError(t, err) assert.Equal(t, &objects.Int{Value: 5}, ret) uf = stdlib.FuncAYRIE(func(a []byte) (int, error) { return 0, errors.New("some error") }) - ret, err = uf.Call(&objects.Bytes{Value: []byte("foo")}) + ret, err = funcCall(uf, &objects.Bytes{Value: []byte("foo")}) assert.NoError(t, err) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASSRI(t *testing.T) { uf := stdlib.FuncASSRI(func(a, b string) int { return len(a) + len(b) }) - ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}) + ret, err := funcCall(uf, &objects.String{Value: "foo"}, &objects.String{Value: "bar"}) assert.NoError(t, err) assert.Equal(t, &objects.Int{Value: 6}, ret) - _, err = uf.Call(&objects.String{Value: "foo"}) + _, err = funcCall(uf, &objects.String{Value: "foo"}) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASSRS(t *testing.T) { uf := stdlib.FuncASSRS(func(a, b string) string { return a + b }) - ret, err := uf.Call(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}) + ret, err := funcCall(uf, &objects.String{Value: "foo"}, &objects.String{Value: "bar"}) assert.NoError(t, err) assert.Equal(t, &objects.String{Value: "foobar"}, ret) - _, err = uf.Call(&objects.String{Value: "foo"}) + _, err = funcCall(uf, &objects.String{Value: "foo"}) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASSRB(t *testing.T) { uf := stdlib.FuncASSRB(func(a, b string) bool { return len(a) > len(b) }) - ret, err := uf.Call(&objects.String{Value: "123"}, &objects.String{Value: "12"}) + ret, err := funcCall(uf, &objects.String{Value: "123"}, &objects.String{Value: "12"}) assert.NoError(t, err) assert.Equal(t, objects.TrueValue, ret) - _, err = uf.Call(&objects.String{Value: "foo"}) + _, err = funcCall(uf, &objects.String{Value: "foo"}) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAIRS(t *testing.T) { uf := stdlib.FuncAIRS(func(a int) string { return strconv.Itoa(a) }) - ret, err := uf.Call(&objects.Int{Value: 55}) + ret, err := funcCall(uf, &objects.Int{Value: 55}) assert.NoError(t, err) assert.Equal(t, &objects.String{Value: "55"}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAIRIs(t *testing.T) { uf := stdlib.FuncAIRIs(func(a int) []int { return []int{a, a} }) - ret, err := uf.Call(&objects.Int{Value: 55}) + ret, err := funcCall(uf, &objects.Int{Value: 55}) assert.NoError(t, err) assert.Equal(t, array(&objects.Int{Value: 55}, &objects.Int{Value: 55}), ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAI64R(t *testing.T) { uf := stdlib.FuncAIR(func(a int) {}) - ret, err := uf.Call(&objects.Int{Value: 55}) + ret, err := funcCall(uf, &objects.Int{Value: 55}) assert.NoError(t, err) assert.Equal(t, objects.UndefinedValue, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncARI64(t *testing.T) { uf := stdlib.FuncARI64(func() int64 { return 55 }) - ret, err := uf.Call() + ret, err := funcCall(uf) assert.NoError(t, err) assert.Equal(t, &objects.Int{Value: 55}, ret) - _, err = uf.Call(&objects.Int{Value: 55}) + _, err = funcCall(uf, &objects.Int{Value: 55}) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncASsSRS(t *testing.T) { uf := stdlib.FuncASsSRS(func(a []string, b string) string { return strings.Join(a, b) }) - ret, err := uf.Call(array(&objects.String{Value: "abc"}, &objects.String{Value: "def"}), &objects.String{Value: "-"}) + ret, err := funcCall(uf, array(&objects.String{Value: "abc"}, &objects.String{Value: "def"}), &objects.String{Value: "-"}) assert.NoError(t, err) assert.Equal(t, &objects.String{Value: "abc-def"}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } func TestFuncAI64RI64(t *testing.T) { uf := stdlib.FuncAI64RI64(func(a int64) int64 { return a * 2 }) - ret, err := uf.Call(&objects.Int{Value: 55}) + ret, err := funcCall(uf, &objects.Int{Value: 55}) assert.NoError(t, err) assert.Equal(t, &objects.Int{Value: 110}, ret) - _, err = uf.Call() + _, err = funcCall(uf) assert.Equal(t, objects.ErrWrongNumArguments, err) } +func funcCall(fn objects.CallableFunc, args ...objects.Object) (objects.Object, error) { + userFunc := &objects.UserFunction{Value: fn} + return userFunc.Call(args...) +} + func array(elements ...objects.Object) *objects.Array { return &objects.Array{Value: elements} } diff --git a/stdlib/math.go b/stdlib/math.go new file mode 100644 index 0000000..08d82bd --- /dev/null +++ b/stdlib/math.go @@ -0,0 +1,74 @@ +package stdlib + +import ( + "math" + + "github.com/d5/tengo/objects" +) + +var mathModule = map[string]objects.Object{ + "e": &objects.Float{Value: math.E}, + "pi": &objects.Float{Value: math.Pi}, + "phi": &objects.Float{Value: math.Phi}, + "sqrt2": &objects.Float{Value: math.Sqrt2}, + "sqrtE": &objects.Float{Value: math.SqrtE}, + "sqrtPi": &objects.Float{Value: math.SqrtPi}, + "sqrtPhi": &objects.Float{Value: math.SqrtPhi}, + "ln2": &objects.Float{Value: math.Ln2}, + "log2E": &objects.Float{Value: math.Log2E}, + "ln10": &objects.Float{Value: math.Ln10}, + "log10E": &objects.Float{Value: math.Log10E}, + "abs": &objects.UserFunction{Name: "abs", Value: FuncAFRF(math.Abs)}, + "acos": &objects.UserFunction{Name: "acos", Value: FuncAFRF(math.Acos)}, + "acosh": &objects.UserFunction{Name: "acosh", Value: FuncAFRF(math.Acosh)}, + "asin": &objects.UserFunction{Name: "asin", Value: FuncAFRF(math.Asin)}, + "asinh": &objects.UserFunction{Name: "asinh", Value: FuncAFRF(math.Asinh)}, + "atan": &objects.UserFunction{Name: "atan", Value: FuncAFRF(math.Atan)}, + "atan2": &objects.UserFunction{Name: "atan2", Value: FuncAFFRF(math.Atan2)}, + "atanh": &objects.UserFunction{Name: "atanh", Value: FuncAFRF(math.Atanh)}, + "cbrt": &objects.UserFunction{Name: "cbrt", Value: FuncAFRF(math.Cbrt)}, + "ceil": &objects.UserFunction{Name: "ceil", Value: FuncAFRF(math.Ceil)}, + "copysign": &objects.UserFunction{Name: "copysign", Value: FuncAFFRF(math.Copysign)}, + "cos": &objects.UserFunction{Name: "cos", Value: FuncAFRF(math.Cos)}, + "cosh": &objects.UserFunction{Name: "cosh", Value: FuncAFRF(math.Cosh)}, + "dim": &objects.UserFunction{Name: "dim", Value: FuncAFFRF(math.Dim)}, + "erf": &objects.UserFunction{Name: "erf", Value: FuncAFRF(math.Erf)}, + "erfc": &objects.UserFunction{Name: "erfc", Value: FuncAFRF(math.Erfc)}, + "exp": &objects.UserFunction{Name: "exp", Value: FuncAFRF(math.Exp)}, + "exp2": &objects.UserFunction{Name: "exp2", Value: FuncAFRF(math.Exp2)}, + "expm1": &objects.UserFunction{Name: "expm1", Value: FuncAFRF(math.Expm1)}, + "floor": &objects.UserFunction{Name: "floor", Value: FuncAFRF(math.Floor)}, + "gamma": &objects.UserFunction{Name: "gamma", Value: FuncAFRF(math.Gamma)}, + "hypot": &objects.UserFunction{Name: "hypot", Value: FuncAFFRF(math.Hypot)}, + "ilogb": &objects.UserFunction{Name: "ilogb", Value: FuncAFRI(math.Ilogb)}, + "inf": &objects.UserFunction{Name: "inf", Value: FuncAIRF(math.Inf)}, + "is_inf": &objects.UserFunction{Name: "is_inf", Value: FuncAFIRB(math.IsInf)}, + "is_nan": &objects.UserFunction{Name: "is_nan", Value: FuncAFRB(math.IsNaN)}, + "j0": &objects.UserFunction{Name: "j0", Value: FuncAFRF(math.J0)}, + "j1": &objects.UserFunction{Name: "j1", Value: FuncAFRF(math.J1)}, + "jn": &objects.UserFunction{Name: "jn", Value: FuncAIFRF(math.Jn)}, + "ldexp": &objects.UserFunction{Name: "ldexp", Value: FuncAFIRF(math.Ldexp)}, + "log": &objects.UserFunction{Name: "log", Value: FuncAFRF(math.Log)}, + "log10": &objects.UserFunction{Name: "log10", Value: FuncAFRF(math.Log10)}, + "log1p": &objects.UserFunction{Name: "log1p", Value: FuncAFRF(math.Log1p)}, + "log2": &objects.UserFunction{Name: "log2", Value: FuncAFRF(math.Log2)}, + "logb": &objects.UserFunction{Name: "logb", Value: FuncAFRF(math.Logb)}, + "max": &objects.UserFunction{Name: "max", Value: FuncAFFRF(math.Max)}, + "min": &objects.UserFunction{Name: "min", Value: FuncAFFRF(math.Min)}, + "mod": &objects.UserFunction{Name: "mod", Value: FuncAFFRF(math.Mod)}, + "nan": &objects.UserFunction{Name: "nan", Value: FuncARF(math.NaN)}, + "nextafter": &objects.UserFunction{Name: "nextafter", Value: FuncAFFRF(math.Nextafter)}, + "pow": &objects.UserFunction{Name: "pow", Value: FuncAFFRF(math.Pow)}, + "pow10": &objects.UserFunction{Name: "pow10", Value: FuncAIRF(math.Pow10)}, + "remainder": &objects.UserFunction{Name: "remainder", Value: FuncAFFRF(math.Remainder)}, + "signbit": &objects.UserFunction{Name: "signbit", Value: FuncAFRB(math.Signbit)}, + "sin": &objects.UserFunction{Name: "sin", Value: FuncAFRF(math.Sin)}, + "sinh": &objects.UserFunction{Name: "sinh", Value: FuncAFRF(math.Sinh)}, + "sqrt": &objects.UserFunction{Name: "sqrt", Value: FuncAFRF(math.Sqrt)}, + "tan": &objects.UserFunction{Name: "tan", Value: FuncAFRF(math.Tan)}, + "tanh": &objects.UserFunction{Name: "tanh", Value: FuncAFRF(math.Tanh)}, + "trunc": &objects.UserFunction{Name: "trunc", Value: FuncAFRF(math.Trunc)}, + "y0": &objects.UserFunction{Name: "y0", Value: FuncAFRF(math.Y0)}, + "y1": &objects.UserFunction{Name: "y1", Value: FuncAFRF(math.Y1)}, + "yn": &objects.UserFunction{Name: "yn", Value: FuncAIFRF(math.Yn)}, +} diff --git a/stdlib/os.go b/stdlib/os.go new file mode 100644 index 0000000..e68d510 --- /dev/null +++ b/stdlib/os.go @@ -0,0 +1,437 @@ +package stdlib + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "os/exec" + + "github.com/d5/tengo/objects" +) + +var osModule = map[string]objects.Object{ + "o_rdonly": &objects.Int{Value: int64(os.O_RDONLY)}, + "o_wronly": &objects.Int{Value: int64(os.O_WRONLY)}, + "o_rdwr": &objects.Int{Value: int64(os.O_RDWR)}, + "o_append": &objects.Int{Value: int64(os.O_APPEND)}, + "o_create": &objects.Int{Value: int64(os.O_CREATE)}, + "o_excl": &objects.Int{Value: int64(os.O_EXCL)}, + "o_sync": &objects.Int{Value: int64(os.O_SYNC)}, + "o_trunc": &objects.Int{Value: int64(os.O_TRUNC)}, + "mode_dir": &objects.Int{Value: int64(os.ModeDir)}, + "mode_append": &objects.Int{Value: int64(os.ModeAppend)}, + "mode_exclusive": &objects.Int{Value: int64(os.ModeExclusive)}, + "mode_temporary": &objects.Int{Value: int64(os.ModeTemporary)}, + "mode_symlink": &objects.Int{Value: int64(os.ModeSymlink)}, + "mode_device": &objects.Int{Value: int64(os.ModeDevice)}, + "mode_named_pipe": &objects.Int{Value: int64(os.ModeNamedPipe)}, + "mode_socket": &objects.Int{Value: int64(os.ModeSocket)}, + "mode_setuid": &objects.Int{Value: int64(os.ModeSetuid)}, + "mode_setgui": &objects.Int{Value: int64(os.ModeSetgid)}, + "mode_char_device": &objects.Int{Value: int64(os.ModeCharDevice)}, + "mode_sticky": &objects.Int{Value: int64(os.ModeSticky)}, + "mode_type": &objects.Int{Value: int64(os.ModeType)}, + "mode_perm": &objects.Int{Value: int64(os.ModePerm)}, + "path_separator": &objects.Char{Value: os.PathSeparator}, + "path_list_separator": &objects.Char{Value: os.PathListSeparator}, + "dev_null": &objects.String{Value: os.DevNull}, + "seek_set": &objects.Int{Value: int64(io.SeekStart)}, + "seek_cur": &objects.Int{Value: int64(io.SeekCurrent)}, + "seek_end": &objects.Int{Value: int64(io.SeekEnd)}, + "args": &objects.UserFunction{Value: osArgs}, // args() => array(string) + "chdir": &objects.UserFunction{Name: "chdir", Value: FuncASRE(os.Chdir)}, // chdir(dir string) => error + "chmod": osFuncASFmRE(os.Chmod), // chmod(name string, mode int) => error + "chown": &objects.UserFunction{Name: "chown", Value: FuncASIIRE(os.Chown)}, // chown(name string, uid int, gid int) => error + "clearenv": &objects.UserFunction{Name: "clearenv", Value: FuncAR(os.Clearenv)}, // clearenv() + "environ": &objects.UserFunction{Name: "environ", Value: FuncARSs(os.Environ)}, // environ() => array(string) + "exit": &objects.UserFunction{Name: "exit", Value: FuncAIR(os.Exit)}, // exit(code int) + "expand_env": &objects.UserFunction{Name: "expand_env", Value: FuncASRS(os.ExpandEnv)}, // expand_env(s string) => string + "getegid": &objects.UserFunction{Name: "getegid", Value: FuncARI(os.Getegid)}, // getegid() => int + "getenv": &objects.UserFunction{Name: "getenv", Value: FuncASRS(os.Getenv)}, // getenv(s string) => string + "geteuid": &objects.UserFunction{Name: "geteuid", Value: FuncARI(os.Geteuid)}, // geteuid() => int + "getgid": &objects.UserFunction{Name: "getgid", Value: FuncARI(os.Getgid)}, // getgid() => int + "getgroups": &objects.UserFunction{Name: "getgroups", Value: FuncARIsE(os.Getgroups)}, // getgroups() => array(string)/error + "getpagesize": &objects.UserFunction{Name: "getpagesize", Value: FuncARI(os.Getpagesize)}, // getpagesize() => int + "getpid": &objects.UserFunction{Name: "getpid", Value: FuncARI(os.Getpid)}, // getpid() => int + "getppid": &objects.UserFunction{Name: "getppid", Value: FuncARI(os.Getppid)}, // getppid() => int + "getuid": &objects.UserFunction{Name: "getuid", Value: FuncARI(os.Getuid)}, // getuid() => int + "getwd": &objects.UserFunction{Name: "getwd", Value: FuncARSE(os.Getwd)}, // getwd() => string/error + "hostname": &objects.UserFunction{Name: "hostname", Value: FuncARSE(os.Hostname)}, // hostname() => string/error + "lchown": &objects.UserFunction{Name: "lchown", Value: FuncASIIRE(os.Lchown)}, // lchown(name string, uid int, gid int) => error + "link": &objects.UserFunction{Name: "link", Value: FuncASSRE(os.Link)}, // link(oldname string, newname string) => error + "lookup_env": &objects.UserFunction{Value: osLookupEnv}, // lookup_env(key string) => string/false + "mkdir": osFuncASFmRE(os.Mkdir), // mkdir(name string, perm int) => error + "mkdir_all": osFuncASFmRE(os.MkdirAll), // mkdir_all(name string, perm int) => error + "readlink": &objects.UserFunction{Name: "readlink", Value: FuncASRSE(os.Readlink)}, // readlink(name string) => string/error + "remove": &objects.UserFunction{Name: "remove", Value: FuncASRE(os.Remove)}, // remove(name string) => error + "remove_all": &objects.UserFunction{Name: "remove_all", Value: FuncASRE(os.RemoveAll)}, // remove_all(name string) => error + "rename": &objects.UserFunction{Name: "rename", Value: FuncASSRE(os.Rename)}, // rename(oldpath string, newpath string) => error + "setenv": &objects.UserFunction{Name: "setenv", Value: FuncASSRE(os.Setenv)}, // setenv(key string, value string) => error + "symlink": &objects.UserFunction{Name: "symlink", Value: FuncASSRE(os.Symlink)}, // symlink(oldname string newname string) => error + "temp_dir": &objects.UserFunction{Name: "temp_dir", Value: FuncARS(os.TempDir)}, // temp_dir() => string + "truncate": &objects.UserFunction{Name: "truncate", Value: FuncASI64RE(os.Truncate)}, // truncate(name string, size int) => error + "unsetenv": &objects.UserFunction{Name: "unsetenv", Value: FuncASRE(os.Unsetenv)}, // unsetenv(key string) => error + "create": &objects.UserFunction{Value: osCreate}, // create(name string) => imap(file)/error + "open": &objects.UserFunction{Value: osOpen}, // open(name string) => imap(file)/error + "open_file": &objects.UserFunction{Value: osOpenFile}, // open_file(name string, flag int, perm int) => imap(file)/error + "find_process": &objects.UserFunction{Value: osFindProcess}, // find_process(pid int) => imap(process)/error + "start_process": &objects.UserFunction{Value: osStartProcess}, // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error + "exec_look_path": &objects.UserFunction{Name: "exec_look_path", Value: FuncASRSE(exec.LookPath)}, // exec_look_path(file) => string/error + "exec": &objects.UserFunction{Value: osExec}, // exec(name, args...) => command + "stat": &objects.UserFunction{Value: osStat}, // stat(name) => imap(fileinfo)/error + "read_file": &objects.UserFunction{Value: osReadFile}, // readfile(name) => array(byte)/error +} + +func osReadFile(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + fname, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + bytes, err := ioutil.ReadFile(fname) + if err != nil { + return wrapError(err), nil + } + + return &objects.Bytes{Value: bytes}, nil +} + +func osStat(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + fname, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + stat, err := os.Stat(fname) + if err != nil { + return wrapError(err), nil + } + + fstat := &objects.ImmutableMap{ + Value: map[string]objects.Object{ + "name": &objects.String{Value: stat.Name()}, + "mtime": &objects.Time{Value: stat.ModTime()}, + "size": &objects.Int{Value: stat.Size()}, + "mode": &objects.Int{Value: int64(stat.Mode())}, + }, + } + + if stat.IsDir() { + fstat.Value["directory"] = objects.TrueValue + } else { + fstat.Value["directory"] = objects.FalseValue + } + + return fstat, nil +} + +func osCreate(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := os.Create(s1) + if err != nil { + return wrapError(err), nil + } + + return makeOSFile(res), nil +} + +func osOpen(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, err := os.Open(s1) + if err != nil { + return wrapError(err), nil + } + + return makeOSFile(res), nil +} + +func osOpenFile(args ...objects.Object) (objects.Object, error) { + if len(args) != 3 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + } + + res, err := os.OpenFile(s1, i2, os.FileMode(i3)) + if err != nil { + return wrapError(err), nil + } + + return makeOSFile(res), nil +} + +func osArgs(args ...objects.Object) (objects.Object, error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + arr := &objects.Array{} + for _, osArg := range os.Args { + arr.Value = append(arr.Value, &objects.String{Value: osArg}) + } + + return arr, nil +} + +func osFuncASFmRE(fn func(string, os.FileMode) error) *objects.UserFunction { + return &objects.UserFunction{ + Value: func(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + i2, ok := objects.ToInt64(args[1]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + } + + return wrapError(fn(s1, os.FileMode(i2))), nil + }, + } +} + +func osLookupEnv(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + s1, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + res, ok := os.LookupEnv(s1) + if !ok { + return objects.FalseValue, nil + } + + return &objects.String{Value: res}, nil +} + +func osExec(args ...objects.Object) (objects.Object, error) { + if len(args) == 0 { + return nil, objects.ErrWrongNumArguments + } + + name, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + var execArgs []string + for idx, arg := range args[1:] { + execArg, ok := objects.ToString(arg) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: fmt.Sprintf("args[%d]", idx), + Expected: "string(compatible)", + Found: args[1+idx].TypeName(), + } + } + + execArgs = append(execArgs, execArg) + } + + return makeOSExecCommand(exec.Command(name, execArgs...)), nil +} + +func osFindProcess(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + proc, err := os.FindProcess(i1) + if err != nil { + return wrapError(err), nil + } + + return makeOSProcess(proc), nil +} + +func osStartProcess(args ...objects.Object) (objects.Object, error) { + if len(args) != 4 { + return nil, objects.ErrWrongNumArguments + } + + name, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + } + + var argv []string + var err error + switch arg1 := args[1].(type) { + case *objects.Array: + argv, err = stringArray(arg1.Value, "second") + if err != nil { + return nil, err + } + case *objects.ImmutableArray: + argv, err = stringArray(arg1.Value, "second") + if err != nil { + return nil, err + } + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "array", + Found: arg1.TypeName(), + } + } + + dir, ok := objects.ToString(args[2]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + } + + var env []string + switch arg3 := args[3].(type) { + case *objects.Array: + env, err = stringArray(arg3.Value, "fourth") + if err != nil { + return nil, err + } + case *objects.ImmutableArray: + env, err = stringArray(arg3.Value, "fourth") + if err != nil { + return nil, err + } + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "array", + Found: arg3.TypeName(), + } + } + + proc, err := os.StartProcess(name, argv, &os.ProcAttr{ + Dir: dir, + Env: env, + }) + if err != nil { + return wrapError(err), nil + } + + return makeOSProcess(proc), nil +} + +func stringArray(arr []objects.Object, argName string) ([]string, error) { + var sarr []string + for idx, elem := range arr { + str, ok := elem.(*objects.String) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: fmt.Sprintf("%s[%d]", argName, idx), + Expected: "string", + Found: elem.TypeName(), + } + } + + sarr = append(sarr, str.Value) + } + + return sarr, nil +} diff --git a/compiler/stdlib/os_exec.go b/stdlib/os_exec.go similarity index 53% rename from compiler/stdlib/os_exec.go rename to stdlib/os_exec.go index b2bc743..809c581 100644 --- a/compiler/stdlib/os_exec.go +++ b/stdlib/os_exec.go @@ -10,15 +10,15 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap { return &objects.ImmutableMap{ Value: map[string]objects.Object{ // combined_output() => bytes/error - "combined_output": FuncARYE(cmd.CombinedOutput), + "combined_output": &objects.UserFunction{Name: "combined_output", Value: FuncARYE(cmd.CombinedOutput)}, // // output() => bytes/error - "output": FuncARYE(cmd.Output), + "output": &objects.UserFunction{Name: "output", Value: FuncARYE(cmd.Output)}, // // run() => error - "run": FuncARE(cmd.Run), + "run": &objects.UserFunction{Name: "run", Value: FuncARE(cmd.Run)}, // // start() => error - "start": FuncARE(cmd.Start), + "start": &objects.UserFunction{Name: "start", Value: FuncARE(cmd.Start)}, // // wait() => error - "wait": FuncARE(cmd.Wait), + "wait": &objects.UserFunction{Name: "wait", Value: FuncARE(cmd.Wait)}, // // set_path(path string) "set_path": &objects.UserFunction{ Value: func(args ...objects.Object) (ret objects.Object, err error) { @@ -28,7 +28,11 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap { s1, ok := objects.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidTypeConversion + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } } cmd.Path = s1 @@ -45,7 +49,11 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap { s1, ok := objects.ToString(args[0]) if !ok { - return nil, objects.ErrInvalidTypeConversion + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } } cmd.Dir = s1 @@ -55,17 +63,33 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap { }, // set_env(env array(string)) "set_env": &objects.UserFunction{ - Value: func(args ...objects.Object) (ret objects.Object, err error) { + Value: func(args ...objects.Object) (objects.Object, error) { if len(args) != 1 { return nil, objects.ErrWrongNumArguments } - envs, err := stringArray(args[0]) - if err != nil { - return nil, err + var env []string + var err error + switch arg0 := args[0].(type) { + case *objects.Array: + env, err = stringArray(arg0.Value, "first") + if err != nil { + return nil, err + } + case *objects.ImmutableArray: + env, err = stringArray(arg0.Value, "first") + if err != nil { + return nil, err + } + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "array", + Found: arg0.TypeName(), + } } - cmd.Env = envs + cmd.Env = env return objects.UndefinedValue, nil }, diff --git a/compiler/stdlib/os_file.go b/stdlib/os_file.go similarity index 56% rename from compiler/stdlib/os_file.go rename to stdlib/os_file.go index de91d7a..4fc41dd 100644 --- a/compiler/stdlib/os_file.go +++ b/stdlib/os_file.go @@ -10,23 +10,23 @@ func makeOSFile(file *os.File) *objects.ImmutableMap { return &objects.ImmutableMap{ Value: map[string]objects.Object{ // chdir() => true/error - "chdir": FuncARE(file.Chdir), + "chdir": &objects.UserFunction{Name: "chdir", Value: FuncARE(file.Chdir)}, // // chown(uid int, gid int) => true/error - "chown": FuncAIIRE(file.Chown), + "chown": &objects.UserFunction{Name: "chown", Value: FuncAIIRE(file.Chown)}, // // close() => error - "close": FuncARE(file.Close), + "close": &objects.UserFunction{Name: "close", Value: FuncARE(file.Close)}, // // name() => string - "name": FuncARS(file.Name), + "name": &objects.UserFunction{Name: "name", Value: FuncARS(file.Name)}, // // readdirnames(n int) => array(string)/error - "readdirnames": FuncAIRSsE(file.Readdirnames), + "readdirnames": &objects.UserFunction{Name: "readdirnames", Value: FuncAIRSsE(file.Readdirnames)}, // // sync() => error - "sync": FuncARE(file.Sync), + "sync": &objects.UserFunction{Name: "sync", Value: FuncARE(file.Sync)}, // // write(bytes) => int/error - "write": FuncAYRIE(file.Write), + "write": &objects.UserFunction{Name: "write", Value: FuncAYRIE(file.Write)}, // // write(string) => int/error - "write_string": FuncASRIE(file.WriteString), + "write_string": &objects.UserFunction{Name: "write_string", Value: FuncASRIE(file.WriteString)}, // // read(bytes) => int/error - "read": FuncAYRIE(file.Read), + "read": &objects.UserFunction{Name: "read", Value: FuncAYRIE(file.Read)}, // // chmod(mode int) => error "chmod": &objects.UserFunction{ Value: func(args ...objects.Object) (ret objects.Object, err error) { @@ -36,7 +36,11 @@ func makeOSFile(file *os.File) *objects.ImmutableMap { i1, ok := objects.ToInt64(args[0]) if !ok { - return nil, objects.ErrInvalidTypeConversion + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } } return wrapError(file.Chmod(os.FileMode(i1))), nil @@ -51,11 +55,19 @@ func makeOSFile(file *os.File) *objects.ImmutableMap { i1, ok := objects.ToInt64(args[0]) if !ok { - return nil, objects.ErrInvalidTypeConversion + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } } i2, ok := objects.ToInt(args[1]) if !ok { - return nil, objects.ErrInvalidTypeConversion + return nil, objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } } res, err := file.Seek(i1, i2) diff --git a/compiler/stdlib/os_process.go b/stdlib/os_process.go similarity index 60% rename from compiler/stdlib/os_process.go rename to stdlib/os_process.go index 5cdc856..0f4a9f7 100644 --- a/compiler/stdlib/os_process.go +++ b/stdlib/os_process.go @@ -10,10 +10,10 @@ import ( func makeOSProcessState(state *os.ProcessState) *objects.ImmutableMap { return &objects.ImmutableMap{ Value: map[string]objects.Object{ - "exited": FuncARB(state.Exited), - "pid": FuncARI(state.Pid), - "string": FuncARS(state.String), - "success": FuncARB(state.Success), + "exited": &objects.UserFunction{Name: "exited", Value: FuncARB(state.Exited)}, // + "pid": &objects.UserFunction{Name: "pid", Value: FuncARI(state.Pid)}, // + "string": &objects.UserFunction{Name: "string", Value: FuncARS(state.String)}, // + "success": &objects.UserFunction{Name: "success", Value: FuncARB(state.Success)}, // }, } } @@ -21,8 +21,8 @@ func makeOSProcessState(state *os.ProcessState) *objects.ImmutableMap { func makeOSProcess(proc *os.Process) *objects.ImmutableMap { return &objects.ImmutableMap{ Value: map[string]objects.Object{ - "kill": FuncARE(proc.Kill), - "release": FuncARE(proc.Release), + "kill": &objects.UserFunction{Name: "kill", Value: FuncARE(proc.Kill)}, // + "release": &objects.UserFunction{Name: "release", Value: FuncARE(proc.Release)}, // "signal": &objects.UserFunction{ Value: func(args ...objects.Object) (ret objects.Object, err error) { if len(args) != 1 { @@ -31,7 +31,11 @@ func makeOSProcess(proc *os.Process) *objects.ImmutableMap { i1, ok := objects.ToInt64(args[0]) if !ok { - return nil, objects.ErrInvalidTypeConversion + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } } return wrapError(proc.Signal(syscall.Signal(i1))), nil diff --git a/compiler/stdlib/os_test.go b/stdlib/os_test.go similarity index 100% rename from compiler/stdlib/os_test.go rename to stdlib/os_test.go diff --git a/stdlib/rand.go b/stdlib/rand.go new file mode 100644 index 0000000..248d8e7 --- /dev/null +++ b/stdlib/rand.go @@ -0,0 +1,99 @@ +package stdlib + +import ( + "math/rand" + + "github.com/d5/tengo/objects" +) + +var randModule = map[string]objects.Object{ + "int": &objects.UserFunction{Name: "int", Value: FuncARI64(rand.Int63)}, + "float": &objects.UserFunction{Name: "float", Value: FuncARF(rand.Float64)}, + "intn": &objects.UserFunction{Name: "intn", Value: FuncAI64RI64(rand.Int63n)}, + "exp_float": &objects.UserFunction{Name: "exp_float", Value: FuncARF(rand.ExpFloat64)}, + "norm_float": &objects.UserFunction{Name: "norm_float", Value: FuncARF(rand.NormFloat64)}, + "perm": &objects.UserFunction{Name: "perm", Value: FuncAIRIs(rand.Perm)}, + "seed": &objects.UserFunction{Name: "seed", Value: FuncAI64R(rand.Seed)}, + "read": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + y1, ok := args[0].(*objects.Bytes) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes", + Found: args[0].TypeName(), + } + } + + res, err := rand.Read(y1.Value) + if err != nil { + ret = wrapError(err) + return + } + + return &objects.Int{Value: int64(res)}, nil + }, + }, + "rand": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } + } + + src := rand.NewSource(i1) + + return randRand(rand.New(src)), nil + }, + }, +} + +func randRand(r *rand.Rand) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + "int": &objects.UserFunction{Name: "int", Value: FuncARI64(r.Int63)}, + "float": &objects.UserFunction{Name: "float", Value: FuncARF(r.Float64)}, + "intn": &objects.UserFunction{Name: "intn", Value: FuncAI64RI64(r.Int63n)}, + "exp_float": &objects.UserFunction{Name: "exp_float", Value: FuncARF(r.ExpFloat64)}, + "norm_float": &objects.UserFunction{Name: "norm_float", Value: FuncARF(r.NormFloat64)}, + "perm": &objects.UserFunction{Name: "perm", Value: FuncAIRIs(r.Perm)}, + "seed": &objects.UserFunction{Name: "seed", Value: FuncAI64R(r.Seed)}, + "read": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + y1, ok := args[0].(*objects.Bytes) + if !ok { + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes", + Found: args[0].TypeName(), + } + } + + res, err := r.Read(y1.Value) + if err != nil { + ret = wrapError(err) + return + } + + return &objects.Int{Value: int64(res)}, nil + }, + }, + }, + } +} diff --git a/compiler/stdlib/rand_test.go b/stdlib/rand_test.go similarity index 100% rename from compiler/stdlib/rand_test.go rename to stdlib/rand_test.go diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go new file mode 100644 index 0000000..d34fbc8 --- /dev/null +++ b/stdlib/stdlib.go @@ -0,0 +1,16 @@ +package stdlib + +import "github.com/d5/tengo/objects" + +// Modules contain the standard modules. +var Modules = map[string]*objects.Object{ + "math": objectPtr(&objects.ImmutableMap{Value: mathModule}), + "os": objectPtr(&objects.ImmutableMap{Value: osModule}), + "text": objectPtr(&objects.ImmutableMap{Value: textModule}), + "times": objectPtr(&objects.ImmutableMap{Value: timesModule}), + "rand": objectPtr(&objects.ImmutableMap{Value: randModule}), +} + +func objectPtr(o objects.Object) *objects.Object { + return &o +} diff --git a/compiler/stdlib/stdlib_test.go b/stdlib/stdlib_test.go similarity index 96% rename from compiler/stdlib/stdlib_test.go rename to stdlib/stdlib_test.go index 16b7728..f0ba6a6 100644 --- a/compiler/stdlib/stdlib_test.go +++ b/stdlib/stdlib_test.go @@ -6,8 +6,8 @@ import ( "time" "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler/stdlib" "github.com/d5/tengo/objects" + "github.com/d5/tengo/stdlib" ) type ARR = []interface{} @@ -66,7 +66,7 @@ func module(t *testing.T, moduleName string) callres { return callres{t: t, e: fmt.Errorf("module not found: %s", moduleName)} } - return callres{t: t, o: mod} + return callres{t: t, o: (*mod).(*objects.ImmutableMap)} } func object(v interface{}) objects.Object { diff --git a/stdlib/text.go b/stdlib/text.go new file mode 100644 index 0000000..053bebf --- /dev/null +++ b/stdlib/text.go @@ -0,0 +1,585 @@ +package stdlib + +import ( + "regexp" + "strconv" + "strings" + + "github.com/d5/tengo/objects" +) + +var textModule = map[string]objects.Object{ + "re_match": &objects.UserFunction{Value: textREMatch}, // re_match(pattern, text) => bool/error + "re_find": &objects.UserFunction{Value: textREFind}, // re_find(pattern, text, count) => [[{text:,begin:,end:}]]/undefined + "re_replace": &objects.UserFunction{Value: textREReplace}, // re_replace(pattern, text, repl) => string/error + "re_split": &objects.UserFunction{Value: textRESplit}, // re_split(pattern, text, count) => [string]/error + "re_compile": &objects.UserFunction{Value: textRECompile}, // re_compile(pattern) => Regexp/error + "compare": &objects.UserFunction{Name: "compare", Value: FuncASSRI(strings.Compare)}, // compare(a, b) => int + "contains": &objects.UserFunction{Name: "contains", Value: FuncASSRB(strings.Contains)}, // contains(s, substr) => bool + "contains_any": &objects.UserFunction{Name: "contains_any", Value: FuncASSRB(strings.ContainsAny)}, // contains_any(s, chars) => bool + "count": &objects.UserFunction{Name: "count", Value: FuncASSRI(strings.Count)}, // count(s, substr) => int + "equal_fold": &objects.UserFunction{Name: "equal_fold", Value: FuncASSRB(strings.EqualFold)}, // "equal_fold(s, t) => bool + "fields": &objects.UserFunction{Name: "fields", Value: FuncASRSs(strings.Fields)}, // fields(s) => [string] + "has_prefix": &objects.UserFunction{Name: "has_prefix", Value: FuncASSRB(strings.HasPrefix)}, // has_prefix(s, prefix) => bool + "has_suffix": &objects.UserFunction{Name: "has_suffix", Value: FuncASSRB(strings.HasSuffix)}, // has_suffix(s, suffix) => bool + "index": &objects.UserFunction{Name: "index", Value: FuncASSRI(strings.Index)}, // index(s, substr) => int + "index_any": &objects.UserFunction{Name: "index_any", Value: FuncASSRI(strings.IndexAny)}, // index_any(s, chars) => int + "join": &objects.UserFunction{Name: "join", Value: FuncASsSRS(strings.Join)}, // join(arr, sep) => string + "last_index": &objects.UserFunction{Name: "last_index", Value: FuncASSRI(strings.LastIndex)}, // last_index(s, substr) => int + "last_index_any": &objects.UserFunction{Name: "last_index_any", Value: FuncASSRI(strings.LastIndexAny)}, // last_index_any(s, chars) => int + "repeat": &objects.UserFunction{Name: "repeat", Value: FuncASIRS(strings.Repeat)}, // repeat(s, count) => string + "replace": &objects.UserFunction{Value: textReplace}, // replace(s, old, new, n) => string + "split": &objects.UserFunction{Name: "split", Value: FuncASSRSs(strings.Split)}, // split(s, sep) => [string] + "split_after": &objects.UserFunction{Name: "split_after", Value: FuncASSRSs(strings.SplitAfter)}, // split_after(s, sep) => [string] + "split_after_n": &objects.UserFunction{Name: "split_after_n", Value: FuncASSIRSs(strings.SplitAfterN)}, // split_after_n(s, sep, n) => [string] + "split_n": &objects.UserFunction{Name: "split_n", Value: FuncASSIRSs(strings.SplitN)}, // split_n(s, sep, n) => [string] + "title": &objects.UserFunction{Name: "title", Value: FuncASRS(strings.Title)}, // title(s) => string + "to_lower": &objects.UserFunction{Name: "to_lower", Value: FuncASRS(strings.ToLower)}, // to_lower(s) => string + "to_title": &objects.UserFunction{Name: "to_title", Value: FuncASRS(strings.ToTitle)}, // to_title(s) => string + "to_upper": &objects.UserFunction{Name: "to_upper", Value: FuncASRS(strings.ToUpper)}, // to_upper(s) => string + "trim_left": &objects.UserFunction{Name: "trim_left", Value: FuncASSRS(strings.TrimLeft)}, // trim_left(s, cutset) => string + "trim_prefix": &objects.UserFunction{Name: "trim_prefix", Value: FuncASSRS(strings.TrimPrefix)}, // trim_prefix(s, prefix) => string + "trim_right": &objects.UserFunction{Name: "trim_right", Value: FuncASSRS(strings.TrimRight)}, // trim_right(s, cutset) => string + "trim_space": &objects.UserFunction{Name: "trim_space", Value: FuncASRS(strings.TrimSpace)}, // trim_space(s) => string + "trim_suffix": &objects.UserFunction{Name: "trim_suffix", Value: FuncASSRS(strings.TrimSuffix)}, // trim_suffix(s, suffix) => string + "atoi": &objects.UserFunction{Name: "atoi", Value: FuncASRIE(strconv.Atoi)}, // atoi(str) => int/error + "format_bool": &objects.UserFunction{Value: textFormatBool}, // format_bool(b) => string + "format_float": &objects.UserFunction{Value: textFormatFloat}, // format_float(f, fmt, prec, bits) => string + "format_int": &objects.UserFunction{Value: textFormatInt}, // format_int(i, base) => string + "itoa": &objects.UserFunction{Name: "itoa", Value: FuncAIRS(strconv.Itoa)}, // itoa(i) => string + "parse_bool": &objects.UserFunction{Value: textParseBool}, // parse_bool(str) => bool/error + "parse_float": &objects.UserFunction{Value: textParseFloat}, // parse_float(str, bits) => float/error + "parse_int": &objects.UserFunction{Value: textParseInt}, // parse_int(str, base, bits) => int/error + "quote": &objects.UserFunction{Name: "quote", Value: FuncASRS(strconv.Quote)}, // quote(str) => string + "unquote": &objects.UserFunction{Name: "unquote", Value: FuncASRSE(strconv.Unquote)}, // unquote(str) => string/error +} + +func textREMatch(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + matched, err := regexp.MatchString(s1, s2) + if err != nil { + ret = wrapError(err) + return + } + + if matched { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return +} + +func textREFind(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs != 2 && numArgs != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + if numArgs < 3 { + m := re.FindStringSubmatchIndex(s2) + if m == nil { + ret = objects.UndefinedValue + return + } + + arr := &objects.Array{} + for i := 0; i < len(m); i += 2 { + arr.Value = append(arr.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ + "text": &objects.String{Value: s2[m[i]:m[i+1]]}, + "begin": &objects.Int{Value: int64(m[i])}, + "end": &objects.Int{Value: int64(m[i+1])}, + }}) + } + + ret = &objects.Array{Value: []objects.Object{arr}} + + return + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + m := re.FindAllStringSubmatchIndex(s2, i3) + if m == nil { + ret = objects.UndefinedValue + return + } + + arr := &objects.Array{} + for _, m := range m { + subMatch := &objects.Array{} + for i := 0; i < len(m); i += 2 { + subMatch.Value = append(subMatch.Value, &objects.ImmutableMap{Value: map[string]objects.Object{ + "text": &objects.String{Value: s2[m[i]:m[i+1]]}, + "begin": &objects.Int{Value: int64(m[i])}, + "end": &objects.Int{Value: int64(m[i+1])}, + }}) + } + + arr.Value = append(arr.Value, subMatch) + } + + ret = arr + + return +} + +func textREReplace(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + s3, ok := objects.ToString(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + return + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + } else { + ret = &objects.String{Value: re.ReplaceAllString(s2, s3)} + } + + return +} + +func textRESplit(args ...objects.Object) (ret objects.Object, err error) { + numArgs := len(args) + if numArgs != 2 && numArgs != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + var i3 = -1 + if numArgs > 2 { + i3, ok = objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + return + } + + arr := &objects.Array{} + for _, s := range re.Split(s2, i3) { + arr.Value = append(arr.Value, &objects.String{Value: s}) + } + + ret = arr + + return +} + +func textRECompile(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + re, err := regexp.Compile(s1) + if err != nil { + ret = wrapError(err) + } else { + ret = makeTextRegexp(re) + } + + return +} + +func textReplace(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 4 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := objects.ToString(args[0]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + s3, ok := objects.ToString(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "string(compatible)", + Found: args[2].TypeName(), + } + return + } + + i4, ok := objects.ToInt(args[3]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } + return + } + + ret = &objects.String{Value: strings.Replace(s1, s2, s3, i4)} + + return +} + +func textFormatBool(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + b1, ok := args[0].(*objects.Bool) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bool", + Found: args[0].TypeName(), + } + return + } + + if b1 == objects.TrueValue { + ret = &objects.String{Value: "true"} + } else { + ret = &objects.String{Value: "false"} + } + + return +} + +func textFormatFloat(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 4 { + err = objects.ErrWrongNumArguments + return + } + + f1, ok := args[0].(*objects.Float) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "float", + Found: args[0].TypeName(), + } + return + } + + s2, ok := objects.ToString(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } + return + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + + i4, ok := objects.ToInt(args[3]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } + return + } + + ret = &objects.String{Value: strconv.FormatFloat(f1.Value, s2[0], i3, i4)} + + return +} + +func textFormatInt(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + i1, ok := args[0].(*objects.Int) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + ret = &objects.String{Value: strconv.FormatInt(i1.Value, i2)} + + return +} + +func textParseBool(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := args[0].(*objects.String) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string", + Found: args[0].TypeName(), + } + return + } + + parsed, err := strconv.ParseBool(s1.Value) + if err != nil { + ret = wrapError(err) + return + } + + if parsed { + ret = objects.TrueValue + } else { + ret = objects.FalseValue + } + + return +} + +func textParseFloat(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := args[0].(*objects.String) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + parsed, err := strconv.ParseFloat(s1.Value, i2) + if err != nil { + ret = wrapError(err) + return + } + + ret = &objects.Float{Value: parsed} + + return +} + +func textParseInt(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 3 { + err = objects.ErrWrongNumArguments + return + } + + s1, ok := args[0].(*objects.String) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string", + Found: args[0].TypeName(), + } + return + } + + i2, ok := objects.ToInt(args[1]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } + return + } + + i3, ok := objects.ToInt(args[2]) + if !ok { + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } + return + } + + parsed, err := strconv.ParseInt(s1.Value, i2, i3) + if err != nil { + ret = wrapError(err) + return + } + + ret = &objects.Int{Value: parsed} + + return +} diff --git a/compiler/stdlib/text_regexp.go b/stdlib/text_regexp.go similarity index 76% rename from compiler/stdlib/text_regexp.go rename to stdlib/text_regexp.go index 94bf2ed..3fb8b3b 100644 --- a/compiler/stdlib/text_regexp.go +++ b/stdlib/text_regexp.go @@ -19,7 +19,11 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { s1, ok := objects.ToString(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } return } @@ -45,7 +49,11 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { s1, ok := objects.ToString(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } return } @@ -72,7 +80,11 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { i2, ok := objects.ToInt(args[1]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } return } m := re.FindAllStringSubmatchIndex(s1, i2) @@ -111,13 +123,21 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { s1, ok := objects.ToString(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } return } s2, ok := objects.ToString(args[1]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } return } @@ -139,7 +159,11 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { s1, ok := objects.ToString(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } return } @@ -147,7 +171,11 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap { if numArgs > 1 { i2, ok = objects.ToInt(args[1]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } return } } diff --git a/compiler/stdlib/text_test.go b/stdlib/text_test.go similarity index 100% rename from compiler/stdlib/text_test.go rename to stdlib/text_test.go diff --git a/compiler/stdlib/times.go b/stdlib/times.go similarity index 52% rename from compiler/stdlib/times.go rename to stdlib/times.go index fd4f7cc..16d6d14 100644 --- a/compiler/stdlib/times.go +++ b/stdlib/times.go @@ -40,41 +40,41 @@ var timesModule = map[string]objects.Object{ "october": &objects.Int{Value: int64(time.October)}, "november": &objects.Int{Value: int64(time.November)}, "december": &objects.Int{Value: int64(time.December)}, - "sleep": &objects.UserFunction{Value: timesSleep}, // sleep(int) - "parse_duration": &objects.UserFunction{Value: timesParseDuration}, // parse_duration(str) => int - "since": &objects.UserFunction{Value: timesSince}, // since(time) => int - "until": &objects.UserFunction{Value: timesUntil}, // until(time) => int - "duration_hours": &objects.UserFunction{Value: timesDurationHours}, // duration_hours(int) => float - "duration_minutes": &objects.UserFunction{Value: timesDurationMinutes}, // duration_minutes(int) => float - "duration_nanoseconds": &objects.UserFunction{Value: timesDurationNanoseconds}, // duration_nanoseconds(int) => int - "duration_seconds": &objects.UserFunction{Value: timesDurationSeconds}, // duration_seconds(int) => float - "duration_string": &objects.UserFunction{Value: timesDurationString}, // duration_string(int) => string - "month_string": &objects.UserFunction{Value: timesMonthString}, // month_string(int) => string - "date": &objects.UserFunction{Value: timesDate}, // date(year, month, day, hour, min, sec, nsec) => time - "now": &objects.UserFunction{Value: timesNow}, // now() => time - "parse": &objects.UserFunction{Value: timesParse}, // parse(format, str) => time - "unix": &objects.UserFunction{Value: timesUnix}, // unix(sec, nsec) => time - "add": &objects.UserFunction{Value: timesAdd}, // add(time, int) => time - "add_date": &objects.UserFunction{Value: timesAddDate}, // add_date(time, years, months, days) => time - "sub": &objects.UserFunction{Value: timesSub}, // sub(t time, u time) => int - "after": &objects.UserFunction{Value: timesAfter}, // after(t time, u time) => bool - "before": &objects.UserFunction{Value: timesBefore}, // before(t time, u time) => bool - "time_year": &objects.UserFunction{Value: timesTimeYear}, // time_year(time) => int - "time_month": &objects.UserFunction{Value: timesTimeMonth}, // time_month(time) => int - "time_day": &objects.UserFunction{Value: timesTimeDay}, // time_day(time) => int - "time_weekday": &objects.UserFunction{Value: timesTimeWeekday}, // time_weekday(time) => int - "time_hour": &objects.UserFunction{Value: timesTimeHour}, // time_hour(time) => int - "time_minute": &objects.UserFunction{Value: timesTimeMinute}, // time_minute(time) => int - "time_second": &objects.UserFunction{Value: timesTimeSecond}, // time_second(time) => int - "time_nanosecond": &objects.UserFunction{Value: timesTimeNanosecond}, // time_nanosecond(time) => int - "time_unix": &objects.UserFunction{Value: timesTimeUnix}, // time_unix(time) => int - "time_unix_nano": &objects.UserFunction{Value: timesTimeUnixNano}, // time_unix_nano(time) => int - "time_format": &objects.UserFunction{Value: timesTimeFormat}, // time_format(time, format) => string - "time_location": &objects.UserFunction{Value: timesTimeLocation}, // time_location(time) => string - "time_string": &objects.UserFunction{Value: timesTimeString}, // time_string(time) => string - "is_zero": &objects.UserFunction{Value: timesIsZero}, // is_zero(time) => bool - "to_local": &objects.UserFunction{Value: timesToLocal}, // to_local(time) => time - "to_utc": &objects.UserFunction{Value: timesToUTC}, // to_utc(time) => time + "sleep": &objects.UserFunction{Name: "sleep", Value: timesSleep}, // sleep(int) + "parse_duration": &objects.UserFunction{Name: "parse_duration", Value: timesParseDuration}, // parse_duration(str) => int + "since": &objects.UserFunction{Name: "since", Value: timesSince}, // since(time) => int + "until": &objects.UserFunction{Name: "until", Value: timesUntil}, // until(time) => int + "duration_hours": &objects.UserFunction{Name: "duration_hours", Value: timesDurationHours}, // duration_hours(int) => float + "duration_minutes": &objects.UserFunction{Name: "duration_minutes", Value: timesDurationMinutes}, // duration_minutes(int) => float + "duration_nanoseconds": &objects.UserFunction{Name: "duration_nanoseconds", Value: timesDurationNanoseconds}, // duration_nanoseconds(int) => int + "duration_seconds": &objects.UserFunction{Name: "duration_seconds", Value: timesDurationSeconds}, // duration_seconds(int) => float + "duration_string": &objects.UserFunction{Name: "duration_string", Value: timesDurationString}, // duration_string(int) => string + "month_string": &objects.UserFunction{Name: "month_string", Value: timesMonthString}, // month_string(int) => string + "date": &objects.UserFunction{Name: "date", Value: timesDate}, // date(year, month, day, hour, min, sec, nsec) => time + "now": &objects.UserFunction{Name: "now", Value: timesNow}, // now() => time + "parse": &objects.UserFunction{Name: "parse", Value: timesParse}, // parse(format, str) => time + "unix": &objects.UserFunction{Name: "unix", Value: timesUnix}, // unix(sec, nsec) => time + "add": &objects.UserFunction{Name: "add", Value: timesAdd}, // add(time, int) => time + "add_date": &objects.UserFunction{Name: "add_date", Value: timesAddDate}, // add_date(time, years, months, days) => time + "sub": &objects.UserFunction{Name: "sub", Value: timesSub}, // sub(t time, u time) => int + "after": &objects.UserFunction{Name: "after", Value: timesAfter}, // after(t time, u time) => bool + "before": &objects.UserFunction{Name: "before", Value: timesBefore}, // before(t time, u time) => bool + "time_year": &objects.UserFunction{Name: "time_year", Value: timesTimeYear}, // time_year(time) => int + "time_month": &objects.UserFunction{Name: "time_month", Value: timesTimeMonth}, // time_month(time) => int + "time_day": &objects.UserFunction{Name: "time_day", Value: timesTimeDay}, // time_day(time) => int + "time_weekday": &objects.UserFunction{Name: "time_weekday", Value: timesTimeWeekday}, // time_weekday(time) => int + "time_hour": &objects.UserFunction{Name: "time_hour", Value: timesTimeHour}, // time_hour(time) => int + "time_minute": &objects.UserFunction{Name: "time_minute", Value: timesTimeMinute}, // time_minute(time) => int + "time_second": &objects.UserFunction{Name: "time_second", Value: timesTimeSecond}, // time_second(time) => int + "time_nanosecond": &objects.UserFunction{Name: "time_nanosecond", Value: timesTimeNanosecond}, // time_nanosecond(time) => int + "time_unix": &objects.UserFunction{Name: "time_unix", Value: timesTimeUnix}, // time_unix(time) => int + "time_unix_nano": &objects.UserFunction{Name: "time_unix_nano", Value: timesTimeUnixNano}, // time_unix_nano(time) => int + "time_format": &objects.UserFunction{Name: "time_format", Value: timesTimeFormat}, // time_format(time, format) => string + "time_location": &objects.UserFunction{Name: "time_location", Value: timesTimeLocation}, // time_location(time) => string + "time_string": &objects.UserFunction{Name: "time_string", Value: timesTimeString}, // time_string(time) => string + "is_zero": &objects.UserFunction{Name: "is_zero", Value: timesIsZero}, // is_zero(time) => bool + "to_local": &objects.UserFunction{Name: "to_local", Value: timesToLocal}, // to_local(time) => time + "to_utc": &objects.UserFunction{Name: "to_utc", Value: timesToUTC}, // to_utc(time) => time } func timesSleep(args ...objects.Object) (ret objects.Object, err error) { @@ -85,7 +85,11 @@ func timesSleep(args ...objects.Object) (ret objects.Object, err error) { i1, ok := objects.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } return } @@ -103,7 +107,11 @@ func timesParseDuration(args ...objects.Object) (ret objects.Object, err error) s1, ok := objects.ToString(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } return } @@ -126,7 +134,11 @@ func timesSince(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -143,7 +155,11 @@ func timesUntil(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -160,7 +176,11 @@ func timesDurationHours(args ...objects.Object) (ret objects.Object, err error) i1, ok := objects.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } return } @@ -177,7 +197,11 @@ func timesDurationMinutes(args ...objects.Object) (ret objects.Object, err error i1, ok := objects.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } return } @@ -194,7 +218,11 @@ func timesDurationNanoseconds(args ...objects.Object) (ret objects.Object, err e i1, ok := objects.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } return } @@ -211,7 +239,11 @@ func timesDurationSeconds(args ...objects.Object) (ret objects.Object, err error i1, ok := objects.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } return } @@ -228,7 +260,11 @@ func timesDurationString(args ...objects.Object) (ret objects.Object, err error) i1, ok := objects.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } return } @@ -245,7 +281,11 @@ func timesMonthString(args ...objects.Object) (ret objects.Object, err error) { i1, ok := objects.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } return } @@ -262,37 +302,65 @@ func timesDate(args ...objects.Object) (ret objects.Object, err error) { i1, ok := objects.ToInt(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } return } i2, ok := objects.ToInt(args[1]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } return } i3, ok := objects.ToInt(args[2]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } return } i4, ok := objects.ToInt(args[3]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } return } i5, ok := objects.ToInt(args[4]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "fifth", + Expected: "int(compatible)", + Found: args[4].TypeName(), + } return } i6, ok := objects.ToInt(args[5]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "sixth", + Expected: "int(compatible)", + Found: args[5].TypeName(), + } return } i7, ok := objects.ToInt(args[6]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "seventh", + Expected: "int(compatible)", + Found: args[6].TypeName(), + } return } @@ -320,13 +388,21 @@ func timesParse(args ...objects.Object) (ret objects.Object, err error) { s1, ok := objects.ToString(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "string(compatible)", + Found: args[0].TypeName(), + } return } s2, ok := objects.ToString(args[1]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } return } @@ -349,13 +425,21 @@ func timesUnix(args ...objects.Object) (ret objects.Object, err error) { i1, ok := objects.ToInt64(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "int(compatible)", + Found: args[0].TypeName(), + } return } i2, ok := objects.ToInt64(args[1]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } return } @@ -372,13 +456,21 @@ func timesAdd(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } i2, ok := objects.ToInt64(args[1]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } return } @@ -395,13 +487,21 @@ func timesSub(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } t2, ok := objects.ToTime(args[1]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "time(compatible)", + Found: args[1].TypeName(), + } return } @@ -418,25 +518,41 @@ func timesAddDate(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } i2, ok := objects.ToInt(args[1]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "int(compatible)", + Found: args[1].TypeName(), + } return } i3, ok := objects.ToInt(args[2]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "third", + Expected: "int(compatible)", + Found: args[2].TypeName(), + } return } i4, ok := objects.ToInt(args[3]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "fourth", + Expected: "int(compatible)", + Found: args[3].TypeName(), + } return } @@ -453,13 +569,21 @@ func timesAfter(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } t2, ok := objects.ToTime(args[1]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "time(compatible)", + Found: args[1].TypeName(), + } return } @@ -480,13 +604,21 @@ func timesBefore(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } t2, ok := objects.ToTime(args[1]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -507,7 +639,11 @@ func timesTimeYear(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -524,7 +660,11 @@ func timesTimeMonth(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -541,7 +681,11 @@ func timesTimeDay(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -558,7 +702,11 @@ func timesTimeWeekday(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -575,7 +723,11 @@ func timesTimeHour(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -592,7 +744,11 @@ func timesTimeMinute(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -609,7 +765,11 @@ func timesTimeSecond(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -626,7 +786,11 @@ func timesTimeNanosecond(args ...objects.Object) (ret objects.Object, err error) t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -643,7 +807,11 @@ func timesTimeUnix(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -660,7 +828,11 @@ func timesTimeUnixNano(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -677,13 +849,21 @@ func timesTimeFormat(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } s2, ok := objects.ToString(args[1]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "second", + Expected: "string(compatible)", + Found: args[1].TypeName(), + } return } @@ -700,7 +880,11 @@ func timesIsZero(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -721,7 +905,11 @@ func timesToLocal(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -738,7 +926,11 @@ func timesToUTC(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -755,7 +947,11 @@ func timesTimeLocation(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } @@ -772,7 +968,11 @@ func timesTimeString(args ...objects.Object) (ret objects.Object, err error) { t1, ok := objects.ToTime(args[0]) if !ok { - err = objects.ErrInvalidTypeConversion + err = objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "time(compatible)", + Found: args[0].TypeName(), + } return } diff --git a/compiler/stdlib/times_test.go b/stdlib/times_test.go similarity index 100% rename from compiler/stdlib/times_test.go rename to stdlib/times_test.go