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.
func Nil(t *testing.T, v interface{}, msg ...interface{}) bool {
if v == nil {
if isNil(v) {
return true
}
@ -60,7 +60,7 @@ func False(t *testing.T, v bool, msg ...interface{}) bool {
// NotNil asserts v is not nil.
func NotNil(t *testing.T, v interface{}, msg ...interface{}) bool {
if v != nil {
if !isNil(v) {
return true
}
@ -78,7 +78,7 @@ func IsType(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
// Equal asserts expected and actual are equal.
func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool {
if expected == nil {
if isNil(expected) {
return Nil(t, actual, "expected nil, but got not nil")
}
if !NotNil(t, actual, "expected not nil, but got nil") {
@ -175,6 +175,13 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool
if !expected.Equals(actual.(objects.Object)) {
return failExpectedActual(t, expected, actual, msg...)
}
case *source.FileSet:
return equalFileSet(t, expected, actual.(*source.FileSet), msg...)
case *source.File:
return Equal(t, expected.Name, actual.(*source.File).Name, msg...) &&
Equal(t, expected.Base, actual.(*source.File).Base, msg...) &&
Equal(t, expected.Size, actual.(*source.File).Size, msg...) &&
True(t, equalIntSlice(expected.Lines, actual.(*source.File).Lines), msg...)
case error:
if expected != actual.(error) {
return failExpectedActual(t, expected, actual, msg...)
@ -245,8 +252,6 @@ func equalSymbol(a, b *compiler.Symbol) bool {
}
func equalObjectSlice(t *testing.T, expected, actual []objects.Object, msg ...interface{}) bool {
// TODO: this test does not differentiate nil vs empty slice
if !Equal(t, len(expected), len(actual), msg...) {
return false
}
@ -260,6 +265,20 @@ func equalObjectSlice(t *testing.T, expected, actual []objects.Object, msg ...in
return true
}
func equalFileSet(t *testing.T, expected, actual *source.FileSet, msg ...interface{}) bool {
if !Equal(t, len(expected.Files), len(actual.Files), msg...) {
return false
}
for i, f := range expected.Files {
if !Equal(t, f, actual.Files[i], msg...) {
return false
}
}
return Equal(t, expected.Base, actual.Base) &&
Equal(t, expected.LastFile, actual.LastFile)
}
func equalObjectMap(t *testing.T, expected, actual map[string]objects.Object, msg ...interface{}) bool {
if !Equal(t, len(expected), len(actual), msg...) {
return false
@ -303,3 +322,17 @@ func equalClosure(t *testing.T, expected, actual objects.Object, msg ...interfac
return true
}
func isNil(v interface{}) bool {
if v == nil {
return true
}
value := reflect.ValueOf(v)
kind := value.Kind()
if kind >= reflect.Chan && kind <= reflect.Slice && value.IsNil() {
return true
}
return false
}

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

View file

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

View file

@ -14,12 +14,12 @@ type File struct {
// Pos returns the position of first character belonging to the node.
func (n *File) Pos() source.Pos {
return source.Pos(n.InputFile.Base())
return source.Pos(n.InputFile.Base)
}
// End returns the position of first character immediately after the node.
func (n *File) End() source.Pos {
return source.Pos(n.InputFile.Base() + n.InputFile.Size())
return source.Pos(n.InputFile.Base + n.InputFile.Size)
}
func (n *File) String() string {

View file

@ -6,12 +6,14 @@ import (
"io"
"reflect"
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/objects"
)
// Bytecode is a compiled instructions and constants.
type Bytecode struct {
Instructions []byte
FileSet *source.FileSet
MainFunction *objects.CompiledFunction
Constants []objects.Object
}
@ -19,7 +21,13 @@ type Bytecode struct {
func (b *Bytecode) Decode(r io.Reader) error {
dec := gob.NewDecoder(r)
if err := dec.Decode(&b.Instructions); err != nil {
if err := dec.Decode(&b.FileSet); err != nil {
return err
}
// TODO: files in b.FileSet.File does not have their 'set' field properly set to b.FileSet
// as it's private field and not serialized by gob encoder/decoder.
if err := dec.Decode(&b.MainFunction); err != nil {
return err
}
@ -39,7 +47,11 @@ func (b *Bytecode) Decode(r io.Reader) error {
func (b *Bytecode) Encode(w io.Writer) error {
enc := gob.NewEncoder(w)
if err := enc.Encode(b.Instructions); err != nil {
if err := enc.Encode(b.FileSet); err != nil {
return err
}
if err := enc.Encode(b.MainFunction); err != nil {
return err
}
@ -50,7 +62,7 @@ func (b *Bytecode) Encode(w io.Writer) error {
// FormatInstructions returns human readable string representations of
// compiled instructions.
func (b *Bytecode) FormatInstructions() []string {
return FormatInstructions(b.Instructions, 0)
return FormatInstructions(b.MainFunction.Instructions, 0)
}
// FormatConstants returns human readable string representations of
@ -94,21 +106,29 @@ func cleanupObjects(o objects.Object) objects.Object {
}
func init() {
gob.Register(&objects.Int{})
gob.Register(&objects.Float{})
gob.Register(&objects.String{})
gob.Register(&objects.Bool{})
gob.Register(&objects.Char{})
gob.Register(&source.FileSet{})
gob.Register(&source.File{})
gob.Register(&objects.Array{})
gob.Register(&objects.ImmutableArray{})
gob.Register(&objects.Map{})
gob.Register(&objects.ImmutableMap{})
gob.Register(&objects.CompiledFunction{})
gob.Register(&objects.Undefined{})
gob.Register(&objects.Error{})
gob.Register(&objects.Bytes{})
gob.Register(&objects.StringIterator{})
gob.Register(&objects.MapIterator{})
gob.Register(&objects.ArrayIterator{})
gob.Register(&objects.Bool{})
gob.Register(&objects.Break{})
gob.Register(&objects.BuiltinFunction{})
gob.Register(&objects.Bytes{})
gob.Register(&objects.Char{})
gob.Register(&objects.Closure{})
gob.Register(&objects.CompiledFunction{})
gob.Register(&objects.Continue{})
gob.Register(&objects.Error{})
gob.Register(&objects.Float{})
gob.Register(&objects.ImmutableArray{})
gob.Register(&objects.ImmutableMap{})
gob.Register(&objects.Int{})
gob.Register(&objects.Map{})
gob.Register(&objects.MapIterator{})
gob.Register(&objects.ReturnValue{})
gob.Register(&objects.String{})
gob.Register(&objects.StringIterator{})
gob.Register(&objects.Time{})
gob.Register(&objects.Undefined{})
gob.Register(&objects.UserFunction{})
}

View file

@ -7,11 +7,17 @@ import (
"github.com/d5/tengo/assert"
"github.com/d5/tengo/compiler"
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/objects"
)
type srcfile struct {
name string
size int
}
func TestBytecode(t *testing.T) {
testBytecodeSerialization(t, &compiler.Bytecode{})
testBytecodeSerialization(t, bytecode(concat(), objectsArray()))
testBytecodeSerialization(t, bytecode(
concat(), objectsArray(
@ -48,7 +54,7 @@ func TestBytecode(t *testing.T) {
&objects.String{Value: "bar"},
objects.UndefinedValue)))
testBytecodeSerialization(t, bytecode(
testBytecodeSerialization(t, bytecodeFileSet(
concat(
compiler.MakeInstruction(compiler.OpConstant, 0),
compiler.MakeInstruction(compiler.OpSetGlobal, 0),
@ -82,7 +88,24 @@ func TestBytecode(t *testing.T) {
compiler.MakeInstruction(compiler.OpSetLocal, 0),
compiler.MakeInstruction(compiler.OpGetLocal, 0),
compiler.MakeInstruction(compiler.OpClosure, 5, 1),
compiler.MakeInstruction(compiler.OpReturnValue)))))
compiler.MakeInstruction(compiler.OpReturnValue))),
fileSet(srcfile{name: "file1", size: 100}, srcfile{name: "file2", size: 200})))
}
func fileSet(files ...srcfile) *source.FileSet {
fileSet := source.NewFileSet()
for _, f := range files {
fileSet.AddFile(f.name, -1, f.size)
}
return fileSet
}
func bytecodeFileSet(instructions []byte, constants []objects.Object, fileSet *source.FileSet) *compiler.Bytecode {
return &compiler.Bytecode{
FileSet: fileSet,
MainFunction: &objects.CompiledFunction{Instructions: instructions},
Constants: constants,
}
}
func testBytecodeSerialization(t *testing.T, b *compiler.Bytecode) {
@ -94,6 +117,7 @@ func testBytecodeSerialization(t *testing.T, b *compiler.Bytecode) {
err = r.Decode(bytes.NewReader(buf.Bytes()))
assert.NoError(t, err)
assert.Equal(t, b.Instructions, r.Instructions)
assert.Equal(t, b.FileSet, r.FileSet)
assert.Equal(t, b.MainFunction, r.MainFunction)
assert.Equal(t, b.Constants, r.Constants)
}

View file

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

View file

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

View file

@ -1,50 +1,37 @@
package compiler
import (
"errors"
"fmt"
"github.com/d5/tengo/compiler/ast"
"github.com/d5/tengo/compiler/token"
)
func (c *Compiler) compileAssign(lhs, rhs []ast.Expr, op token.Token) error {
func (c *Compiler) compileAssign(node ast.Node, lhs, rhs []ast.Expr, op token.Token) error {
numLHS, numRHS := len(lhs), len(rhs)
if numLHS < numRHS {
// # of LHS must be >= # of RHS
return fmt.Errorf("assigntment count error: %d < %d", numLHS, numRHS)
if numLHS > 1 || numRHS > 1 {
return c.errorf(node, "tuple assignment not allowed")
}
if numLHS > 1 {
// TODO: until we fully implement the tuple assignment
return fmt.Errorf("tuple assignment not implemented")
}
//if numLHS > 1 && op != token.Assign && op != token.Define {
// return fmt.Errorf("invalid operator for tuple assignment: %s", op.String())
//}
// resolve and compile left-hand side
ident, selectors, err := resolveAssignLHS(lhs[0])
if err != nil {
return err
}
ident, selectors := resolveAssignLHS(lhs[0])
numSel := len(selectors)
if op == token.Define && numSel > 0 {
// using selector on new variable does not make sense
return errors.New("cannot use selector with ':='")
return c.errorf(node, "operator ':=' not allowed with selector")
}
symbol, depth, exists := c.symbolTable.Resolve(ident)
if op == token.Define {
if depth == 0 && exists {
return fmt.Errorf("'%s' redeclared in this block", ident)
return c.errorf(node, "'%s' redeclared in this block", ident)
}
symbol = c.symbolTable.Define(ident)
} else {
if !exists {
return fmt.Errorf("unresolved reference '%s'", ident)
return c.errorf(node, "unresolved reference '%s'", ident)
}
}
@ -64,27 +51,27 @@ func (c *Compiler) compileAssign(lhs, rhs []ast.Expr, op token.Token) error {
switch op {
case token.AddAssign:
c.emit(OpAdd)
c.emit(node, OpAdd)
case token.SubAssign:
c.emit(OpSub)
c.emit(node, OpSub)
case token.MulAssign:
c.emit(OpMul)
c.emit(node, OpMul)
case token.QuoAssign:
c.emit(OpDiv)
c.emit(node, OpDiv)
case token.RemAssign:
c.emit(OpRem)
c.emit(node, OpRem)
case token.AndAssign:
c.emit(OpBAnd)
c.emit(node, OpBAnd)
case token.OrAssign:
c.emit(OpBOr)
c.emit(node, OpBOr)
case token.AndNotAssign:
c.emit(OpBAndNot)
c.emit(node, OpBAndNot)
case token.XorAssign:
c.emit(OpBXor)
c.emit(node, OpBXor)
case token.ShlAssign:
c.emit(OpBShiftLeft)
c.emit(node, OpBShiftLeft)
case token.ShrAssign:
c.emit(OpBShiftRight)
c.emit(node, OpBShiftRight)
}
// compile selector expressions (right to left)
@ -97,18 +84,18 @@ func (c *Compiler) compileAssign(lhs, rhs []ast.Expr, op token.Token) error {
switch symbol.Scope {
case ScopeGlobal:
if numSel > 0 {
c.emit(OpSetSelGlobal, symbol.Index, numSel)
c.emit(node, OpSetSelGlobal, symbol.Index, numSel)
} else {
c.emit(OpSetGlobal, symbol.Index)
c.emit(node, OpSetGlobal, symbol.Index)
}
case ScopeLocal:
if numSel > 0 {
c.emit(OpSetSelLocal, symbol.Index, numSel)
c.emit(node, OpSetSelLocal, symbol.Index, numSel)
} else {
if op == token.Define && !symbol.LocalAssigned {
c.emit(OpDefineLocal, symbol.Index)
c.emit(node, OpDefineLocal, symbol.Index)
} else {
c.emit(OpSetLocal, symbol.Index)
c.emit(node, OpSetLocal, symbol.Index)
}
}
@ -116,39 +103,30 @@ func (c *Compiler) compileAssign(lhs, rhs []ast.Expr, op token.Token) error {
symbol.LocalAssigned = true
case ScopeFree:
if numSel > 0 {
c.emit(OpSetSelFree, symbol.Index, numSel)
c.emit(node, OpSetSelFree, symbol.Index, numSel)
} else {
c.emit(OpSetFree, symbol.Index)
c.emit(node, OpSetFree, symbol.Index)
}
default:
return fmt.Errorf("invalid assignment variable scope: %s", symbol.Scope)
panic(fmt.Errorf("invalid assignment variable scope: %s", symbol.Scope))
}
return nil
}
func resolveAssignLHS(expr ast.Expr) (name string, selectors []ast.Expr, err error) {
func resolveAssignLHS(expr ast.Expr) (name string, selectors []ast.Expr) {
switch term := expr.(type) {
case *ast.SelectorExpr:
name, selectors, err = resolveAssignLHS(term.Expr)
if err != nil {
return
}
name, selectors = resolveAssignLHS(term.Expr)
selectors = append(selectors, term.Sel)
return
case *ast.IndexExpr:
name, selectors, err = resolveAssignLHS(term.Expr)
if err != nil {
return
}
name, selectors = resolveAssignLHS(term.Expr)
selectors = append(selectors, term.Index)
case *ast.Ident:
return term.Name, nil, nil
name = term.Name
}
return

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

View file

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

View file

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

View file

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

View file

@ -874,11 +874,11 @@ func() {
intObject(1),
intObject(1))))
expectError(t, `import("user1")`) // unknown module name
expectError(t, `import("user1")`, "no such file or directory") // unknown module name
}
func concat(instructions ...[]byte) []byte {
concat := make([]byte, 0)
var concat []byte
for _, i := range instructions {
concat = append(concat, i...)
}
@ -888,7 +888,8 @@ func concat(instructions ...[]byte) []byte {
func bytecode(instructions []byte, constants []objects.Object) *compiler.Bytecode {
return &compiler.Bytecode{
Instructions: instructions,
FileSet: source.NewFileSet(),
MainFunction: &objects.CompiledFunction{Instructions: instructions},
Constants: constants,
}
}
@ -913,7 +914,7 @@ func expect(t *testing.T, input string, expected *compiler.Bytecode) (ok bool) {
return
}
func expectError(t *testing.T, input string) (ok bool) {
func expectError(t *testing.T, input, expected string) (ok bool) {
_, trace, err := traceCompile(input, nil)
defer func() {
@ -924,16 +925,21 @@ func expectError(t *testing.T, input string) (ok bool) {
}
}()
ok = assert.Error(t, err)
if !assert.Error(t, err) {
return
}
if !assert.True(t, strings.Contains(err.Error(), expected), "expected error string: %s, got: %s", expected, err.Error()) {
return
}
ok = true
return
}
func equalBytecode(t *testing.T, expected, actual *compiler.Bytecode) bool {
expectedInstructions := strings.Join(compiler.FormatInstructions(expected.Instructions, 0), "\n")
actualInstructions := strings.Join(compiler.FormatInstructions(actual.Instructions, 0), "\n")
return assert.Equal(t, expectedInstructions, actualInstructions) &&
return assert.Equal(t, expected.MainFunction, actual.MainFunction) &&
equalConstants(t, expected.Constants, actual.Constants)
}
@ -975,7 +981,7 @@ func traceCompile(input string, symbols map[string]objects.Object) (res *compile
}
tr := &tracer{}
c := compiler.NewCompiler(symTable, nil, nil, tr)
c := compiler.NewCompiler(file, symTable, nil, nil, tr)
parsed, err := p.ParseFile()
if err != nil {
return

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

View file

@ -8,9 +8,9 @@ import (
)
// ParseSource parses source code 'src' and builds an AST.
func ParseSource(src []byte, trace io.Writer) (res *ast.File, err error) {
func ParseSource(filename string, src []byte, trace io.Writer) (res *ast.File, err error) {
fileSet := source.NewFileSet()
file := fileSet.AddFile("", -1, len(src))
file := fileSet.AddFile(filename, -1, len(src))
return ParseFile(file, src, trace)
}

View file

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

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

View file

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

View file

@ -213,7 +213,7 @@ func TestStripCR(t *testing.T) {
}
func scanExpect(t *testing.T, input string, mode scanner.Mode, expected ...scanResult) bool {
testFile := testFileSet.AddFile("", testFileSet.Base(), len(input))
testFile := testFileSet.AddFile("test", -1, len(input))
s := scanner.NewScanner(
testFile,

View file

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

View file

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

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) {
intIdx, ok := ToInt(index)
if !ok {
err = ErrInvalidTypeConversion
err = ErrInvalidIndexType
return
}

View file

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

View file

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

View file

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

View file

@ -1,9 +1,6 @@
package objects
import (
"fmt"
)
// len(obj object) => int
func builtinLen(args ...Object) (Object, error) {
if len(args) != 1 {
return nil, ErrWrongNumArguments
@ -23,6 +20,10 @@ func builtinLen(args ...Object) (Object, error) {
case *ImmutableMap:
return &Int{Value: int64(len(arg.Value))}, nil
default:
return nil, fmt.Errorf("unsupported type for 'len' function: %s", arg.TypeName())
return nil, ErrInvalidArgumentType{
Name: "first",
Expected: "array/string/bytes/map",
Found: arg.TypeName(),
}
}
}

View file

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

View file

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

View file

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

View file

@ -1,24 +1,32 @@
package objects
import "errors"
// ErrNotIndexable means the type is not indexable.
var ErrNotIndexable = errors.New("type is not indexable")
// ErrNotIndexAssignable means the type is not index-assignable.
var ErrNotIndexAssignable = errors.New("type is not index-assignable")
import (
"errors"
"fmt"
)
// ErrIndexOutOfBounds is an error where a given index is out of the bounds.
var ErrIndexOutOfBounds = errors.New("index out of bounds")
// ErrInvalidIndexType means the type is not supported as an index.
// ErrInvalidIndexType represents an invalid index type.
var ErrInvalidIndexType = errors.New("invalid index type")
// ErrInvalidIndexValueType represents an invalid index value type.
var ErrInvalidIndexValueType = errors.New("invalid index value type")
// ErrInvalidOperator represents an error for invalid operator usage.
var ErrInvalidOperator = errors.New("invalid operator")
// ErrWrongNumArguments represents a wrong number of arguments error.
var ErrWrongNumArguments = errors.New("wrong number of arguments")
// ErrInvalidTypeConversion represents an invalid type conversion error.
var ErrInvalidTypeConversion = errors.New("invalid type conversion")
// ErrInvalidArgumentType represents an invalid argument value type error.
type ErrInvalidArgumentType struct {
Name string
Expected string
Found string
}
func (e ErrInvalidArgumentType) Error() string {
return fmt.Sprintf("invalid type for argument '%s': expected %s, found %s", e.Name, e.Expected, e.Found)
}

View file

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

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) {
strIdx, ok := ToString(index)
if !ok {
err = ErrInvalidTypeConversion
err = ErrInvalidIndexType
return
}

View file

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

View file

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

View file

@ -1,13 +1,14 @@
package runtime
import (
"errors"
"fmt"
"sync/atomic"
"github.com/d5/tengo/compiler"
"github.com/d5/tengo/compiler/source"
"github.com/d5/tengo/compiler/token"
"github.com/d5/tengo/objects"
"github.com/d5/tengo/stdlib"
)
const (
@ -30,42 +31,50 @@ var (
// VM is a virtual machine that executes the bytecode compiled by Compiler.
type VM struct {
constants []objects.Object
stack []*objects.Object
sp int
globals []*objects.Object
frames []Frame
framesIndex int
curFrame *Frame
curInsts []byte
curIPLimit int
ip int
aborting int64
constants []objects.Object
stack []*objects.Object
sp int
globals []*objects.Object
fileSet *source.FileSet
frames []Frame
framesIndex int
curFrame *Frame
curInsts []byte
curIPLimit int
ip int
aborting int64
builtinModules map[string]*objects.Object
}
// NewVM creates a VM.
func NewVM(bytecode *compiler.Bytecode, globals []*objects.Object) *VM {
func NewVM(bytecode *compiler.Bytecode, globals []*objects.Object, builtinModules map[string]*objects.Object) *VM {
if globals == nil {
globals = make([]*objects.Object, GlobalsSize)
}
if builtinModules == nil {
builtinModules = stdlib.Modules
}
frames := make([]Frame, MaxFrames)
frames[0].fn = &objects.CompiledFunction{Instructions: bytecode.Instructions}
frames[0].fn = bytecode.MainFunction
frames[0].freeVars = nil
frames[0].ip = -1
frames[0].basePointer = 0
return &VM{
constants: bytecode.Constants,
stack: make([]*objects.Object, StackSize),
sp: 0,
globals: globals,
frames: frames,
framesIndex: 1,
curFrame: &(frames[0]),
curInsts: frames[0].fn.Instructions,
curIPLimit: len(frames[0].fn.Instructions) - 1,
ip: -1,
constants: bytecode.Constants,
stack: make([]*objects.Object, StackSize),
sp: 0,
globals: globals,
fileSet: bytecode.FileSet,
frames: frames,
framesIndex: 1,
curFrame: &(frames[0]),
curInsts: frames[0].fn.Instructions,
curIPLimit: len(frames[0].fn.Instructions) - 1,
ip: -1,
builtinModules: builtinModules,
}
}
@ -85,10 +94,11 @@ func (v *VM) Run() error {
v.ip = -1
atomic.StoreInt64(&v.aborting, 0)
mainloop:
for v.ip < v.curIPLimit && (atomic.LoadInt64(&v.aborting) == 0) {
v.ip++
switch compiler.Opcode(v.curInsts[v.ip]) {
switch v.curInsts[v.ip] {
case compiler.OpConstant:
cidx := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
v.ip += 2
@ -115,7 +125,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Add, *right)
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
if err == objects.ErrInvalidOperator {
return fmt.Errorf("%s: invalid operation: %s + %s",
filePos, (*left).TypeName(), (*right).TypeName())
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
if v.sp >= StackSize {
@ -132,7 +148,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Sub, *right)
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
if err == objects.ErrInvalidOperator {
return fmt.Errorf("%s: invalid operation: %s - %s",
filePos, (*left).TypeName(), (*right).TypeName())
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
if v.sp >= StackSize {
@ -149,7 +171,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Mul, *right)
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
if err == objects.ErrInvalidOperator {
return fmt.Errorf("%s: invalid operation: %s * %s",
filePos, (*left).TypeName(), (*right).TypeName())
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
if v.sp >= StackSize {
@ -166,7 +194,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Quo, *right)
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
if err == objects.ErrInvalidOperator {
return fmt.Errorf("%s: invalid operation: %s / %s",
filePos, (*left).TypeName(), (*right).TypeName())
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
if v.sp >= StackSize {
@ -183,7 +217,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Rem, *right)
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
if err == objects.ErrInvalidOperator {
return fmt.Errorf("%s: invalid operation: %s %% %s",
filePos, (*left).TypeName(), (*right).TypeName())
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
if v.sp >= StackSize {
@ -200,7 +240,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.And, *right)
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
if err == objects.ErrInvalidOperator {
return fmt.Errorf("%s: invalid operation: %s & %s",
filePos, (*left).TypeName(), (*right).TypeName())
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
if v.sp >= StackSize {
@ -217,7 +263,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Or, *right)
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
if err == objects.ErrInvalidOperator {
return fmt.Errorf("%s: invalid operation: %s | %s",
filePos, (*left).TypeName(), (*right).TypeName())
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
if v.sp >= StackSize {
@ -234,7 +286,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Xor, *right)
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
if err == objects.ErrInvalidOperator {
return fmt.Errorf("%s: invalid operation: %s ^ %s",
filePos, (*left).TypeName(), (*right).TypeName())
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
if v.sp >= StackSize {
@ -251,7 +309,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.AndNot, *right)
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
if err == objects.ErrInvalidOperator {
return fmt.Errorf("%s: invalid operation: %s &^ %s",
filePos, (*left).TypeName(), (*right).TypeName())
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
if v.sp >= StackSize {
@ -268,7 +332,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Shl, *right)
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
if err == objects.ErrInvalidOperator {
return fmt.Errorf("%s: invalid operation: %s << %s",
filePos, (*left).TypeName(), (*right).TypeName())
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
if v.sp >= StackSize {
@ -285,7 +355,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Shr, *right)
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
if err == objects.ErrInvalidOperator {
return fmt.Errorf("%s: invalid operation: %s >> %s",
filePos, (*left).TypeName(), (*right).TypeName())
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
if v.sp >= StackSize {
@ -334,7 +410,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.Greater, *right)
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
if err == objects.ErrInvalidOperator {
return fmt.Errorf("%s: invalid operation: %s > %s",
filePos, (*left).TypeName(), (*right).TypeName())
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
if v.sp >= StackSize {
@ -351,7 +433,13 @@ func (v *VM) Run() error {
res, err := (*left).BinaryOp(token.GreaterEq, *right)
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
if err == objects.ErrInvalidOperator {
return fmt.Errorf("%s: invalid operation: %s >= %s",
filePos, (*left).TypeName(), (*right).TypeName())
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
if v.sp >= StackSize {
@ -410,7 +498,8 @@ func (v *VM) Run() error {
v.stack[v.sp] = &res
v.sp++
default:
return fmt.Errorf("invalid operation on %s", (*operand).TypeName())
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
return fmt.Errorf("%s: invalid operation: ^%s", filePos, (*operand).TypeName())
}
case compiler.OpMinus:
@ -437,7 +526,8 @@ func (v *VM) Run() error {
v.stack[v.sp] = &res
v.sp++
default:
return fmt.Errorf("invalid operation on %s", (*operand).TypeName())
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
return fmt.Errorf("%s: invalid operation: -%s", filePos, (*operand).TypeName())
}
case compiler.OpJumpFalsy:
@ -496,7 +586,8 @@ func (v *VM) Run() error {
v.sp -= numSelectors + 1
if err := indexAssign(v.globals[globalIndex], val, selectors); err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-3])
return fmt.Errorf("%s: %s", filePos, err.Error())
}
case compiler.OpGetGlobal:
@ -586,7 +677,13 @@ func (v *VM) Run() error {
case objects.Indexable:
val, err := left.IndexGet(*index)
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
if err == objects.ErrInvalidIndexType {
return fmt.Errorf("%s: invalid index type: %s", filePos, (*index).TypeName())
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
if val == nil {
val = objects.UndefinedValue
@ -602,7 +699,8 @@ func (v *VM) Run() error {
case *objects.Error: // err.value
key, ok := (*index).(*objects.String)
if !ok || key.Value != "value" {
return errors.New("invalid selector on error")
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
return fmt.Errorf("%s: invalid index on error", filePos)
}
if v.sp >= StackSize {
@ -613,7 +711,8 @@ func (v *VM) Run() error {
v.sp++
default:
return objects.ErrNotIndexable
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
return fmt.Errorf("%s: not indexable: %s", filePos, left.TypeName())
}
case compiler.OpSliceIndex:
@ -627,7 +726,8 @@ func (v *VM) Run() error {
if low, ok := (*low).(*objects.Int); ok {
lowIdx = low.Value
} else {
return fmt.Errorf("non-integer slice index: %s", low.TypeName())
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
return fmt.Errorf("%s: invalid slice index type: %s", filePos, low.TypeName())
}
}
@ -640,11 +740,13 @@ func (v *VM) Run() error {
} else if high, ok := (*high).(*objects.Int); ok {
highIdx = high.Value
} else {
return fmt.Errorf("non-integer slice index: %s", high.TypeName())
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
return fmt.Errorf("%s: invalid slice index type: %s", filePos, high.TypeName())
}
if lowIdx > highIdx {
return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx)
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
return fmt.Errorf("%s: invalid slice index: %d > %d", filePos, lowIdx, highIdx)
}
if lowIdx < 0 {
@ -675,11 +777,13 @@ func (v *VM) Run() error {
} else if high, ok := (*high).(*objects.Int); ok {
highIdx = high.Value
} else {
return fmt.Errorf("non-integer slice index: %s", high.TypeName())
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
return fmt.Errorf("%s: invalid slice index type: %s", filePos, high.TypeName())
}
if lowIdx > highIdx {
return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx)
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
return fmt.Errorf("%s: invalid slice index: %d > %d", filePos, lowIdx, highIdx)
}
if lowIdx < 0 {
@ -711,11 +815,13 @@ func (v *VM) Run() error {
} else if high, ok := (*high).(*objects.Int); ok {
highIdx = high.Value
} else {
return fmt.Errorf("non-integer slice index: %s", high.TypeName())
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
return fmt.Errorf("%s: invalid slice index type: %s", filePos, high.TypeName())
}
if lowIdx > highIdx {
return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx)
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
return fmt.Errorf("%s: invalid slice index: %d > %d", filePos, lowIdx, highIdx)
}
if lowIdx < 0 {
@ -747,11 +853,13 @@ func (v *VM) Run() error {
} else if high, ok := (*high).(*objects.Int); ok {
highIdx = high.Value
} else {
return fmt.Errorf("non-integer slice index: %s", high.TypeName())
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
return fmt.Errorf("%s: invalid slice index type: %s", filePos, high.TypeName())
}
if lowIdx > highIdx {
return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx)
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
return fmt.Errorf("%s: invalid slice index: %d > %d", filePos, lowIdx, highIdx)
}
if lowIdx < 0 {
@ -780,17 +888,75 @@ func (v *VM) Run() error {
numArgs := int(v.curInsts[v.ip+1])
v.ip++
callee := *v.stack[v.sp-1-numArgs]
value := *v.stack[v.sp-1-numArgs]
switch callee := callee.(type) {
switch callee := value.(type) {
case *objects.Closure:
if err := v.callFunction(callee.Fn, callee.Free, numArgs); err != nil {
return err
if numArgs != callee.Fn.NumParameters {
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-1])
return fmt.Errorf("%s: wrong number of arguments: want=%d, got=%d",
filePos, callee.Fn.NumParameters, numArgs)
}
// test if it's tail-call
if callee.Fn == v.curFrame.fn { // recursion
nextOp := v.curInsts[v.ip+1]
if nextOp == compiler.OpReturnValue ||
(nextOp == compiler.OpPop && compiler.OpReturn == v.curInsts[v.ip+2]) {
for p := 0; p < numArgs; p++ {
v.stack[v.curFrame.basePointer+p] = v.stack[v.sp-numArgs+p]
}
v.sp -= numArgs + 1
v.ip = -1 // reset IP to beginning of the frame
continue mainloop
}
}
// update call frame
v.curFrame.ip = v.ip // store current ip before call
v.curFrame = &(v.frames[v.framesIndex])
v.curFrame.fn = callee.Fn
v.curFrame.freeVars = callee.Free
v.curFrame.basePointer = v.sp - numArgs
v.curInsts = callee.Fn.Instructions
v.ip = -1
v.curIPLimit = len(v.curInsts) - 1
v.framesIndex++
v.sp = v.sp - numArgs + callee.Fn.NumLocals
case *objects.CompiledFunction:
if err := v.callFunction(callee, nil, numArgs); err != nil {
return err
if numArgs != callee.NumParameters {
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-1])
return fmt.Errorf("%s: wrong number of arguments: want=%d, got=%d",
filePos, callee.NumParameters, numArgs)
}
// test if it's tail-call
if callee == v.curFrame.fn { // recursion
nextOp := v.curInsts[v.ip+1]
if nextOp == compiler.OpReturnValue ||
(nextOp == compiler.OpPop && compiler.OpReturn == v.curInsts[v.ip+2]) {
for p := 0; p < numArgs; p++ {
v.stack[v.curFrame.basePointer+p] = v.stack[v.sp-numArgs+p]
}
v.sp -= numArgs + 1
v.ip = -1 // reset IP to beginning of the frame
continue mainloop
}
}
// update call frame
v.curFrame.ip = v.ip // store current ip before call
v.curFrame = &(v.frames[v.framesIndex])
v.curFrame.fn = callee
v.curFrame.freeVars = nil
v.curFrame.basePointer = v.sp - numArgs
v.curInsts = callee.Instructions
v.ip = -1
v.curIPLimit = len(v.curInsts) - 1
v.framesIndex++
v.sp = v.sp - numArgs + callee.NumLocals
case objects.Callable:
var args []objects.Object
for _, arg := range v.stack[v.sp-numArgs : v.sp] {
@ -802,7 +968,19 @@ func (v *VM) Run() error {
// runtime error
if err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-1])
if err == objects.ErrWrongNumArguments {
return fmt.Errorf("%s: wrong number of arguments in call to '%s'",
filePos, value.TypeName())
}
if err, ok := err.(objects.ErrInvalidArgumentType); ok {
return fmt.Errorf("%s: invalid type for argument '%s' in call to '%s': expected %s, found %s",
filePos, err.Name, value.TypeName(), err.Expected, err.Found)
}
return fmt.Errorf("%s: %s", filePos, err.Error())
}
// nil return -> undefined
@ -816,8 +994,10 @@ func (v *VM) Run() error {
v.stack[v.sp] = &ret
v.sp++
default:
return fmt.Errorf("calling non-function: %s", callee.TypeName())
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-1])
return fmt.Errorf("%s: not callable: %s", filePos, callee.TypeName())
}
case compiler.OpReturnValue:
@ -834,9 +1014,10 @@ func (v *VM) Run() error {
//v.sp = lastFrame.basePointer - 1
v.sp = lastFrame.basePointer
if v.sp-1 >= StackSize {
return ErrStackOverflow
}
// skip stack overflow check because (newSP) <= (oldSP)
//if v.sp-1 >= StackSize {
// return ErrStackOverflow
//}
v.stack[v.sp-1] = retVal
//v.sp++
@ -852,9 +1033,10 @@ func (v *VM) Run() error {
//v.sp = lastFrame.basePointer - 1
v.sp = lastFrame.basePointer
if v.sp-1 >= StackSize {
return ErrStackOverflow
}
// skip stack overflow check because (newSP) <= (oldSP)
//if v.sp-1 >= StackSize {
// return ErrStackOverflow
//}
v.stack[v.sp-1] = undefinedPtr
//v.sp++
@ -898,7 +1080,8 @@ func (v *VM) Run() error {
sp := v.curFrame.basePointer + localIndex
if err := indexAssign(v.stack[sp], val, selectors); err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-2])
return fmt.Errorf("%s: %s", filePos, err.Error())
}
case compiler.OpGetLocal:
@ -925,15 +1108,54 @@ func (v *VM) Run() error {
v.stack[v.sp] = &builtinFuncs[builtinIndex]
v.sp++
case compiler.OpGetBuiltinModule:
val := v.stack[v.sp-1]
v.sp--
moduleName := (*val).(*objects.String).Value
module, ok := v.builtinModules[moduleName]
if !ok {
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-3])
return fmt.Errorf("%s: module '%s' not found", filePos, moduleName)
}
if v.sp >= StackSize {
return ErrStackOverflow
}
v.stack[v.sp] = module
v.sp++
case compiler.OpClosure:
constIndex := int(v.curInsts[v.ip+2]) | int(v.curInsts[v.ip+1])<<8
numFree := int(v.curInsts[v.ip+3])
v.ip += 3
if err := v.pushClosure(constIndex, numFree); err != nil {
return err
fn, ok := v.constants[constIndex].(*objects.CompiledFunction)
if !ok {
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-3])
return fmt.Errorf("%s: not function: %s", filePos, fn.TypeName())
}
free := make([]*objects.Object, numFree)
for i := 0; i < numFree; i++ {
free[i] = v.stack[v.sp-numFree+i]
}
v.sp -= numFree
if v.sp >= StackSize {
return ErrStackOverflow
}
var cl objects.Object = &objects.Closure{
Fn: fn,
Free: free,
}
v.stack[v.sp] = &cl
v.sp++
case compiler.OpGetFree:
freeIndex := int(v.curInsts[v.ip+1])
v.ip++
@ -958,7 +1180,8 @@ func (v *VM) Run() error {
v.sp -= numSelectors + 1
if err := indexAssign(v.curFrame.freeVars[freeIndex], val, selectors); err != nil {
return err
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip-2])
return fmt.Errorf("%s: %s", filePos, err.Error())
}
case compiler.OpSetFree:
@ -978,7 +1201,8 @@ func (v *VM) Run() error {
iterable, ok := (*dst).(objects.Iterable)
if !ok {
return fmt.Errorf("non-iterable type: %s", (*dst).TypeName())
filePos := v.fileSet.Position(v.curFrame.fn.SourceMap[v.ip])
return fmt.Errorf("%s: not iterable: %s", filePos, (*dst).TypeName())
}
iterator = iterable.Iterate()
@ -1034,13 +1258,13 @@ func (v *VM) Run() error {
v.sp++
default:
return fmt.Errorf("unknown opcode: %d", v.curInsts[v.ip])
panic(fmt.Errorf("unknown opcode: %d", v.curInsts[v.ip]))
}
}
// check if stack still has some objects left
if v.sp > 0 && atomic.LoadInt64(&v.aborting) == 0 {
return fmt.Errorf("non empty stack after execution: %d", v.sp)
panic(fmt.Errorf("non empty stack after execution: %d", v.sp))
}
return nil
@ -1056,141 +1280,21 @@ func (v *VM) FrameInfo() (frameIndex, ip int) {
return v.framesIndex - 1, v.ip
}
func (v *VM) pushClosure(constIndex, numFree int) error {
c := v.constants[constIndex]
fn, ok := c.(*objects.CompiledFunction)
if !ok {
return fmt.Errorf("not a function: %s", fn.TypeName())
}
free := make([]*objects.Object, numFree)
for i := 0; i < numFree; i++ {
free[i] = v.stack[v.sp-numFree+i]
}
v.sp -= numFree
if v.sp >= StackSize {
return ErrStackOverflow
}
var cl objects.Object = &objects.Closure{
Fn: fn,
Free: free,
}
v.stack[v.sp] = &cl
v.sp++
return nil
}
func (v *VM) callFunction(fn *objects.CompiledFunction, freeVars []*objects.Object, numArgs int) error {
if numArgs != fn.NumParameters {
return fmt.Errorf("wrong number of arguments: want=%d, got=%d",
fn.NumParameters, numArgs)
}
// check if this is a tail-call (recursive call right before return)
if fn == v.curFrame.fn { // recursion
nextOp := compiler.Opcode(v.curInsts[v.ip+1])
if nextOp == compiler.OpReturnValue || // tail call
(nextOp == compiler.OpPop &&
compiler.OpReturn == compiler.Opcode(v.curInsts[v.ip+2])) {
// stack before tail-call
//
// |--------|
// | | <- SP current
// |--------|
// | *ARG2 | for next function (tail-call)
// |--------|
// | *ARG1 | for next function (tail-call)
// |--------|
// | FUNC | function itself
// |--------|
// | LOCAL3 | for current function
// |--------|
// | LOCAL2 | for current function
// |--------|
// | ARG2 | for current function
// |--------|
// | ARG1 | <- BP for current function
// |--------|
for p := 0; p < numArgs; p++ {
v.stack[v.curFrame.basePointer+p] = v.stack[v.sp-numArgs+p]
}
v.sp -= numArgs + 1
v.ip = -1 // reset IP to beginning of the frame
// stack after tail-call
//
// |--------|
// | |
// |--------|
// | *ARG2 |
// |--------|
// | *ARG1 |
// |--------|
// | FUNC | <- SP current
// |--------|
// | LOCAL3 | for current function
// |--------|
// | LOCAL2 | for current function
// |--------|
// | *ARG2 | (copied)
// |--------|
// | *ARG1 | <- BP (copied)
// |--------|
return nil
}
}
// store current ip before call
v.curFrame.ip = v.ip
// update call frame
v.curFrame = &(v.frames[v.framesIndex])
v.curFrame.fn = fn
v.curFrame.freeVars = freeVars
v.curFrame.basePointer = v.sp - numArgs
v.curInsts = fn.Instructions
v.ip = -1
v.curIPLimit = len(v.curInsts) - 1
v.framesIndex++
v.sp = v.sp - numArgs + fn.NumLocals
// stack after the function call
//
// |--------|
// | | <- SP after function call
// |--------|
// | LOCAL4 | (BP+3)
// |--------|
// | LOCAL3 | (BP+2) <- SP before function call
// |--------|
// | ARG2 | (BP+1)
// |--------|
// | ARG1 | (BP+0) <- BP
// |--------|
return nil
}
func indexAssign(dst, src *objects.Object, selectors []*objects.Object) error {
numSel := len(selectors)
for sidx := numSel - 1; sidx > 0; sidx-- {
indexable, ok := (*dst).(objects.Indexable)
if !ok {
return objects.ErrNotIndexable
return fmt.Errorf("not indexable: %s", (*dst).TypeName())
}
next, err := indexable.IndexGet(*selectors[sidx])
if err != nil {
if err == objects.ErrInvalidIndexType {
return fmt.Errorf("invalid index type: %s", (*selectors[sidx]).TypeName())
}
return err
}
@ -1199,15 +1303,26 @@ func indexAssign(dst, src *objects.Object, selectors []*objects.Object) error {
indexAssignable, ok := (*dst).(objects.IndexAssignable)
if !ok {
return objects.ErrNotIndexAssignable
return fmt.Errorf("not index-assignable: %s", (*dst).TypeName())
}
return indexAssignable.IndexSet(*selectors[0], *src)
if err := indexAssignable.IndexSet(*selectors[0], *src); err != nil {
if err == objects.ErrInvalidIndexValueType {
return fmt.Errorf("invaid index value type: %s", (*src).TypeName())
}
return err
}
return nil
}
func init() {
builtinFuncs = make([]objects.Object, len(objects.Builtins))
for i, b := range objects.Builtins {
builtinFuncs[i] = &objects.BuiltinFunction{Value: b.Func}
builtinFuncs[i] = &objects.BuiltinFunction{
Name: b.Name,
Value: b.Func,
}
}
}

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

View file

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

View file

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

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

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

View file

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

View file

@ -14,11 +14,11 @@ func TestImmutable(t *testing.T) {
expect(t, `a := immutable(1); a = 5; out = a`, 5)
// array
expectError(t, `a := immutable([1, 2, 3]); a[1] = 5`)
expectError(t, `a := immutable(["foo", [1,2,3]]); a[1] = "bar"`)
expectError(t, `a := immutable([1, 2, 3]); a[1] = 5`, "not index-assignable")
expectError(t, `a := immutable(["foo", [1,2,3]]); a[1] = "bar"`, "not index-assignable")
expect(t, `a := immutable(["foo", [1,2,3]]); a[1][1] = "bar"; out = a`, IARR{"foo", ARR{1, "bar", 3}})
expectError(t, `a := immutable(["foo", immutable([1,2,3])]); a[1][1] = "bar"`)
expectError(t, `a := ["foo", immutable([1,2,3])]; a[1][1] = "bar"`)
expectError(t, `a := immutable(["foo", immutable([1,2,3])]); a[1][1] = "bar"`, "not index-assignable")
expectError(t, `a := ["foo", immutable([1,2,3])]; a[1][1] = "bar"`, "not index-assignable")
expect(t, `a := immutable([1,2,3]); b := copy(a); b[1] = 5; out = b`, ARR{1, 5, 3})
expect(t, `a := immutable([1,2,3]); b := copy(a); b[1] = 5; out = a`, IARR{1, 2, 3})
expect(t, `out = immutable([1,2,3]) == [1,2,3]`, true)
@ -33,11 +33,11 @@ func TestImmutable(t *testing.T) {
expect(t, `a := immutable([1, 2, 3]); out = a[5]`, objects.UndefinedValue)
// map
expectError(t, `a := immutable({b: 1, c: 2}); a.b = 5`)
expectError(t, `a := immutable({b: 1, c: 2}); a["b"] = "bar"`)
expectError(t, `a := immutable({b: 1, c: 2}); a.b = 5`, "not index-assignable")
expectError(t, `a := immutable({b: 1, c: 2}); a["b"] = "bar"`, "not index-assignable")
expect(t, `a := immutable({b: 1, c: [1,2,3]}); a.c[1] = "bar"; out = a`, IMAP{"b": 1, "c": ARR{1, "bar", 3}})
expectError(t, `a := immutable({b: 1, c: immutable([1,2,3])}); a.c[1] = "bar"`)
expectError(t, `a := {b: 1, c: immutable([1,2,3])}; a.c[1] = "bar"`)
expectError(t, `a := immutable({b: 1, c: immutable([1,2,3])}); a.c[1] = "bar"`, "not index-assignable")
expectError(t, `a := {b: 1, c: immutable([1,2,3])}; a.c[1] = "bar"`, "not index-assignable")
expect(t, `out = immutable({a:1,b:2}) == {a:1,b:2}`, true)
expect(t, `out = immutable({a:1,b:2}) == immutable({a:1,b:2})`, true)
expect(t, `out = {a:1,b:2} == immutable({a:1,b:2})`, true)
@ -50,5 +50,5 @@ func TestImmutable(t *testing.T) {
expect(t, `a := immutable({a:1,b:2}); out = a.c`, objects.UndefinedValue)
expect(t, `a := immutable({b: 5, c: "foo"}); out = a.b`, 5)
expectError(t, `a := immutable({b: 5, c: "foo"}); a.b = 10`)
expectError(t, `a := immutable({b: 5, c: "foo"}); a.b = 10`, "not index-assignable")
}

View file

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

View file

@ -51,7 +51,7 @@ func (o *StringDict) IndexSet(index, value objects.Object) error {
strVal, ok := objects.ToString(value)
if !ok {
return objects.ErrInvalidTypeConversion
return objects.ErrInvalidIndexValueType
}
o.Value[strings.ToLower(strIdx.Value)] = strVal
@ -95,7 +95,7 @@ func (o *StringCircle) IndexSet(index, value objects.Object) error {
strVal, ok := objects.ToString(value)
if !ok {
return objects.ErrInvalidTypeConversion
return objects.ErrInvalidIndexValueType
}
o.Value[r] = strVal
@ -184,7 +184,7 @@ func (o *StringArray) IndexGet(index objects.Object) (objects.Object, error) {
func (o *StringArray) IndexSet(index, value objects.Object) error {
strVal, ok := objects.ToString(value)
if !ok {
return objects.ErrInvalidTypeConversion
return objects.ErrInvalidIndexValueType
}
intIdx, ok := index.(*objects.Int)
@ -207,7 +207,11 @@ func (o *StringArray) Call(args ...objects.Object) (ret objects.Object, err erro
s1, ok := objects.ToString(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "string(compatible)",
Found: args[0].TypeName(),
}
}
for i, v := range o.Value {
@ -224,7 +228,7 @@ func TestIndexable(t *testing.T) {
expectWithSymbols(t, `out = dict["a"]`, "foo", SYM{"dict": dict()})
expectWithSymbols(t, `out = dict["B"]`, "bar", SYM{"dict": dict()})
expectWithSymbols(t, `out = dict["x"]`, objects.UndefinedValue, SYM{"dict": dict()})
expectErrorWithSymbols(t, `out = dict[0]`, SYM{"dict": dict()})
expectErrorWithSymbols(t, `dict[0]`, SYM{"dict": dict()}, "invalid index type")
strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} }
expectWithSymbols(t, `out = cir[0]`, "one", SYM{"cir": strCir()})
@ -232,7 +236,7 @@ func TestIndexable(t *testing.T) {
expectWithSymbols(t, `out = cir[-1]`, "three", SYM{"cir": strCir()})
expectWithSymbols(t, `out = cir[-2]`, "two", SYM{"cir": strCir()})
expectWithSymbols(t, `out = cir[3]`, "one", SYM{"cir": strCir()})
expectErrorWithSymbols(t, `out = cir["a"]`, SYM{"cir": strCir()})
expectErrorWithSymbols(t, `cir["a"]`, SYM{"cir": strCir()}, "invalid index type")
strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} }
expectWithSymbols(t, `out = arr["one"]`, 0, SYM{"arr": strArr()})
@ -240,7 +244,7 @@ func TestIndexable(t *testing.T) {
expectWithSymbols(t, `out = arr["four"]`, objects.UndefinedValue, SYM{"arr": strArr()})
expectWithSymbols(t, `out = arr[0]`, "one", SYM{"arr": strArr()})
expectWithSymbols(t, `out = arr[1]`, "two", SYM{"arr": strArr()})
expectErrorWithSymbols(t, `out = arr[-1]`, SYM{"arr": strArr()})
expectErrorWithSymbols(t, `arr[-1]`, SYM{"arr": strArr()}, "index out of bounds")
}
func TestIndexAssignable(t *testing.T) {
@ -248,17 +252,17 @@ func TestIndexAssignable(t *testing.T) {
expectWithSymbols(t, `dict["a"] = "1984"; out = dict["a"]`, "1984", SYM{"dict": dict()})
expectWithSymbols(t, `dict["c"] = "1984"; out = dict["c"]`, "1984", SYM{"dict": dict()})
expectWithSymbols(t, `dict["c"] = 1984; out = dict["C"]`, "1984", SYM{"dict": dict()})
expectErrorWithSymbols(t, `dict[0] = "1984"`, SYM{"dict": dict()})
expectErrorWithSymbols(t, `dict[0] = "1984"`, SYM{"dict": dict()}, "invalid index type")
strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} }
expectWithSymbols(t, `cir[0] = "ONE"; out = cir[0]`, "ONE", SYM{"cir": strCir()})
expectWithSymbols(t, `cir[1] = "TWO"; out = cir[1]`, "TWO", SYM{"cir": strCir()})
expectWithSymbols(t, `cir[-1] = "THREE"; out = cir[2]`, "THREE", SYM{"cir": strCir()})
expectWithSymbols(t, `cir[0] = "ONE"; out = cir[3]`, "ONE", SYM{"cir": strCir()})
expectErrorWithSymbols(t, `cir["a"] = "ONE"`, SYM{"cir": strCir()})
expectErrorWithSymbols(t, `cir["a"] = "ONE"`, SYM{"cir": strCir()}, "invalid index type")
strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} }
expectWithSymbols(t, `arr[0] = "ONE"; out = arr[0]`, "ONE", SYM{"arr": strArr()})
expectWithSymbols(t, `arr[1] = "TWO"; out = arr[1]`, "TWO", SYM{"arr": strArr()})
expectErrorWithSymbols(t, `arr["one"] = "ONE"`, SYM{"arr": strArr()})
expectErrorWithSymbols(t, `arr["one"] = "ONE"`, SYM{"arr": strArr()}, "invalid index type")
}

View file

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

View file

@ -21,7 +21,7 @@ a := {
}
out = a.b.c`, 4)
expectError(t, `
expect(t, `
a := {
b: {
c: 4,
@ -29,9 +29,9 @@ a := {
},
c: "foo bar"
}
out = a.x.c`)
b := a.x.c`, objects.UndefinedValue)
expectError(t, `
expect(t, `
a := {
b: {
c: 4,
@ -39,7 +39,7 @@ a := {
},
c: "foo bar"
}
out = a.x.y`)
b := a.x.y`, objects.UndefinedValue)
expect(t, `a := {b: 1, c: "foo"}; a.b = 2; out = a.b`, 2)
expect(t, `a := {b: 1, c: "foo"}; a.c = 2; out = a.c`, 2) // type not checked on sub-field
@ -81,10 +81,10 @@ func() {
}()
`, 9)
expectError(t, `a := {b: {c: 1}}; a.d.c = 2`)
expectError(t, `a := [1, 2, 3]; a.b = 2`)
expectError(t, `a := "foo"; a.b = 2`)
expectError(t, `func() { a := {b: {c: 1}}; a.d.c = 2 }()`)
expectError(t, `func() { a := [1, 2, 3]; a.b = 2 }()`)
expectError(t, `func() { a := "foo"; a.b = 2 }()`)
expectError(t, `a := {b: {c: 1}}; a.d.c = 2`, "not index-assignable")
expectError(t, `a := [1, 2, 3]; a.b = 2`, "invalid index type")
expectError(t, `a := "foo"; a.b = 2`, "not index-assignable")
expectError(t, `func() { a := {b: {c: 1}}; a.d.c = 2 }()`, "not index-assignable")
expectError(t, `func() { a := [1, 2, 3]; a.b = 2 }()`, "invalid index type")
expectError(t, `func() { a := "foo"; a.b = 2 }()`, "not index-assignable")
}

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

View file

@ -78,6 +78,19 @@ iter := func(n, max) {
return iter(n+1, max)
}
out = iter(0, 9999)
`, 9999)
expect(t, `
c := 0
iter := func(n, max) {
if n == max {
return
}
c++
iter(n+1, max)
}
iter(0, 9999)
out = c
`, 9999)
}

View file

@ -52,11 +52,16 @@ func expectWithUserModules(t *testing.T, input string, expected interface{}, use
runVM(t, file, expected, nil, userModules)
}
func expectError(t *testing.T, input string) {
expectErrorWithUserModules(t, input, nil)
func expectError(t *testing.T, input, expected string) {
expected = strings.TrimSpace(expected)
if expected == "" {
panic("expected must not be empty")
}
expectErrorWithUserModules(t, input, nil, expected)
}
func expectErrorWithUserModules(t *testing.T, input string, userModules map[string]string) {
func expectErrorWithUserModules(t *testing.T, input string, userModules map[string]string, expected string) {
// parse
program := parse(t, input)
if program == nil {
@ -64,10 +69,14 @@ func expectErrorWithUserModules(t *testing.T, input string, userModules map[stri
}
// compiler/VM
runVMError(t, program, nil, userModules)
_, trace, err := traceCompileRun(program, nil, userModules)
if !assert.Error(t, err) ||
!assert.True(t, strings.Contains(err.Error(), expected), "expected error string: %s, got: %s", expected, err.Error()) {
t.Log("\n" + strings.Join(trace, "\n"))
}
}
func expectErrorWithSymbols(t *testing.T, input string, symbols map[string]objects.Object) {
func expectErrorWithSymbols(t *testing.T, input string, symbols map[string]objects.Object, expected string) {
// parse
program := parse(t, input)
if program == nil {
@ -75,7 +84,11 @@ func expectErrorWithSymbols(t *testing.T, input string, symbols map[string]objec
}
// compiler/VM
runVMError(t, program, symbols, nil)
_, trace, err := traceCompileRun(program, symbols, nil)
if !assert.Error(t, err) ||
!assert.True(t, strings.Contains(err.Error(), expected), "expected error string: %s, got: %s", expected, err.Error()) {
t.Log("\n" + strings.Join(trace, "\n"))
}
}
func runVM(t *testing.T, file *ast.File, expected interface{}, symbols map[string]objects.Object, userModules map[string]string) (ok bool) {
@ -103,21 +116,6 @@ func runVM(t *testing.T, file *ast.File, expected interface{}, symbols map[strin
return
}
// TODO: should differentiate compile-time error, runtime error, and, error object returned
func runVMError(t *testing.T, file *ast.File, symbols map[string]objects.Object, userModules map[string]string) (ok bool) {
_, trace, err := traceCompileRun(file, symbols, userModules)
defer func() {
if !ok {
t.Log("\n" + strings.Join(trace, "\n"))
}
}()
ok = assert.Error(t, err)
return
}
func errorObject(v interface{}) *objects.Error {
return &objects.Error{Value: toObject(v)}
}
@ -230,7 +228,7 @@ func traceCompileRun(file *ast.File, symbols map[string]objects.Object, userModu
}
tr := &tracer{}
c := compiler.NewCompiler(symTable, nil, nil, tr)
c := compiler.NewCompiler(file.InputFile, symTable, nil, nil, tr)
c.SetModuleLoader(func(moduleName string) ([]byte, error) {
if src, ok := userModules[moduleName]; ok {
return []byte(src), nil
@ -248,7 +246,7 @@ func traceCompileRun(file *ast.File, symbols map[string]objects.Object, userModu
trace = append(trace, fmt.Sprintf("\n[Compiled Constants]\n\n%s", strings.Join(bytecode.FormatConstants(), "\n")))
trace = append(trace, fmt.Sprintf("\n[Compiled Instructions]\n\n%s\n", strings.Join(bytecode.FormatInstructions(), "\n")))
v = runtime.NewVM(bytecode, globals)
v = runtime.NewVM(bytecode, globals, nil)
err = v.Run()
{
@ -296,7 +294,7 @@ func formatGlobals(globals []*objects.Object) (formatted []string) {
func parse(t *testing.T, input string) *ast.File {
testFileSet := source.NewFileSet()
testFile := testFileSet.AddFile("", -1, len(input))
testFile := testFileSet.AddFile("test", -1, len(input))
file, err := parser.ParseFile(testFile, []byte(input), nil)
if !assert.NoError(t, err) {

View file

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

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

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

View file

@ -10,23 +10,23 @@ func makeOSFile(file *os.File) *objects.ImmutableMap {
return &objects.ImmutableMap{
Value: map[string]objects.Object{
// chdir() => true/error
"chdir": FuncARE(file.Chdir),
"chdir": &objects.UserFunction{Name: "chdir", Value: FuncARE(file.Chdir)}, //
// chown(uid int, gid int) => true/error
"chown": FuncAIIRE(file.Chown),
"chown": &objects.UserFunction{Name: "chown", Value: FuncAIIRE(file.Chown)}, //
// close() => error
"close": FuncARE(file.Close),
"close": &objects.UserFunction{Name: "close", Value: FuncARE(file.Close)}, //
// name() => string
"name": FuncARS(file.Name),
"name": &objects.UserFunction{Name: "name", Value: FuncARS(file.Name)}, //
// readdirnames(n int) => array(string)/error
"readdirnames": FuncAIRSsE(file.Readdirnames),
"readdirnames": &objects.UserFunction{Name: "readdirnames", Value: FuncAIRSsE(file.Readdirnames)}, //
// sync() => error
"sync": FuncARE(file.Sync),
"sync": &objects.UserFunction{Name: "sync", Value: FuncARE(file.Sync)}, //
// write(bytes) => int/error
"write": FuncAYRIE(file.Write),
"write": &objects.UserFunction{Name: "write", Value: FuncAYRIE(file.Write)}, //
// write(string) => int/error
"write_string": FuncASRIE(file.WriteString),
"write_string": &objects.UserFunction{Name: "write_string", Value: FuncASRIE(file.WriteString)}, //
// read(bytes) => int/error
"read": FuncAYRIE(file.Read),
"read": &objects.UserFunction{Name: "read", Value: FuncAYRIE(file.Read)}, //
// chmod(mode int) => error
"chmod": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) {
@ -36,7 +36,11 @@ func makeOSFile(file *os.File) *objects.ImmutableMap {
i1, ok := objects.ToInt64(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
}
return wrapError(file.Chmod(os.FileMode(i1))), nil
@ -51,11 +55,19 @@ func makeOSFile(file *os.File) *objects.ImmutableMap {
i1, ok := objects.ToInt64(args[0])
if !ok {
return nil, objects.ErrInvalidTypeConversion
return nil, objects.ErrInvalidArgumentType{
Name: "first",
Expected: "int(compatible)",
Found: args[0].TypeName(),
}
}
i2, ok := objects.ToInt(args[1])
if !ok {
return nil, objects.ErrInvalidTypeConversion
return nil, objects.ErrInvalidArgumentType{
Name: "second",
Expected: "int(compatible)",
Found: args[1].TypeName(),
}
}
res, err := file.Seek(i1, i2)

View file

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

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"
"github.com/d5/tengo/assert"
"github.com/d5/tengo/compiler/stdlib"
"github.com/d5/tengo/objects"
"github.com/d5/tengo/stdlib"
)
type ARR = []interface{}
@ -66,7 +66,7 @@ func module(t *testing.T, moduleName string) callres {
return callres{t: t, e: fmt.Errorf("module not found: %s", moduleName)}
}
return callres{t: t, o: mod}
return callres{t: t, o: (*mod).(*objects.ImmutableMap)}
}
func object(v interface{}) objects.Object {

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

View file

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