diff --git a/.goreleaser.yml b/.goreleaser.yml index 283c13c..f7e2bd0 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -6,6 +6,14 @@ builds: - darwin - linux - windows + - env: + - CGO_ENABLED=0 + main: ./cmd/tengomin/main.go + binary: tengomin + goos: + - darwin + - linux + - windows archive: files: - none* diff --git a/cmd/tengo/main.go b/cmd/tengo/main.go index 3ff57c2..b214918 100644 --- a/cmd/tengo/main.go +++ b/cmd/tengo/main.go @@ -17,6 +17,7 @@ import ( "github.com/d5/tengo/compiler/source" "github.com/d5/tengo/objects" "github.com/d5/tengo/runtime" + "github.com/d5/tengo/stdlib" ) const ( @@ -25,10 +26,12 @@ const ( ) var ( - compileOutput string - showHelp bool - showVersion bool - version = "dev" + compileOutput string + showHelp bool + showVersion bool + version = "dev" + bm map[string]bool + builtinModules map[string]*objects.Object ) func init() { @@ -47,6 +50,13 @@ func main() { return } + bm = make(map[string]bool, len(stdlib.Modules)) + builtinModules = make(map[string]*objects.Object, len(stdlib.Modules)) + for k, mod := range stdlib.Modules { + bm[k] = true + builtinModules[k] = objectPtr(mod) + } + inputFile := flag.Arg(0) if inputFile == "" { // REPL @@ -148,7 +158,7 @@ func compileAndRun(data []byte, inputFile string) (err error) { return } - machine := runtime.NewVM(bytecode, nil, nil, nil) + machine := runtime.NewVM(bytecode, nil, nil, builtinModules) err = machine.Run() if err != nil { @@ -165,7 +175,7 @@ func runCompiled(data []byte) (err error) { return } - machine := runtime.NewVM(bytecode, nil, nil, nil) + machine := runtime.NewVM(bytecode, nil, nil, builtinModules) err = machine.Run() if err != nil { @@ -208,7 +218,7 @@ func runREPL(in io.Reader, out io.Writer) { file = addPrints(file) - c := compiler.NewCompiler(srcFile, symbolTable, constants, nil, nil) + c := compiler.NewCompiler(srcFile, symbolTable, constants, bm, nil) if err := c.Compile(file); err != nil { _, _ = fmt.Fprintln(out, err.Error()) continue @@ -216,7 +226,7 @@ func runREPL(in io.Reader, out io.Writer) { bytecode := c.Bytecode() - machine := runtime.NewVM(bytecode, globals, nil, nil) + machine := runtime.NewVM(bytecode, globals, nil, builtinModules) if err := machine.Run(); err != nil { _, _ = fmt.Fprintln(out, err.Error()) continue @@ -236,7 +246,7 @@ func compileSrc(src []byte, filename string) (*compiler.Bytecode, error) { return nil, err } - c := compiler.NewCompiler(srcFile, nil, nil, nil, nil) + c := compiler.NewCompiler(srcFile, nil, nil, bm, nil) if err := c.Compile(file); err != nil { return nil, err } @@ -291,3 +301,7 @@ func basename(s string) string { return s } + +func objectPtr(o objects.Object) *objects.Object { + return &o +} diff --git a/cmd/tengomin/main.go b/cmd/tengomin/main.go new file mode 100644 index 0000000..2cec221 --- /dev/null +++ b/cmd/tengomin/main.go @@ -0,0 +1,293 @@ +package main + +import ( + "bufio" + "bytes" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/d5/tengo/compiler" + "github.com/d5/tengo/compiler/ast" + "github.com/d5/tengo/compiler/parser" + "github.com/d5/tengo/compiler/source" + "github.com/d5/tengo/objects" + "github.com/d5/tengo/runtime" +) + +const ( + sourceFileExt = ".tengo" + replPrompt = ">> " +) + +var ( + compileOutput string + showHelp bool + showVersion bool + version = "dev" +) + +func init() { + flag.BoolVar(&showHelp, "help", false, "Show help") + flag.StringVar(&compileOutput, "o", "", "Compile output file") + flag.BoolVar(&showVersion, "version", false, "Show version") + flag.Parse() +} + +func main() { + if showHelp { + doHelp() + os.Exit(2) + } else if showVersion { + fmt.Println(version) + return + } + + inputFile := flag.Arg(0) + if inputFile == "" { + // REPL + runREPL(os.Stdin, os.Stdout) + return + } + + inputData, err := ioutil.ReadFile(inputFile) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "Error reading input file: %s", err.Error()) + os.Exit(1) + } + + if compileOutput != "" { + if err := compileOnly(inputData, inputFile, compileOutput); err != nil { + _, _ = fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + } else if filepath.Ext(inputFile) == sourceFileExt { + if err := compileAndRun(inputData, inputFile); err != nil { + _, _ = fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + } else { + if err := runCompiled(inputData); err != nil { + _, _ = fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + } +} + +func doHelp() { + fmt.Println("Usage:") + fmt.Println() + fmt.Println(" tengo [flags] {input-file}") + fmt.Println() + fmt.Println("Flags:") + fmt.Println() + fmt.Println(" -o compile output file") + fmt.Println(" -version show version") + fmt.Println() + fmt.Println("Examples:") + fmt.Println() + fmt.Println(" tengo") + fmt.Println() + fmt.Println(" Start Tengo REPL") + fmt.Println() + fmt.Println(" tengo myapp.tengo") + fmt.Println() + fmt.Println(" Compile and run source file (myapp.tengo)") + fmt.Println(" Source file must have .tengo extension") + fmt.Println() + fmt.Println(" tengo -o myapp myapp.tengo") + fmt.Println() + fmt.Println(" Compile source file (myapp.tengo) into bytecode file (myapp)") + fmt.Println() + fmt.Println(" tengo myapp") + fmt.Println() + fmt.Println(" Run bytecode file (myapp)") + fmt.Println() + fmt.Println() +} + +func compileOnly(data []byte, inputFile, outputFile string) (err error) { + bytecode, err := compileSrc(data, filepath.Base(inputFile)) + if err != nil { + return + } + + if outputFile == "" { + outputFile = basename(inputFile) + ".out" + } + + out, err := os.OpenFile(outputFile, os.O_CREATE|os.O_WRONLY, os.ModePerm) + if err != nil { + return + } + defer func() { + if err != nil { + _ = out.Close() + } else { + err = out.Close() + } + }() + + err = bytecode.Encode(out) + if err != nil { + return + } + + fmt.Println(outputFile) + + return +} + +func compileAndRun(data []byte, inputFile string) (err error) { + bytecode, err := compileSrc(data, filepath.Base(inputFile)) + if err != nil { + return + } + + machine := runtime.NewVM(bytecode, nil, nil, nil) + + err = machine.Run() + if err != nil { + return + } + + return +} + +func runCompiled(data []byte) (err error) { + bytecode := &compiler.Bytecode{} + err = bytecode.Decode(bytes.NewReader(data)) + if err != nil { + return + } + + machine := runtime.NewVM(bytecode, nil, nil, nil) + + err = machine.Run() + if err != nil { + return + } + + return +} + +func runREPL(in io.Reader, out io.Writer) { + stdin := bufio.NewScanner(in) + + fileSet := source.NewFileSet() + globals := make([]*objects.Object, runtime.GlobalsSize) + + symbolTable := compiler.NewSymbolTable() + for idx, fn := range objects.Builtins { + symbolTable.DefineBuiltin(idx, fn.Name) + } + + var constants []objects.Object + + for { + _, _ = fmt.Fprint(out, replPrompt) + + scanned := stdin.Scan() + if !scanned { + return + } + + line := stdin.Text() + + srcFile := fileSet.AddFile("repl", -1, len(line)) + p := parser.NewParser(srcFile, []byte(line), nil) + file, err := p.ParseFile() + if err != nil { + _, _ = fmt.Fprintln(out, err.Error()) + continue + } + + file = addPrints(file) + + c := compiler.NewCompiler(srcFile, symbolTable, constants, nil, nil) + if err := c.Compile(file); err != nil { + _, _ = fmt.Fprintln(out, err.Error()) + continue + } + + bytecode := c.Bytecode() + + machine := runtime.NewVM(bytecode, globals, nil, nil) + if err := machine.Run(); err != nil { + _, _ = fmt.Fprintln(out, err.Error()) + continue + } + + constants = bytecode.Constants + } +} + +func compileSrc(src []byte, filename string) (*compiler.Bytecode, error) { + fileSet := source.NewFileSet() + srcFile := fileSet.AddFile(filename, -1, len(src)) + + p := parser.NewParser(srcFile, src, nil) + file, err := p.ParseFile() + if err != nil { + return nil, err + } + + c := compiler.NewCompiler(srcFile, nil, nil, nil, nil) + if err := c.Compile(file); err != nil { + return nil, err + } + + return c.Bytecode(), nil +} + +func addPrints(file *ast.File) *ast.File { + var stmts []ast.Stmt + for _, s := range file.Stmts { + switch s := s.(type) { + case *ast.ExprStmt: + stmts = append(stmts, &ast.ExprStmt{ + Expr: &ast.CallExpr{ + Func: &ast.Ident{ + Name: "print", + }, + Args: []ast.Expr{s.Expr}, + }, + }) + + case *ast.AssignStmt: + stmts = append(stmts, s) + + stmts = append(stmts, &ast.ExprStmt{ + Expr: &ast.CallExpr{ + Func: &ast.Ident{ + Name: "print", + }, + Args: s.LHS, + }, + }) + + default: + stmts = append(stmts, s) + } + } + + return &ast.File{ + InputFile: file.InputFile, + Stmts: stmts, + } +} + +func basename(s string) string { + s = filepath.Base(s) + + n := strings.LastIndexByte(s, '.') + if n > 0 { + return s[:n] + } + + return s +} diff --git a/compiler/compiler.go b/compiler/compiler.go index 021542c..d8bc05f 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -10,7 +10,6 @@ import ( "github.com/d5/tengo/compiler/source" "github.com/d5/tengo/compiler/token" "github.com/d5/tengo/objects" - "github.com/d5/tengo/stdlib" ) // Compiler compiles the AST into a bytecode. @@ -55,9 +54,6 @@ func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []object // builtin modules if builtinModules == nil { builtinModules = make(map[string]bool) - for name := range stdlib.Modules { - builtinModules[name] = true - } } return &Compiler{ diff --git a/docs/interoperability.md b/docs/interoperability.md index 6a6dc84..ca1c04f 100644 --- a/docs/interoperability.md +++ b/docs/interoperability.md @@ -136,7 +136,7 @@ _, err := s.Run() // prints [1, 2, 3] #### Script.SetBuiltinModules(modules map[string]*objects.ImmutableMap) -SetBuiltinModules resets all [standard library](https://github.com/d5/tengo/blob/master/docs/stdlib.md) modules with modules provided in the input parameter. Compile will report a compile-time error if the code tries to import a module that hasn't been included. All standard library modules are included by default unless `SetBuiltinModules` is called. +SetBuiltinModules adds builtin modules provided in the input parameter. This can be used to add [standard library](https://github.com/d5/tengo/blob/master/docs/stdlib.md) modules into the compiler and VM. Compiler will report a compile-time error if the code tries to import a module that hasn't been included. No standard library modules are included by default unless `SetBuiltinModules` is called. ```golang s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`)) @@ -145,7 +145,15 @@ s.SetBuiltinModules(nil) _, err := s.Run() // compile error -s.SetBuiltinModules(map[string]*objects.ImmutableMap{"math": objectPtr(*stdlib.Modules["math"])}) +s.SetBuiltinModules(stdlib.Modules) + +_, err := s.Run() // a = 19.84 + +s.SetBuiltinModules(nil) + +_, err := s.Run() // compile error + +s.SetBuiltinModules(map[string]*objects.ImmutableMap{"math": stdlib.Modules["math"]}) _, err := s.Run() // a = 19.84 ``` diff --git a/runtime/vm.go b/runtime/vm.go index 6e586a9..d82fa07 100644 --- a/runtime/vm.go +++ b/runtime/vm.go @@ -8,7 +8,6 @@ import ( "github.com/d5/tengo/compiler/source" "github.com/d5/tengo/compiler/token" "github.com/d5/tengo/objects" - "github.com/d5/tengo/stdlib" ) const ( @@ -53,7 +52,7 @@ func NewVM(bytecode *compiler.Bytecode, globals []*objects.Object, builtinFuncs } if builtinModules == nil { - builtinModules = stdlib.Modules + builtinModules = make(map[string]*objects.Object) } if builtinFuncs == nil { diff --git a/runtime/vm_assignment_test.go b/runtime/vm_assignment_test.go index a4a943c..c07bae3 100644 --- a/runtime/vm_assignment_test.go +++ b/runtime/vm_assignment_test.go @@ -193,8 +193,8 @@ out = func() { `, 136) // assigning different type value - expect(t, `a := 1; a = "foo"; out = a`, "foo") // global - expect(t, `func() { a := 1; a = "foo"; out = a }()`, "foo") // local + expect(t, `a := 1; a = "foo"; out = a`, "foo") // global + expect(t, `func() { a := 1; a = "foo"; out = a }()`, "foo") // local expect(t, ` out = func() { a := 5 diff --git a/runtime/vm_module_test.go b/runtime/vm_module_test.go index 65eb935..47adf18 100644 --- a/runtime/vm_module_test.go +++ b/runtime/vm_module_test.go @@ -1,63 +1,28 @@ package runtime_test import ( + "math" + "math/rand" "testing" "github.com/d5/tengo/objects" ) -func TestStdLib(t *testing.T) { - // 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) +func TestBuiltin(t *testing.T) { - // 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 + mathModule := map[string]*objects.Object{ + "math": objectPtr(&objects.ImmutableMap{Value: map[string]objects.Object{ + "abs": &objects.UserFunction{Name: "abs", Value: func(args ...objects.Object) (ret objects.Object, err error) { + v, _ := objects.ToFloat64(args[0]) + return &objects.Float{Value: math.Abs(v)}, nil + }}, + }}), } - - 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, ` -os := import("os") -cmd := os.exec("echo", "foo", "bar") -if !is_error(cmd) { - out = cmd.output() -} -`, []byte("foo bar\n")) - + // builtin + expectWithBuiltinModules(t, `math := import("math"); out = math.abs(1)`, 1.0, mathModule) + expectWithBuiltinModules(t, `math := import("math"); out = math.abs(-1)`, 1.0, mathModule) + expectWithBuiltinModules(t, `math := import("math"); out = math.abs(1.0)`, 1.0, mathModule) + expectWithBuiltinModules(t, `math := import("math"); out = math.abs(-1.0)`, 1.0, mathModule) } func TestUserModules(t *testing.T) { @@ -222,8 +187,17 @@ export func(a) { } func TestModuleBlockScopes(t *testing.T) { + randModule := map[string]*objects.Object{ + "rand": objectPtr(&objects.ImmutableMap{Value: map[string]objects.Object{ + "intn": &objects.UserFunction{Name: "abs", Value: func(args ...objects.Object) (ret objects.Object, err error) { + v, _ := objects.ToInt64(args[0]) + return &objects.Int{Value: rand.Int63n(v)}, nil + }}, + }}), + } + // block scopes in module - expectWithUserModules(t, `out = import("mod1")()`, 1, map[string]string{ + expectWithUserAndBuiltinModules(t, `out = import("mod1")()`, 1, map[string]string{ "mod1": ` rand := import("rand") foo := func() { return 1 } @@ -232,9 +206,9 @@ func TestModuleBlockScopes(t *testing.T) { return foo() } `, - }) + }, randModule) - expectWithUserModules(t, `out = import("mod1")()`, 10, map[string]string{ + expectWithUserAndBuiltinModules(t, `out = import("mod1")()`, 10, map[string]string{ "mod1": ` rand := import("rand") foo := func() { return 1 } @@ -244,9 +218,9 @@ export func() { return 10 } `, - }) + }, randModule) - expectWithUserModules(t, `out = import("mod1")()`, 10, map[string]string{ + expectWithUserAndBuiltinModules(t, `out = import("mod1")()`, 10, map[string]string{ "mod1": ` rand := import("rand") foo := func() { return 1 } @@ -256,5 +230,5 @@ export func() { return 10 } `, - }) + }, randModule) } diff --git a/runtime/vm_test.go b/runtime/vm_test.go index ee097f6..5a8db0c 100644 --- a/runtime/vm_test.go +++ b/runtime/vm_test.go @@ -31,30 +31,38 @@ func expect(t *testing.T, input string, expected interface{}) { } func expectNoMod(t *testing.T, input string, expected interface{}) { - runVM(t, input, expected, nil, nil, true) + runVM(t, input, expected, nil, nil, nil, true) } func expectWithSymbols(t *testing.T, input string, expected interface{}, symbols map[string]objects.Object) { - runVM(t, input, expected, symbols, nil, true) + runVM(t, input, expected, symbols, nil, nil, true) } func expectWithUserModules(t *testing.T, input string, expected interface{}, userModules map[string]string) { - runVM(t, input, expected, nil, userModules, false) + runVM(t, input, expected, nil, userModules, nil, false) +} + +func expectWithBuiltinModules(t *testing.T, input string, expected interface{}, builtinModules map[string]*objects.Object) { + runVM(t, input, expected, nil, nil, builtinModules, false) +} + +func expectWithUserAndBuiltinModules(t *testing.T, input string, expected interface{}, userModules map[string]string, builtinModules map[string]*objects.Object) { + runVM(t, input, expected, nil, userModules, builtinModules, false) } func expectError(t *testing.T, input, expected string) { - runVMError(t, input, nil, nil, expected) + runVMError(t, input, nil, nil, nil, expected) } func expectErrorWithUserModules(t *testing.T, input string, userModules map[string]string, expected string) { - runVMError(t, input, nil, userModules, expected) + runVMError(t, input, nil, userModules, nil, expected) } func expectErrorWithSymbols(t *testing.T, input string, symbols map[string]objects.Object, expected string) { - runVMError(t, input, symbols, nil, expected) + runVMError(t, input, symbols, nil, nil, expected) } -func runVM(t *testing.T, input string, expected interface{}, symbols map[string]objects.Object, userModules map[string]string, skipModuleTest bool) { +func runVM(t *testing.T, input string, expected interface{}, symbols map[string]objects.Object, userModules map[string]string, builtinModules map[string]*objects.Object, skipModuleTest bool) { expectedObj := toObject(expected) if symbols == nil { @@ -71,7 +79,7 @@ func runVM(t *testing.T, input string, expected interface{}, symbols map[string] } // compiler/VM - res, trace, err := traceCompileRun(file, symbols, userModules) + res, trace, err := traceCompileRun(file, symbols, userModules, builtinModules) if !assert.NoError(t, err) || !assert.Equal(t, expectedObj, res[testOut]) { t.Log("\n" + strings.Join(trace, "\n")) @@ -98,7 +106,7 @@ func runVM(t *testing.T, input string, expected interface{}, symbols map[string] } userModules["__code__"] = fmt.Sprintf("out := undefined; %s; export out", input) - res, trace, err := traceCompileRun(file, symbols, userModules) + res, trace, err := traceCompileRun(file, symbols, userModules, builtinModules) if !assert.NoError(t, err) || !assert.Equal(t, expectedObj, res[testOut]) { t.Log("\n" + strings.Join(trace, "\n")) @@ -106,7 +114,7 @@ func runVM(t *testing.T, input string, expected interface{}, symbols map[string] } } -func runVMError(t *testing.T, input string, symbols map[string]objects.Object, userModules map[string]string, expected string) { +func runVMError(t *testing.T, input string, symbols map[string]objects.Object, userModules map[string]string, builtinModules map[string]*objects.Object, expected string) { expected = strings.TrimSpace(expected) if expected == "" { panic("expected must not be empty") @@ -119,7 +127,7 @@ func runVMError(t *testing.T, input string, symbols map[string]objects.Object, u } // compiler/VM - _, trace, err := traceCompileRun(program, symbols, userModules) + _, trace, err := traceCompileRun(program, symbols, userModules, builtinModules) if !assert.Error(t, err) || !assert.True(t, strings.Contains(err.Error(), expected), "expected error string: %s, got: %s", expected, err.Error()) { t.Log("\n" + strings.Join(trace, "\n")) @@ -135,7 +143,7 @@ func (o *tracer) Write(p []byte) (n int, err error) { return len(p), nil } -func traceCompileRun(file *ast.File, symbols map[string]objects.Object, userModules map[string]string) (res map[string]objects.Object, trace []string, err error) { +func traceCompileRun(file *ast.File, symbols map[string]objects.Object, userModules map[string]string, builtinModules map[string]*objects.Object) (res map[string]objects.Object, trace []string, err error) { var v *runtime.VM defer func() { @@ -171,8 +179,13 @@ func traceCompileRun(file *ast.File, symbols map[string]objects.Object, userModu symTable.DefineBuiltin(idx, fn.Name) } + bm := make(map[string]bool) + for k := range builtinModules { + bm[k] = true + } + tr := &tracer{} - c := compiler.NewCompiler(file.InputFile, symTable, nil, nil, tr) + c := compiler.NewCompiler(file.InputFile, symTable, nil, bm, tr) c.SetModuleLoader(func(moduleName string) ([]byte, error) { if src, ok := userModules[moduleName]; ok { return []byte(src), nil @@ -190,7 +203,7 @@ func traceCompileRun(file *ast.File, symbols map[string]objects.Object, userModu trace = append(trace, fmt.Sprintf("\n[Compiled Constants]\n\n%s", strings.Join(bytecode.FormatConstants(), "\n"))) trace = append(trace, fmt.Sprintf("\n[Compiled Instructions]\n\n%s\n", strings.Join(bytecode.FormatInstructions(), "\n"))) - v = runtime.NewVM(bytecode, globals, nil, nil) + v = runtime.NewVM(bytecode, globals, nil, builtinModules) err = v.Run() { @@ -338,3 +351,7 @@ func objectZeroCopy(o objects.Object) objects.Object { panic(fmt.Errorf("unknown object type: %s", o.TypeName())) } } + +func objectPtr(o objects.Object) *objects.Object { + return &o +} diff --git a/script/script.go b/script/script.go index 6529c11..e8db52a 100644 --- a/script/script.go +++ b/script/script.go @@ -9,7 +9,6 @@ import ( "github.com/d5/tengo/compiler/source" "github.com/d5/tengo/objects" "github.com/d5/tengo/runtime" - "github.com/d5/tengo/stdlib" ) // Script can simplify compilation and execution of embedded scripts. @@ -161,7 +160,7 @@ func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, builtinModule } if s.builtinModules == nil { - s.builtinModules = stdlib.Modules + s.builtinModules = make(map[string]*objects.Object) } for idx, fn := range s.builtinFuncs { diff --git a/script/script_module_test.go b/script/script_module_test.go index 71a026e..8e0338f 100644 --- a/script/script_module_test.go +++ b/script/script_module_test.go @@ -2,9 +2,11 @@ package script_test import ( "errors" + "strings" "testing" "github.com/d5/tengo/assert" + "github.com/d5/tengo/objects" "github.com/d5/tengo/script" ) @@ -38,8 +40,17 @@ func TestScript_SetUserModuleLoader(t *testing.T) { _, err = scr.Run() assert.Error(t, err) - // disabled stdlib scr = script.New([]byte(`out := import("mod")`)) + scr.SetBuiltinModules(map[string]*objects.ImmutableMap{ + "text": objectPtr(&objects.ImmutableMap{ + Value: map[string]objects.Object{ + "title": &objects.UserFunction{Name: "title", Value: func(args ...objects.Object) (ret objects.Object, err error) { + s, _ := objects.ToString(args[0]) + return &objects.String{Value: strings.Title(s)}, nil + }}, + }, + }), + }) scr.SetUserModuleLoader(func(name string) ([]byte, error) { if name == "mod" { return []byte(`text := import("text"); export text.title("foo")`), nil diff --git a/script/script_test.go b/script/script_test.go index c7505c2..3097f3f 100644 --- a/script/script_test.go +++ b/script/script_test.go @@ -2,12 +2,12 @@ package script_test import ( "errors" + "math" "testing" "github.com/d5/tengo/assert" "github.com/d5/tengo/objects" "github.com/d5/tengo/script" - "github.com/d5/tengo/stdlib" ) func TestScript_Add(t *testing.T) { @@ -92,18 +92,27 @@ func TestScript_SetBuiltinFunctions(t *testing.T) { func TestScript_SetBuiltinModules(t *testing.T) { s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`)) + s.SetBuiltinModules(map[string]*objects.ImmutableMap{ + "math": objectPtr(&objects.ImmutableMap{ + Value: map[string]objects.Object{ + "abs": &objects.UserFunction{Name: "abs", Value: func(args ...objects.Object) (ret objects.Object, err error) { + v, _ := objects.ToFloat64(args[0]) + return &objects.Float{Value: math.Abs(v)}, nil + }}, + }, + }), + }) c, err := s.Run() assert.NoError(t, err) assert.NotNil(t, c) compiledGet(t, c, "a", 19.84) - s.SetBuiltinModules(map[string]*objects.ImmutableMap{"math": objectPtr(*stdlib.Modules["math"])}) c, err = s.Run() assert.NoError(t, err) assert.NotNil(t, c) compiledGet(t, c, "a", 19.84) - s.SetBuiltinModules(map[string]*objects.ImmutableMap{"os": objectPtr(*stdlib.Modules["os"])}) + s.SetBuiltinModules(map[string]*objects.ImmutableMap{"os": objectPtr(&objects.ImmutableMap{Value: map[string]objects.Object{}})}) _, err = s.Run() assert.Error(t, err) diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index f5181a2..882c614 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -3,12 +3,12 @@ package stdlib import "github.com/d5/tengo/objects" // Modules contain the standard modules. -var Modules = map[string]*objects.Object{ - "math": objectPtr(&objects.ImmutableMap{Value: mathModule}), - "os": objectPtr(&objects.ImmutableMap{Value: osModule}), - "text": objectPtr(&objects.ImmutableMap{Value: textModule}), - "times": objectPtr(&objects.ImmutableMap{Value: timesModule}), - "rand": objectPtr(&objects.ImmutableMap{Value: randModule}), +var Modules = map[string]*objects.ImmutableMap{ + "math": &objects.ImmutableMap{Value: mathModule}, + "os": &objects.ImmutableMap{Value: osModule}, + "text": &objects.ImmutableMap{Value: textModule}, + "times": &objects.ImmutableMap{Value: timesModule}, + "rand": &objects.ImmutableMap{Value: randModule}, } // AllModuleNames returns a list of all default module names. @@ -26,7 +26,7 @@ func GetModules(names ...string) map[string]*objects.ImmutableMap { modules := make(map[string]*objects.ImmutableMap) for _, name := range names { if mod := Modules[name]; mod != nil { - modules[name] = (*mod).(*objects.ImmutableMap) + modules[name] = mod } } return modules diff --git a/stdlib/stdlib_test.go b/stdlib/stdlib_test.go index bd3ac4c..304adc9 100644 --- a/stdlib/stdlib_test.go +++ b/stdlib/stdlib_test.go @@ -7,6 +7,7 @@ import ( "github.com/d5/tengo/assert" "github.com/d5/tengo/objects" + "github.com/d5/tengo/script" "github.com/d5/tengo/stdlib" ) @@ -25,6 +26,56 @@ func TestAllModuleNames(t *testing.T) { } } +func TestModulesRun(t *testing.T) { + // os.File + expect(t, ` +os := import("os") +out := "" + +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, ` +out := "" +os := import("os") +cmd := os.exec("echo", "foo", "bar") +if !is_error(cmd) { + out = cmd.output() +} +`, []byte("foo bar\n")) + +} + func TestGetModules(t *testing.T) { mods := stdlib.GetModules() assert.Equal(t, 0, len(mods)) @@ -98,7 +149,7 @@ func module(t *testing.T, moduleName string) callres { return callres{t: t, e: fmt.Errorf("module not found: %s", moduleName)} } - return callres{t: t, o: (*mod).(*objects.ImmutableMap)} + return callres{t: t, o: mod} } func object(v interface{}) objects.Object { @@ -165,3 +216,17 @@ func object(v interface{}) objects.Object { panic(fmt.Errorf("unknown type: %T", v)) } + +func expect(t *testing.T, input string, expected interface{}) { + s := script.New([]byte(input)) + s.SetBuiltinModules(stdlib.Modules) + c, err := s.Run() + assert.NoError(t, err) + assert.NotNil(t, c) + v := c.Get("out") + if !assert.NotNil(t, v) { + return + } + + assert.Equal(t, expected, v.Value()) +}