Improvements on compiler/VM error reporting (filename:line:col)

- add type infos to VM error messages
- add 'Name' to UserFunction objects 
- add 'expectErrorString' to VM tests
- replace vm.expectError() with vm.expectErrorString() to make it more explicit
- add source map info to VM error messages
- optimization in function calls
- add file/line/col info to compiler errors
- change stdlib module to be loaded from VM (instead of compiler) so they can be properly loaded after the source is compiled into binary
- VM can take builtin modules optionally
This commit is contained in:
Daniel 2019-02-20 16:26:11 -08:00 committed by GitHub
parent cb1c8a405e
commit 3500c686b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
78 changed files with 3785 additions and 2858 deletions

View file

@ -33,7 +33,7 @@ func Error(t *testing.T, err error, msg ...interface{}) bool {
// Nil asserts v is nil. // Nil asserts v is nil.
func Nil(t *testing.T, v interface{}, msg ...interface{}) bool { func Nil(t *testing.T, v interface{}, msg ...interface{}) bool {
if v == nil { if isNil(v) {
return true return true
} }
@ -60,7 +60,7 @@ func False(t *testing.T, v bool, msg ...interface{}) bool {
// NotNil asserts v is not nil. // NotNil asserts v is not nil.
func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool { func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool {
if v != nil { if !isNil(v) {
return true return true
} }
@ -78,7 +78,7 @@ func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
// Equal asserts expected and actual are equal. // Equal asserts expected and actual are equal.
func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool { 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") return Nil(t, actual, "expected nil, but got not nil")
} }
if !NotNil(t, actual, "expected not nil, but got 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)) { if !expected.Equals(actual.(objects.Object)) {
return failExpectedActual(t, expected, actual, msg...) 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: case error:
if expected != actual.(error) { if expected != actual.(error) {
return failExpectedActual(t, expected, actual, msg...) 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 { 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...) { if !Equal(t, len(expected), len(actual), msg...) {
return false return false
} }
@ -260,6 +265,20 @@ func equalObjectSlice(t *testing.T, expected, actual []objects.Object, msg ...in
return true 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 { func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object, msg ...interface{}) bool {
if !Equal(t, len(expected), len(actual), msg...) { if !Equal(t, len(expected), len(actual), msg...) {
return false return false
@ -303,3 +322,17 @@ func equalClosure(t *testing.T, expected, actual objects.Object, msg ...interfac
return true 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
}

View file

@ -175,7 +175,7 @@ func runBench(input []byte) (parseTime time.Duration, compileTime time.Duration,
func parse(input []byte) (time.Duration, *ast.File, error) { func parse(input []byte) (time.Duration, *ast.File, error) {
fileSet := source.NewFileSet() fileSet := source.NewFileSet()
inputFile := fileSet.AddFile("test", -1, len(input)) inputFile := fileSet.AddFile("bench", -1, len(input))
start := time.Now() start := time.Now()
@ -193,7 +193,7 @@ func compileFile(file *ast.File) (time.Duration, *compiler.Bytecode, error) {
start := time.Now() 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 { if err := c.Compile(file); err != nil {
return time.Since(start), nil, err return time.Since(start), nil, err
} }
@ -206,7 +206,7 @@ func runVM(bytecode *compiler.Bytecode) (time.Duration, objects.Object, error) {
start := time.Now() start := time.Now()
v := runtime.NewVM(bytecode, globals) v := runtime.NewVM(bytecode, globals, nil)
if err := v.Run(); err != nil { if err := v.Run(); err != nil {
return time.Since(start), nil, err return time.Since(start), nil, err
} }

View file

@ -148,7 +148,7 @@ func compileAndRun(data []byte, inputFile string) (err error) {
return return
} }
machine := runtime.NewVM(bytecode, nil) machine := runtime.NewVM(bytecode, nil, nil)
err = machine.Run() err = machine.Run()
if err != nil { if err != nil {
@ -165,7 +165,7 @@ func runCompiled(data []byte) (err error) {
return return
} }
machine := runtime.NewVM(bytecode, nil) machine := runtime.NewVM(bytecode, nil, nil)
err = machine.Run() err = machine.Run()
if err != nil { if err != nil {
@ -198,7 +198,8 @@ func runREPL(in io.Reader, out io.Writer) {
line := stdin.Text() 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 { if err != nil {
_, _ = fmt.Fprintf(out, "error: %s\n", err.Error()) _, _ = fmt.Fprintf(out, "error: %s\n", err.Error())
continue continue
@ -206,7 +207,7 @@ func runREPL(in io.Reader, out io.Writer) {
file = addPrints(file) 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 { if err := c.Compile(file); err != nil {
_, _ = fmt.Fprintf(out, "Compilation error:\n %s\n", err.Error()) _, _ = fmt.Fprintf(out, "Compilation error:\n %s\n", err.Error())
continue continue
@ -214,7 +215,7 @@ func runREPL(in io.Reader, out io.Writer) {
bytecode := c.Bytecode() bytecode := c.Bytecode()
machine := runtime.NewVM(bytecode, globals) machine := runtime.NewVM(bytecode, globals, nil)
if err != nil { if err != nil {
_, _ = fmt.Fprintf(out, "VM error:\n %s\n", err.Error()) _, _ = fmt.Fprintf(out, "VM error:\n %s\n", err.Error())
continue continue
@ -230,14 +231,15 @@ func runREPL(in io.Reader, out io.Writer) {
func compileSrc(src []byte, filename string) (*compiler.Bytecode, error) { func compileSrc(src []byte, filename string) (*compiler.Bytecode, error) {
fileSet := source.NewFileSet() 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() file, err := p.ParseFile()
if err != nil { if err != nil {
return nil, err 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 { if err := c.Compile(file); err != nil {
return nil, err return nil, err
} }

View file

@ -14,12 +14,12 @@ type File struct {
// Pos returns the position of first character belonging to the node. // Pos returns the position of first character belonging to the node.
func (n *File) Pos() source.Pos { 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. // End returns the position of first character immediately after the node.
func (n *File) End() source.Pos { 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 { func (n *File) String() string {

View file

@ -6,12 +6,14 @@ import (
"io" "io"
"reflect" "reflect"
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
) )
// Bytecode is a compiled instructions and constants. // Bytecode is a compiled instructions and constants.
type Bytecode struct { type Bytecode struct {
Instructions []byte FileSet *source.FileSet
MainFunction *objects.CompiledFunction
Constants []objects.Object Constants []objects.Object
} }
@ -19,7 +21,13 @@ type Bytecode struct {
func (b *Bytecode) Decode(r io.Reader) error { func (b *Bytecode) Decode(r io.Reader) error {
dec := gob.NewDecoder(r) 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 return err
} }
@ -39,7 +47,11 @@ func (b *Bytecode) Decode(r io.Reader) error {
func (b *Bytecode) Encode(w io.Writer) error { func (b *Bytecode) Encode(w io.Writer) error {
enc := gob.NewEncoder(w) 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 return err
} }
@ -50,7 +62,7 @@ func (b *Bytecode) Encode(w io.Writer) error {
// FormatInstructions returns human readable string representations of // FormatInstructions returns human readable string representations of
// compiled instructions. // compiled instructions.
func (b *Bytecode) FormatInstructions() []string { func (b *Bytecode) FormatInstructions() []string {
return FormatInstructions(b.Instructions, 0) return FormatInstructions(b.MainFunction.Instructions, 0)
} }
// FormatConstants returns human readable string representations of // FormatConstants returns human readable string representations of
@ -94,21 +106,29 @@ func cleanupObjects(o objects.Object) objects.Object {
} }
func init() { func init() {
gob.Register(&objects.Int{}) gob.Register(&source.FileSet{})
gob.Register(&objects.Float{}) gob.Register(&source.File{})
gob.Register(&objects.String{})
gob.Register(&objects.Bool{})
gob.Register(&objects.Char{})
gob.Register(&objects.Array{}) 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.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.Time{})
gob.Register(&objects.Undefined{})
gob.Register(&objects.UserFunction{})
} }

View file

@ -7,11 +7,17 @@ import (
"github.com/d5/tengo/assert" "github.com/d5/tengo/assert"
"github.com/d5/tengo/compiler" "github.com/d5/tengo/compiler"
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
) )
type srcfile struct {
name string
size int
}
func TestBytecode(t *testing.T) { func TestBytecode(t *testing.T) {
testBytecodeSerialization(t, &compiler.Bytecode{}) testBytecodeSerialization(t, bytecode(concat(), objectsArray()))
testBytecodeSerialization(t, bytecode( testBytecodeSerialization(t, bytecode(
concat(), objectsArray( concat(), objectsArray(
@ -48,7 +54,7 @@ func TestBytecode(t *testing.T) {
&objects.String{Value: "bar"}, &objects.String{Value: "bar"},
objects.UndefinedValue))) objects.UndefinedValue)))
testBytecodeSerialization(t, bytecode( testBytecodeSerialization(t, bytecodeFileSet(
concat( concat(
compiler.MakeInstruction(compiler.OpConstant, 0), compiler.MakeInstruction(compiler.OpConstant, 0),
compiler.MakeInstruction(compiler.OpSetGlobal, 0), compiler.MakeInstruction(compiler.OpSetGlobal, 0),
@ -82,7 +88,24 @@ func TestBytecode(t *testing.T) {
compiler.MakeInstruction(compiler.OpSetLocal, 0), compiler.MakeInstruction(compiler.OpSetLocal, 0),
compiler.MakeInstruction(compiler.OpGetLocal, 0), compiler.MakeInstruction(compiler.OpGetLocal, 0),
compiler.MakeInstruction(compiler.OpClosure, 5, 1), 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) { 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())) err = r.Decode(bytes.NewReader(buf.Bytes()))
assert.NoError(t, err) 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) assert.Equal(t, b.Constants, r.Constants)
} }

View file

@ -1,9 +1,12 @@
package compiler package compiler
import "github.com/d5/tengo/compiler/source"
// CompilationScope represents a compiled instructions // CompilationScope represents a compiled instructions
// and the last two instructions that were emitted. // and the last two instructions that were emitted.
type CompilationScope struct { type CompilationScope struct {
instructions []byte instructions []byte
lastInstructions [2]EmittedInstruction lastInstructions [2]EmittedInstruction
symbolInit map[string]bool symbolInit map[string]bool
sourceMap map[int]source.Pos
} }

View file

@ -6,13 +6,15 @@ import (
"reflect" "reflect"
"github.com/d5/tengo/compiler/ast" "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/compiler/token"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
"github.com/d5/tengo/stdlib"
) )
// Compiler compiles the AST into a bytecode. // Compiler compiles the AST into a bytecode.
type Compiler struct { type Compiler struct {
file *source.File
parent *Compiler parent *Compiler
moduleName string moduleName string
constants []objects.Object constants []objects.Object
@ -20,7 +22,7 @@ type Compiler struct {
scopes []CompilationScope scopes []CompilationScope
scopeIndex int scopeIndex int
moduleLoader ModuleLoader moduleLoader ModuleLoader
stdModules map[string]*objects.ImmutableMap builtinModules map[string]bool
compiledModules map[string]*objects.CompiledFunction compiledModules map[string]*objects.CompiledFunction
loops []*Loop loops []*Loop
loopIndex int loopIndex int
@ -34,9 +36,10 @@ type Compiler struct {
// a new symbol table and use the default builtin functions. Likewise, standard // 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. // modules can be explicitly provided if user wants to add or remove some modules.
// By default, Compile will use all the standard modules otherwise. // 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{ mainScope := CompilationScope{
instructions: make([]byte, 0), symbolInit: make(map[string]bool),
sourceMap: make(map[int]source.Pos),
} }
// symbol table // symbol table
@ -48,19 +51,23 @@ func NewCompiler(symbolTable *SymbolTable, constants []objects.Object, stdModule
} }
} }
// standard modules // builtin modules
if stdModules == nil { if builtinModules == nil {
stdModules = stdlib.Modules builtinModules = make(map[string]bool)
for name := range stdlib.Modules {
builtinModules[name] = true
}
} }
return &Compiler{ return &Compiler{
file: file,
symbolTable: symbolTable, symbolTable: symbolTable,
constants: constants, constants: constants,
scopes: []CompilationScope{mainScope}, scopes: []CompilationScope{mainScope},
scopeIndex: 0, scopeIndex: 0,
loopIndex: -1, loopIndex: -1,
trace: trace, trace: trace,
stdModules: stdModules, builtinModules: builtinModules,
compiledModules: make(map[string]*objects.CompiledFunction), 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 { if err := c.Compile(node.Expr); err != nil {
return err return err
} }
c.emit(OpPop) c.emit(node, OpPop)
case *ast.IncDecStmt: case *ast.IncDecStmt:
op := token.AddAssign op := token.AddAssign
@ -95,7 +102,7 @@ func (c *Compiler) Compile(node ast.Node) error {
op = token.SubAssign 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: case *ast.ParenExpr:
if err := c.Compile(node.Expr); err != nil { if err := c.Compile(node.Expr); err != nil {
@ -116,7 +123,7 @@ func (c *Compiler) Compile(node ast.Node) error {
return err return err
} }
c.emit(OpGreaterThan) c.emit(node, OpGreaterThan)
return nil return nil
} else if node.Token == token.LessEq { } else if node.Token == token.LessEq {
@ -127,7 +134,7 @@ func (c *Compiler) Compile(node ast.Node) error {
return err return err
} }
c.emit(OpGreaterThanEqual) c.emit(node, OpGreaterThanEqual)
return nil return nil
} }
@ -141,60 +148,60 @@ func (c *Compiler) Compile(node ast.Node) error {
switch node.Token { switch node.Token {
case token.Add: case token.Add:
c.emit(OpAdd) c.emit(node, OpAdd)
case token.Sub: case token.Sub:
c.emit(OpSub) c.emit(node, OpSub)
case token.Mul: case token.Mul:
c.emit(OpMul) c.emit(node, OpMul)
case token.Quo: case token.Quo:
c.emit(OpDiv) c.emit(node, OpDiv)
case token.Rem: case token.Rem:
c.emit(OpRem) c.emit(node, OpRem)
case token.Greater: case token.Greater:
c.emit(OpGreaterThan) c.emit(node, OpGreaterThan)
case token.GreaterEq: case token.GreaterEq:
c.emit(OpGreaterThanEqual) c.emit(node, OpGreaterThanEqual)
case token.Equal: case token.Equal:
c.emit(OpEqual) c.emit(node, OpEqual)
case token.NotEqual: case token.NotEqual:
c.emit(OpNotEqual) c.emit(node, OpNotEqual)
case token.And: case token.And:
c.emit(OpBAnd) c.emit(node, OpBAnd)
case token.Or: case token.Or:
c.emit(OpBOr) c.emit(node, OpBOr)
case token.Xor: case token.Xor:
c.emit(OpBXor) c.emit(node, OpBXor)
case token.AndNot: case token.AndNot:
c.emit(OpBAndNot) c.emit(node, OpBAndNot)
case token.Shl: case token.Shl:
c.emit(OpBShiftLeft) c.emit(node, OpBShiftLeft)
case token.Shr: case token.Shr:
c.emit(OpBShiftRight) c.emit(node, OpBShiftRight)
default: default:
return fmt.Errorf("unknown operator: %s", node.Token.String()) return c.errorf(node, "invalid binary operator: %s", node.Token.String())
} }
case *ast.IntLit: 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: 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: case *ast.BoolLit:
if node.Value { if node.Value {
c.emit(OpTrue) c.emit(node, OpTrue)
} else { } else {
c.emit(OpFalse) c.emit(node, OpFalse)
} }
case *ast.StringLit: 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: 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: case *ast.UndefinedLit:
c.emit(OpNull) c.emit(node, OpNull)
case *ast.UnaryExpr: case *ast.UnaryExpr:
if err := c.Compile(node.Expr); err != nil { if err := c.Compile(node.Expr); err != nil {
@ -203,15 +210,15 @@ func (c *Compiler) Compile(node ast.Node) error {
switch node.Token { switch node.Token {
case token.Not: case token.Not:
c.emit(OpLNot) c.emit(node, OpLNot)
case token.Sub: case token.Sub:
c.emit(OpMinus) c.emit(node, OpMinus)
case token.Xor: case token.Xor:
c.emit(OpBComplement) c.emit(node, OpBComplement)
case token.Add: case token.Add:
// do nothing? // do nothing?
default: default:
return fmt.Errorf("unknown operator: %s", node.Token.String()) return c.errorf(node, "invalid unary operator: %s", node.Token.String())
} }
case *ast.IfStmt: case *ast.IfStmt:
@ -232,7 +239,7 @@ func (c *Compiler) Compile(node ast.Node) error {
} }
// first jump placeholder // first jump placeholder
jumpPos1 := c.emit(OpJumpFalsy, 0) jumpPos1 := c.emit(node, OpJumpFalsy, 0)
if err := c.Compile(node.Body); err != nil { if err := c.Compile(node.Body); err != nil {
return err return err
@ -240,7 +247,7 @@ func (c *Compiler) Compile(node ast.Node) error {
if node.Else != nil { if node.Else != nil {
// second jump placeholder // second jump placeholder
jumpPos2 := c.emit(OpJump, 0) jumpPos2 := c.emit(node, OpJump, 0)
// update first jump offset // update first jump offset
curPos := len(c.currentInstructions()) curPos := len(c.currentInstructions())
@ -269,19 +276,19 @@ func (c *Compiler) Compile(node ast.Node) error {
if node.Token == token.Break { if node.Token == token.Break {
curLoop := c.currentLoop() curLoop := c.currentLoop()
if curLoop == nil { 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) curLoop.Breaks = append(curLoop.Breaks, pos)
} else if node.Token == token.Continue { } else if node.Token == token.Continue {
curLoop := c.currentLoop() curLoop := c.currentLoop()
if curLoop == nil { 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) curLoop.Continues = append(curLoop.Continues, pos)
} else { } else {
return fmt.Errorf("unknown branch statement: %s", node.Token.String()) panic(fmt.Errorf("invalid branch statement: %s", node.Token.String()))
} }
case *ast.BlockStmt: case *ast.BlockStmt:
@ -292,25 +299,25 @@ func (c *Compiler) Compile(node ast.Node) error {
} }
case *ast.AssignStmt: 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 return err
} }
case *ast.Ident: case *ast.Ident:
symbol, _, ok := c.symbolTable.Resolve(node.Name) symbol, _, ok := c.symbolTable.Resolve(node.Name)
if !ok { if !ok {
return fmt.Errorf("undefined variable: %s", node.Name) return c.errorf(node, "unresolved reference '%s'", node.Name)
} }
switch symbol.Scope { switch symbol.Scope {
case ScopeGlobal: case ScopeGlobal:
c.emit(OpGetGlobal, symbol.Index) c.emit(node, OpGetGlobal, symbol.Index)
case ScopeLocal: case ScopeLocal:
c.emit(OpGetLocal, symbol.Index) c.emit(node, OpGetLocal, symbol.Index)
case ScopeBuiltin: case ScopeBuiltin:
c.emit(OpGetBuiltin, symbol.Index) c.emit(node, OpGetBuiltin, symbol.Index)
case ScopeFree: case ScopeFree:
c.emit(OpGetFree, symbol.Index) c.emit(node, OpGetFree, symbol.Index)
} }
case *ast.ArrayLit: 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: case *ast.MapLit:
for _, elt := range node.Elements { for _, elt := range node.Elements {
// key // key
c.emit(OpConstant, c.addConstant(&objects.String{Value: elt.Key})) c.emit(node, OpConstant, c.addConstant(&objects.String{Value: elt.Key}))
// value // value
if err := c.Compile(elt.Value); err != nil { 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 case *ast.SelectorExpr: // selector on RHS side
if err := c.Compile(node.Expr); err != nil { if err := c.Compile(node.Expr); err != nil {
@ -344,7 +351,7 @@ func (c *Compiler) Compile(node ast.Node) error {
return err return err
} }
c.emit(OpIndex) c.emit(node, OpIndex)
case *ast.IndexExpr: case *ast.IndexExpr:
if err := c.Compile(node.Expr); err != nil { if err := c.Compile(node.Expr); err != nil {
@ -355,7 +362,7 @@ func (c *Compiler) Compile(node ast.Node) error {
return err return err
} }
c.emit(OpIndex) c.emit(node, OpIndex)
case *ast.SliceExpr: case *ast.SliceExpr:
if err := c.Compile(node.Expr); err != nil { if err := c.Compile(node.Expr); err != nil {
@ -367,7 +374,7 @@ func (c *Compiler) Compile(node ast.Node) error {
return err return err
} }
} else { } else {
c.emit(OpNull) c.emit(node, OpNull)
} }
if node.High != nil { if node.High != nil {
@ -375,10 +382,10 @@ func (c *Compiler) Compile(node ast.Node) error {
return err return err
} }
} else { } else {
c.emit(OpNull) c.emit(node, OpNull)
} }
c.emit(OpSliceIndex) c.emit(node, OpSliceIndex)
case *ast.FuncLit: case *ast.FuncLit:
c.enterScope() c.enterScope()
@ -396,12 +403,12 @@ func (c *Compiler) Compile(node ast.Node) error {
// add OpReturn if function returns nothing // add OpReturn if function returns nothing
if !c.lastInstructionIs(OpReturnValue) && !c.lastInstructionIs(OpReturn) { if !c.lastInstructionIs(OpReturnValue) && !c.lastInstructionIs(OpReturn) {
c.emit(OpReturn) c.emit(node, OpReturn)
} }
freeSymbols := c.symbolTable.FreeSymbols() freeSymbols := c.symbolTable.FreeSymbols()
numLocals := c.symbolTable.MaxSymbols() numLocals := c.symbolTable.MaxSymbols()
instructions := c.leaveScope() instructions, sourceMap := c.leaveScope()
for _, s := range freeSymbols { for _, s := range freeSymbols {
switch s.Scope { switch s.Scope {
@ -444,15 +451,15 @@ func (c *Compiler) Compile(node ast.Node) error {
// 0009 SETL 0 // 0009 SETL 0
// //
c.emit(OpNull) c.emit(node, OpNull)
c.emit(OpDefineLocal, s.Index) c.emit(node, OpDefineLocal, s.Index)
s.LocalAssigned = true s.LocalAssigned = true
} }
c.emit(OpGetLocal, s.Index) c.emit(node, OpGetLocal, s.Index)
case ScopeFree: 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, Instructions: instructions,
NumLocals: numLocals, NumLocals: numLocals,
NumParameters: len(node.Type.Params.List), NumParameters: len(node.Type.Params.List),
SourceMap: sourceMap,
} }
if len(freeSymbols) > 0 { if len(freeSymbols) > 0 {
c.emit(OpClosure, c.addConstant(compiledFunction), len(freeSymbols)) c.emit(node, OpClosure, c.addConstant(compiledFunction), len(freeSymbols))
} else { } else {
c.emit(OpConstant, c.addConstant(compiledFunction)) c.emit(node, OpConstant, c.addConstant(compiledFunction))
} }
case *ast.ReturnStmt: case *ast.ReturnStmt:
if c.symbolTable.Parent(true) == nil { if c.symbolTable.Parent(true) == nil {
// outside the function // outside the function
return fmt.Errorf("return statement outside function") return c.errorf(node, "return not allowed outside function")
} }
if node.Result == nil { if node.Result == nil {
c.emit(OpReturn) c.emit(node, OpReturn)
} else { } else {
if err := c.Compile(node.Result); err != nil { if err := c.Compile(node.Result); err != nil {
return err return err
} }
c.emit(OpReturnValue) c.emit(node, OpReturnValue)
} }
case *ast.CallExpr: 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: case *ast.ImportExpr:
stdMod, ok := c.stdModules[node.ModuleName] if c.builtinModules[node.ModuleName] {
if ok { c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.ModuleName}))
// standard modules contain only globals with no code. c.emit(node, OpGetBuiltinModule)
// so no need to compile anything
c.emit(OpConstant, c.addConstant(stdMod))
} else { } else {
userMod, err := c.compileModule(node.ModuleName) userMod, err := c.compileModule(node)
if err != nil { if err != nil {
return err return err
} }
c.emit(OpConstant, c.addConstant(userMod)) c.emit(node, OpConstant, c.addConstant(userMod))
c.emit(OpCall, 0) c.emit(node, OpCall, 0)
} }
case *ast.ExportStmt: case *ast.ExportStmt:
// export statement must be in top-level scope // export statement must be in top-level scope
if c.scopeIndex != 0 { 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 // export statement is simply ignore when compiling non-module code
@ -528,22 +534,22 @@ func (c *Compiler) Compile(node ast.Node) error {
return err return err
} }
c.emit(OpImmutable) c.emit(node, OpImmutable)
c.emit(OpReturnValue) c.emit(node, OpReturnValue)
case *ast.ErrorExpr: case *ast.ErrorExpr:
if err := c.Compile(node.Expr); err != nil { if err := c.Compile(node.Expr); err != nil {
return err return err
} }
c.emit(OpError) c.emit(node, OpError)
case *ast.ImmutableExpr: case *ast.ImmutableExpr:
if err := c.Compile(node.Expr); err != nil { if err := c.Compile(node.Expr); err != nil {
return err return err
} }
c.emit(OpImmutable) c.emit(node, OpImmutable)
case *ast.CondExpr: case *ast.CondExpr:
if err := c.Compile(node.Cond); err != nil { if err := c.Compile(node.Cond); err != nil {
@ -551,14 +557,14 @@ func (c *Compiler) Compile(node ast.Node) error {
} }
// first jump placeholder // first jump placeholder
jumpPos1 := c.emit(OpJumpFalsy, 0) jumpPos1 := c.emit(node, OpJumpFalsy, 0)
if err := c.Compile(node.True); err != nil { if err := c.Compile(node.True); err != nil {
return err return err
} }
// second jump placeholder // second jump placeholder
jumpPos2 := c.emit(OpJump, 0) jumpPos2 := c.emit(node, OpJump, 0)
// update first jump offset // update first jump offset
curPos := len(c.currentInstructions()) curPos := len(c.currentInstructions())
@ -579,8 +585,12 @@ func (c *Compiler) Compile(node ast.Node) error {
// Bytecode returns a compiled bytecode. // Bytecode returns a compiled bytecode.
func (c *Compiler) Bytecode() *Bytecode { func (c *Compiler) Bytecode() *Bytecode {
return &Bytecode{ return &Bytecode{
Instructions: c.currentInstructions(), FileSet: c.file.Set(),
Constants: c.constants, 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 c.moduleLoader = moduleLoader
} }
func (c *Compiler) fork(moduleName string, symbolTable *SymbolTable) *Compiler { func (c *Compiler) fork(file *source.File, moduleName string, symbolTable *SymbolTable) *Compiler {
child := NewCompiler(symbolTable, nil, c.stdModules, c.trace) child := NewCompiler(file, symbolTable, nil, c.builtinModules, c.trace)
child.moduleName = moduleName // name of the module to compile child.moduleName = moduleName // name of the module to compile
child.parent = c // parent to set to current compiler child.parent = c // parent to set to current compiler
child.moduleLoader = c.moduleLoader // share module loader child.moduleLoader = c.moduleLoader // share module loader
@ -600,6 +610,14 @@ func (c *Compiler) fork(moduleName string, symbolTable *SymbolTable) *Compiler {
return child 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 { func (c *Compiler) addConstant(o objects.Object) int {
if c.parent != nil { if c.parent != nil {
// module compilers will use their parent's constants array // 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) 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...) inst := MakeInstruction(opcode, operands...)
pos := c.addInstruction(inst) pos := c.addInstruction(inst)
c.scopes[c.scopeIndex].sourceMap[pos] = filePos
c.setLastInstruction(opcode, pos) c.setLastInstruction(opcode, pos)
if c.trace != nil { if c.trace != nil {

View file

@ -1,50 +1,37 @@
package compiler package compiler
import ( import (
"errors"
"fmt" "fmt"
"github.com/d5/tengo/compiler/ast" "github.com/d5/tengo/compiler/ast"
"github.com/d5/tengo/compiler/token" "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) numLHS, numRHS := len(lhs), len(rhs)
if numLHS < numRHS { if numLHS > 1 || numRHS > 1 {
// # of LHS must be >= # of RHS return c.errorf(node, "tuple assignment not allowed")
return fmt.Errorf("assigntment count error: %d < %d", numLHS, numRHS)
} }
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 // resolve and compile left-hand side
ident, selectors, err := resolveAssignLHS(lhs[0]) ident, selectors := resolveAssignLHS(lhs[0])
if err != nil {
return err
}
numSel := len(selectors) numSel := len(selectors)
if op == token.Define && numSel > 0 { if op == token.Define && numSel > 0 {
// using selector on new variable does not make sense // 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) symbol, depth, exists := c.symbolTable.Resolve(ident)
if op == token.Define { if op == token.Define {
if depth == 0 && exists { 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) symbol = c.symbolTable.Define(ident)
} else { } else {
if !exists { 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 { switch op {
case token.AddAssign: case token.AddAssign:
c.emit(OpAdd) c.emit(node, OpAdd)
case token.SubAssign: case token.SubAssign:
c.emit(OpSub) c.emit(node, OpSub)
case token.MulAssign: case token.MulAssign:
c.emit(OpMul) c.emit(node, OpMul)
case token.QuoAssign: case token.QuoAssign:
c.emit(OpDiv) c.emit(node, OpDiv)
case token.RemAssign: case token.RemAssign:
c.emit(OpRem) c.emit(node, OpRem)
case token.AndAssign: case token.AndAssign:
c.emit(OpBAnd) c.emit(node, OpBAnd)
case token.OrAssign: case token.OrAssign:
c.emit(OpBOr) c.emit(node, OpBOr)
case token.AndNotAssign: case token.AndNotAssign:
c.emit(OpBAndNot) c.emit(node, OpBAndNot)
case token.XorAssign: case token.XorAssign:
c.emit(OpBXor) c.emit(node, OpBXor)
case token.ShlAssign: case token.ShlAssign:
c.emit(OpBShiftLeft) c.emit(node, OpBShiftLeft)
case token.ShrAssign: case token.ShrAssign:
c.emit(OpBShiftRight) c.emit(node, OpBShiftRight)
} }
// compile selector expressions (right to left) // 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 { switch symbol.Scope {
case ScopeGlobal: case ScopeGlobal:
if numSel > 0 { if numSel > 0 {
c.emit(OpSetSelGlobal, symbol.Index, numSel) c.emit(node, OpSetSelGlobal, symbol.Index, numSel)
} else { } else {
c.emit(OpSetGlobal, symbol.Index) c.emit(node, OpSetGlobal, symbol.Index)
} }
case ScopeLocal: case ScopeLocal:
if numSel > 0 { if numSel > 0 {
c.emit(OpSetSelLocal, symbol.Index, numSel) c.emit(node, OpSetSelLocal, symbol.Index, numSel)
} else { } else {
if op == token.Define && !symbol.LocalAssigned { if op == token.Define && !symbol.LocalAssigned {
c.emit(OpDefineLocal, symbol.Index) c.emit(node, OpDefineLocal, symbol.Index)
} else { } 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 symbol.LocalAssigned = true
case ScopeFree: case ScopeFree:
if numSel > 0 { if numSel > 0 {
c.emit(OpSetSelFree, symbol.Index, numSel) c.emit(node, OpSetSelFree, symbol.Index, numSel)
} else { } else {
c.emit(OpSetFree, symbol.Index) c.emit(node, OpSetFree, symbol.Index)
} }
default: default:
return fmt.Errorf("invalid assignment variable scope: %s", symbol.Scope) panic(fmt.Errorf("invalid assignment variable scope: %s", symbol.Scope))
} }
return nil 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) { switch term := expr.(type) {
case *ast.SelectorExpr: case *ast.SelectorExpr:
name, selectors, err = resolveAssignLHS(term.Expr) name, selectors = resolveAssignLHS(term.Expr)
if err != nil {
return
}
selectors = append(selectors, term.Sel) selectors = append(selectors, term.Sel)
return return
case *ast.IndexExpr: case *ast.IndexExpr:
name, selectors, err = resolveAssignLHS(term.Expr) name, selectors = resolveAssignLHS(term.Expr)
if err != nil {
return
}
selectors = append(selectors, term.Index) selectors = append(selectors, term.Index)
case *ast.Ident: case *ast.Ident:
return term.Name, nil, nil name = term.Name
} }
return return

View file

@ -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")
}

View file

@ -27,7 +27,7 @@ func (c *Compiler) compileForStmt(stmt *ast.ForStmt) error {
return err return err
} }
// condition jump position // condition jump position
postCondPos = c.emit(OpJumpFalsy, 0) postCondPos = c.emit(stmt, OpJumpFalsy, 0)
} }
// enter loop // enter loop
@ -52,7 +52,7 @@ func (c *Compiler) compileForStmt(stmt *ast.ForStmt) error {
} }
// back to condition // back to condition
c.emit(OpJump, preCondPos) c.emit(stmt, OpJump, preCondPos)
// post-statement position // post-statement position
postStmtPos := len(c.currentInstructions()) postStmtPos := len(c.currentInstructions())
@ -94,11 +94,11 @@ func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error {
if err := c.Compile(stmt.Iterable); err != nil { if err := c.Compile(stmt.Iterable); err != nil {
return err return err
} }
c.emit(OpIteratorInit) c.emit(stmt, OpIteratorInit)
if itSymbol.Scope == ScopeGlobal { if itSymbol.Scope == ScopeGlobal {
c.emit(OpSetGlobal, itSymbol.Index) c.emit(stmt, OpSetGlobal, itSymbol.Index)
} else { } else {
c.emit(OpDefineLocal, itSymbol.Index) c.emit(stmt, OpDefineLocal, itSymbol.Index)
} }
// pre-condition position // pre-condition position
@ -107,14 +107,14 @@ func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error {
// condition // condition
// :it.HasMore() // :it.HasMore()
if itSymbol.Scope == ScopeGlobal { if itSymbol.Scope == ScopeGlobal {
c.emit(OpGetGlobal, itSymbol.Index) c.emit(stmt, OpGetGlobal, itSymbol.Index)
} else { } else {
c.emit(OpGetLocal, itSymbol.Index) c.emit(stmt, OpGetLocal, itSymbol.Index)
} }
c.emit(OpIteratorNext) c.emit(stmt, OpIteratorNext)
// condition jump position // condition jump position
postCondPos := c.emit(OpJumpFalsy, 0) postCondPos := c.emit(stmt, OpJumpFalsy, 0)
// enter loop // enter loop
loop := c.enterLoop() loop := c.enterLoop()
@ -123,15 +123,15 @@ func (c *Compiler) compileForInStmt(stmt *ast.ForInStmt) error {
if stmt.Key.Name != "_" { if stmt.Key.Name != "_" {
keySymbol := c.symbolTable.Define(stmt.Key.Name) keySymbol := c.symbolTable.Define(stmt.Key.Name)
if itSymbol.Scope == ScopeGlobal { if itSymbol.Scope == ScopeGlobal {
c.emit(OpGetGlobal, itSymbol.Index) c.emit(stmt, OpGetGlobal, itSymbol.Index)
} else { } else {
c.emit(OpGetLocal, itSymbol.Index) c.emit(stmt, OpGetLocal, itSymbol.Index)
} }
c.emit(OpIteratorKey) c.emit(stmt, OpIteratorKey)
if keySymbol.Scope == ScopeGlobal { if keySymbol.Scope == ScopeGlobal {
c.emit(OpSetGlobal, keySymbol.Index) c.emit(stmt, OpSetGlobal, keySymbol.Index)
} else { } 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 != "_" { if stmt.Value.Name != "_" {
valueSymbol := c.symbolTable.Define(stmt.Value.Name) valueSymbol := c.symbolTable.Define(stmt.Value.Name)
if itSymbol.Scope == ScopeGlobal { if itSymbol.Scope == ScopeGlobal {
c.emit(OpGetGlobal, itSymbol.Index) c.emit(stmt, OpGetGlobal, itSymbol.Index)
} else { } else {
c.emit(OpGetLocal, itSymbol.Index) c.emit(stmt, OpGetLocal, itSymbol.Index)
} }
c.emit(OpIteratorValue) c.emit(stmt, OpIteratorValue)
if valueSymbol.Scope == ScopeGlobal { if valueSymbol.Scope == ScopeGlobal {
c.emit(OpSetGlobal, valueSymbol.Index) c.emit(stmt, OpSetGlobal, valueSymbol.Index)
} else { } 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()) postBodyPos := len(c.currentInstructions())
// back to condition // back to condition
c.emit(OpJump, preCondPos) c.emit(stmt, OpJump, preCondPos)
// post-statement position // post-statement position
postStmtPos := len(c.currentInstructions()) postStmtPos := len(c.currentInstructions())

View file

@ -14,9 +14,9 @@ func (c *Compiler) compileLogical(node *ast.BinaryExpr) error {
// jump position // jump position
var jumpPos int var jumpPos int
if node.Token == token.LAnd { if node.Token == token.LAnd {
jumpPos = c.emit(OpAndJump, 0) jumpPos = c.emit(node, OpAndJump, 0)
} else { } else {
jumpPos = c.emit(OpOrJump, 0) jumpPos = c.emit(node, OpOrJump, 0)
} }
// right side term // right side term

View file

@ -1,25 +1,22 @@
package compiler package compiler
import ( import (
"fmt"
"io/ioutil" "io/ioutil"
"strings" "strings"
"github.com/d5/tengo/compiler/ast"
"github.com/d5/tengo/compiler/parser" "github.com/d5/tengo/compiler/parser"
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
) )
var ( func (c *Compiler) compileModule(expr *ast.ImportExpr) (*objects.CompiledFunction, error) {
fileSet = source.NewFileSet() compiledModule, exists := c.loadCompiledModule(expr.ModuleName)
)
func (c *Compiler) compileModule(moduleName string) (*objects.CompiledFunction, error) {
compiledModule, exists := c.loadCompiledModule(moduleName)
if exists { if exists {
return compiledModule, nil return compiledModule, nil
} }
moduleName := expr.ModuleName
// read module source from loader // read module source from loader
var moduleSrc []byte var moduleSrc []byte
if c.moduleLoader == nil { if c.moduleLoader == nil {
@ -28,17 +25,17 @@ func (c *Compiler) compileModule(moduleName string) (*objects.CompiledFunction,
moduleName += ".tengo" moduleName += ".tengo"
} }
if err := c.checkCyclicImports(moduleName); err != nil { if err := c.checkCyclicImports(expr, moduleName); err != nil {
return nil, err return nil, err
} }
var err error var err error
moduleSrc, err = ioutil.ReadFile(moduleName) moduleSrc, err = ioutil.ReadFile(moduleName)
if err != nil { if err != nil {
return nil, err return nil, c.errorf(expr, "module file read error: %s", err.Error())
} }
} else { } else {
if err := c.checkCyclicImports(moduleName); err != nil { if err := c.checkCyclicImports(expr, moduleName); err != nil {
return nil, err return nil, err
} }
@ -59,18 +56,19 @@ func (c *Compiler) compileModule(moduleName string) (*objects.CompiledFunction,
return compiledModule, nil return compiledModule, nil
} }
func (c *Compiler) checkCyclicImports(moduleName string) error { func (c *Compiler) checkCyclicImports(node ast.Node, moduleName string) error {
if c.moduleName == moduleName { 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 { } else if c.parent != nil {
return c.parent.checkCyclicImports(moduleName) return c.parent.checkCyclicImports(node, moduleName)
} }
return nil return nil
} }
func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.CompiledFunction, error) { 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() file, err := p.ParseFile()
if err != nil { if err != nil {
return nil, err return nil, err
@ -90,20 +88,20 @@ func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.Comp
symbolTable = symbolTable.Fork(false) symbolTable = symbolTable.Fork(false)
// compile module // compile module
moduleCompiler := c.fork(moduleName, symbolTable) moduleCompiler := c.fork(modFile, moduleName, symbolTable)
if err := moduleCompiler.Compile(file); err != nil { if err := moduleCompiler.Compile(file); err != nil {
return nil, err return nil, err
} }
// add OpReturn (== export undefined) if export is missing // add OpReturn (== export undefined) if export is missing
if !moduleCompiler.lastInstructionIs(OpReturnValue) { if !moduleCompiler.lastInstructionIs(OpReturnValue) {
moduleCompiler.emit(OpReturn) moduleCompiler.emit(nil, OpReturn)
} }
return &objects.CompiledFunction{ compiledFunc := moduleCompiler.Bytecode().MainFunction
Instructions: moduleCompiler.Bytecode().Instructions, compiledFunc.NumLocals = symbolTable.MaxSymbols()
NumLocals: symbolTable.MaxSymbols(),
}, nil return compiledFunc, nil
} }
func (c *Compiler) loadCompiledModule(moduleName string) (mod *objects.CompiledFunction, ok bool) { func (c *Compiler) loadCompiledModule(moduleName string) (mod *objects.CompiledFunction, ok bool) {

View file

@ -1,13 +1,19 @@
package compiler package compiler
import "github.com/d5/tengo/compiler/source"
func (c *Compiler) currentInstructions() []byte { func (c *Compiler) currentInstructions() []byte {
return c.scopes[c.scopeIndex].instructions return c.scopes[c.scopeIndex].instructions
} }
func (c *Compiler) currentSourceMap() map[int]source.Pos {
return c.scopes[c.scopeIndex].sourceMap
}
func (c *Compiler) enterScope() { func (c *Compiler) enterScope() {
scope := CompilationScope{ 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) c.scopes = append(c.scopes, scope)
@ -20,8 +26,9 @@ func (c *Compiler) enterScope() {
} }
} }
func (c *Compiler) leaveScope() []byte { func (c *Compiler) leaveScope() (instructions []byte, sourceMap map[int]source.Pos) {
instructions := c.currentInstructions() instructions = c.currentInstructions()
sourceMap = c.currentSourceMap()
c.scopes = c.scopes[:len(c.scopes)-1] c.scopes = c.scopes[:len(c.scopes)-1]
c.scopeIndex-- c.scopeIndex--
@ -32,5 +39,5 @@ func (c *Compiler) leaveScope() []byte {
c.printTrace("SCOPL", c.scopeIndex) c.printTrace("SCOPL", c.scopeIndex)
} }
return instructions return
} }

View file

@ -874,11 +874,11 @@ func() {
intObject(1), intObject(1),
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 { func concat(instructions ...[]byte) []byte {
concat := make([]byte, 0) var concat []byte
for _, i := range instructions { for _, i := range instructions {
concat = append(concat, i...) concat = append(concat, i...)
} }
@ -888,7 +888,8 @@ func concat(instructions ...[]byte) []byte {
func bytecode(instructions []byte, constants []objects.Object) *compiler.Bytecode { func bytecode(instructions []byte, constants []objects.Object) *compiler.Bytecode {
return &compiler.Bytecode{ return &compiler.Bytecode{
Instructions: instructions, FileSet: source.NewFileSet(),
MainFunction: &objects.CompiledFunction{Instructions: instructions},
Constants: constants, Constants: constants,
} }
} }
@ -913,7 +914,7 @@ func expect(t *testing.T, input string, expected *compiler.Bytecode) (ok bool) {
return 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) _, trace, err := traceCompile(input, nil)
defer func() { 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 return
} }
func equalBytecode(t *testing.T, expected, actual *compiler.Bytecode) bool { func equalBytecode(t *testing.T, expected, actual *compiler.Bytecode) bool {
expectedInstructions := strings.Join(compiler.FormatInstructions(expected.Instructions, 0), "\n") return assert.Equal(t, expected.MainFunction, actual.MainFunction) &&
actualInstructions := strings.Join(compiler.FormatInstructions(actual.Instructions, 0), "\n")
return assert.Equal(t, expectedInstructions, actualInstructions) &&
equalConstants(t, expected.Constants, actual.Constants) equalConstants(t, expected.Constants, actual.Constants)
} }
@ -975,7 +981,7 @@ func traceCompile(input string, symbols map[string]objects.Object) (res *compile
} }
tr := &tracer{} tr := &tracer{}
c := compiler.NewCompiler(symTable, nil, nil, tr) c := compiler.NewCompiler(file, symTable, nil, nil, tr)
parsed, err := p.ParseFile() parsed, err := p.ParseFile()
if err != nil { if err != nil {
return return

20
compiler/error.go Normal file
View file

@ -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())
}

View file

@ -1,7 +1,7 @@
package compiler package compiler
// Opcode represents a single byte operation code. // Opcode represents a single byte operation code.
type Opcode byte type Opcode = byte
// List of opcodes // List of opcodes
const ( const (
@ -53,6 +53,7 @@ const (
OpSetFree // Set free variables OpSetFree // Set free variables
OpSetSelFree // Set free variables using selectors OpSetSelFree // Set free variables using selectors
OpGetBuiltin // Get builtin function OpGetBuiltin // Get builtin function
OpGetBuiltinModule // Get builtin module
OpClosure // Push closure OpClosure // Push closure
OpIteratorInit // Iterator init OpIteratorInit // Iterator init
OpIteratorNext // Iterator next OpIteratorNext // Iterator next
@ -107,6 +108,7 @@ var OpcodeNames = [...]string{
OpDefineLocal: "DEFL", OpDefineLocal: "DEFL",
OpSetSelLocal: "SETSL", OpSetSelLocal: "SETSL",
OpGetBuiltin: "BUILTIN", OpGetBuiltin: "BUILTIN",
OpGetBuiltinModule: "BLTMOD",
OpClosure: "CLOSURE", OpClosure: "CLOSURE",
OpGetFree: "GETF", OpGetFree: "GETF",
OpSetFree: "SETF", OpSetFree: "SETF",
@ -164,6 +166,7 @@ var OpcodeOperands = [...][]int{
OpDefineLocal: {1}, OpDefineLocal: {1},
OpSetSelLocal: {1, 1}, OpSetSelLocal: {1, 1},
OpGetBuiltin: {1}, OpGetBuiltin: {1},
OpGetBuiltinModule: {},
OpClosure: {2, 1}, OpClosure: {2, 1},
OpGetFree: {1}, OpGetFree: {1},
OpSetFree: {1}, OpSetFree: {1},

View file

@ -8,9 +8,9 @@ import (
) )
// ParseSource parses source code 'src' and builds an AST. // 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() fileSet := source.NewFileSet()
file := fileSet.AddFile("", -1, len(src)) file := fileSet.AddFile(filename, -1, len(src))
return ParseFile(file, src, trace) return ParseFile(file, src, trace)
} }

View file

@ -1158,8 +1158,8 @@ func (p *Parser) printTrace(a ...interface{}) {
} }
func (p *Parser) safePos(pos source.Pos) source.Pos { func (p *Parser) safePos(pos source.Pos) source.Pos {
fileBase := p.file.Base() fileBase := p.file.Base
fileSize := p.file.Size() fileSize := p.file.Size
if int(pos) < fileBase || int(pos) > fileBase+fileSize { if int(pos) < fileBase || int(pos) > fileBase+fileSize {
return source.Pos(fileBase + fileSize) return source.Pos(fileBase + fileSize)

View file

@ -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) { func expect(t *testing.T, input string, fn expectedFn) (ok bool) {
testFileSet := source.NewFileSet() testFileSet := source.NewFileSet()
testFile := testFileSet.AddFile("", -1, len(input)) testFile := testFileSet.AddFile("test", -1, len(input))
defer func() { defer func() {
if !ok { 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) { func expectError(t *testing.T, input string) (ok bool) {
testFileSet := source.NewFileSet() testFileSet := source.NewFileSet()
testFile := testFileSet.AddFile("", -1, len(input)) testFile := testFileSet.AddFile("test", -1, len(input))
defer func() { defer func() {
if !ok { if !ok {
@ -102,12 +102,12 @@ func expectString(t *testing.T, input, expected string) (ok bool) {
if !ok { if !ok {
// print trace // print trace
tr := &tracer{} tr := &tracer{}
_, _ = parser.ParseSource([]byte(input), tr) _, _ = parser.ParseSource("test", []byte(input), tr)
t.Logf("Trace:\n%s", strings.Join(tr.out, "")) 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) { if !assert.NoError(t, err) {
return return
} }

View file

@ -38,8 +38,8 @@ type Scanner struct {
// NewScanner creates a Scanner. // NewScanner creates a Scanner.
func NewScanner(file *source.File, src []byte, errorHandler ErrorHandler, mode Mode) *Scanner { func NewScanner(file *source.File, src []byte, errorHandler ErrorHandler, mode Mode) *Scanner {
if 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))) panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size, len(src)))
} }
s := &Scanner{ s := &Scanner{

View file

@ -213,7 +213,7 @@ func TestStripCR(t *testing.T) {
} }
func scanExpect(t *testing.T, input string, mode scanner.Mode, expected ...scanResult) bool { 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( s := scanner.NewScanner(
testFile, testFile,

View file

@ -1,50 +1,34 @@
package source package source
import (
"sync"
)
// File represents a source file. // File represents a source file.
type File struct { type File struct {
set *FileSet // File set for the file
name string // file name as provided to AddFile set *FileSet
base int // Pos value range for this file is [base...base+size] // File name as provided to AddFile
size int // file size as provided to AddFile Name string
mutex sync.Mutex // Pos value range for this file is [base...base+size]
lines []int // lines contains the offset of the first character for each line (the first entry is always 0) 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. // Set returns FileSet.
func (f *File) Name() string { func (f *File) Set() *FileSet {
return f.name return f.set
}
// 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
} }
// LineCount returns the current number of lines. // LineCount returns the current number of lines.
func (f *File) LineCount() int { func (f *File) LineCount() int {
f.mutex.Lock() return len(f.Lines)
n := len(f.lines)
f.mutex.Unlock()
return n
} }
// AddLine adds a new line. // AddLine adds a new line.
func (f *File) AddLine(offset int) { func (f *File) AddLine(offset int) {
f.mutex.Lock() if i := len(f.Lines); (i == 0 || f.Lines[i-1] < offset) && offset < f.Size {
if i := len(f.lines); (i == 0 || f.lines[i-1] < offset) && offset < f.size { f.Lines = append(f.Lines, offset)
f.lines = append(f.lines, offset)
} }
f.mutex.Unlock()
} }
// LineStart returns the position of the first character in the line. // 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)") panic("illegal line number (line numbering starts at 1)")
} }
f.mutex.Lock() if line > len(f.Lines) {
defer f.mutex.Unlock()
if line > len(f.lines) {
panic("illegal line number") 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. // FileSetPos returns the position in the file set.
func (f *File) FileSetPos(offset int) Pos { func (f *File) FileSetPos(offset int) Pos {
if offset > f.size { if offset > f.Size {
panic("illegal file offset") panic("illegal file offset")
} }
return Pos(f.base + offset) return Pos(f.Base + offset)
} }
// Offset translates the file set position into the file offset. // Offset translates the file set position into the file offset.
func (f *File) Offset(p Pos) int { 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") panic("illegal Pos value")
} }
return int(p) - f.base return int(p) - f.Base
} }
// Position translates the file set position into the file position. // Position translates the file set position into the file position.
func (f *File) Position(p Pos) (pos FilePos) { func (f *File) Position(p Pos) (pos FilePos) {
if p != NoPos { 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") panic("illegal Pos value")
} }
@ -95,7 +76,7 @@ func (f *File) Position(p Pos) (pos FilePos) {
} }
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.Offset = offset
pos.Filename, pos.Line, pos.Column = f.unpack(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) { func (f *File) unpack(offset int) (filename string, line, column int) {
f.mutex.Lock() filename = f.Name
defer f.mutex.Unlock() 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 return

View file

@ -2,51 +2,37 @@ package source
import ( import (
"sort" "sort"
"sync"
) )
// FileSet represents a set of source files. // FileSet represents a set of source files.
type FileSet struct { type FileSet struct {
mutex sync.RWMutex // protects the file set Base int // base offset for the next file
base int // base offset for the next file Files []*File // list of files in the order added to the set
files []*File // list of files in the order added to the set LastFile *File // cache of last file looked up
last *File // cache of last file looked up
} }
// NewFileSet creates a new file set. // NewFileSet creates a new file set.
func NewFileSet() *FileSet { func NewFileSet() *FileSet {
return &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. // AddFile adds a new file in the file set.
func (s *FileSet) AddFile(filename string, base, size int) *File { func (s *FileSet) AddFile(filename string, base, size int) *File {
s.mutex.Lock()
defer s.mutex.Unlock()
if base < 0 { 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") panic("illegal base or size")
} }
f := &File{ f := &File{
set: s, set: s,
name: filename, Name: filename,
base: base, Base: base,
size: size, Size: size,
lines: []int{0}, Lines: []int{0},
} }
base += size + 1 // +1 because EOF also has a position 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 // add the file to the file set
s.base = base s.Base = base
s.files = append(s.files, f) s.Files = append(s.Files, f)
s.last = f s.LastFile = f
return f return f
} }
@ -86,32 +72,25 @@ func (s *FileSet) Position(p Pos) (pos FilePos) {
} }
func (s *FileSet) file(p Pos) *File { func (s *FileSet) file(p Pos) *File {
s.mutex.RLock()
// common case: p is in last file // common case: p is in last file
if f := s.last; f != nil && f.base <= int(p) && int(p) <= f.base+f.size { if f := s.LastFile; f != nil && f.Base <= int(p) && int(p) <= f.Base+f.Size {
s.mutex.RUnlock()
return f return f
} }
// p is not in last file - search all files // p is not in last file - search all files
if i := searchFiles(s.files, int(p)); i >= 0 { if i := searchFiles(s.Files, int(p)); i >= 0 {
f := s.files[i] f := s.Files[i]
// f.base <= int(p) by definition of searchFiles // f.base <= int(p) by definition of searchFiles
if int(p) <= f.base+f.size { if int(p) <= f.Base+f.Size {
s.mutex.RUnlock() s.LastFile = f // race is ok - s.last is only a cache
s.mutex.Lock()
s.last = f // race is ok - s.last is only a cache
s.mutex.Unlock()
return f return f
} }
} }
s.mutex.RUnlock()
return nil return nil
} }
func searchFiles(a []*File, x int) int { 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
} }

View file

@ -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
},
}
}

View file

@ -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),
}

View file

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

View file

@ -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
},
},
},
}
}

View file

@ -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},
}

View file

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

View file

@ -107,7 +107,7 @@ func (o *Array) IndexGet(index Object) (res Object, err error) {
func (o *Array) IndexSet(index, value Object) (err error) { func (o *Array) IndexSet(index, value Object) (err error) {
intIdx, ok := ToInt(index) intIdx, ok := ToInt(index)
if !ok { if !ok {
err = ErrInvalidTypeConversion err = ErrInvalidIndexType
return return
} }

View file

@ -1,10 +1,6 @@
package objects package objects
import ( // append(arr, items...)
"fmt"
)
// append(src, items...)
func builtinAppend(args ...Object) (Object, error) { func builtinAppend(args ...Object) (Object, error) {
if len(args) < 2 { if len(args) < 2 {
return nil, ErrWrongNumArguments return nil, ErrWrongNumArguments
@ -13,7 +9,13 @@ func builtinAppend(args ...Object) (Object, error) {
switch arg := args[0].(type) { switch arg := args[0].(type) {
case *Array: case *Array:
return &Array{Value: append(arg.Value, args[1:]...)}, nil return &Array{Value: append(arg.Value, args[1:]...)}, nil
case *ImmutableArray:
return &Array{Value: append(arg.Value, args[1:]...)}, nil
default: default:
return nil, fmt.Errorf("unsupported type for 'append' function: %s", arg.TypeName()) return nil, ErrInvalidArgumentType{
Name: "first",
Expected: "array",
Found: arg.TypeName(),
}
} }
} }

View file

@ -6,12 +6,13 @@ import (
// BuiltinFunction represents a builtin function. // BuiltinFunction represents a builtin function.
type BuiltinFunction struct { type BuiltinFunction struct {
Name string
Value CallableFunc Value CallableFunc
} }
// TypeName returns the name of the type. // TypeName returns the name of the type.
func (o *BuiltinFunction) TypeName() string { func (o *BuiltinFunction) TypeName() string {
return "builtin-function" return "builtin-function:" + o.Name
} }
func (o *BuiltinFunction) String() string { func (o *BuiltinFunction) String() string {

View file

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
) )
// to_json(v object) => bytes
func builtinToJSON(args ...Object) (Object, error) { func builtinToJSON(args ...Object) (Object, error) {
if len(args) != 1 { if len(args) != 1 {
return nil, ErrWrongNumArguments return nil, ErrWrongNumArguments
@ -11,12 +12,13 @@ func builtinToJSON(args ...Object) (Object, error) {
res, err := json.Marshal(objectToInterface(args[0])) res, err := json.Marshal(objectToInterface(args[0]))
if err != nil { if err != nil {
return nil, err return &Error{Value: &String{Value: err.Error()}}, nil
} }
return &Bytes{Value: res}, nil return &Bytes{Value: res}, nil
} }
// from_json(data string/bytes) => object
func builtinFromJSON(args ...Object) (Object, error) { func builtinFromJSON(args ...Object) (Object, error) {
if len(args) != 1 { if len(args) != 1 {
return nil, ErrWrongNumArguments return nil, ErrWrongNumArguments
@ -28,15 +30,19 @@ func builtinFromJSON(args ...Object) (Object, error) {
case *Bytes: case *Bytes:
err := json.Unmarshal(o.Value, &target) err := json.Unmarshal(o.Value, &target)
if err != nil { if err != nil {
return nil, err return &Error{Value: &String{Value: err.Error()}}, nil
} }
case *String: case *String:
err := json.Unmarshal([]byte(o.Value), &target) err := json.Unmarshal([]byte(o.Value), &target)
if err != nil { if err != nil {
return nil, err return &Error{Value: &String{Value: err.Error()}}, nil
} }
default: default:
return nil, ErrInvalidTypeConversion return nil, ErrInvalidArgumentType{
Name: "first",
Expected: "bytes/string",
Found: args[0].TypeName(),
}
} }
res, err := FromInterface(target) res, err := FromInterface(target)

View file

@ -1,9 +1,6 @@
package objects package objects
import ( // len(obj object) => int
"fmt"
)
func builtinLen(args ...Object) (Object, error) { func builtinLen(args ...Object) (Object, error) {
if len(args) != 1 { if len(args) != 1 {
return nil, ErrWrongNumArguments return nil, ErrWrongNumArguments
@ -23,6 +20,10 @@ func builtinLen(args ...Object) (Object, error) {
case *ImmutableMap: case *ImmutableMap:
return &Int{Value: int64(len(arg.Value))}, nil return &Int{Value: int64(len(arg.Value))}, nil
default: 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(),
}
} }
} }

View file

@ -26,7 +26,11 @@ func builtinPrintf(args ...Object) (Object, error) {
format, ok := args[0].(*String) format, ok := args[0].(*String)
if !ok { if !ok {
return nil, ErrInvalidTypeConversion return nil, ErrInvalidArgumentType{
Name: "format",
Expected: "string",
Found: args[0].TypeName(),
}
} }
if numArgs == 1 { if numArgs == 1 {
fmt.Print(format) fmt.Print(format)
@ -52,7 +56,11 @@ func builtinSprintf(args ...Object) (Object, error) {
format, ok := args[0].(*String) format, ok := args[0].(*String)
if !ok { if !ok {
return nil, ErrInvalidTypeConversion return nil, ErrInvalidArgumentType{
Name: "format",
Expected: "string",
Found: args[0].TypeName(),
}
} }
if numArgs == 1 { if numArgs == 1 {
return format, nil // okay to return 'format' directly as String is immutable return format, nil // okay to return 'format' directly as String is immutable

View file

@ -1,14 +1,16 @@
package objects package objects
import ( import (
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/compiler/token" "github.com/d5/tengo/compiler/token"
) )
// CompiledFunction represents a compiled function. // CompiledFunction represents a compiled function.
type CompiledFunction struct { type CompiledFunction struct {
Instructions []byte Instructions []byte
NumLocals int NumLocals int // number of local variables (including function parameters)
NumParameters int NumParameters int
SourceMap map[int]source.Pos
} }
// TypeName returns the name of the type. // TypeName returns the name of the type.

View file

@ -245,5 +245,5 @@ func FromInterface(v interface{}) (Object, error) {
return v, nil return v, nil
} }
return nil, fmt.Errorf("unsupported value type: %T", v) return nil, fmt.Errorf("cannot convert to object: %T", v)
} }

View file

@ -1,24 +1,32 @@
package objects package objects
import "errors" import (
"errors"
// ErrNotIndexable means the type is not indexable. "fmt"
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")
// ErrIndexOutOfBounds is an error where a given index is out of the bounds. // ErrIndexOutOfBounds is an error where a given index is out of the bounds.
var ErrIndexOutOfBounds = errors.New("index out of 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") 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. // ErrInvalidOperator represents an error for invalid operator usage.
var ErrInvalidOperator = errors.New("invalid operator") var ErrInvalidOperator = errors.New("invalid operator")
// ErrWrongNumArguments represents a wrong number of arguments error. // ErrWrongNumArguments represents a wrong number of arguments error.
var ErrWrongNumArguments = errors.New("wrong number of arguments") var ErrWrongNumArguments = errors.New("wrong number of arguments")
// ErrInvalidTypeConversion represents an invalid type conversion error. // ErrInvalidArgumentType represents an invalid argument value type error.
var ErrInvalidTypeConversion = errors.New("invalid type conversion") 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)
}

View file

@ -51,7 +51,7 @@ func (o *ImmutableMap) IsFalsy() bool {
func (o *ImmutableMap) IndexGet(index Object) (res Object, err error) { func (o *ImmutableMap) IndexGet(index Object) (res Object, err error) {
strIdx, ok := ToString(index) strIdx, ok := ToString(index)
if !ok { if !ok {
err = ErrInvalidTypeConversion err = ErrInvalidIndexType
return return
} }

View file

@ -94,7 +94,7 @@ func (o *Map) IndexGet(index Object) (res Object, err error) {
func (o *Map) IndexSet(index, value Object) (err error) { func (o *Map) IndexSet(index, value Object) (err error) {
strIdx, ok := ToString(index) strIdx, ok := ToString(index)
if !ok { if !ok {
err = ErrInvalidTypeConversion err = ErrInvalidIndexType
return return
} }

View file

@ -34,8 +34,10 @@ func TestObject_TypeName(t *testing.T) {
assert.Equal(t, "break", o.TypeName()) assert.Equal(t, "break", o.TypeName())
o = &objects.Continue{} o = &objects.Continue{}
assert.Equal(t, "continue", o.TypeName()) assert.Equal(t, "continue", o.TypeName())
o = &objects.BuiltinFunction{} o = &objects.BuiltinFunction{Name: "fn"}
assert.Equal(t, "builtin-function", o.TypeName()) assert.Equal(t, "builtin-function:fn", o.TypeName())
o = &objects.UserFunction{Name: "fn"}
assert.Equal(t, "user-function:fn", o.TypeName())
o = &objects.Closure{} o = &objects.Closure{}
assert.Equal(t, "closure", o.TypeName()) assert.Equal(t, "closure", o.TypeName())
o = &objects.CompiledFunction{} o = &objects.CompiledFunction{}

View file

@ -6,12 +6,13 @@ import (
// UserFunction represents a user function. // UserFunction represents a user function.
type UserFunction struct { type UserFunction struct {
Name string
Value CallableFunc Value CallableFunc
} }
// TypeName returns the name of the type. // TypeName returns the name of the type.
func (o *UserFunction) TypeName() string { func (o *UserFunction) TypeName() string {
return "user-function" return "user-function:" + o.Name
} }
func (o *UserFunction) String() string { func (o *UserFunction) String() string {

View file

@ -1,13 +1,14 @@
package runtime package runtime
import ( import (
"errors"
"fmt" "fmt"
"sync/atomic" "sync/atomic"
"github.com/d5/tengo/compiler" "github.com/d5/tengo/compiler"
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/compiler/token" "github.com/d5/tengo/compiler/token"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
"github.com/d5/tengo/stdlib"
) )
const ( const (
@ -30,42 +31,50 @@ var (
// VM is a virtual machine that executes the bytecode compiled by Compiler. // VM is a virtual machine that executes the bytecode compiled by Compiler.
type VM struct { type VM struct {
constants []objects.Object constants []objects.Object
stack []*objects.Object stack []*objects.Object
sp int sp int
globals []*objects.Object globals []*objects.Object
frames []Frame fileSet *source.FileSet
framesIndex int frames []Frame
curFrame *Frame framesIndex int
curInsts []byte curFrame *Frame
curIPLimit int curInsts []byte
ip int curIPLimit int
aborting int64 ip int
aborting int64
builtinModules map[string]*objects.Object
} }
// NewVM creates a VM. // 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 { if globals == nil {
globals = make([]*objects.Object, GlobalsSize) globals = make([]*objects.Object, GlobalsSize)
} }
if builtinModules == nil {
builtinModules = stdlib.Modules
}
frames := make([]Frame, MaxFrames) frames := make([]Frame, MaxFrames)
frames[0].fn = &objects.CompiledFunction{Instructions: bytecode.Instructions} frames[0].fn = bytecode.MainFunction
frames[0].freeVars = nil frames[0].freeVars = nil
frames[0].ip = -1 frames[0].ip = -1
frames[0].basePointer = 0 frames[0].basePointer = 0
return &VM{ return &VM{
constants: bytecode.Constants, constants: bytecode.Constants,
stack: make([]*objects.Object, StackSize), stack: make([]*objects.Object, StackSize),
sp: 0, sp: 0,
globals: globals, globals: globals,
frames: frames, fileSet: bytecode.FileSet,
framesIndex: 1, frames: frames,
curFrame: &(frames[0]), framesIndex: 1,
curInsts: frames[0].fn.Instructions, curFrame: &(frames[0]),
curIPLimit: len(frames[0].fn.Instructions) - 1, curInsts: frames[0].fn.Instructions,
ip: -1, curIPLimit: len(frames[0].fn.Instructions) - 1,
ip: -1,
builtinModules: builtinModules,
} }
} }
@ -85,10 +94,11 @@ func (v *VM) Run() error {
v.ip = -1 v.ip = -1
atomic.StoreInt64(&v.aborting, 0) atomic.StoreInt64(&v.aborting, 0)
mainloop:
for v.ip < v.curIPLimit && (atomic.LoadInt64(&v.aborting) == 0) { for v.ip < v.curIPLimit && (atomic.LoadInt64(&v.aborting) == 0) {
v.ip++ v.ip++
switch compiler.Opcode(v.curInsts[v.ip]) { switch v.curInsts[v.ip] {
case compiler.OpConstant: case compiler.OpConstant:
cidx := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 cidx := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
v.ip += 2 v.ip += 2
@ -115,7 +125,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Add, *right) res, err := (*left).BinaryOp(token.Add, *right)
if err != nil { 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 { if v.sp >= StackSize {
@ -132,7 +148,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Sub, *right) res, err := (*left).BinaryOp(token.Sub, *right)
if err != nil { 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 { if v.sp >= StackSize {
@ -149,7 +171,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Mul, *right) res, err := (*left).BinaryOp(token.Mul, *right)
if err != nil { 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 { if v.sp >= StackSize {
@ -166,7 +194,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Quo, *right) res, err := (*left).BinaryOp(token.Quo, *right)
if err != nil { 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 { if v.sp >= StackSize {
@ -183,7 +217,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Rem, *right) res, err := (*left).BinaryOp(token.Rem, *right)
if err != nil { 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 { if v.sp >= StackSize {
@ -200,7 +240,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.And, *right) res, err := (*left).BinaryOp(token.And, *right)
if err != nil { 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 { if v.sp >= StackSize {
@ -217,7 +263,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Or, *right) res, err := (*left).BinaryOp(token.Or, *right)
if err != nil { 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 { if v.sp >= StackSize {
@ -234,7 +286,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Xor, *right) res, err := (*left).BinaryOp(token.Xor, *right)
if err != nil { 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 { if v.sp >= StackSize {
@ -251,7 +309,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.AndNot, *right) res, err := (*left).BinaryOp(token.AndNot, *right)
if err != nil { 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 { if v.sp >= StackSize {
@ -268,7 +332,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Shl, *right) res, err := (*left).BinaryOp(token.Shl, *right)
if err != nil { 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 { if v.sp >= StackSize {
@ -285,7 +355,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Shr, *right) res, err := (*left).BinaryOp(token.Shr, *right)
if err != nil { 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 { if v.sp >= StackSize {
@ -334,7 +410,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Greater, *right) res, err := (*left).BinaryOp(token.Greater, *right)
if err != nil { 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 { if v.sp >= StackSize {
@ -351,7 +433,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.GreaterEq, *right) res, err := (*left).BinaryOp(token.GreaterEq, *right)
if err != nil { 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 { if v.sp >= StackSize {
@ -410,7 +498,8 @@ func (v *VM) Run() error {
v.stack[v.sp] = &res v.stack[v.sp] = &res
v.sp++ v.sp++
default: 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: case compiler.OpMinus:
@ -437,7 +526,8 @@ func (v *VM) Run() error {
v.stack[v.sp] = &res v.stack[v.sp] = &res
v.sp++ v.sp++
default: 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: case compiler.OpJumpFalsy:
@ -496,7 +586,8 @@ func (v *VM) Run() error {
v.sp -= numSelectors + 1 v.sp -= numSelectors + 1
if err := indexAssign(v.globals[globalIndex], val, selectors); err != nil { 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: case compiler.OpGetGlobal:
@ -586,7 +677,13 @@ func (v *VM) Run() error {
case objects.Indexable: case objects.Indexable:
val, err := left.IndexGet(*index) val, err := left.IndexGet(*index)
if err != nil { 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 { if val == nil {
val = objects.UndefinedValue val = objects.UndefinedValue
@ -602,7 +699,8 @@ func (v *VM) Run() error {
case *objects.Error: // err.value case *objects.Error: // err.value
key, ok := (*index).(*objects.String) key, ok := (*index).(*objects.String)
if !ok || key.Value != "value" { 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 { if v.sp >= StackSize {
@ -613,7 +711,8 @@ func (v *VM) Run() error {
v.sp++ v.sp++
default: 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: case compiler.OpSliceIndex:
@ -627,7 +726,8 @@ func (v *VM) Run() error {
if low, ok := (*low).(*objects.Int); ok { if low, ok := (*low).(*objects.Int); ok {
lowIdx = low.Value lowIdx = low.Value
} else { } 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 { } else if high, ok := (*high).(*objects.Int); ok {
highIdx = high.Value highIdx = high.Value
} else { } 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 { 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 { if lowIdx < 0 {
@ -675,11 +777,13 @@ func (v *VM) Run() error {
} else if high, ok := (*high).(*objects.Int); ok { } else if high, ok := (*high).(*objects.Int); ok {
highIdx = high.Value highIdx = high.Value
} else { } 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 { 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 { if lowIdx < 0 {
@ -711,11 +815,13 @@ func (v *VM) Run() error {
} else if high, ok := (*high).(*objects.Int); ok { } else if high, ok := (*high).(*objects.Int); ok {
highIdx = high.Value highIdx = high.Value
} else { } 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 { 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 { if lowIdx < 0 {
@ -747,11 +853,13 @@ func (v *VM) Run() error {
} else if high, ok := (*high).(*objects.Int); ok { } else if high, ok := (*high).(*objects.Int); ok {
highIdx = high.Value highIdx = high.Value
} else { } 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 { 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 { if lowIdx < 0 {
@ -780,17 +888,75 @@ func (v *VM) Run() error {
numArgs := int(v.curInsts[v.ip+1]) numArgs := int(v.curInsts[v.ip+1])
v.ip++ 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: case *objects.Closure:
if err := v.callFunction(callee.Fn, callee.Free, numArgs); err != nil { if numArgs != callee.Fn.NumParameters {
return err 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: case *objects.CompiledFunction:
if err := v.callFunction(callee, nil, numArgs); err != nil { if numArgs != callee.NumParameters {
return err 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: case objects.Callable:
var args []objects.Object var args []objects.Object
for _, arg := range v.stack[v.sp-numArgs : v.sp] { for _, arg := range v.stack[v.sp-numArgs : v.sp] {
@ -802,7 +968,19 @@ func (v *VM) Run() error {
// runtime error // runtime error
if err != nil { 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 // nil return -> undefined
@ -816,8 +994,10 @@ func (v *VM) Run() error {
v.stack[v.sp] = &ret v.stack[v.sp] = &ret
v.sp++ v.sp++
default: 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: case compiler.OpReturnValue:
@ -834,9 +1014,10 @@ func (v *VM) Run() error {
//v.sp = lastFrame.basePointer - 1 //v.sp = lastFrame.basePointer - 1
v.sp = lastFrame.basePointer v.sp = lastFrame.basePointer
if v.sp-1 >= StackSize { // skip stack overflow check because (newSP) <= (oldSP)
return ErrStackOverflow //if v.sp-1 >= StackSize {
} // return ErrStackOverflow
//}
v.stack[v.sp-1] = retVal v.stack[v.sp-1] = retVal
//v.sp++ //v.sp++
@ -852,9 +1033,10 @@ func (v *VM) Run() error {
//v.sp = lastFrame.basePointer - 1 //v.sp = lastFrame.basePointer - 1
v.sp = lastFrame.basePointer v.sp = lastFrame.basePointer
if v.sp-1 >= StackSize { // skip stack overflow check because (newSP) <= (oldSP)
return ErrStackOverflow //if v.sp-1 >= StackSize {
} // return ErrStackOverflow
//}
v.stack[v.sp-1] = undefinedPtr v.stack[v.sp-1] = undefinedPtr
//v.sp++ //v.sp++
@ -898,7 +1080,8 @@ func (v *VM) Run() error {
sp := v.curFrame.basePointer + localIndex sp := v.curFrame.basePointer + localIndex
if err := indexAssign(v.stack[sp], val, selectors); err != nil { 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: case compiler.OpGetLocal:
@ -925,15 +1108,54 @@ func (v *VM) Run() error {
v.stack[v.sp] = &builtinFuncs[builtinIndex] v.stack[v.sp] = &builtinFuncs[builtinIndex]
v.sp++ 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: case compiler.OpClosure:
constIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8 constIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
numFree := int(v.curInsts[v.ip+3]) numFree := int(v.curInsts[v.ip+3])
v.ip += 3 v.ip += 3
if err := v.pushClosure(constIndex, numFree); err != nil { fn, ok := v.constants[constIndex].(*objects.CompiledFunction)
return err 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: case compiler.OpGetFree:
freeIndex := int(v.curInsts[v.ip+1]) freeIndex := int(v.curInsts[v.ip+1])
v.ip++ v.ip++
@ -958,7 +1180,8 @@ func (v *VM) Run() error {
v.sp -= numSelectors + 1 v.sp -= numSelectors + 1
if err := indexAssign(v.curFrame.freeVars[freeIndex], val, selectors); err != nil { 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: case compiler.OpSetFree:
@ -978,7 +1201,8 @@ func (v *VM) Run() error {
iterable, ok := (*dst).(objects.Iterable) iterable, ok := (*dst).(objects.Iterable)
if !ok { 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() iterator = iterable.Iterate()
@ -1034,13 +1258,13 @@ func (v *VM) Run() error {
v.sp++ v.sp++
default: 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 // check if stack still has some objects left
if v.sp > 0 && atomic.LoadInt64(&v.aborting) == 0 { 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 return nil
@ -1056,141 +1280,21 @@ func (v *VM) FrameInfo() (frameIndex, ip int) {
return v.framesIndex - 1, v.ip 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 { func indexAssign(dst, src *objects.Object, selectors []*objects.Object) error {
numSel := len(selectors) numSel := len(selectors)
for sidx := numSel - 1; sidx > 0; sidx-- { for sidx := numSel - 1; sidx > 0; sidx-- {
indexable, ok := (*dst).(objects.Indexable) indexable, ok := (*dst).(objects.Indexable)
if !ok { if !ok {
return objects.ErrNotIndexable return fmt.Errorf("not indexable: %s", (*dst).TypeName())
} }
next, err := indexable.IndexGet(*selectors[sidx]) next, err := indexable.IndexGet(*selectors[sidx])
if err != nil { if err != nil {
if err == objects.ErrInvalidIndexType {
return fmt.Errorf("invalid index type: %s", (*selectors[sidx]).TypeName())
}
return err return err
} }
@ -1199,15 +1303,26 @@ func indexAssign(dst, src *objects.Object, selectors []*objects.Object) error {
indexAssignable, ok := (*dst).(objects.IndexAssignable) indexAssignable, ok := (*dst).(objects.IndexAssignable)
if !ok { 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() { func init() {
builtinFuncs = make([]objects.Object, len(objects.Builtins)) builtinFuncs = make([]objects.Object, len(objects.Builtins))
for i, b := range 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,
}
} }
} }

View file

@ -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}) expect(t, `func () { a1 := [1, 2, 3]; a2 := a1; a1[0] = 5; out = a2 }()`, ARR{5, 2, 3})
// array index set // 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 // index operator
arr := ARR{1, 2, 3, 4, 5, 6} 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]", arrStr, arrLen+1), arr)
expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, 2, 2), 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("%s[:%d]", arrStr, -1), "invalid slice index")
expectError(t, fmt.Sprintf("out = %s[%d:]", arrStr, arrLen+1)) expectError(t, fmt.Sprintf("%s[%d:]", arrStr, arrLen+1), "invalid slice index")
expectError(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, 0, -1)) expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 0, -1), "invalid slice index")
expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 2, 1)) expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 2, 1), "invalid slice index")
} }

View file

@ -35,8 +35,8 @@ func() {
}() }()
}()`, 4) }()`, 4)
expectError(t, `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 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 += 2; out = a`, 3)
expect(t, `a := 1; a += 4 - 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 /= 2;; out = a`, 5)
expect(t, `a := 10; a /= 5 - 3;; out = a`, 5) expect(t, `a := 10; a /= 5 - 3;; out = a`, 5)
// +=, -=, *=, /= operator does not define new variable // composite assignment operator does not define new variable
expectError(t, `a += 4`) expectError(t, `a += 4`, "unresolved reference")
expectError(t, `a -= 4`) expectError(t, `a -= 4`, "unresolved reference")
expectError(t, `a *= 4`) expectError(t, `a *= 4`, "unresolved reference")
expectError(t, `a /= 4`) expectError(t, `a /= 4`, "unresolved reference")
expect(t, ` expect(t, `
f1 := func() { f1 := func() {
@ -193,8 +193,8 @@ out = func() {
`, 136) `, 136)
// assigning different type value // assigning different type value
expect(t, `a := 1; a = "foo"; out = a`, "foo") // global expect(t, `a := 1; a = "foo"; out = a`, "foo") // global
expect(t, `func() { a := 1; a = "foo"; out = a }()`, "foo") // local expect(t, `func() { a := 1; a = "foo"; out = a }()`, "foo") // local
expect(t, ` expect(t, `
out = func() { out = func() {
a := 5 a := 5
@ -250,7 +250,7 @@ a := {
f: [9, 8] f: [9, 8]
} }
} }
a.x.e = "bar"`) a.x.e = "bar"`, "not index-assignable")
// multi-variables // multi-variables
//expect(t, `a, b = 1, 2; out = a + b`, 3) //expect(t, `a, b = 1, 2; out = a + b`, 3)

View file

@ -31,23 +31,25 @@ func TestBoolean(t *testing.T) {
expect(t, `out = (1 > 2) == true`, false) expect(t, `out = (1 > 2) == true`, false)
expect(t, `out = (1 > 2) == false`, true) expect(t, `out = (1 > 2) == false`, true)
expectError(t, `5 + true`) expectError(t, `5 + true`, "invalid operation")
expectError(t, `5 + true; 5`) expectError(t, `5 + true; 5`, "invalid operation")
expectError(t, `-true`) expectError(t, `-true`, "invalid operation")
expectError(t, `true + false`) expectError(t, `true + false`, "invalid operation")
expectError(t, `5; true + false; 5`) expectError(t, `5; true + false; 5`, "invalid operation")
expectError(t, `if (10 > 1) { true + false; }`) expectError(t, `if (10 > 1) { true + false; }`, "invalid operation")
expectError(t, ` expectError(t, `
if (10 > 1) { func() {
if (10 > 1) { if (10 > 1) {
return true + false; if (10 > 1) {
} return true + false;
}
return 1; return 1;
} }
`) }()
expectError(t, `if (true + false) { 10 }`) `, "invalid operation")
expectError(t, `10 + (true + false)`) expectError(t, `if (true + false) { 10 }`, "invalid operation")
expectError(t, `(true + false) + 20`) expectError(t, `10 + (true + false)`, "invalid operation")
expectError(t, `!(true + false)`) expectError(t, `(true + false) + 20`, "invalid operation")
expectError(t, `!(true + false)`, "invalid operation")
} }

View file

@ -18,11 +18,11 @@ func TestBuiltinFunction(t *testing.T) {
expect(t, `out = len(immutable([1, 2, 3]))`, 3) expect(t, `out = len(immutable([1, 2, 3]))`, 3)
expect(t, `out = len(immutable({}))`, 0) expect(t, `out = len(immutable({}))`, 0)
expect(t, `out = len(immutable({a:1, b:2}))`, 2) expect(t, `out = len(immutable({a:1, b:2}))`, 2)
expectError(t, `len(1)`) expectError(t, `len(1)`, "invalid type for argument")
expectError(t, `len("one", "two")`) expectError(t, `len("one", "two")`, "wrong number of arguments")
expect(t, `out = copy(1)`, 1) 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)`, ARR{1, 2, 3, 4})
expect(t, `out = append([1, 2, 3], 4, 5, 6)`, ARR{1, 2, 3, 4, 5, 6}) 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 %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", [1, "bar", true])`, "foo [1 bar true]")
expect(t, `out = sprintf("foo %v %d", [1, "bar", true], 19)`, "foo [1 bar true] 19") 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(1)`, "invalid type for argument") // format has to be String
expectError(t, `sprintf('c')`) // format has to be String expectError(t, `sprintf('c')`, "invalid type for argument") // format has to be String
// type_name // type_name
expect(t, `out = type_name(1)`, "int") 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)`, true) // function
expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a(5))`, true) // closure 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 expectWithSymbols(t, `out = is_callable(x)`, true, SYM{"x": &StringArray{Value: []string{"foo", "bar"}}}) // user object
} }

View file

@ -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")
}

View file

@ -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")
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").err`, "invalid index on error")
expectError(t, `error("error").value_`) expectError(t, `error("error").value_`, "invalid index on error")
expectError(t, `error([1,2,3])[1]`) expectError(t, `error([1,2,3])[1]`, "invalid index on error")
} }

View file

@ -135,9 +135,9 @@ func TestFunction(t *testing.T) {
out = outer() + g out = outer() + g
`, 50) `, 50)
expectError(t, `func() { return 1; }(1)`) expectError(t, `func() { return 1; }(1)`, "wrong number of arguments")
expectError(t, `func(a) { return a; }()`) expectError(t, `func(a) { return a; }()`, "wrong number of arguments")
expectError(t, `func(a, b) { return a + b; }(1)`) expectError(t, `func(a, b) { return a + b; }(1)`, "wrong number of arguments")
expect(t, ` expect(t, `
f1 := func(a) { f1 := func(a) {
@ -218,7 +218,7 @@ out = func() {
return sum(5) return sum(5)
}()`, 15) }()`, 15)
expectError(t, `return 5`) expectError(t, `return 5`, "return not allowed outside function")
// closure and block scopes // closure and block scopes
expect(t, ` expect(t, `

View file

@ -14,11 +14,11 @@ func TestImmutable(t *testing.T) {
expect(t, `a := immutable(1); a = 5; out = a`, 5) expect(t, `a := immutable(1); a = 5; out = a`, 5)
// array // array
expectError(t, `a := immutable([1, 2, 3]); a[1] = 5`) expectError(t, `a := immutable([1, 2, 3]); a[1] = 5`, "not index-assignable")
expectError(t, `a := immutable(["foo", [1,2,3]]); a[1] = "bar"`) 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}}) 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 := 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"`) 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 = 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, `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) 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) expect(t, `a := immutable([1, 2, 3]); out = a[5]`, objects.UndefinedValue)
// map // map
expectError(t, `a := immutable({b: 1, c: 2}); a.b = 5`) 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"`) 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}}) 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 := 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"`) 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}) == {a:1,b:2}`, true)
expect(t, `out = immutable({a:1,b:2}) == immutable({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) 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({a:1,b:2}); out = a.c`, objects.UndefinedValue)
expect(t, `a := immutable({b: 5, c: "foo"}); out = a.b`, 5) 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")
} }

View file

@ -13,10 +13,10 @@ func TestIncDec(t *testing.T) {
// this seems strange but it works because 'a += b' is // this seems strange but it works because 'a += b' is
// translated into 'a = a + b' and string type takes other types for + operator. // translated into 'a = a + b' and string type takes other types for + operator.
expect(t, `a := "foo"; a++; out = a`, "foo1") 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++`, "unresolved reference") // not declared
expectError(t, `a--`) // 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, `a := 0; b := a++`) // inc-dec is statement not expression <- parser error
expectError(t, `4++`) expectError(t, `4++`, "unresolved reference")
} }

View file

@ -51,7 +51,7 @@ func (o *StringDict) IndexSet(index, value objects.Object) error {
strVal, ok := objects.ToString(value) strVal, ok := objects.ToString(value)
if !ok { if !ok {
return objects.ErrInvalidTypeConversion return objects.ErrInvalidIndexValueType
} }
o.Value[strings.ToLower(strIdx.Value)] = strVal 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) strVal, ok := objects.ToString(value)
if !ok { if !ok {
return objects.ErrInvalidTypeConversion return objects.ErrInvalidIndexValueType
} }
o.Value[r] = strVal 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 { func (o *StringArray) IndexSet(index, value objects.Object) error {
strVal, ok := objects.ToString(value) strVal, ok := objects.ToString(value)
if !ok { if !ok {
return objects.ErrInvalidTypeConversion return objects.ErrInvalidIndexValueType
} }
intIdx, ok := index.(*objects.Int) 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]) s1, ok := objects.ToString(args[0])
if !ok { 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 { 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["a"]`, "foo", SYM{"dict": dict()})
expectWithSymbols(t, `out = dict["B"]`, "bar", SYM{"dict": dict()}) expectWithSymbols(t, `out = dict["B"]`, "bar", SYM{"dict": dict()})
expectWithSymbols(t, `out = dict["x"]`, objects.UndefinedValue, 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"}} } strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} }
expectWithSymbols(t, `out = cir[0]`, "one", SYM{"cir": strCir()}) 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[-1]`, "three", SYM{"cir": strCir()})
expectWithSymbols(t, `out = cir[-2]`, "two", SYM{"cir": strCir()}) expectWithSymbols(t, `out = cir[-2]`, "two", SYM{"cir": strCir()})
expectWithSymbols(t, `out = cir[3]`, "one", 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"}} } strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} }
expectWithSymbols(t, `out = arr["one"]`, 0, SYM{"arr": strArr()}) 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["four"]`, objects.UndefinedValue, SYM{"arr": strArr()})
expectWithSymbols(t, `out = arr[0]`, "one", SYM{"arr": strArr()}) expectWithSymbols(t, `out = arr[0]`, "one", SYM{"arr": strArr()})
expectWithSymbols(t, `out = arr[1]`, "two", 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) { 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["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()})
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"}} } 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[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] = "TWO"; out = cir[1]`, "TWO", SYM{"cir": strCir()})
expectWithSymbols(t, `cir[-1] = "THREE"; out = cir[2]`, "THREE", 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()}) 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"}} } 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[0] = "ONE"; out = arr[0]`, "ONE", SYM{"arr": strArr()})
expectWithSymbols(t, `arr[1] = "TWO"; out = arr[1]`, "TWO", 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")
} }

View file

@ -86,10 +86,10 @@ func TestUserModules(t *testing.T) {
// export value is immutable // export value is immutable
expectErrorWithUserModules(t, `m1 := import("mod1"); m1.a = 5`, map[string]string{ expectErrorWithUserModules(t, `m1 := import("mod1"); m1.a = 5`, map[string]string{
"mod1": `export {a: 1, b: 2}`, "mod1": `export {a: 1, b: 2}`,
}) }, "not index-assignable")
expectErrorWithUserModules(t, `m1 := import("mod1"); m1[1] = 5`, map[string]string{ expectErrorWithUserModules(t, `m1 := import("mod1"); m1[1] = 5`, map[string]string{
"mod1": `export [1, 2, 3]`, "mod1": `export [1, 2, 3]`,
}) }, "not index-assignable")
// code after export statement will not be executed // code after export statement will not be executed
expectWithUserModules(t, `out = import("mod1")`, 10, map[string]string{ expectWithUserModules(t, `out = import("mod1")`, 10, map[string]string{
@ -160,27 +160,27 @@ export func() {
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ expectErrorWithUserModules(t, `import("mod1")`, map[string]string{
"mod1": `import("mod2")`, "mod1": `import("mod2")`,
"mod2": `import("mod1")`, "mod2": `import("mod1")`,
}) }, "mod2:1:1: cyclic module import")
// (main) -> mod1 -> mod2 -> mod3 -> mod1 // (main) -> mod1 -> mod2 -> mod3 -> mod1
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ expectErrorWithUserModules(t, `import("mod1")`, map[string]string{
"mod1": `import("mod2")`, "mod1": `import("mod2")`,
"mod2": `import("mod3")`, "mod2": `import("mod3")`,
"mod3": `import("mod1")`, "mod3": `import("mod1")`,
}) }, "mod3:1:1: cyclic module import")
// (main) -> mod1 -> mod2 -> mod3 -> mod2 // (main) -> mod1 -> mod2 -> mod3 -> mod2
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ expectErrorWithUserModules(t, `import("mod1")`, map[string]string{
"mod1": `import("mod2")`, "mod1": `import("mod2")`,
"mod2": `import("mod3")`, "mod2": `import("mod3")`,
"mod3": `import("mod2")`, "mod3": `import("mod2")`,
}) }, "mod3:1:1: cyclic module import")
// unknown modules // unknown modules
expectErrorWithUserModules(t, `import("mod0")`, map[string]string{ expectErrorWithUserModules(t, `import("mod0")`, map[string]string{
"mod1": `a := 5`, "mod1": `a := 5`,
}) }, "module 'mod0' not found")
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ expectErrorWithUserModules(t, `import("mod1")`, map[string]string{
"mod1": `import("mod2")`, "mod1": `import("mod2")`,
}) }, "module 'mod2' not found")
// module is immutable but its variables is not necessarily immutable. // 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{ 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 // 'export' must be in the top-level
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ expectErrorWithUserModules(t, `import("mod1")`, map[string]string{
"mod1": `func() { export 5 }()`, "mod1": `func() { export 5 }()`,
}) }, "mod1:1:10: export not allowed inside function")
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ expectErrorWithUserModules(t, `import("mod1")`, map[string]string{
"mod1": `func() { func() { export 5 }() }()`, "mod1": `func() { func() { export 5 }() }()`,
}) }, "mod1:1:19: export not allowed inside function")
// module cannot access outer scope // 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": `export a`,
}) }, "mod1:1:8: unresolved reference 'a'")
} }
func TestModuleBlockScopes(t *testing.T) { func TestModuleBlockScopes(t *testing.T) {

View file

@ -21,7 +21,7 @@ a := {
} }
out = a.b.c`, 4) out = a.b.c`, 4)
expectError(t, ` expect(t, `
a := { a := {
b: { b: {
c: 4, c: 4,
@ -29,9 +29,9 @@ a := {
}, },
c: "foo bar" c: "foo bar"
} }
out = a.x.c`) b := a.x.c`, objects.UndefinedValue)
expectError(t, ` expect(t, `
a := { a := {
b: { b: {
c: 4, c: 4,
@ -39,7 +39,7 @@ a := {
}, },
c: "foo bar" 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.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 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) `, 9)
expectError(t, `a := {b: {c: 1}}; a.d.c = 2`) expectError(t, `a := {b: {c: 1}}; a.d.c = 2`, "not index-assignable")
expectError(t, `a := [1, 2, 3]; a.b = 2`) expectError(t, `a := [1, 2, 3]; a.b = 2`, "invalid index type")
expectError(t, `a := "foo"; a.b = 2`) expectError(t, `a := "foo"; a.b = 2`, "not index-assignable")
expectError(t, `func() { a := {b: {c: 1}}; a.d.c = 2 }()`) expectError(t, `func() { a := {b: {c: 1}}; a.d.c = 2 }()`, "not index-assignable")
expectError(t, `func() { a := [1, 2, 3]; a.b = 2 }()`) expectError(t, `func() { a := [1, 2, 3]; a.b = 2 }()`, "invalid index type")
expectError(t, `func() { a := "foo"; a.b = 2 }()`) expectError(t, `func() { a := "foo"; a.b = 2 }()`, "not index-assignable")
} }

View file

@ -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]", strStr, strLen+1), str)
expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, 2, 2), "") expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, 2, 2), "")
expectError(t, fmt.Sprintf("out = %s[:%d]", strStr, -1)) expectError(t, fmt.Sprintf("%s[:%d]", strStr, -1), "invalid slice index")
expectError(t, fmt.Sprintf("out = %s[%d:]", strStr, strLen+1)) expectError(t, fmt.Sprintf("%s[%d:]", strStr, strLen+1), "invalid slice index")
expectError(t, fmt.Sprintf("out = %s[%d:%d]", strStr, 0, -1)) expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 0, -1), "invalid slice index")
expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 2, 1)) expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 2, 1), "invalid slice index")
// string concatenation with other types // string concatenation with other types
expect(t, `out = "foo" + 1`, "foo1") expect(t, `out = "foo" + 1`, "foo1")
@ -68,7 +68,7 @@ func TestString(t *testing.T) {
// also works with "+=" operator // also works with "+=" operator
expect(t, `out = "foo"; out += 1.5`, "foo1.5") expect(t, `out = "foo"; out += 1.5`, "foo1.5")
// string concats works only when string is LHS // 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")
} }

View file

@ -78,6 +78,19 @@ iter := func(n, max) {
return iter(n+1, max) return iter(n+1, max)
} }
out = iter(0, 9999) 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) `, 9999)
} }

View file

@ -52,11 +52,16 @@ func expectWithUserModules(t *testing.T, input string, expected interface{}, use
runVM(t, file, expected, nil, userModules) runVM(t, file, expected, nil, userModules)
} }
func expectError(t *testing.T, input string) { func expectError(t *testing.T, input, expected string) {
expectErrorWithUserModules(t, input, nil) 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 // parse
program := parse(t, input) program := parse(t, input)
if program == nil { if program == nil {
@ -64,10 +69,14 @@ func expectErrorWithUserModules(t *testing.T, input string, userModules map[stri
} }
// compiler/VM // 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 // parse
program := parse(t, input) program := parse(t, input)
if program == nil { if program == nil {
@ -75,7 +84,11 @@ func expectErrorWithSymbols(t *testing.T, input string, symbols map[string]objec
} }
// compiler/VM // 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) { 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 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 { func errorObject(v interface{}) *objects.Error {
return &objects.Error{Value: toObject(v)} return &objects.Error{Value: toObject(v)}
} }
@ -230,7 +228,7 @@ func traceCompileRun(file *ast.File, symbols map[string]objects.Object, userModu
} }
tr := &tracer{} 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) { c.SetModuleLoader(func(moduleName string) ([]byte, error) {
if src, ok := userModules[moduleName]; ok { if src, ok := userModules[moduleName]; ok {
return []byte(src), nil 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 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"))) 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() err = v.Run()
{ {
@ -296,7 +294,7 @@ func formatGlobals(globals []*objects.Object) (formatted []string) {
func parse(t *testing.T, input string) *ast.File { func parse(t *testing.T, input string) *ast.File {
testFileSet := source.NewFileSet() testFileSet := source.NewFileSet()
testFile := testFileSet.AddFile("", -1, len(input)) testFile := testFileSet.AddFile("test", -1, len(input))
file, err := parser.ParseFile(testFile, []byte(input), nil) file, err := parser.ParseFile(testFile, []byte(input), nil)
if !assert.NoError(t, err) { if !assert.NoError(t, err) {

View file

@ -7,9 +7,9 @@ import (
"github.com/d5/tengo/compiler" "github.com/d5/tengo/compiler"
"github.com/d5/tengo/compiler/parser" "github.com/d5/tengo/compiler/parser"
"github.com/d5/tengo/compiler/source" "github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/compiler/stdlib"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
"github.com/d5/tengo/runtime" "github.com/d5/tengo/runtime"
"github.com/d5/tengo/stdlib"
) )
// Script can simplify compilation and execution of embedded scripts. // Script can simplify compilation and execution of embedded scripts.
@ -87,14 +87,15 @@ func (s *Script) Compile() (*Compiled, error) {
} }
fileSet := source.NewFileSet() 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() file, err := p.ParseFile()
if err != nil { if err != nil {
return nil, fmt.Errorf("parse error: %s", err.Error()) 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 { if s.userModuleLoader != nil {
c.SetModuleLoader(s.userModuleLoader) c.SetModuleLoader(s.userModuleLoader)
@ -106,7 +107,7 @@ func (s *Script) Compile() (*Compiled, error) {
return &Compiled{ return &Compiled{
symbolTable: symbolTable, symbolTable: symbolTable,
machine: runtime.NewVM(c.Bytecode(), globals), machine: runtime.NewVM(c.Bytecode(), globals, nil),
}, nil }, nil
} }
@ -135,7 +136,7 @@ func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error)
return 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 var names []string
for name := range s.variables { for name := range s.variables {
names = append(names, name) names = append(names, name)
@ -148,10 +149,10 @@ func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, stdModules ma
} }
} }
stdModules = make(map[string]*objects.ImmutableMap) stdModules = make(map[string]bool)
for name, mod := range stdlib.Modules { for name := range stdlib.Modules {
if !s.removedStdModules[name] { if !s.removedStdModules[name] {
stdModules[name] = mod stdModules[name] = true
} }
} }

1057
stdlib/func_typedefs.go Normal file

File diff suppressed because it is too large Load diff

View file

@ -7,215 +7,216 @@ import (
"testing" "testing"
"github.com/d5/tengo/assert" "github.com/d5/tengo/assert"
"github.com/d5/tengo/compiler/stdlib"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
"github.com/d5/tengo/stdlib"
) )
func TestFuncAIR(t *testing.T) { func TestFuncAIR(t *testing.T) {
uf := stdlib.FuncAIR(func(int) {}) 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.NoError(t, err)
assert.Equal(t, objects.UndefinedValue, ret) assert.Equal(t, objects.UndefinedValue, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncAR(t *testing.T) { func TestFuncAR(t *testing.T) {
uf := stdlib.FuncAR(func() {}) uf := stdlib.FuncAR(func() {})
ret, err := uf.Call() ret, err := funcCall(uf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, objects.UndefinedValue, ret) assert.Equal(t, objects.UndefinedValue, ret)
_, err = uf.Call(objects.TrueValue) _, err = funcCall(uf, objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncARI(t *testing.T) { func TestFuncARI(t *testing.T) {
uf := stdlib.FuncARI(func() int { return 10 }) uf := stdlib.FuncARI(func() int { return 10 })
ret, err := uf.Call() ret, err := funcCall(uf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Int{Value: 10}, ret) assert.Equal(t, &objects.Int{Value: 10}, ret)
_, err = uf.Call(objects.TrueValue) _, err = funcCall(uf, objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncARE(t *testing.T) { func TestFuncARE(t *testing.T) {
uf := stdlib.FuncARE(func() error { return nil }) uf := stdlib.FuncARE(func() error { return nil })
ret, err := uf.Call() ret, err := funcCall(uf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret) assert.Equal(t, objects.TrueValue, ret)
uf = stdlib.FuncARE(func() error { return errors.New("some error") }) uf = stdlib.FuncARE(func() error { return errors.New("some error") })
ret, err = uf.Call() ret, err = funcCall(uf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncARIsE(t *testing.T) { func TestFuncARIsE(t *testing.T) {
uf := stdlib.FuncARIsE(func() ([]int, error) { return []int{1, 2, 3}, nil }) 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.NoError(t, err)
assert.Equal(t, array(&objects.Int{Value: 1}, &objects.Int{Value: 2}, &objects.Int{Value: 3}), ret) 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") }) 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.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncARS(t *testing.T) { func TestFuncARS(t *testing.T) {
uf := stdlib.FuncARS(func() string { return "foo" }) uf := stdlib.FuncARS(func() string { return "foo" })
ret, err := uf.Call() ret, err := funcCall(uf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.String{Value: "foo"}, ret) assert.Equal(t, &objects.String{Value: "foo"}, ret)
_, err = uf.Call(objects.TrueValue) _, err = funcCall(uf, objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncARSE(t *testing.T) { func TestFuncARSE(t *testing.T) {
uf := stdlib.FuncARSE(func() (string, error) { return "foo", nil }) uf := stdlib.FuncARSE(func() (string, error) { return "foo", nil })
ret, err := uf.Call() ret, err := funcCall(uf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.String{Value: "foo"}, ret) assert.Equal(t, &objects.String{Value: "foo"}, ret)
uf = stdlib.FuncARSE(func() (string, error) { return "", errors.New("some error") }) uf = stdlib.FuncARSE(func() (string, error) { return "", errors.New("some error") })
ret, err = uf.Call() ret, err = funcCall(uf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncARSs(t *testing.T) { func TestFuncARSs(t *testing.T) {
uf := stdlib.FuncARSs(func() []string { return []string{"foo", "bar"} }) uf := stdlib.FuncARSs(func() []string { return []string{"foo", "bar"} })
ret, err := uf.Call() ret, err := funcCall(uf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASRE(t *testing.T) { func TestFuncASRE(t *testing.T) {
uf := stdlib.FuncASRE(func(a string) error { return nil }) 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.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret) assert.Equal(t, objects.TrueValue, ret)
uf = stdlib.FuncASRE(func(a string) error { return errors.New("some error") }) 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.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASRS(t *testing.T) { func TestFuncASRS(t *testing.T) {
uf := stdlib.FuncASRS(func(a string) string { return a }) 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.NoError(t, err)
assert.Equal(t, &objects.String{Value: "foo"}, ret) assert.Equal(t, &objects.String{Value: "foo"}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASRSs(t *testing.T) { func TestFuncASRSs(t *testing.T) {
uf := stdlib.FuncASRSs(func(a string) []string { return []string{a} }) 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.NoError(t, err)
assert.Equal(t, array(&objects.String{Value: "foo"}), ret) assert.Equal(t, array(&objects.String{Value: "foo"}), ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASI64RE(t *testing.T) { func TestFuncASI64RE(t *testing.T) {
uf := stdlib.FuncASI64RE(func(a string, b int64) error { return nil }) 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.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret) assert.Equal(t, objects.TrueValue, ret)
uf = stdlib.FuncASI64RE(func(a string, b int64) error { return errors.New("some error") }) 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.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncAIIRE(t *testing.T) { func TestFuncAIIRE(t *testing.T) {
uf := stdlib.FuncAIIRE(func(a, b int) error { return nil }) 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.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret) assert.Equal(t, objects.TrueValue, ret)
uf = stdlib.FuncAIIRE(func(a, b int) error { return errors.New("some error") }) 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.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASIIRE(t *testing.T) { func TestFuncASIIRE(t *testing.T) {
uf := stdlib.FuncASIIRE(func(a string, b, c int) error { return nil }) 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.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret) assert.Equal(t, objects.TrueValue, ret)
uf = stdlib.FuncASIIRE(func(a string, b, c int) error { return errors.New("some error") }) 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.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASRSE(t *testing.T) { func TestFuncASRSE(t *testing.T) {
uf := stdlib.FuncASRSE(func(a string) (string, error) { return a, nil }) 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.NoError(t, err)
assert.Equal(t, &objects.String{Value: "foo"}, ret) assert.Equal(t, &objects.String{Value: "foo"}, ret)
uf = stdlib.FuncASRSE(func(a string) (string, error) { return a, errors.New("some error") }) 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.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASSRE(t *testing.T) { func TestFuncASSRE(t *testing.T) {
uf := stdlib.FuncASSRE(func(a, b string) error { return nil }) 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.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret)
uf = stdlib.FuncASSRE(func(a, b string) error { return errors.New("some error") }) 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.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASsRS(t *testing.T) { func TestFuncASsRS(t *testing.T) {
uf := stdlib.FuncASsSRS(func(a []string, b string) string { return strings.Join(a, b) }) 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.NoError(t, err)
assert.Equal(t, &objects.String{Value: "foo bar"}, ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncARF(t *testing.T) { func TestFuncARF(t *testing.T) {
uf := stdlib.FuncARF(func() float64 { return 10.0 }) uf := stdlib.FuncARF(func() float64 { return 10.0 })
ret, err := uf.Call() ret, err := funcCall(uf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Float{Value: 10.0}, ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncAFRF(t *testing.T) { func TestFuncAFRF(t *testing.T) {
uf := stdlib.FuncAFRF(func(a float64) float64 { return a }) 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.NoError(t, err)
assert.Equal(t, &objects.Float{Value: 10.0}, ret) assert.Equal(t, &objects.Float{Value: 10.0}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
@ -223,12 +224,12 @@ func TestFuncAIRF(t *testing.T) {
uf := stdlib.FuncAIRF(func(a int) float64 { uf := stdlib.FuncAIRF(func(a int) float64 {
return float64(a) 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.NoError(t, err)
assert.Equal(t, &objects.Float{Value: 10.0}, ret) assert.Equal(t, &objects.Float{Value: 10.0}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
@ -236,12 +237,12 @@ func TestFuncAFRI(t *testing.T) {
uf := stdlib.FuncAFRI(func(a float64) int { uf := stdlib.FuncAFRI(func(a float64) int {
return int(a) 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.NoError(t, err)
assert.Equal(t, &objects.Int{Value: 10}, ret) assert.Equal(t, &objects.Int{Value: 10}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
@ -249,12 +250,12 @@ func TestFuncAFRB(t *testing.T) {
uf := stdlib.FuncAFRB(func(a float64) bool { uf := stdlib.FuncAFRB(func(a float64) bool {
return a > 0.0 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.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret) assert.Equal(t, objects.TrueValue, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
@ -262,23 +263,23 @@ func TestFuncAFFRF(t *testing.T) {
uf := stdlib.FuncAFFRF(func(a, b float64) float64 { uf := stdlib.FuncAFFRF(func(a, b float64) float64 {
return a + b 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.NoError(t, err)
assert.Equal(t, &objects.Float{Value: 30.0}, ret) assert.Equal(t, &objects.Float{Value: 30.0}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
_, err = uf.Call(objects.TrueValue) _, err = funcCall(uf, objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASIRS(t *testing.T) { func TestFuncASIRS(t *testing.T) {
uf := stdlib.FuncASIRS(func(a string, b int) string { return strings.Repeat(a, b) }) 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.NoError(t, err)
assert.Equal(t, &objects.String{Value: "abab"}, ret) assert.Equal(t, &objects.String{Value: "abab"}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
_, err = uf.Call(objects.TrueValue) _, err = funcCall(uf, objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
@ -286,12 +287,12 @@ func TestFuncAIFRF(t *testing.T) {
uf := stdlib.FuncAIFRF(func(a int, b float64) float64 { uf := stdlib.FuncAIFRF(func(a int, b float64) float64 {
return float64(a) + b 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.NoError(t, err)
assert.Equal(t, &objects.Float{Value: 30.0}, ret) assert.Equal(t, &objects.Float{Value: 30.0}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
_, err = uf.Call(objects.TrueValue) _, err = funcCall(uf, objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
@ -299,12 +300,12 @@ func TestFuncAFIRF(t *testing.T) {
uf := stdlib.FuncAFIRF(func(a float64, b int) float64 { uf := stdlib.FuncAFIRF(func(a float64, b int) float64 {
return a + float64(b) 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.NoError(t, err)
assert.Equal(t, &objects.Float{Value: 30.0}, ret) assert.Equal(t, &objects.Float{Value: 30.0}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
_, err = uf.Call(objects.TrueValue) _, err = funcCall(uf, objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
@ -312,12 +313,12 @@ func TestFuncAFIRB(t *testing.T) {
uf := stdlib.FuncAFIRB(func(a float64, b int) bool { uf := stdlib.FuncAFIRB(func(a float64, b int) bool {
return a < float64(b) 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.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret) assert.Equal(t, objects.TrueValue, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
_, err = uf.Call(objects.TrueValue) _, err = funcCall(uf, objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
@ -325,43 +326,43 @@ func TestFuncAIRSsE(t *testing.T) {
uf := stdlib.FuncAIRSsE(func(a int) ([]string, error) { uf := stdlib.FuncAIRSsE(func(a int) ([]string, error) {
return []string{"foo", "bar"}, nil 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.NoError(t, err)
assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret) assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret)
uf = stdlib.FuncAIRSsE(func(a int) ([]string, error) { uf = stdlib.FuncAIRSsE(func(a int) ([]string, error) {
return nil, errors.New("some 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.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASSRSs(t *testing.T) { func TestFuncASSRSs(t *testing.T) {
uf := stdlib.FuncASSRSs(func(a, b string) []string { return []string{a, b} }) 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.NoError(t, err)
assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASSIRSs(t *testing.T) { func TestFuncASSIRSs(t *testing.T) {
uf := stdlib.FuncASSIRSs(func(a, b string, c int) []string { return []string{a, b, strconv.Itoa(c)} }) 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.NoError(t, err)
assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}, &objects.String{Value: "5"}), ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncARB(t *testing.T) { func TestFuncARB(t *testing.T) {
uf := stdlib.FuncARB(func() bool { return true }) uf := stdlib.FuncARB(func() bool { return true })
ret, err := uf.Call() ret, err := funcCall(uf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret) assert.Equal(t, objects.TrueValue, ret)
_, err = uf.Call(objects.TrueValue) _, err = funcCall(uf, objects.TrueValue)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
@ -369,126 +370,131 @@ func TestFuncARYE(t *testing.T) {
uf := stdlib.FuncARYE(func() ([]byte, error) { uf := stdlib.FuncARYE(func() ([]byte, error) {
return []byte("foo bar"), nil return []byte("foo bar"), nil
}) })
ret, err := uf.Call() ret, err := funcCall(uf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Bytes{Value: []byte("foo bar")}, ret) assert.Equal(t, &objects.Bytes{Value: []byte("foo bar")}, ret)
uf = stdlib.FuncARYE(func() ([]byte, error) { uf = stdlib.FuncARYE(func() ([]byte, error) {
return nil, errors.New("some error") return nil, errors.New("some error")
}) })
ret, err = uf.Call() ret, err = funcCall(uf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASRIE(t *testing.T) { func TestFuncASRIE(t *testing.T) {
uf := stdlib.FuncASRIE(func(a string) (int, error) { return 5, nil }) 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.NoError(t, err)
assert.Equal(t, &objects.Int{Value: 5}, ret) assert.Equal(t, &objects.Int{Value: 5}, ret)
uf = stdlib.FuncASRIE(func(a string) (int, error) { return 0, errors.New("some error") }) 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.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncAYRIE(t *testing.T) { func TestFuncAYRIE(t *testing.T) {
uf := stdlib.FuncAYRIE(func(a []byte) (int, error) { return 5, nil }) 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.NoError(t, err)
assert.Equal(t, &objects.Int{Value: 5}, ret) assert.Equal(t, &objects.Int{Value: 5}, ret)
uf = stdlib.FuncAYRIE(func(a []byte) (int, error) { return 0, errors.New("some error") }) 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.NoError(t, err)
assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASSRI(t *testing.T) { func TestFuncASSRI(t *testing.T) {
uf := stdlib.FuncASSRI(func(a, b string) int { return len(a) + len(b) }) 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.NoError(t, err)
assert.Equal(t, &objects.Int{Value: 6}, ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASSRS(t *testing.T) { func TestFuncASSRS(t *testing.T) {
uf := stdlib.FuncASSRS(func(a, b string) string { return a + b }) 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.NoError(t, err)
assert.Equal(t, &objects.String{Value: "foobar"}, ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASSRB(t *testing.T) { func TestFuncASSRB(t *testing.T) {
uf := stdlib.FuncASSRB(func(a, b string) bool { return len(a) > len(b) }) 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.NoError(t, err)
assert.Equal(t, objects.TrueValue, ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncAIRS(t *testing.T) { func TestFuncAIRS(t *testing.T) {
uf := stdlib.FuncAIRS(func(a int) string { return strconv.Itoa(a) }) 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.NoError(t, err)
assert.Equal(t, &objects.String{Value: "55"}, ret) assert.Equal(t, &objects.String{Value: "55"}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncAIRIs(t *testing.T) { func TestFuncAIRIs(t *testing.T) {
uf := stdlib.FuncAIRIs(func(a int) []int { return []int{a, a} }) 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.NoError(t, err)
assert.Equal(t, array(&objects.Int{Value: 55}, &objects.Int{Value: 55}), ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncAI64R(t *testing.T) { func TestFuncAI64R(t *testing.T) {
uf := stdlib.FuncAIR(func(a int) {}) 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.NoError(t, err)
assert.Equal(t, objects.UndefinedValue, ret) assert.Equal(t, objects.UndefinedValue, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncARI64(t *testing.T) { func TestFuncARI64(t *testing.T) {
uf := stdlib.FuncARI64(func() int64 { return 55 }) uf := stdlib.FuncARI64(func() int64 { return 55 })
ret, err := uf.Call() ret, err := funcCall(uf)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, &objects.Int{Value: 55}, ret) 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) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncASsSRS(t *testing.T) { func TestFuncASsSRS(t *testing.T) {
uf := stdlib.FuncASsSRS(func(a []string, b string) string { return strings.Join(a, b) }) 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.NoError(t, err)
assert.Equal(t, &objects.String{Value: "abc-def"}, ret) assert.Equal(t, &objects.String{Value: "abc-def"}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) assert.Equal(t, objects.ErrWrongNumArguments, err)
} }
func TestFuncAI64RI64(t *testing.T) { func TestFuncAI64RI64(t *testing.T) {
uf := stdlib.FuncAI64RI64(func(a int64) int64 { return a * 2 }) 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.NoError(t, err)
assert.Equal(t, &objects.Int{Value: 110}, ret) assert.Equal(t, &objects.Int{Value: 110}, ret)
_, err = uf.Call() _, err = funcCall(uf)
assert.Equal(t, objects.ErrWrongNumArguments, err) 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 { func array(elements ...objects.Object) *objects.Array {
return &objects.Array{Value: elements} return &objects.Array{Value: elements}
} }

74
stdlib/math.go Normal file
View file

@ -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)},
}

437
stdlib/os.go Normal file
View file

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

View file

@ -10,15 +10,15 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap {
return &objects.ImmutableMap{ return &objects.ImmutableMap{
Value: map[string]objects.Object{ Value: map[string]objects.Object{
// combined_output() => bytes/error // combined_output() => bytes/error
"combined_output": FuncARYE(cmd.CombinedOutput), "combined_output": &objects.UserFunction{Name: "combined_output", Value: FuncARYE(cmd.CombinedOutput)}, //
// output() => bytes/error // output() => bytes/error
"output": FuncARYE(cmd.Output), "output": &objects.UserFunction{Name: "output", Value: FuncARYE(cmd.Output)}, //
// run() => error // run() => error
"run": FuncARE(cmd.Run), "run": &objects.UserFunction{Name: "run", Value: FuncARE(cmd.Run)}, //
// start() => error // start() => error
"start": FuncARE(cmd.Start), "start": &objects.UserFunction{Name: "start", Value: FuncARE(cmd.Start)}, //
// wait() => error // wait() => error
"wait": FuncARE(cmd.Wait), "wait": &objects.UserFunction{Name: "wait", Value: FuncARE(cmd.Wait)}, //
// set_path(path string) // set_path(path string)
"set_path": &objects.UserFunction{ "set_path": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) { 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]) s1, ok := objects.ToString(args[0])
if !ok { if !ok {
return nil, objects.ErrInvalidTypeConversion return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
} }
cmd.Path = s1 cmd.Path = s1
@ -45,7 +49,11 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap {
s1, ok := objects.ToString(args[0]) s1, ok := objects.ToString(args[0])
if !ok { if !ok {
return nil, objects.ErrInvalidTypeConversion return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
} }
cmd.Dir = s1 cmd.Dir = s1
@ -55,17 +63,33 @@ func makeOSExecCommand(cmd *exec.Cmd) *objects.ImmutableMap {
}, },
// set_env(env array(string)) // set_env(env array(string))
"set_env": &objects.UserFunction{ "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 { if len(args) != 1 {
return nil, objects.ErrWrongNumArguments return nil, objects.ErrWrongNumArguments
} }
envs, err := stringArray(args[0]) var env []string
if err != nil { var err error
return nil, err 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 return objects.UndefinedValue, nil
}, },

View file

@ -10,23 +10,23 @@ func makeOSFile(file *os.File) *objects.ImmutableMap {
return &objects.ImmutableMap{ return &objects.ImmutableMap{
Value: map[string]objects.Object{ Value: map[string]objects.Object{
// chdir() => true/error // chdir() => true/error
"chdir": FuncARE(file.Chdir), "chdir": &objects.UserFunction{Name: "chdir", Value: FuncARE(file.Chdir)}, //
// chown(uid int, gid int) => true/error // chown(uid int, gid int) => true/error
"chown": FuncAIIRE(file.Chown), "chown": &objects.UserFunction{Name: "chown", Value: FuncAIIRE(file.Chown)}, //
// close() => error // close() => error
"close": FuncARE(file.Close), "close": &objects.UserFunction{Name: "close", Value: FuncARE(file.Close)}, //
// name() => string // name() => string
"name": FuncARS(file.Name), "name": &objects.UserFunction{Name: "name", Value: FuncARS(file.Name)}, //
// readdirnames(n int) => array(string)/error // readdirnames(n int) => array(string)/error
"readdirnames": FuncAIRSsE(file.Readdirnames), "readdirnames": &objects.UserFunction{Name: "readdirnames", Value: FuncAIRSsE(file.Readdirnames)}, //
// sync() => error // sync() => error
"sync": FuncARE(file.Sync), "sync": &objects.UserFunction{Name: "sync", Value: FuncARE(file.Sync)}, //
// write(bytes) => int/error // write(bytes) => int/error
"write": FuncAYRIE(file.Write), "write": &objects.UserFunction{Name: "write", Value: FuncAYRIE(file.Write)}, //
// write(string) => int/error // 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(bytes) => int/error
"read": FuncAYRIE(file.Read), "read": &objects.UserFunction{Name: "read", Value: FuncAYRIE(file.Read)}, //
// chmod(mode int) => error // chmod(mode int) => error
"chmod": &objects.UserFunction{ "chmod": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) { 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]) i1, ok := objects.ToInt64(args[0])
if !ok { 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 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]) i1, ok := objects.ToInt64(args[0])
if !ok { 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]) i2, ok := objects.ToInt(args[1])
if !ok { 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) res, err := file.Seek(i1, i2)

View file

@ -10,10 +10,10 @@ import (
func makeOSProcessState(state *os.ProcessState) *objects.ImmutableMap { func makeOSProcessState(state *os.ProcessState) *objects.ImmutableMap {
return &objects.ImmutableMap{ return &objects.ImmutableMap{
Value: map[string]objects.Object{ Value: map[string]objects.Object{
"exited": FuncARB(state.Exited), "exited": &objects.UserFunction{Name: "exited", Value: FuncARB(state.Exited)}, //
"pid": FuncARI(state.Pid), "pid": &objects.UserFunction{Name: "pid", Value: FuncARI(state.Pid)}, //
"string": FuncARS(state.String), "string": &objects.UserFunction{Name: "string", Value: FuncARS(state.String)}, //
"success": FuncARB(state.Success), "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 { func makeOSProcess(proc *os.Process) *objects.ImmutableMap {
return &objects.ImmutableMap{ return &objects.ImmutableMap{
Value: map[string]objects.Object{ Value: map[string]objects.Object{
"kill": FuncARE(proc.Kill), "kill": &objects.UserFunction{Name: "kill", Value: FuncARE(proc.Kill)}, //
"release": FuncARE(proc.Release), "release": &objects.UserFunction{Name: "release", Value: FuncARE(proc.Release)}, //
"signal": &objects.UserFunction{ "signal": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) { Value: func(args ...objects.Object) (ret objects.Object, err error) {
if len(args) != 1 { if len(args) != 1 {
@ -31,7 +31,11 @@ func makeOSProcess(proc *os.Process) *objects.ImmutableMap {
i1, ok := objects.ToInt64(args[0]) i1, ok := objects.ToInt64(args[0])
if !ok { 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 return wrapError(proc.Signal(syscall.Signal(i1))), nil

99
stdlib/rand.go Normal file
View file

@ -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
},
},
},
}
}

16
stdlib/stdlib.go Normal file
View file

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

View file

@ -6,8 +6,8 @@ import (
"time" "time"
"github.com/d5/tengo/assert" "github.com/d5/tengo/assert"
"github.com/d5/tengo/compiler/stdlib"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
"github.com/d5/tengo/stdlib"
) )
type ARR = []interface{} 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, 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 { func object(v interface{}) objects.Object {

585
stdlib/text.go Normal file
View file

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

View file

@ -19,7 +19,11 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap {
s1, ok := objects.ToString(args[0]) s1, ok := objects.ToString(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -45,7 +49,11 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap {
s1, ok := objects.ToString(args[0]) s1, ok := objects.ToString(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -72,7 +80,11 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap {
i2, ok := objects.ToInt(args[1]) i2, ok := objects.ToInt(args[1])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return return
} }
m := re.FindAllStringSubmatchIndex(s1, i2) m := re.FindAllStringSubmatchIndex(s1, i2)
@ -111,13 +123,21 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap {
s1, ok := objects.ToString(args[0]) s1, ok := objects.ToString(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return return
} }
s2, ok := objects.ToString(args[1]) s2, ok := objects.ToString(args[1])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "string(compatible)",
Found: args[1].TypeName(),
}
return return
} }
@ -139,7 +159,11 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap {
s1, ok := objects.ToString(args[0]) s1, ok := objects.ToString(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -147,7 +171,11 @@ func makeTextRegexp(re *regexp.Regexp) *objects.ImmutableMap {
if numArgs > 1 { if numArgs > 1 {
i2, ok = objects.ToInt(args[1]) i2, ok = objects.ToInt(args[1])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return return
} }
} }

View file

@ -40,41 +40,41 @@ var timesModule = map[string]objects.Object{
"october": &objects.Int{Value: int64(time.October)}, "october": &objects.Int{Value: int64(time.October)},
"november": &objects.Int{Value: int64(time.November)}, "november": &objects.Int{Value: int64(time.November)},
"december": &objects.Int{Value: int64(time.December)}, "december": &objects.Int{Value: int64(time.December)},
"sleep": &objects.UserFunction{Value: timesSleep}, // sleep(int) "sleep": &objects.UserFunction{Name: "sleep", Value: timesSleep}, // sleep(int)
"parse_duration": &objects.UserFunction{Value: timesParseDuration}, // parse_duration(str) => int "parse_duration": &objects.UserFunction{Name: "parse_duration", Value: timesParseDuration}, // parse_duration(str) => int
"since": &objects.UserFunction{Value: timesSince}, // since(time) => int "since": &objects.UserFunction{Name: "since", Value: timesSince}, // since(time) => int
"until": &objects.UserFunction{Value: timesUntil}, // until(time) => int "until": &objects.UserFunction{Name: "until", Value: timesUntil}, // until(time) => int
"duration_hours": &objects.UserFunction{Value: timesDurationHours}, // duration_hours(int) => float "duration_hours": &objects.UserFunction{Name: "duration_hours", Value: timesDurationHours}, // duration_hours(int) => float
"duration_minutes": &objects.UserFunction{Value: timesDurationMinutes}, // duration_minutes(int) => float "duration_minutes": &objects.UserFunction{Name: "duration_minutes", Value: timesDurationMinutes}, // duration_minutes(int) => float
"duration_nanoseconds": &objects.UserFunction{Value: timesDurationNanoseconds}, // duration_nanoseconds(int) => int "duration_nanoseconds": &objects.UserFunction{Name: "duration_nanoseconds", Value: timesDurationNanoseconds}, // duration_nanoseconds(int) => int
"duration_seconds": &objects.UserFunction{Value: timesDurationSeconds}, // duration_seconds(int) => float "duration_seconds": &objects.UserFunction{Name: "duration_seconds", Value: timesDurationSeconds}, // duration_seconds(int) => float
"duration_string": &objects.UserFunction{Value: timesDurationString}, // duration_string(int) => string "duration_string": &objects.UserFunction{Name: "duration_string", Value: timesDurationString}, // duration_string(int) => string
"month_string": &objects.UserFunction{Value: timesMonthString}, // month_string(int) => string "month_string": &objects.UserFunction{Name: "month_string", Value: timesMonthString}, // month_string(int) => string
"date": &objects.UserFunction{Value: timesDate}, // date(year, month, day, hour, min, sec, nsec) => time "date": &objects.UserFunction{Name: "date", Value: timesDate}, // date(year, month, day, hour, min, sec, nsec) => time
"now": &objects.UserFunction{Value: timesNow}, // now() => time "now": &objects.UserFunction{Name: "now", Value: timesNow}, // now() => time
"parse": &objects.UserFunction{Value: timesParse}, // parse(format, str) => time "parse": &objects.UserFunction{Name: "parse", Value: timesParse}, // parse(format, str) => time
"unix": &objects.UserFunction{Value: timesUnix}, // unix(sec, nsec) => time "unix": &objects.UserFunction{Name: "unix", Value: timesUnix}, // unix(sec, nsec) => time
"add": &objects.UserFunction{Value: timesAdd}, // add(time, int) => time "add": &objects.UserFunction{Name: "add", Value: timesAdd}, // add(time, int) => time
"add_date": &objects.UserFunction{Value: timesAddDate}, // add_date(time, years, months, days) => time "add_date": &objects.UserFunction{Name: "add_date", Value: timesAddDate}, // add_date(time, years, months, days) => time
"sub": &objects.UserFunction{Value: timesSub}, // sub(t time, u time) => int "sub": &objects.UserFunction{Name: "sub", Value: timesSub}, // sub(t time, u time) => int
"after": &objects.UserFunction{Value: timesAfter}, // after(t time, u time) => bool "after": &objects.UserFunction{Name: "after", Value: timesAfter}, // after(t time, u time) => bool
"before": &objects.UserFunction{Value: timesBefore}, // before(t time, u time) => bool "before": &objects.UserFunction{Name: "before", Value: timesBefore}, // before(t time, u time) => bool
"time_year": &objects.UserFunction{Value: timesTimeYear}, // time_year(time) => int "time_year": &objects.UserFunction{Name: "time_year", Value: timesTimeYear}, // time_year(time) => int
"time_month": &objects.UserFunction{Value: timesTimeMonth}, // time_month(time) => int "time_month": &objects.UserFunction{Name: "time_month", Value: timesTimeMonth}, // time_month(time) => int
"time_day": &objects.UserFunction{Value: timesTimeDay}, // time_day(time) => int "time_day": &objects.UserFunction{Name: "time_day", Value: timesTimeDay}, // time_day(time) => int
"time_weekday": &objects.UserFunction{Value: timesTimeWeekday}, // time_weekday(time) => int "time_weekday": &objects.UserFunction{Name: "time_weekday", Value: timesTimeWeekday}, // time_weekday(time) => int
"time_hour": &objects.UserFunction{Value: timesTimeHour}, // time_hour(time) => int "time_hour": &objects.UserFunction{Name: "time_hour", Value: timesTimeHour}, // time_hour(time) => int
"time_minute": &objects.UserFunction{Value: timesTimeMinute}, // time_minute(time) => int "time_minute": &objects.UserFunction{Name: "time_minute", Value: timesTimeMinute}, // time_minute(time) => int
"time_second": &objects.UserFunction{Value: timesTimeSecond}, // time_second(time) => int "time_second": &objects.UserFunction{Name: "time_second", Value: timesTimeSecond}, // time_second(time) => int
"time_nanosecond": &objects.UserFunction{Value: timesTimeNanosecond}, // time_nanosecond(time) => int "time_nanosecond": &objects.UserFunction{Name: "time_nanosecond", Value: timesTimeNanosecond}, // time_nanosecond(time) => int
"time_unix": &objects.UserFunction{Value: timesTimeUnix}, // time_unix(time) => int "time_unix": &objects.UserFunction{Name: "time_unix", Value: timesTimeUnix}, // time_unix(time) => int
"time_unix_nano": &objects.UserFunction{Value: timesTimeUnixNano}, // time_unix_nano(time) => int "time_unix_nano": &objects.UserFunction{Name: "time_unix_nano", Value: timesTimeUnixNano}, // time_unix_nano(time) => int
"time_format": &objects.UserFunction{Value: timesTimeFormat}, // time_format(time, format) => string "time_format": &objects.UserFunction{Name: "time_format", Value: timesTimeFormat}, // time_format(time, format) => string
"time_location": &objects.UserFunction{Value: timesTimeLocation}, // time_location(time) => string "time_location": &objects.UserFunction{Name: "time_location", Value: timesTimeLocation}, // time_location(time) => string
"time_string": &objects.UserFunction{Value: timesTimeString}, // time_string(time) => string "time_string": &objects.UserFunction{Name: "time_string", Value: timesTimeString}, // time_string(time) => string
"is_zero": &objects.UserFunction{Value: timesIsZero}, // is_zero(time) => bool "is_zero": &objects.UserFunction{Name: "is_zero", Value: timesIsZero}, // is_zero(time) => bool
"to_local": &objects.UserFunction{Value: timesToLocal}, // to_local(time) => time "to_local": &objects.UserFunction{Name: "to_local", Value: timesToLocal}, // to_local(time) => time
"to_utc": &objects.UserFunction{Value: timesToUTC}, // to_utc(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) { 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]) i1, ok := objects.ToInt64(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -103,7 +107,11 @@ func timesParseDuration(args ...objects.Object) (ret objects.Object, err error)
s1, ok := objects.ToString(args[0]) s1, ok := objects.ToString(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -126,7 +134,11 @@ func timesSince(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -143,7 +155,11 @@ func timesUntil(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -160,7 +176,11 @@ func timesDurationHours(args ...objects.Object) (ret objects.Object, err error)
i1, ok := objects.ToInt64(args[0]) i1, ok := objects.ToInt64(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -177,7 +197,11 @@ func timesDurationMinutes(args ...objects.Object) (ret objects.Object, err error
i1, ok := objects.ToInt64(args[0]) i1, ok := objects.ToInt64(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -194,7 +218,11 @@ func timesDurationNanoseconds(args ...objects.Object) (ret objects.Object, err e
i1, ok := objects.ToInt64(args[0]) i1, ok := objects.ToInt64(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -211,7 +239,11 @@ func timesDurationSeconds(args ...objects.Object) (ret objects.Object, err error
i1, ok := objects.ToInt64(args[0]) i1, ok := objects.ToInt64(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -228,7 +260,11 @@ func timesDurationString(args ...objects.Object) (ret objects.Object, err error)
i1, ok := objects.ToInt64(args[0]) i1, ok := objects.ToInt64(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -245,7 +281,11 @@ func timesMonthString(args ...objects.Object) (ret objects.Object, err error) {
i1, ok := objects.ToInt64(args[0]) i1, ok := objects.ToInt64(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -262,37 +302,65 @@ func timesDate(args ...objects.Object) (ret objects.Object, err error) {
i1, ok := objects.ToInt(args[0]) i1, ok := objects.ToInt(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return return
} }
i2, ok := objects.ToInt(args[1]) i2, ok := objects.ToInt(args[1])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return return
} }
i3, ok := objects.ToInt(args[2]) i3, ok := objects.ToInt(args[2])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "third",
Expected: "int(compatible)",
Found: args[2].TypeName(),
}
return return
} }
i4, ok := objects.ToInt(args[3]) i4, ok := objects.ToInt(args[3])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "fourth",
Expected: "int(compatible)",
Found: args[3].TypeName(),
}
return return
} }
i5, ok := objects.ToInt(args[4]) i5, ok := objects.ToInt(args[4])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "fifth",
Expected: "int(compatible)",
Found: args[4].TypeName(),
}
return return
} }
i6, ok := objects.ToInt(args[5]) i6, ok := objects.ToInt(args[5])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "sixth",
Expected: "int(compatible)",
Found: args[5].TypeName(),
}
return return
} }
i7, ok := objects.ToInt(args[6]) i7, ok := objects.ToInt(args[6])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "seventh",
Expected: "int(compatible)",
Found: args[6].TypeName(),
}
return return
} }
@ -320,13 +388,21 @@ func timesParse(args ...objects.Object) (ret objects.Object, err error) {
s1, ok := objects.ToString(args[0]) s1, ok := objects.ToString(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
return return
} }
s2, ok := objects.ToString(args[1]) s2, ok := objects.ToString(args[1])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "string(compatible)",
Found: args[1].TypeName(),
}
return return
} }
@ -349,13 +425,21 @@ func timesUnix(args ...objects.Object) (ret objects.Object, err error) {
i1, ok := objects.ToInt64(args[0]) i1, ok := objects.ToInt64(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
return return
} }
i2, ok := objects.ToInt64(args[1]) i2, ok := objects.ToInt64(args[1])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return return
} }
@ -372,13 +456,21 @@ func timesAdd(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
i2, ok := objects.ToInt64(args[1]) i2, ok := objects.ToInt64(args[1])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return return
} }
@ -395,13 +487,21 @@ func timesSub(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
t2, ok := objects.ToTime(args[1]) t2, ok := objects.ToTime(args[1])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "time(compatible)",
Found: args[1].TypeName(),
}
return return
} }
@ -418,25 +518,41 @@ func timesAddDate(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
i2, ok := objects.ToInt(args[1]) i2, ok := objects.ToInt(args[1])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
return return
} }
i3, ok := objects.ToInt(args[2]) i3, ok := objects.ToInt(args[2])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "third",
Expected: "int(compatible)",
Found: args[2].TypeName(),
}
return return
} }
i4, ok := objects.ToInt(args[3]) i4, ok := objects.ToInt(args[3])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "fourth",
Expected: "int(compatible)",
Found: args[3].TypeName(),
}
return return
} }
@ -453,13 +569,21 @@ func timesAfter(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
t2, ok := objects.ToTime(args[1]) t2, ok := objects.ToTime(args[1])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "time(compatible)",
Found: args[1].TypeName(),
}
return return
} }
@ -480,13 +604,21 @@ func timesBefore(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
t2, ok := objects.ToTime(args[1]) t2, ok := objects.ToTime(args[1])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -507,7 +639,11 @@ func timesTimeYear(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -524,7 +660,11 @@ func timesTimeMonth(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -541,7 +681,11 @@ func timesTimeDay(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -558,7 +702,11 @@ func timesTimeWeekday(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -575,7 +723,11 @@ func timesTimeHour(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -592,7 +744,11 @@ func timesTimeMinute(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -609,7 +765,11 @@ func timesTimeSecond(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -626,7 +786,11 @@ func timesTimeNanosecond(args ...objects.Object) (ret objects.Object, err error)
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -643,7 +807,11 @@ func timesTimeUnix(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -660,7 +828,11 @@ func timesTimeUnixNano(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -677,13 +849,21 @@ func timesTimeFormat(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
s2, ok := objects.ToString(args[1]) s2, ok := objects.ToString(args[1])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "second",
Expected: "string(compatible)",
Found: args[1].TypeName(),
}
return return
} }
@ -700,7 +880,11 @@ func timesIsZero(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -721,7 +905,11 @@ func timesToLocal(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -738,7 +926,11 @@ func timesToUTC(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -755,7 +947,11 @@ func timesTimeLocation(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }
@ -772,7 +968,11 @@ func timesTimeString(args ...objects.Object) (ret objects.Object, err error) {
t1, ok := objects.ToTime(args[0]) t1, ok := objects.ToTime(args[0])
if !ok { if !ok {
err = objects.ErrInvalidTypeConversion err = objects.ErrInvalidArgumentType{
Name: "first",
Expected: "time(compatible)",
Found: args[0].TypeName(),
}
return return
} }