diff --git a/README.md b/README.md index ab86de3..677582b 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,8 @@ c5 := char("X") // 'X' ``` > [Run in Playground](https://tengolang.com/?s=8d57905b82959eb244e9bbd2111e12ee04a33045) +_See [Variable Types](https://github.com/d5/tengo/wiki/Variable-Types) for more details on the variable types._ + You can use the dot selector (`.`) and indexer (`[]`) operator to read or write elemens of arrays, strings, or maps. ```golang @@ -115,7 +117,7 @@ m.x = 5 // add 'x' to map 'm' ``` > [Run in Playground](https://tengolang.com/?s=d510c75ed8f06ef1e22c1aaf8a7d4565c793514c) -For sequence types (string or array), you can use slice operator (`[:]`) too. +For sequence types (string, bytes, array), you can use slice operator (`[:]`) too. ```golang a := [1, 2, 3, 4, 5][1:3] // == [2, 3] @@ -210,6 +212,13 @@ foo := 2 Basically `import` expression returns all the global variables defined in the module as a Map-like value. One can access the functions or variables defined in the module using `.` selector or `["key"]` indexer, but, module variables are immutable. +Also you can use `import` to load the [standard libraries](https://github.com/d5/tengo/wiki/Standard-Libraries). + +```golang +math := import("math") +a := math.abs(-19.84) // == 19.84 +``` + ## Embedding Tengo in Go diff --git a/assert/assert.go b/assert/assert.go index 35b7117..29efa82 100644 --- a/assert/assert.go +++ b/assert/assert.go @@ -165,6 +165,10 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool return Equal(t, expected.Value, actual.(objects.ReturnValue).Value) case *objects.Array: return equalArray(t, expected, actual.(*objects.Array)) + case *objects.Bytes: + if bytes.Compare(expected.Value, actual.(*objects.Bytes).Value) != 0 { + return failExpectedActual(t, expected.Value, actual.(*objects.Bytes).Value, msg...) + } case *objects.Map: return equalMap(t, expected, actual.(*objects.Map)) case *objects.CompiledFunction: @@ -175,6 +179,10 @@ func Equal(t *testing.T, expected, actual interface{}, msg ...interface{}) bool return true case *objects.Error: return Equal(t, expected.Value, actual.(*objects.Error).Value) + case error: + if expected != actual.(error) { + return failExpectedActual(t, expected, actual, msg...) + } default: panic(fmt.Errorf("type not implemented: %T", expected)) } diff --git a/cmd/bench/main.go b/cmd/bench/main.go index e275608..17a8093 100644 --- a/cmd/bench/main.go +++ b/cmd/bench/main.go @@ -193,7 +193,7 @@ func compileFile(file *ast.File) (time.Duration, *compiler.Bytecode, error) { start := time.Now() - c := compiler.NewCompiler(symTable, nil) + c := compiler.NewCompiler(symTable, nil, nil) if err := c.Compile(file); err != nil { return time.Since(start), nil, err } diff --git a/cmd/tengo/main.go b/cmd/tengo/main.go index 777321c..5ebb054 100644 --- a/cmd/tengo/main.go +++ b/cmd/tengo/main.go @@ -171,7 +171,11 @@ func doRepl(in io.Reader, out io.Writer) { fileSet := source.NewFileSet() globals := make([]*objects.Object, runtime.GlobalsSize) + symbolTable := compiler.NewSymbolTable() + for idx, fn := range objects.Builtins { + symbolTable.DefineBuiltin(idx, fn.Name) + } for { _, _ = fmt.Fprintf(out, replPrompt) @@ -191,7 +195,7 @@ func doRepl(in io.Reader, out io.Writer) { file = addPrints(file) - c := compiler.NewCompiler(symbolTable, nil) + c := compiler.NewCompiler(symbolTable, nil, nil) if err := c.Compile(file); err != nil { _, _ = fmt.Fprintf(out, "Compilation error:\n %s\n", err.Error()) continue @@ -218,7 +222,7 @@ func compileSrc(src []byte, filename string) (*compiler.Bytecode, error) { return nil, err } - c := compiler.NewCompiler(nil, nil) + c := compiler.NewCompiler(nil, nil, nil) if err := c.Compile(file); err != nil { return nil, err } diff --git a/compiler/bytecode.go b/compiler/bytecode.go index 04bd1db..a0f10ba 100644 --- a/compiler/bytecode.go +++ b/compiler/bytecode.go @@ -45,4 +45,11 @@ func init() { gob.Register(&objects.Map{}) gob.Register(&objects.CompiledFunction{}) gob.Register(&objects.Undefined{}) + gob.Register(&objects.Error{}) + gob.Register(&objects.ImmutableMap{}) + gob.Register(&objects.Bytes{}) + gob.Register(&objects.StringIterator{}) + gob.Register(&objects.MapIterator{}) + gob.Register(&objects.ImmutableMapIterator{}) + gob.Register(&objects.ArrayIterator{}) } diff --git a/compiler/compiler.go b/compiler/compiler.go index f78c302..1b3b331 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -6,7 +6,7 @@ import ( "reflect" "github.com/d5/tengo/compiler/ast" - "github.com/d5/tengo/compiler/stdmods" + "github.com/d5/tengo/compiler/stdlib" "github.com/d5/tengo/compiler/token" "github.com/d5/tengo/objects" ) @@ -20,6 +20,7 @@ type Compiler struct { scopes []CompilationScope scopeIndex int moduleLoader ModuleLoader + stdModules map[string]*objects.ImmutableMap compiledModules map[string]*objects.CompiledModule loops []*Loop loopIndex int @@ -28,17 +29,28 @@ type Compiler struct { } // NewCompiler creates a Compiler. -func NewCompiler(symbolTable *SymbolTable, trace io.Writer) *Compiler { +// User can optionally provide the symbol table if one wants to add or remove +// some global- or builtin- scope symbols. If not (nil), Compile will create +// 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, stdModules map[string]*objects.ImmutableMap, trace io.Writer) *Compiler { mainScope := CompilationScope{ instructions: make([]byte, 0), } + // symbol table if symbolTable == nil { symbolTable = NewSymbolTable() + + for idx, fn := range objects.Builtins { + symbolTable.DefineBuiltin(idx, fn.Name) + } } - for idx, fn := range objects.Builtins { - symbolTable.DefineBuiltin(idx, fn.Name) + // standard modules + if stdModules == nil { + stdModules = stdlib.Modules } return &Compiler{ @@ -47,6 +59,7 @@ func NewCompiler(symbolTable *SymbolTable, trace io.Writer) *Compiler { scopeIndex: 0, loopIndex: -1, trace: trace, + stdModules: stdModules, compiledModules: make(map[string]*objects.CompiledModule), } } @@ -440,7 +453,7 @@ func (c *Compiler) Compile(node ast.Node) error { c.emit(OpCall, len(node.Args)) case *ast.ImportExpr: - stdMod, ok := stdmods.Modules[node.ModuleName] + stdMod, ok := c.stdModules[node.ModuleName] if ok { // standard modules contain only globals with no code. // so no need to compile anything @@ -474,12 +487,14 @@ func (c *Compiler) Bytecode() *Bytecode { } // SetModuleLoader sets or replaces the current module loader. +// Note that the module loader is used for user modules, +// not for the standard modules. func (c *Compiler) SetModuleLoader(moduleLoader ModuleLoader) { c.moduleLoader = moduleLoader } func (c *Compiler) fork(moduleName string, symbolTable *SymbolTable) *Compiler { - child := NewCompiler(symbolTable, c.trace) + child := NewCompiler(symbolTable, c.stdModules, 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 diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index c63b053..5dfefe5 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -972,9 +972,12 @@ func traceCompile(input string, symbols map[string]objects.Object) (res *compile for name := range symbols { symTable.Define(name) } + for idx, fn := range objects.Builtins { + symTable.DefineBuiltin(idx, fn.Name) + } tr := &tracer{} - c := compiler.NewCompiler(symTable, tr) + c := compiler.NewCompiler(symTable, nil, tr) parsed, err := p.ParseFile() if err != nil { return diff --git a/compiler/stdlib/errors.go b/compiler/stdlib/errors.go new file mode 100644 index 0000000..a2942bb --- /dev/null +++ b/compiler/stdlib/errors.go @@ -0,0 +1,11 @@ +package stdlib + +import "github.com/d5/tengo/objects" + +func wrapError(err error) objects.Object { + if err == nil { + return objects.TrueValue + } + + return &objects.Error{Value: &objects.String{Value: err.Error()}} +} diff --git a/compiler/stdlib/exec.go b/compiler/stdlib/exec.go new file mode 100644 index 0000000..2577d10 --- /dev/null +++ b/compiler/stdlib/exec.go @@ -0,0 +1,116 @@ +package stdlib + +import ( + "os/exec" + + "github.com/d5/tengo/objects" +) + +var execModule = map[string]objects.Object{ + // look_path(file string) => string/error + "look_path": FuncASRSE(exec.LookPath), + // command(name string, args array(string)) => imap(cmd) + "command": &objects.UserFunction{Value: execCommand}, +} + +func execCmdImmutableMap(cmd *exec.Cmd) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + // combined_output() => bytes/error + "combined_output": FuncARYE(cmd.CombinedOutput), + // output() => bytes/error + "output": FuncARYE(cmd.Output), + // run() => error + "run": FuncARE(cmd.Run), + // start() => error + "start": FuncARE(cmd.Start), + // wait() => error + "wait": FuncARE(cmd.Wait), + // set_path(path string) + "set_path": &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 + } + + cmd.Path = s1 + + return objects.UndefinedValue, nil + }, + }, + // set_dir(dir string) + "set_dir": &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 + } + + cmd.Dir = s1 + + return objects.UndefinedValue, nil + }, + }, + // set_env(env array(string)) + "set_env": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + envs, err := stringArray(args[0]) + if err != nil { + return nil, err + } + + cmd.Env = envs + + return objects.UndefinedValue, nil + }, + }, + // process() => imap(process) + "process": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + return osProcessImmutableMap(cmd.Process), nil + }, + }, + // TODO: implement pipes + //"stderr_pipe": nil, + //"stdin_pipe": nil, + //"stdout_pipe": nil, + }, + } +} + +func execCommand(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + name, ok := objects.ToString(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + arg, err := stringArray(args[1]) + if err != nil { + return nil, err + } + + res := exec.Command(name, arg...) + + return execCmdImmutableMap(res), nil +} diff --git a/compiler/stdlib/func_typedefs.go b/compiler/stdlib/func_typedefs.go new file mode 100644 index 0000000..ab07845 --- /dev/null +++ b/compiler/stdlib/func_typedefs.go @@ -0,0 +1,605 @@ +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 + }, + } +} + +// 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 + } + + return &objects.Bool{Value: fn()}, 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 + }, + } +} + +// 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 + } + + return &objects.Bool{Value: fn(f1, i2)}, 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 + } + + return &objects.Bool{Value: fn(f1)}, 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 + }, + } +} + +// 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 + }, + } +} + +// 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 + }, + } +} + +// 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 _, osArg := range res { + arr.Value = append(arr.Value, &objects.String{Value: osArg}) + } + + return arr, nil + }, + } +} diff --git a/compiler/stdlib/func_typedefs_test.go b/compiler/stdlib/func_typedefs_test.go new file mode 100644 index 0000000..610e132 --- /dev/null +++ b/compiler/stdlib/func_typedefs_test.go @@ -0,0 +1,360 @@ +package stdlib_test + +import ( + "errors" + "testing" + + "github.com/d5/tengo/assert" + "github.com/d5/tengo/compiler/stdlib" + "github.com/d5/tengo/objects" +) + +func TestFuncAIR(t *testing.T) { + uf := stdlib.FuncAIR(func(int) {}) + ret, err := uf.Call(&objects.Int{Value: 10}) + assert.NoError(t, err) + assert.Equal(t, &objects.Undefined{}, ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncAR(t *testing.T) { + uf := stdlib.FuncAR(func() {}) + ret, err := uf.Call() + assert.NoError(t, err) + assert.Equal(t, &objects.Undefined{}, ret) + ret, err = uf.Call(objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncARI(t *testing.T) { + uf := stdlib.FuncARI(func() int { return 10 }) + ret, err := uf.Call() + assert.NoError(t, err) + assert.Equal(t, &objects.Int{Value: 10}, ret) + ret, err = uf.Call(objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncARE(t *testing.T) { + uf := stdlib.FuncARE(func() error { return nil }) + ret, err := uf.Call() + assert.NoError(t, err) + assert.Equal(t, objects.TrueValue, ret) + uf = stdlib.FuncARE(func() error { return errors.New("some error") }) + ret, err = uf.Call() + assert.NoError(t, err) + assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err = uf.Call(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() + 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() + assert.NoError(t, err) + assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err = uf.Call(objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncARS(t *testing.T) { + uf := stdlib.FuncARS(func() string { return "foo" }) + ret, err := uf.Call() + assert.NoError(t, err) + assert.Equal(t, &objects.String{Value: "foo"}, ret) + ret, err = uf.Call(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() + 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() + assert.NoError(t, err) + assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err = uf.Call(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() + assert.NoError(t, err) + assert.Equal(t, array(&objects.String{Value: "foo"}, &objects.String{Value: "bar"}), ret) + ret, err = uf.Call(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"}) + 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"}) + assert.NoError(t, err) + assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err = uf.Call() + 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"}) + assert.NoError(t, err) + assert.Equal(t, &objects.String{Value: "foo"}, ret) + ret, err = uf.Call() + 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}) + 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}) + assert.NoError(t, err) + assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err = uf.Call() + 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}) + 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}) + assert.NoError(t, err) + assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err = uf.Call() + 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}) + 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}) + assert.NoError(t, err) + assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err = uf.Call() + 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"}) + 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"}) + assert.NoError(t, err) + assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncASSRE(t *testing.T) { + +} + +func TestFuncARF(t *testing.T) { + uf := stdlib.FuncARF(func() float64 { + return 10.0 + }) + ret, err := uf.Call() + assert.NoError(t, err) + assert.Equal(t, &objects.Float{Value: 10.0}, ret) + ret, err = uf.Call(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}) + assert.NoError(t, err) + assert.Equal(t, &objects.Float{Value: 10.0}, ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) + ret, err = uf.Call(objects.TrueValue, objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncAIRF(t *testing.T) { + uf := stdlib.FuncAIRF(func(a int) float64 { + return float64(a) + }) + ret, err := uf.Call(&objects.Int{Value: 10.0}) + assert.NoError(t, err) + assert.Equal(t, &objects.Float{Value: 10.0}, ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) + ret, err = uf.Call(objects.TrueValue, objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncAFRI(t *testing.T) { + uf := stdlib.FuncAFRI(func(a float64) int { + return int(a) + }) + ret, err := uf.Call(&objects.Float{Value: 10.5}) + assert.NoError(t, err) + assert.Equal(t, &objects.Int{Value: 10}, ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) + ret, err = uf.Call(objects.TrueValue, objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +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}) + assert.NoError(t, err) + assert.Equal(t, &objects.Bool{Value: true}, ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) + ret, err = uf.Call(objects.TrueValue, objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +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}) + assert.NoError(t, err) + assert.Equal(t, &objects.Float{Value: 30.0}, ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) + ret, err = uf.Call(objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +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}) + assert.NoError(t, err) + assert.Equal(t, &objects.Float{Value: 30.0}, ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) + ret, err = uf.Call(objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +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}) + assert.NoError(t, err) + assert.Equal(t, &objects.Float{Value: 30.0}, ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) + ret, err = uf.Call(objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +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}) + assert.NoError(t, err) + assert.Equal(t, &objects.Bool{Value: true}, ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) + ret, err = uf.Call(objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +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}) + 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}) + assert.NoError(t, err) + assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncARB(t *testing.T) { + uf := stdlib.FuncARB(func() bool { return true }) + ret, err := uf.Call() + assert.NoError(t, err) + assert.Equal(t, objects.TrueValue, ret) + ret, err = uf.Call(objects.TrueValue) + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func TestFuncARYE(t *testing.T) { + uf := stdlib.FuncARYE(func() ([]byte, error) { + return []byte("foo bar"), nil + }) + ret, err := uf.Call() + 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() + assert.NoError(t, err) + assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err = uf.Call(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"}) + 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"}) + assert.NoError(t, err) + assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err = uf.Call() + 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")}) + 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")}) + assert.NoError(t, err) + assert.Equal(t, &objects.Error{Value: &objects.String{Value: "some error"}}, ret) + ret, err = uf.Call() + assert.Equal(t, objects.ErrWrongNumArguments, err) +} + +func array(elements ...objects.Object) *objects.Array { + return &objects.Array{Value: elements} +} diff --git a/compiler/stdmods/math.go b/compiler/stdlib/math.go similarity index 99% rename from compiler/stdmods/math.go rename to compiler/stdlib/math.go index d2bb075..a5b2182 100644 --- a/compiler/stdmods/math.go +++ b/compiler/stdlib/math.go @@ -1,4 +1,4 @@ -package stdmods +package stdlib import ( "math" diff --git a/compiler/stdlib/os.go b/compiler/stdlib/os.go new file mode 100644 index 0000000..660594d --- /dev/null +++ b/compiler/stdlib/os.go @@ -0,0 +1,199 @@ +package stdlib + +import ( + "os" + + "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_irregular": &objects.Int{Value: int64(os.ModeIrregular)}, + "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(os.SEEK_SET)}, + "seek_cur": &objects.Int{Value: int64(os.SEEK_CUR)}, + "seek_end": &objects.Int{Value: int64(os.SEEK_END)}, + // args() => array(string) + "args": &objects.UserFunction{Value: osArgs}, + // chdir(dir string) => error + "chdir": FuncASRE(os.Chdir), + // chmod(name string, mode int) => error + "chmod": osFuncASFmRE(os.Chmod), + // chown(name string, uid int, gid int) => error + "chown": FuncASIIRE(os.Chown), + // clearenv() + "clearenv": FuncAR(os.Clearenv), + // environ() => array(string) + "environ": FuncARSs(os.Environ), + // executable() => string/error + "executable": &objects.UserFunction{Value: osExecutable}, + // exit(code int) + "exit": FuncAIR(os.Exit), + // expand_env(s string) => string + "expand_env": FuncASRS(os.ExpandEnv), + // getegid() => int + "getegid": FuncARI(os.Getegid), + // getenv(s string) => string + "getenv": FuncASRS(os.Getenv), + // geteuid() => int + "geteuid": FuncARI(os.Geteuid), + // getgid() => int + "getgid": FuncARI(os.Getgid), + // getgroups() => array(string)/error + "getgroups": FuncARIsE(os.Getgroups), + // getpagesize() => int + "getpagesize": FuncARI(os.Getpagesize), + // getpid() => int + "getpid": FuncARI(os.Getpid), + // getppid() => int + "getppid": FuncARI(os.Getppid), + // getuid() => int + "getuid": FuncARI(os.Getuid), + // getwd() => string/error + "getwd": FuncARSE(os.Getwd), + // hostname() => string/error + "hostname": FuncARSE(os.Hostname), + // lchown(name string, uid int, gid int) => error + "lchown": FuncASIIRE(os.Lchown), + // link(oldname string, newname string) => error + "link": FuncASSRE(os.Link), + // lookup_env(key string) => string/false + "lookup_env": &objects.UserFunction{Value: osLookupEnv}, + // mkdir(name string, perm int) => error + "mkdir": osFuncASFmRE(os.Mkdir), + // mkdir_all(name string, perm int) => error + "mkdir_all": osFuncASFmRE(os.MkdirAll), + // readlink(name string) => string/error + "readlink": FuncASRSE(os.Readlink), + // remove(name string) => error + "remove": FuncASRE(os.Remove), + // remove_all(name string) => error + "remove_all": FuncASRE(os.RemoveAll), + // rename(oldpath string, newpath string) => error + "rename": FuncASSRE(os.Rename), + // setenv(key string, value string) => error + "setenv": FuncASSRE(os.Setenv), + // symlink(oldname string newname string) => error + "symlink": FuncASSRE(os.Symlink), + // temp_dir() => string + "temp_dir": FuncARS(os.TempDir), + // truncate(name string, size int) => error + "truncate": FuncASI64RE(os.Truncate), + // unsetenv(key string) => error + "unsetenv": FuncASRE(os.Unsetenv), + // user_cache_dir() => string/error + "user_cache_dir": FuncARSE(os.UserCacheDir), + // create(name string) => imap(file)/error + "create": &objects.UserFunction{Value: osCreate}, + // open(name string) => imap(file)/error + "open": &objects.UserFunction{Value: osOpen}, + // open_file(name string, flag int, perm int) => imap(file)/error + "open_file": &objects.UserFunction{Value: osOpenFile}, + // find_process(pid int) => imap(process)/error + "find_process": &objects.UserFunction{Value: osFindProcess}, + // start_process(name string, argv array(string), dir string, env array(string)) => imap(process)/error + "start_process": &objects.UserFunction{Value: osStartProcess}, + + // TODO: implemented more functions + //"stdin": nil, + //"stdout": nil, + //"stderr": nil, + //"chtimes": nil, + //"expand": nil, + //"is_exists": nil, + //"is_not_exist": nil, + //"is_path_separator": nil, + //"is_permission": nil, + //"is_timeout": nil, + //"new_syscall_error": nil, + //"pipe": nil, + //"same_file": 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 osExecutable(args ...objects.Object) (objects.Object, error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + res, err := os.Executable() + if err != nil { + return wrapError(err), nil + } + + return &objects.String{Value: res}, 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 +} diff --git a/compiler/stdlib/os_file.go b/compiler/stdlib/os_file.go new file mode 100644 index 0000000..5c0007b --- /dev/null +++ b/compiler/stdlib/os_file.go @@ -0,0 +1,144 @@ +package stdlib + +import ( + "os" + + "github.com/d5/tengo/objects" +) + +func osFileImmutableMap(file *os.File) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + // chdir() => true/error + "chdir": FuncARE(file.Chdir), + // chown(uid int, gid int) => true/error + "chown": FuncAIIRE(file.Chown), + // close() => error + "close": FuncARE(file.Close), + // name() => string + "name": FuncARS(file.Name), + // readdirnames(n int) => array(string)/error + "readdirnames": FuncAIRSsE(file.Readdirnames), + // sync() => error + "sync": FuncARE(file.Sync), + // write(bytes) => int/error + "write": FuncAYRIE(file.Write), + // write(string) => int/error + "write_string": FuncASRIE(file.WriteString), + // read(bytes) => int/error + "read": FuncAYRIE(file.Read), + // chmod(mode int) => error + "chmod": &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 wrapError(file.Chmod(os.FileMode(i1))), nil + }, + }, + // seek(offset int, whence int) => int/error + "seek": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + i1, ok := objects.ToInt64(args[0]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + i2, ok := objects.ToInt(args[1]) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + res, err := file.Seek(i1, i2) + if err != nil { + return wrapError(err), nil + } + + return &objects.Int{Value: res}, nil + }, + }, + // TODO: implement more functions + //"fd": nil, + //"read_at": nil, + //"readdir": nil, + //"set_deadline": nil, + //"set_read_deadline": nil, + //"set_write_deadline": nil, + //"stat": nil, + //"write_at": 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 osFileImmutableMap(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 osFileImmutableMap(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 osFileImmutableMap(res), nil +} diff --git a/compiler/stdlib/os_process.go b/compiler/stdlib/os_process.go new file mode 100644 index 0000000..45872d7 --- /dev/null +++ b/compiler/stdlib/os_process.go @@ -0,0 +1,129 @@ +package stdlib + +import ( + "os" + "syscall" + + "github.com/d5/tengo/objects" +) + +func osProcessStateImmutableMap(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), + }, + } +} + +func osProcessImmutableMap(proc *os.Process) *objects.ImmutableMap { + return &objects.ImmutableMap{ + Value: map[string]objects.Object{ + "kill": FuncARE(proc.Kill), + "release": FuncARE(proc.Release), + "signal": &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 wrapError(proc.Signal(syscall.Signal(i1))), nil + }, + }, + "wait": &objects.UserFunction{ + Value: func(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 0 { + return nil, objects.ErrWrongNumArguments + } + + state, err := proc.Wait() + if err != nil { + return wrapError(err), nil + } + + return osProcessStateImmutableMap(state), 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 osProcessImmutableMap(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 osProcessImmutableMap(proc), nil +} + +func stringArray(o objects.Object) ([]string, error) { + arr, ok := o.(*objects.Array) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + var sarr []string + for _, elem := range arr.Value { + str, ok := elem.(*objects.String) + if !ok { + return nil, objects.ErrInvalidTypeConversion + } + + sarr = append(sarr, str.Value) + } + + return sarr, nil +} diff --git a/compiler/stdlib/stdlib.go b/compiler/stdlib/stdlib.go new file mode 100644 index 0000000..3ca5dd3 --- /dev/null +++ b/compiler/stdlib/stdlib.go @@ -0,0 +1,10 @@ +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}, + "exec": {Value: execModule}, +} diff --git a/compiler/stdmods/func_typedefs.go b/compiler/stdmods/func_typedefs.go deleted file mode 100644 index cf71edd..0000000 --- a/compiler/stdmods/func_typedefs.go +++ /dev/null @@ -1,243 +0,0 @@ -package stdmods - -import ( - "fmt" - - "github.com/d5/tengo/objects" -) - -// 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 - } - - switch arg := args[0].(type) { - case *objects.Int: - return &objects.Float{Value: fn(float64(arg.Value))}, nil - case *objects.Float: - return &objects.Float{Value: fn(arg.Value)}, nil - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - }, - } -} - -// 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 - }, - } -} - -// 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 - } - - switch arg := args[0].(type) { - case *objects.Int: - return &objects.Float{Value: fn(int(arg.Value))}, nil - case *objects.Float: - return &objects.Float{Value: fn(int(arg.Value))}, nil - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - }, - } -} - -// 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 - } - - switch arg := args[0].(type) { - case *objects.Int: - return &objects.Int{Value: int64(fn(float64(arg.Value)))}, nil - case *objects.Float: - return &objects.Int{Value: int64(fn(arg.Value))}, nil - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - }, - } -} - -// 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 - } - - var arg0, arg1 float64 - - switch arg := args[0].(type) { - case *objects.Int: - arg0 = float64(arg.Value) - case *objects.Float: - arg0 = arg.Value - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - switch arg := args[1].(type) { - case *objects.Int: - arg1 = float64(arg.Value) - case *objects.Float: - arg1 = arg.Value - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - - return &objects.Float{Value: fn(arg0, arg1)}, 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 - } - - var arg0 int - var arg1 float64 - - switch arg := args[0].(type) { - case *objects.Int: - arg0 = int(arg.Value) - case *objects.Float: - arg0 = int(arg.Value) - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - switch arg := args[1].(type) { - case *objects.Int: - arg1 = float64(arg.Value) - case *objects.Float: - arg1 = arg.Value - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - - return &objects.Float{Value: fn(arg0, arg1)}, 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 - } - - var arg0 float64 - var arg1 int - - switch arg := args[0].(type) { - case *objects.Int: - arg0 = float64(arg.Value) - case *objects.Float: - arg0 = float64(arg.Value) - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - switch arg := args[1].(type) { - case *objects.Int: - arg1 = int(arg.Value) - case *objects.Float: - arg1 = int(arg.Value) - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - - return &objects.Float{Value: fn(arg0, arg1)}, 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 - } - - var arg0 float64 - var arg1 int - - switch arg := args[0].(type) { - case *objects.Int: - arg0 = float64(arg.Value) - case *objects.Float: - arg0 = arg.Value - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - switch arg := args[1].(type) { - case *objects.Int: - arg1 = int(arg.Value) - case *objects.Float: - arg1 = int(arg.Value) - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - - return &objects.Bool{Value: fn(arg0, arg1)}, 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 - } - - var arg0 float64 - switch arg := args[0].(type) { - case *objects.Int: - arg0 = float64(arg.Value) - case *objects.Float: - arg0 = arg.Value - default: - return nil, fmt.Errorf("invalid argument type: %s", arg.TypeName()) - } - - return &objects.Bool{Value: fn(arg0)}, nil - }, - } -} diff --git a/compiler/stdmods/func_typedefs_test.go b/compiler/stdmods/func_typedefs_test.go deleted file mode 100644 index c06b930..0000000 --- a/compiler/stdmods/func_typedefs_test.go +++ /dev/null @@ -1,124 +0,0 @@ -package stdmods_test - -import ( - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/compiler/stdmods" - "github.com/d5/tengo/objects" -) - -func TestFuncARF(t *testing.T) { - uf := stdmods.FuncARF(func() float64 { - return 10.0 - }) - ret, err := uf.Call() - assert.NoError(t, err) - assert.Equal(t, &objects.Float{Value: 10.0}, ret) - ret, err = uf.Call(objects.TrueValue) - assert.Error(t, err) -} - -func TestFuncAFRF(t *testing.T) { - uf := stdmods.FuncAFRF(func(a float64) float64 { - return a - }) - ret, err := uf.Call(&objects.Float{Value: 10.0}) - assert.NoError(t, err) - assert.Equal(t, &objects.Float{Value: 10.0}, ret) - ret, err = uf.Call() - assert.Error(t, err) - ret, err = uf.Call(objects.TrueValue, objects.TrueValue) - assert.Error(t, err) -} - -func TestFuncAIRF(t *testing.T) { - uf := stdmods.FuncAIRF(func(a int) float64 { - return float64(a) - }) - ret, err := uf.Call(&objects.Int{Value: 10.0}) - assert.NoError(t, err) - assert.Equal(t, &objects.Float{Value: 10.0}, ret) - ret, err = uf.Call() - assert.Error(t, err) - ret, err = uf.Call(objects.TrueValue, objects.TrueValue) - assert.Error(t, err) -} - -func TestFuncAFRI(t *testing.T) { - uf := stdmods.FuncAFRI(func(a float64) int { - return int(a) - }) - ret, err := uf.Call(&objects.Float{Value: 10.5}) - assert.NoError(t, err) - assert.Equal(t, &objects.Int{Value: 10}, ret) - ret, err = uf.Call() - assert.Error(t, err) - ret, err = uf.Call(objects.TrueValue, objects.TrueValue) - assert.Error(t, err) -} - -func TestFuncAFRB(t *testing.T) { - uf := stdmods.FuncAFRB(func(a float64) bool { - return a > 0.0 - }) - ret, err := uf.Call(&objects.Float{Value: 0.1}) - assert.NoError(t, err) - assert.Equal(t, &objects.Bool{Value: true}, ret) - ret, err = uf.Call() - assert.Error(t, err) - ret, err = uf.Call(objects.TrueValue, objects.TrueValue) - assert.Error(t, err) -} - -func TestFuncAFFRF(t *testing.T) { - uf := stdmods.FuncAFFRF(func(a, b float64) float64 { - return a + b - }) - ret, err := uf.Call(&objects.Float{Value: 10.0}, &objects.Float{Value: 20.0}) - assert.NoError(t, err) - assert.Equal(t, &objects.Float{Value: 30.0}, ret) - ret, err = uf.Call() - assert.Error(t, err) - ret, err = uf.Call(objects.TrueValue) - assert.Error(t, err) -} - -func TestFuncAIFRF(t *testing.T) { - uf := stdmods.FuncAIFRF(func(a int, b float64) float64 { - return float64(a) + b - }) - ret, err := uf.Call(&objects.Int{Value: 10}, &objects.Float{Value: 20.0}) - assert.NoError(t, err) - assert.Equal(t, &objects.Float{Value: 30.0}, ret) - ret, err = uf.Call() - assert.Error(t, err) - ret, err = uf.Call(objects.TrueValue) - assert.Error(t, err) -} - -func TestFuncAFIRF(t *testing.T) { - uf := stdmods.FuncAFIRF(func(a float64, b int) float64 { - return a + float64(b) - }) - ret, err := uf.Call(&objects.Float{Value: 10.0}, &objects.Int{Value: 20}) - assert.NoError(t, err) - assert.Equal(t, &objects.Float{Value: 30.0}, ret) - ret, err = uf.Call() - assert.Error(t, err) - ret, err = uf.Call(objects.TrueValue) - assert.Error(t, err) -} - -func TestFuncAFIRB(t *testing.T) { - uf := stdmods.FuncAFIRB(func(a float64, b int) bool { - return a < float64(b) - }) - ret, err := uf.Call(&objects.Float{Value: 10.0}, &objects.Int{Value: 20}) - assert.NoError(t, err) - assert.Equal(t, &objects.Bool{Value: true}, ret) - ret, err = uf.Call() - assert.Error(t, err) - ret, err = uf.Call(objects.TrueValue) - assert.Error(t, err) -} diff --git a/compiler/stdmods/stdmods.go b/compiler/stdmods/stdmods.go deleted file mode 100644 index 9cf5963..0000000 --- a/compiler/stdmods/stdmods.go +++ /dev/null @@ -1,8 +0,0 @@ -package stdmods - -import "github.com/d5/tengo/objects" - -// Modules contain the standard modules. -var Modules = map[string]*objects.ModuleMap{ - "math": {Value: mathModule}, -} diff --git a/objects/bool.go b/objects/bool.go index 719e48c..b7ca19c 100644 --- a/objects/bool.go +++ b/objects/bool.go @@ -30,7 +30,7 @@ func (o *Bool) BinaryOp(op token.Token, rhs Object) (Object, error) { // Copy returns a copy of the type. func (o *Bool) Copy() Object { - v := Bool(*o) + v := Bool{Value: o.Value} return &v } diff --git a/objects/builtin_convert.go b/objects/builtin_convert.go index 618691d..922efc0 100644 --- a/objects/builtin_convert.go +++ b/objects/builtin_convert.go @@ -1,22 +1,20 @@ package objects -import ( - "strconv" -) - func builtinString(args ...Object) (Object, error) { if len(args) != 1 { return nil, ErrWrongNumArguments } - switch arg := args[0].(type) { - case *String: - return arg, nil - case *Undefined: - return UndefinedValue, nil - default: - return &String{Value: arg.String()}, nil + if _, ok := args[0].(*String); ok { + return args[0], nil } + + v, ok := ToString(args[0]) + if ok { + return &String{Value: v}, nil + } + + return UndefinedValue, nil } func builtinInt(args ...Object) (Object, error) { @@ -24,23 +22,13 @@ func builtinInt(args ...Object) (Object, error) { return nil, ErrWrongNumArguments } - switch arg := args[0].(type) { - case *Int: - return arg, nil - case *Float: - return &Int{Value: int64(arg.Value)}, nil - case *Char: - return &Int{Value: int64(arg.Value)}, nil - case *Bool: - if arg.Value { - return &Int{Value: 1}, nil - } - return &Int{Value: 0}, nil - case *String: - n, err := strconv.ParseInt(arg.Value, 10, 64) - if err == nil { - return &Int{Value: n}, nil - } + if _, ok := args[0].(*Int); ok { + return args[0], nil + } + + v, ok := ToInt64(args[0]) + if ok { + return &Int{Value: v}, nil } return UndefinedValue, nil @@ -51,16 +39,13 @@ func builtinFloat(args ...Object) (Object, error) { return nil, ErrWrongNumArguments } - switch arg := args[0].(type) { - case *Float: - return arg, nil - case *Int: - return &Float{Value: float64(arg.Value)}, nil - case *String: - f, err := strconv.ParseFloat(arg.Value, 64) - if err == nil { - return &Float{Value: f}, nil - } + if _, ok := args[0].(*Float); ok { + return args[0], nil + } + + v, ok := ToFloat64(args[0]) + if ok { + return &Float{Value: v}, nil } return UndefinedValue, nil @@ -71,12 +56,16 @@ func builtinBool(args ...Object) (Object, error) { return nil, ErrWrongNumArguments } - switch arg := args[0].(type) { - case *Bool: - return arg, nil - default: - return &Bool{Value: !arg.IsFalsy()}, nil + if _, ok := args[0].(*Bool); ok { + return args[0], nil } + + v, ok := ToBool(args[0]) + if ok { + return &Bool{Value: v}, nil + } + + return UndefinedValue, nil } func builtinChar(args ...Object) (Object, error) { @@ -84,19 +73,31 @@ func builtinChar(args ...Object) (Object, error) { return nil, ErrWrongNumArguments } - switch arg := args[0].(type) { - case *Char: - return arg, nil - case *Int: - return &Char{Value: rune(arg.Value)}, nil - case *String: - rs := []rune(arg.Value) - switch len(rs) { - case 0: - return &Char{}, nil - case 1: - return &Char{Value: rs[0]}, nil - } + if _, ok := args[0].(*Char); ok { + return args[0], nil + } + + v, ok := ToRune(args[0]) + if ok { + return &Char{Value: v}, nil + } + + return UndefinedValue, nil +} + +func builtinBytes(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + // bytes(N) => create a new bytes with given size N + if n, ok := args[0].(*Int); ok { + return &Bytes{Value: make([]byte, int(n.Value))}, nil + } + + v, ok := ToByteSlice(args[0]) + if ok { + return &Bytes{Value: v}, nil } return UndefinedValue, nil diff --git a/objects/builtin_is_error.go b/objects/builtin_is_error.go deleted file mode 100644 index ffa3f2f..0000000 --- a/objects/builtin_is_error.go +++ /dev/null @@ -1,18 +0,0 @@ -package objects - -import ( - "fmt" -) - -func builtinIsError(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, fmt.Errorf("wrong number of arguments (got=%d, want=1)", len(args)) - } - - switch args[0].(type) { - case *Error: - return TrueValue, nil - } - - return FalseValue, nil -} diff --git a/objects/builtin_is_undefined.go b/objects/builtin_is_undefined.go deleted file mode 100644 index 80c0c9f..0000000 --- a/objects/builtin_is_undefined.go +++ /dev/null @@ -1,18 +0,0 @@ -package objects - -import ( - "fmt" -) - -func builtinIsUndefined(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, fmt.Errorf("wrong number of arguments (got=%d, want=1)", len(args)) - } - - switch args[0].(type) { - case *Undefined: - return TrueValue, nil - } - - return FalseValue, nil -} diff --git a/objects/builtin_len.go b/objects/builtin_len.go index 26d59a9..fd6529a 100644 --- a/objects/builtin_len.go +++ b/objects/builtin_len.go @@ -14,6 +14,12 @@ func builtinLen(args ...Object) (Object, error) { return &Int{Value: int64(len(arg.Value))}, nil case *String: return &Int{Value: int64(len(arg.Value))}, nil + case *Bytes: + return &Int{Value: int64(len(arg.Value))}, nil + case *Map: + return &Int{Value: int64(len(arg.Value))}, nil + case *ImmutableMap: + return &Int{Value: int64(len(arg.Value))}, nil default: return nil, fmt.Errorf("unsupported type for 'len' function: %s", arg.TypeName()) } diff --git a/objects/builtin_print.go b/objects/builtin_print.go index a512721..46be159 100644 --- a/objects/builtin_print.go +++ b/objects/builtin_print.go @@ -6,7 +6,11 @@ import ( func builtinPrint(args ...Object) (Object, error) { for _, arg := range args { - fmt.Println(arg.String()) + if str, ok := arg.(*String); ok { + fmt.Println(str.Value) + } else { + fmt.Println(arg.String()) + } } return nil, nil diff --git a/objects/builtin_type_checks.go b/objects/builtin_type_checks.go new file mode 100644 index 0000000..b2af058 --- /dev/null +++ b/objects/builtin_type_checks.go @@ -0,0 +1,97 @@ +package objects + +func builtinIsString(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*String); ok { + return TrueValue, nil + } + + return FalseValue, nil +} + +func builtinIsInt(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*Int); ok { + return TrueValue, nil + } + + return FalseValue, nil +} + +func builtinIsFloat(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*Float); ok { + return TrueValue, nil + } + + return FalseValue, nil +} + +func builtinIsBool(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*Bool); ok { + return TrueValue, nil + } + + return FalseValue, nil +} + +func builtinIsChar(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*Char); ok { + return TrueValue, nil + } + + return FalseValue, nil +} + +func builtinIsBytes(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*Bytes); ok { + return TrueValue, nil + } + + return FalseValue, nil +} + +func builtinIsError(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*Error); ok { + return TrueValue, nil + } + + return FalseValue, nil +} + +func builtinIsUndefined(args ...Object) (Object, error) { + if len(args) != 1 { + return nil, ErrWrongNumArguments + } + + if _, ok := args[0].(*Undefined); ok { + return TrueValue, nil + } + + return FalseValue, nil +} diff --git a/objects/builtins.go b/objects/builtins.go index 8ec3b7f..fe7085e 100644 --- a/objects/builtins.go +++ b/objects/builtins.go @@ -1,10 +1,13 @@ package objects -// Builtins contains all known builtin functions. -var Builtins = []struct { +// NamedBuiltinFunc is a named builtin function. +type NamedBuiltinFunc struct { Name string Func CallableFunc -}{ +} + +// Builtins contains all default builtin functions. +var Builtins = []NamedBuiltinFunc{ { Name: "print", Func: builtinPrint, @@ -41,6 +44,34 @@ var Builtins = []struct { Name: "char", Func: builtinChar, }, + { + Name: "bytes", + Func: builtinBytes, + }, + { + Name: "is_int", + Func: builtinIsInt, + }, + { + Name: "is_float", + Func: builtinIsFloat, + }, + { + Name: "is_string", + Func: builtinIsString, + }, + { + Name: "is_bool", + Func: builtinIsBool, + }, + { + Name: "is_char", + Func: builtinIsChar, + }, + { + Name: "is_bytes", + Func: builtinIsBytes, + }, { Name: "is_error", Func: builtinIsError, diff --git a/objects/bytes.go b/objects/bytes.go new file mode 100644 index 0000000..58e8059 --- /dev/null +++ b/objects/bytes.go @@ -0,0 +1,56 @@ +package objects + +import ( + "bytes" + + "github.com/d5/tengo/compiler/token" +) + +// Bytes represents a byte array. +type Bytes struct { + Value []byte +} + +func (o *Bytes) String() string { + return string(o.Value) +} + +// TypeName returns the name of the type. +func (o *Bytes) TypeName() string { + return "bytes" +} + +// BinaryOp returns another object that is the result of +// a given binary operator and a right-hand side object. +func (o *Bytes) BinaryOp(op token.Token, rhs Object) (Object, error) { + switch op { + case token.Add: + switch rhs := rhs.(type) { + case *Bytes: + return &Bytes{Value: append(o.Value, rhs.Value...)}, nil + } + } + + return nil, ErrInvalidOperator +} + +// Copy returns a copy of the type. +func (o *Bytes) Copy() Object { + return &Bytes{Value: append([]byte{}, o.Value...)} +} + +// IsFalsy returns true if the value of the type is falsy. +func (o *Bytes) IsFalsy() bool { + return len(o.Value) == 0 +} + +// Equals returns true if the value of the type +// is equal to the value of another object. +func (o *Bytes) Equals(x Object) bool { + t, ok := x.(*Bytes) + if !ok { + return false + } + + return bytes.Compare(o.Value, t.Value) == 0 +} diff --git a/objects/conversion.go b/objects/conversion.go new file mode 100644 index 0000000..99996bb --- /dev/null +++ b/objects/conversion.go @@ -0,0 +1,140 @@ +package objects + +import ( + "strconv" +) + +// ToString will try to convert object o to string value. +func ToString(o Object) (v string, ok bool) { + if _, isUndefined := o.(*Undefined); isUndefined { + //ok = false + return + } + + ok = true + + if str, isStr := o.(*String); isStr { + v = str.Value + } else { + v = o.String() + } + + return +} + +// ToInt will try to convert object o to int value. +func ToInt(o Object) (v int, ok bool) { + switch o := o.(type) { + case *Int: + v = int(o.Value) + ok = true + case *Float: + v = int(o.Value) + ok = true + case *Char: + v = int(o.Value) + ok = true + case *Bool: + if o.Value { + v = 1 + } + ok = true + case *String: + c, err := strconv.ParseInt(o.Value, 10, 64) + if err == nil { + v = int(c) + ok = true + } + } + + //ok = false + return +} + +// ToInt64 will try to convert object o to int64 value. +func ToInt64(o Object) (v int64, ok bool) { + switch o := o.(type) { + case *Int: + v = o.Value + ok = true + case *Float: + v = int64(o.Value) + ok = true + case *Char: + v = int64(o.Value) + ok = true + case *Bool: + if o.Value { + v = 1 + } + ok = true + case *String: + c, err := strconv.ParseInt(o.Value, 10, 64) + if err == nil { + v = c + ok = true + } + } + + //ok = false + return +} + +// ToFloat64 will try to convert object o to float64 value. +func ToFloat64(o Object) (v float64, ok bool) { + switch o := o.(type) { + case *Int: + v = float64(o.Value) + ok = true + case *Float: + v = o.Value + ok = true + case *String: + c, err := strconv.ParseFloat(o.Value, 64) + if err == nil { + v = c + ok = true + } + } + + //ok = false + return +} + +// ToBool will try to convert object o to bool value. +func ToBool(o Object) (v bool, ok bool) { + ok = true + v = !o.IsFalsy() + + return +} + +// ToRune will try to convert object o to rune value. +func ToRune(o Object) (v rune, ok bool) { + switch o := o.(type) { + case *Int: + v = rune(o.Value) + ok = true + case *Char: + v = rune(o.Value) + ok = true + } + + //ok = false + return +} + +// ToByteSlice will try to convert object o to []byte value. +func ToByteSlice(o Object) (v []byte, ok bool) { + switch o := o.(type) { + case *Bytes: + v = o.Value + ok = true + case *String: + v = []byte(o.Value) + ok = true + } + + //ok = false + return +} diff --git a/objects/errors.go b/objects/errors.go index 78d6a40..19fbaa9 100644 --- a/objects/errors.go +++ b/objects/errors.go @@ -2,8 +2,17 @@ package objects import "errors" +// ErrNotCallable represents an error for calling on non-function objects. +var ErrNotCallable = errors.New("not a callable object") + +// ErrNotIndexable represents an error for indexing on non-indexable objects. +var ErrNotIndexable = errors.New("non-indexable object") + // 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") diff --git a/objects/module_map.go b/objects/immutable_map.go similarity index 67% rename from objects/module_map.go rename to objects/immutable_map.go index baa2f46..4e29281 100644 --- a/objects/module_map.go +++ b/objects/immutable_map.go @@ -7,17 +7,17 @@ import ( "github.com/d5/tengo/compiler/token" ) -// ModuleMap represents a module map object. -type ModuleMap struct { +// ImmutableMap represents an immutable map object. +type ImmutableMap struct { Value map[string]Object } // TypeName returns the name of the type. -func (o *ModuleMap) TypeName() string { - return "module" +func (o *ImmutableMap) TypeName() string { + return "immutable-map" } -func (o *ModuleMap) String() string { +func (o *ImmutableMap) String() string { var pairs []string for k, v := range o.Value { pairs = append(pairs, fmt.Sprintf("%s: %s", k, v.String())) @@ -28,27 +28,27 @@ func (o *ModuleMap) String() string { // BinaryOp returns another object that is the result of // a given binary operator and a right-hand side object. -func (o *ModuleMap) BinaryOp(op token.Token, rhs Object) (Object, error) { +func (o *ImmutableMap) BinaryOp(op token.Token, rhs Object) (Object, error) { return nil, ErrInvalidOperator } // Copy returns a copy of the type. -func (o *ModuleMap) Copy() Object { +func (o *ImmutableMap) Copy() Object { c := make(map[string]Object) for k, v := range o.Value { c[k] = v.Copy() } - return &ModuleMap{Value: c} + return &ImmutableMap{Value: c} } // IsFalsy returns true if the value of the type is falsy. -func (o *ModuleMap) IsFalsy() bool { +func (o *ImmutableMap) IsFalsy() bool { return len(o.Value) == 0 } // Get returns the value for the given key. -func (o *ModuleMap) Get(key string) (Object, bool) { +func (o *ImmutableMap) Get(key string) (Object, bool) { val, ok := o.Value[key] return val, ok @@ -56,8 +56,8 @@ func (o *ModuleMap) Get(key string) (Object, bool) { // Equals returns true if the value of the type // is equal to the value of another object. -func (o *ModuleMap) Equals(x Object) bool { - t, ok := x.(*ModuleMap) +func (o *ImmutableMap) Equals(x Object) bool { + t, ok := x.(*ImmutableMap) if !ok { return false } diff --git a/objects/module_map_iterator.go b/objects/immutable_map_iterator.go similarity index 58% rename from objects/module_map_iterator.go rename to objects/immutable_map_iterator.go index 9aa49a1..4222616 100644 --- a/objects/module_map_iterator.go +++ b/objects/immutable_map_iterator.go @@ -2,8 +2,8 @@ package objects import "github.com/d5/tengo/compiler/token" -// ModuleMapIterator represents an iterator for the module map. -type ModuleMapIterator struct { +// ImmutableMapIterator represents an iterator for the immutable map. +type ImmutableMapIterator struct { v map[string]Object k []string i int @@ -11,13 +11,13 @@ type ModuleMapIterator struct { } // NewModuleMapIterator creates a module iterator. -func NewModuleMapIterator(v *ModuleMap) Iterator { +func NewModuleMapIterator(v *ImmutableMap) Iterator { var keys []string for k := range v.Value { keys = append(keys, k) } - return &ModuleMapIterator{ + return &ImmutableMapIterator{ v: v.Value, k: keys, l: len(keys), @@ -25,51 +25,51 @@ func NewModuleMapIterator(v *ModuleMap) Iterator { } // TypeName returns the name of the type. -func (i *ModuleMapIterator) TypeName() string { +func (i *ImmutableMapIterator) TypeName() string { return "module-iterator" } -func (i *ModuleMapIterator) String() string { +func (i *ImmutableMapIterator) String() string { return "" } // BinaryOp returns another object that is the result of // a given binary operator and a right-hand side object. -func (i *ModuleMapIterator) BinaryOp(op token.Token, rhs Object) (Object, error) { +func (i *ImmutableMapIterator) BinaryOp(op token.Token, rhs Object) (Object, error) { return nil, ErrInvalidOperator } // IsFalsy returns true if the value of the type is falsy. -func (i *ModuleMapIterator) IsFalsy() bool { +func (i *ImmutableMapIterator) IsFalsy() bool { return true } // Equals returns true if the value of the type // is equal to the value of another object. -func (i *ModuleMapIterator) Equals(Object) bool { +func (i *ImmutableMapIterator) Equals(Object) bool { return false } // Copy returns a copy of the type. -func (i *ModuleMapIterator) Copy() Object { - return &ModuleMapIterator{v: i.v, k: i.k, i: i.i, l: i.l} +func (i *ImmutableMapIterator) Copy() Object { + return &ImmutableMapIterator{v: i.v, k: i.k, i: i.i, l: i.l} } // Next returns true if there are more elements to iterate. -func (i *ModuleMapIterator) Next() bool { +func (i *ImmutableMapIterator) Next() bool { i.i++ return i.i <= i.l } // Key returns the key or index value of the current element. -func (i *ModuleMapIterator) Key() Object { +func (i *ImmutableMapIterator) Key() Object { k := i.k[i.i-1] return &String{Value: k} } // Value returns the value of the current element. -func (i *ModuleMapIterator) Value() Object { +func (i *ImmutableMapIterator) Value() Object { k := i.k[i.i-1] return i.v[k] diff --git a/objects/object_test.go b/objects/object_test.go index 0159b2b..88ca669 100644 --- a/objects/object_test.go +++ b/objects/object_test.go @@ -46,6 +46,8 @@ func TestObject_TypeName(t *testing.T) { assert.Equal(t, "undefined", o.TypeName()) o = &objects.Error{} assert.Equal(t, "error", o.TypeName()) + o = &objects.Bytes{} + assert.Equal(t, "bytes", o.TypeName()) } func TestObject_IsFalsy(t *testing.T) { @@ -96,6 +98,10 @@ func TestObject_IsFalsy(t *testing.T) { assert.True(t, o.IsFalsy()) o = &objects.Error{} assert.True(t, o.IsFalsy()) + o = &objects.Bytes{} + assert.True(t, o.IsFalsy()) + o = &objects.Bytes{Value: []byte{1, 2}} + assert.False(t, o.IsFalsy()) } func TestObject_String(t *testing.T) { @@ -138,6 +144,10 @@ func TestObject_String(t *testing.T) { assert.Equal(t, "", o.String()) o = &objects.Undefined{} assert.Equal(t, "", o.String()) + o = &objects.Bytes{} + assert.Equal(t, "", o.String()) + o = &objects.Bytes{Value: []byte("foo")} + assert.Equal(t, "foo", o.String()) } func TestObject_BinaryOp(t *testing.T) { diff --git a/objects/string.go b/objects/string.go index c95de59..67d5ddd 100644 --- a/objects/string.go +++ b/objects/string.go @@ -1,7 +1,7 @@ package objects import ( - "fmt" + "strconv" "github.com/d5/tengo/compiler/token" ) @@ -17,7 +17,7 @@ func (o *String) TypeName() string { } func (o *String) String() string { - return fmt.Sprintf("%q", o.Value) + return strconv.Quote(o.Value) } // BinaryOp returns another object that is the result of diff --git a/objects/user_function.go b/objects/user_function.go index c83866a..74b818c 100644 --- a/objects/user_function.go +++ b/objects/user_function.go @@ -40,7 +40,7 @@ func (o *UserFunction) Equals(x Object) bool { return false } -// Call executes a builtin function. +// Call invokes a user function. func (o *UserFunction) Call(args ...Object) (Object, error) { return o.Value(args...) } diff --git a/runtime/vm.go b/runtime/vm.go index 74e5bfe..f4254e8 100644 --- a/runtime/vm.go +++ b/runtime/vm.go @@ -622,6 +622,25 @@ func (v *VM) Run() error { v.stack[v.sp] = &val v.sp++ + case *objects.Bytes: + idx, ok := (*index).(*objects.Int) + if !ok { + return fmt.Errorf("non-integer array index: %s", left.TypeName()) + } + + if idx.Value < 0 || idx.Value >= int64(len(left.Value)) { + return fmt.Errorf("index out of bounds: %d", index) + } + + if v.sp >= StackSize { + return ErrStackOverflow + } + + var val objects.Object = &objects.Int{Value: int64(left.Value[idx.Value])} + + v.stack[v.sp] = &val + v.sp++ + case *objects.Map: key, ok := (*index).(*objects.String) if !ok { @@ -641,7 +660,7 @@ func (v *VM) Run() error { v.stack[v.sp] = &res v.sp++ - case *objects.ModuleMap: + case *objects.ImmutableMap: key, ok := (*index).(*objects.String) if !ok { return fmt.Errorf("non-string key: %s", left.TypeName()) @@ -754,6 +773,31 @@ func (v *VM) Run() error { v.stack[v.sp] = &val v.sp++ + case *objects.Bytes: + numElements := int64(len(left.Value)) + + if lowIdx < 0 || lowIdx >= numElements { + return fmt.Errorf("index out of bounds: %d", lowIdx) + } + if highIdx < 0 { + highIdx = numElements + } else if highIdx < 0 || highIdx > numElements { + return fmt.Errorf("index out of bounds: %d", highIdx) + } + + if lowIdx > highIdx { + return fmt.Errorf("invalid slice index: %d > %d", lowIdx, highIdx) + } + + if v.sp >= StackSize { + return ErrStackOverflow + } + + var val objects.Object = &objects.Bytes{Value: left.Value[lowIdx:highIdx]} + + v.stack[v.sp] = &val + v.sp++ + default: return fmt.Errorf("cannot slice %s", left.TypeName()) } @@ -1005,7 +1049,7 @@ func (v *VM) Run() error { iterator = objects.NewArrayIterator(dst) case *objects.Map: iterator = objects.NewMapIterator(dst) - case *objects.ModuleMap: + case *objects.ImmutableMap: iterator = objects.NewModuleMapIterator(dst) case *objects.String: iterator = objects.NewStringIterator(dst) @@ -1213,7 +1257,7 @@ func (v *VM) callFunction(fn *objects.CompiledFunction, freeVars []*objects.Obje return nil } -// TODO: should reuse *objects.ModuleMap for the same imports? +// TODO: should reuse *objects.ImmutableMap for the same imports? func (v *VM) importModule(compiledModule *objects.CompiledModule) error { // import module is basically to create a new instance of VM // and run the module code and retrieve all global variables after execution. @@ -1230,7 +1274,7 @@ func (v *VM) importModule(compiledModule *objects.CompiledModule) error { mmValue[name] = *moduleVM.globals[index] } - var mm objects.Object = &objects.ModuleMap{Value: mmValue} + var mm objects.Object = &objects.ImmutableMap{Value: mmValue} if v.sp >= StackSize { return ErrStackOverflow diff --git a/runtime/vm_bytes_test.go b/runtime/vm_bytes_test.go new file mode 100644 index 0000000..9ea13c7 --- /dev/null +++ b/runtime/vm_bytes_test.go @@ -0,0 +1,15 @@ +package runtime_test + +import ( + "testing" +) + +func TestBytes(t *testing.T) { + expect(t, `out = bytes("Hello World!")`, []byte("Hello World!")) + expect(t, `out = bytes("Hello") + bytes(" ") + bytes("World!")`, []byte("Hello World!")) + + // bytes[] -> int + expect(t, `out = bytes("abcde")[0]`, 97) + expect(t, `out = bytes("abcde")[1]`, 98) + expect(t, `out = bytes("abcde")[4]`, 101) +} diff --git a/runtime/vm_module_test.go b/runtime/vm_module_test.go index 5c97afc..b63b8b2 100644 --- a/runtime/vm_module_test.go +++ b/runtime/vm_module_test.go @@ -3,12 +3,63 @@ package runtime_test import "testing" func TestModule(t *testing.T) { - // stdmods + // stdlib expect(t, `math := import("math"); out = math.abs(1)`, 1.0) expect(t, `math := import("math"); out = math.abs(-1)`, 1.0) expect(t, `math := import("math"); out = math.abs(1.0)`, 1.0) expect(t, `math := import("math"); out = math.abs(-1.0)`, 1.0) + // os.File + expect(t, ` +os := import("os") + +write_file := func(filename, data) { + file := os.create(filename) + if !file { return file } + + if res := file.write(bytes(data)); is_error(res) { + return res + } + + return file.close() +} + +read_file := func(filename) { + file := os.open(filename) + if !file { return file } + + data := bytes(100) + cnt := file.read(data) + if is_error(cnt) { + return cnt + } + + file.close() + return data[:cnt] +} + +if write_file("./temp", "foobar") { + out = string(read_file("./temp")) +} + +os.remove("./temp") +`, "foobar") + + // exec.command + expect(t, ` +exec := import("exec") + +echo := func(args) { + cmd := exec.command("echo", args) + if is_error(cmd) { return cmd.value } + output := cmd.output() + if is_error(output) { return output.value } + return output +} + +out = echo(["foo", "bar"]) +`, []byte("foo bar\n")) + // user modules expectWithUserModules(t, `out = import("mod1").bar()`, 5.0, map[string]string{ "mod1": `bar := func() { return 5.0 }`, diff --git a/runtime/vm_test.go b/runtime/vm_test.go index c0b36f2..f3f0722 100644 --- a/runtime/vm_test.go +++ b/runtime/vm_test.go @@ -112,6 +112,8 @@ func toObject(v interface{}) objects.Object { return &objects.Char{Value: rune(v)} case float64: return &objects.Float{Value: v} + case []byte: + return &objects.Bytes{Value: v} case MAP: objs := make(map[string]objects.Object) for k, v := range v { @@ -174,9 +176,12 @@ func traceCompileRun(file *ast.File, symbols map[string]objects.Object, userModu sym := symTable.Define(name) globals[sym.Index] = &value } + for idx, fn := range objects.Builtins { + symTable.DefineBuiltin(idx, fn.Name) + } tr := &tracer{} - c := compiler.NewCompiler(symTable, tr) + c := compiler.NewCompiler(symTable, nil, tr) c.SetModuleLoader(func(moduleName string) ([]byte, error) { if src, ok := userModules[moduleName]; ok { return []byte(src), nil @@ -278,6 +283,8 @@ func objectZeroCopy(o objects.Object) objects.Object { return &objects.Undefined{} case *objects.Error: return &objects.Error{} + case *objects.Bytes: + return &objects.Bytes{} case nil: panic("nil") default: diff --git a/script/conversion.go b/script/conversion.go index 9785fba..d1617ff 100644 --- a/script/conversion.go +++ b/script/conversion.go @@ -2,44 +2,10 @@ package script import ( "fmt" - "strconv" - "strings" "github.com/d5/tengo/objects" ) -func objectToString(o objects.Object) string { - switch val := o.(type) { - case *objects.Array: - var s []string - for _, e := range val.Value { - s = append(s, objectToString(e)) - } - return "[" + strings.Join(s, ", ") + "]" - case *objects.Map: - var s []string - for k, v := range val.Value { - s = append(s, k+": "+objectToString(v)) - } - return "{" + strings.Join(s, ", ") + "}" - case *objects.Int: - return strconv.FormatInt(val.Value, 10) - case *objects.Float: - return strconv.FormatFloat(val.Value, 'f', -1, 64) - case *objects.Bool: - if val.Value { - return "true" - } - return "false" - case *objects.Char: - return string(val.Value) - case *objects.String: - return val.Value - } - - return o.String() -} - func objectToInterface(o objects.Object) interface{} { switch val := o.(type) { case *objects.Array: @@ -56,6 +22,8 @@ func objectToInterface(o objects.Object) interface{} { return val.Value case *objects.String: return val.Value + case *objects.Bytes: + return val.Value case *objects.Undefined: return nil } @@ -81,6 +49,10 @@ func interfaceToObject(v interface{}) (objects.Object, error) { return &objects.Char{Value: rune(v)}, nil case float64: return &objects.Float{Value: v}, nil + case []byte: + return &objects.Bytes{Value: v}, nil + case error: + return &objects.Error{Value: &objects.String{Value: v.Error()}}, nil case map[string]objects.Object: return &objects.Map{Value: v}, nil case map[string]interface{}: diff --git a/script/script.go b/script/script.go index 32933eb..418b3f3 100644 --- a/script/script.go +++ b/script/script.go @@ -63,7 +63,7 @@ func (s *Script) Compile() (*Compiled, error) { return nil, fmt.Errorf("parse error: %s", err.Error()) } - c := compiler.NewCompiler(symbolTable, nil) + c := compiler.NewCompiler(symbolTable, nil, nil) if err := c.Compile(file); err != nil { return nil, err } @@ -94,6 +94,10 @@ func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, globals []*ob } symbolTable = compiler.NewSymbolTable() + for idx, fn := range objects.Builtins { + symbolTable.DefineBuiltin(idx, fn.Name) + } + globals = make([]*objects.Object, len(names), len(names)) for idx, name := range names { diff --git a/script/variable.go b/script/variable.go index f0decb4..bec167a 100644 --- a/script/variable.go +++ b/script/variable.go @@ -1,7 +1,7 @@ package script import ( - "strconv" + "errors" "github.com/d5/tengo/objects" ) @@ -43,73 +43,41 @@ func (v *Variable) ValueType() string { // Int returns int value of the variable value. // It returns 0 if the value is not convertible to int. func (v *Variable) Int() int { - return int(v.Int64()) + c, _ := objects.ToInt(*v.value) + + return c } // Int64 returns int64 value of the variable value. // It returns 0 if the value is not convertible to int64. func (v *Variable) Int64() int64 { - switch val := (*v.value).(type) { - case *objects.Int: - return val.Value - case *objects.Float: - return int64(val.Value) - case *objects.Bool: - if val.Value { - return 1 - } - return 0 - case *objects.Char: - return int64(val.Value) - case *objects.String: - n, _ := strconv.ParseInt(val.Value, 10, 64) - return n - } + c, _ := objects.ToInt64(*v.value) - return 0 + return c } // Float returns float64 value of the variable value. // It returns 0.0 if the value is not convertible to float64. func (v *Variable) Float() float64 { - switch val := (*v.value).(type) { - case *objects.Int: - return float64(val.Value) - case *objects.Float: - return val.Value - case *objects.Bool: - if val.Value { - return 1 - } - return 0 - case *objects.String: - f, _ := strconv.ParseFloat(val.Value, 64) - return f - } + c, _ := objects.ToFloat64(*v.value) - return 0 + return c } // Char returns rune value of the variable value. // It returns 0 if the value is not convertible to rune. func (v *Variable) Char() rune { - switch val := (*v.value).(type) { - case *objects.Char: - return val.Value - } + c, _ := objects.ToRune(*v.value) - return 0 + return c } // Bool returns bool value of the variable value. // It returns 0 if the value is not convertible to bool. func (v *Variable) Bool() bool { - switch val := (*v.value).(type) { - case *objects.Bool: - return val.Value - } + c, _ := objects.ToBool(*v.value) - return false + return c } // Array returns []interface value of the variable value. @@ -145,7 +113,28 @@ func (v *Variable) Map() map[string]interface{} { // String returns string value of the variable value. // It returns 0 if the value is not convertible to string. func (v *Variable) String() string { - return objectToString(*v.value) + c, _ := objects.ToString(*v.value) + + return c +} + +// Bytes returns a byte slice of the variable value. +// It returns nil if the value is not convertible to byte slice. +func (v *Variable) Bytes() []byte { + c, _ := objects.ToByteSlice(*v.value) + + return c +} + +// Error returns an error if the underlying value is error object. +// If not, this returns nil. +func (v *Variable) Error() error { + err, ok := (*v.value).(*objects.Error) + if ok { + return errors.New(err.String()) + } + + return nil } // Object returns an underlying Object of the variable value. diff --git a/script/variable_test.go b/script/variable_test.go index 3a3a3d5..45894f8 100644 --- a/script/variable_test.go +++ b/script/variable_test.go @@ -31,6 +31,8 @@ func TestVariable(t *testing.T) { IntValue: 1, Int64Value: 1, FloatValue: 1.0, + CharValue: rune(1), + BoolValue: true, StringValue: "1", Object: &objects.Int{Value: 1}, }, @@ -40,6 +42,7 @@ func TestVariable(t *testing.T) { ValueType: "string", FloatValue: 52.11, StringValue: "52.11", + BoolValue: true, Object: &objects.String{Value: "52.11"}, }, { @@ -48,7 +51,7 @@ func TestVariable(t *testing.T) { ValueType: "bool", IntValue: 1, Int64Value: 1, - FloatValue: 1, + FloatValue: 0, BoolValue: true, StringValue: "true", Object: &objects.Bool{Value: true}, @@ -57,7 +60,7 @@ func TestVariable(t *testing.T) { Name: "d", Value: nil, ValueType: "undefined", - StringValue: "", + StringValue: "", Object: objects.UndefinedValue, IsUndefined: true, }, @@ -66,15 +69,15 @@ func TestVariable(t *testing.T) { for _, tc := range vars { v, err := script.NewVariable(tc.Name, tc.Value) assert.NoError(t, err) - assert.Equal(t, tc.Value, v.Value()) - assert.Equal(t, tc.ValueType, v.ValueType()) - assert.Equal(t, tc.IntValue, v.Int()) - assert.Equal(t, tc.Int64Value, v.Int64()) - assert.Equal(t, tc.FloatValue, v.Float()) - assert.Equal(t, tc.CharValue, v.Char()) - assert.Equal(t, tc.BoolValue, v.Bool()) - assert.Equal(t, tc.StringValue, v.String()) - assert.Equal(t, tc.Object, v.Object()) - assert.Equal(t, tc.IsUndefined, v.IsUndefined()) + assert.Equal(t, tc.Value, v.Value(), "Name: %s", tc.Name) + assert.Equal(t, tc.ValueType, v.ValueType(), "Name: %s", tc.Name) + assert.Equal(t, tc.IntValue, v.Int(), "Name: %s", tc.Name) + assert.Equal(t, tc.Int64Value, v.Int64(), "Name: %s", tc.Name) + assert.Equal(t, tc.FloatValue, v.Float(), "Name: %s", tc.Name) + assert.Equal(t, tc.CharValue, v.Char(), "Name: %s", tc.Name) + assert.Equal(t, tc.BoolValue, v.Bool(), "Name: %s", tc.Name) + assert.Equal(t, tc.StringValue, v.String(), "Name: %s", tc.Name) + assert.Equal(t, tc.Object, v.Object(), "Name: %s", tc.Name) + assert.Equal(t, tc.IsUndefined, v.IsUndefined(), "Name: %s", tc.Name) } }