From 61890b15cbd89f2335b7f69dc89f6005db9554e9 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 18 Mar 2019 08:15:26 -0700 Subject: [PATCH] module refactor (#148) * wip * move print and JSON functions to modules * builtin functions are not replacable now * builtin functions are added for default nil symbol table * importables: builtin modules and source modules * refactoring runtime tests * fix tests * update documentation * cleanup * clean up cli * fix REPL prints --- Makefile | 5 +- cli/cli.go | 88 +++--- cmd/bench/main.go | 2 +- cmd/tengo/main.go | 18 +- compiler/bytecode_optimize.go | 13 +- compiler/compiler.go | 95 +++--- compiler/compiler_error_report_test.go | 2 +- compiler/compiler_module.go | 81 ++---- compiler/compiler_test.go | 6 +- compiler/opcodes.go | 3 - compiler/parser/parser_map_test.go | 7 + docs/builtins.md | 60 ---- docs/interoperability.md | 60 +--- docs/stdlib-fmt.md | 12 + docs/stdlib-json.md | 10 + docs/stdlib.md | 4 +- docs/tutorial.md | 2 +- objects/builtin_json.go | 65 ----- objects/builtin_module.go | 16 + objects/builtins.go | 60 +--- objects/builtins_test.go | 65 ----- objects/importable.go | 7 + objects/source_module.go | 11 + runtime/vm.go | 99 ++----- runtime/vm_array_test.go | 48 +-- runtime/vm_assignment_test.go | 120 ++++---- runtime/vm_bitwise_test.go | 56 ++-- runtime/vm_boolean_test.go | 68 ++--- runtime/vm_builtin_test.go | 339 ++++++++++------------ runtime/vm_bytes_test.go | 12 +- runtime/vm_call_test.go | 8 +- runtime/vm_char_test.go | 32 +- runtime/vm_cond_test.go | 24 +- runtime/vm_equality_test.go | 8 +- runtime/vm_error_report_test.go | 35 +-- runtime/vm_error_test.go | 24 +- runtime/vm_float_test.go | 14 +- runtime/vm_for_in_test.go | 24 +- runtime/vm_for_test.go | 44 +-- runtime/vm_function_test.go | 90 +++--- runtime/vm_if_test.go | 48 +-- runtime/vm_immutable_test.go | 74 ++--- runtime/vm_inc_dec_test.go | 19 +- runtime/vm_indexable_test.go | 56 ++-- runtime/vm_integer_test.go | 42 +-- runtime/vm_iterable_test.go | 6 +- runtime/vm_logic_test.go | 64 ++-- runtime/vm_map_test.go | 22 +- runtime/vm_module_test.go | 238 +++++++-------- runtime/vm_not_operator_test.go | 14 +- runtime/vm_objects_limit_test.go | 10 +- runtime/vm_return_test.go | 12 +- runtime/vm_selector_test.go | 58 ++-- runtime/vm_source_modules_test.go | 13 + runtime/vm_string_test.go | 77 +++-- runtime/vm_tail_call_test.go | 18 +- runtime/vm_test.go | 129 ++++---- runtime/vm_undefined_test.go | 22 +- script/compiled.go | 26 +- script/script.go | 88 ++---- script/script_concurrency_test.go | 17 +- script/script_module_test.go | 40 +-- script/script_test.go | 70 ++--- stdlib/builtin_modules.go | 14 + stdlib/enum_test.go | 7 + objects/builtin_print.go => stdlib/fmt.go | 94 +++--- stdlib/fmt_test.go | 11 + stdlib/gensrcmods.go | 54 ++++ stdlib/json.go | 69 +++++ stdlib/json_test.go | 33 +++ stdlib/source_modules.go | 50 ++++ stdlib/srcmod_enum.tengo | 40 +++ stdlib/stdlib.go | 26 +- stdlib/stdlib_test.go | 60 ++-- 74 files changed, 1629 insertions(+), 1729 deletions(-) create mode 100644 docs/stdlib-fmt.md create mode 100644 docs/stdlib-json.md delete mode 100644 objects/builtin_json.go create mode 100644 objects/builtin_module.go delete mode 100644 objects/builtins_test.go create mode 100644 objects/importable.go create mode 100644 objects/source_module.go create mode 100644 runtime/vm_source_modules_test.go create mode 100644 stdlib/builtin_modules.go create mode 100644 stdlib/enum_test.go rename objects/builtin_print.go => stdlib/fmt.go (52%) create mode 100644 stdlib/fmt_test.go create mode 100644 stdlib/gensrcmods.go create mode 100644 stdlib/json.go create mode 100644 stdlib/json_test.go create mode 100644 stdlib/source_modules.go create mode 100644 stdlib/srcmod_enum.tengo diff --git a/Makefile b/Makefile index e4613390..99daafd1 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,13 @@ vet: go vet ./... +generate: + go generate ./... + lint: golint -set_exit_status ./... -test: vet lint +test: generate vet lint go test -race -cover ./... fmt: diff --git a/cli/cli.go b/cli/cli.go index e7a5b3a0..33c63fe0 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -23,33 +23,28 @@ const ( replPrompt = ">> " ) -//Options represent REPL options +// Options represent CLI options type Options struct { - //Compile output file + // Compile output file CompileOutput string - //Show help flag + // Show help flag ShowHelp bool - //Show version flag + // Show version flag ShowVersion bool - //Input file + // Input file InputFile string - //Version + // Version Version string - //Builtin modules - BuiltinModules map[string]objects.Object + // Import modules + Modules map[string]objects.Importable } -var ( - bm map[string]bool - builtinModules map[string]objects.Object -) - -//Run REPL +// Run CLI func Run(options *Options) { if options.ShowHelp { doHelp() @@ -59,15 +54,9 @@ func Run(options *Options) { return } - builtinModules = options.BuiltinModules - bm = make(map[string]bool, len(builtinModules)) - for k := range builtinModules { - bm[k] = true - } - if options.InputFile == "" { // REPL - runREPL(os.Stdin, os.Stdout) + runREPL(options.Modules, os.Stdin, os.Stdout) return } @@ -78,12 +67,12 @@ func Run(options *Options) { } if options.CompileOutput != "" { - if err := compileOnly(inputData, options.InputFile, options.CompileOutput); err != nil { + if err := compileOnly(options.Modules, inputData, options.InputFile, options.CompileOutput); err != nil { _, _ = fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } } else if filepath.Ext(options.InputFile) == sourceFileExt { - if err := compileAndRun(inputData, options.InputFile); err != nil { + if err := compileAndRun(options.Modules, inputData, options.InputFile); err != nil { _, _ = fmt.Fprintln(os.Stderr, err.Error()) os.Exit(1) } @@ -127,8 +116,8 @@ func doHelp() { fmt.Println() } -func compileOnly(data []byte, inputFile, outputFile string) (err error) { - bytecode, err := compileSrc(data, filepath.Base(inputFile)) +func compileOnly(modules map[string]objects.Importable, data []byte, inputFile, outputFile string) (err error) { + bytecode, err := compileSrc(modules, data, filepath.Base(inputFile)) if err != nil { return } @@ -159,13 +148,13 @@ func compileOnly(data []byte, inputFile, outputFile string) (err error) { return } -func compileAndRun(data []byte, inputFile string) (err error) { - bytecode, err := compileSrc(data, filepath.Base(inputFile)) +func compileAndRun(modules map[string]objects.Importable, data []byte, inputFile string) (err error) { + bytecode, err := compileSrc(modules, data, filepath.Base(inputFile)) if err != nil { return } - machine := runtime.NewVM(bytecode, nil, nil, builtinModules, -1) + machine := runtime.NewVM(bytecode, nil, -1) err = machine.Run() if err != nil { @@ -182,7 +171,7 @@ func runCompiled(data []byte) (err error) { return } - machine := runtime.NewVM(bytecode, nil, nil, builtinModules, -1) + machine := runtime.NewVM(bytecode, nil, -1) err = machine.Run() if err != nil { @@ -192,7 +181,7 @@ func runCompiled(data []byte) (err error) { return } -func runREPL(in io.Reader, out io.Writer) { +func runREPL(modules map[string]objects.Importable, in io.Reader, out io.Writer) { stdin := bufio.NewScanner(in) fileSet := source.NewFileSet() @@ -203,6 +192,28 @@ func runREPL(in io.Reader, out io.Writer) { symbolTable.DefineBuiltin(idx, fn.Name) } + // embed println function + symbol := symbolTable.Define("__repl_println__") + globals[symbol.Index] = &objects.UserFunction{ + Name: "println", + Value: func(args ...objects.Object) (ret objects.Object, err error) { + var printArgs []interface{} + for _, arg := range args { + if _, isUndefined := arg.(*objects.Undefined); isUndefined { + printArgs = append(printArgs, "") + } else { + s, _ := objects.ToString(arg) + printArgs = append(printArgs, s) + } + } + + printArgs = append(printArgs, "\n") + _, _ = fmt.Print(printArgs...) + + return + }, + } + var constants []objects.Object for { @@ -225,7 +236,7 @@ func runREPL(in io.Reader, out io.Writer) { file = addPrints(file) - c := compiler.NewCompiler(srcFile, symbolTable, constants, bm, nil) + c := compiler.NewCompiler(srcFile, symbolTable, constants, modules, nil) if err := c.Compile(file); err != nil { _, _ = fmt.Fprintln(out, err.Error()) continue @@ -233,7 +244,7 @@ func runREPL(in io.Reader, out io.Writer) { bytecode := c.Bytecode() - machine := runtime.NewVM(bytecode, globals, nil, builtinModules, -1) + machine := runtime.NewVM(bytecode, globals, -1) if err := machine.Run(); err != nil { _, _ = fmt.Fprintln(out, err.Error()) continue @@ -243,7 +254,7 @@ func runREPL(in io.Reader, out io.Writer) { } } -func compileSrc(src []byte, filename string) (*compiler.Bytecode, error) { +func compileSrc(modules map[string]objects.Importable, src []byte, filename string) (*compiler.Bytecode, error) { fileSet := source.NewFileSet() srcFile := fileSet.AddFile(filename, -1, len(src)) @@ -253,7 +264,9 @@ func compileSrc(src []byte, filename string) (*compiler.Bytecode, error) { return nil, err } - c := compiler.NewCompiler(srcFile, nil, nil, bm, nil) + c := compiler.NewCompiler(srcFile, nil, nil, modules, nil) + c.EnableFileImport(true) + if err := c.Compile(file); err != nil { return nil, err } @@ -266,14 +279,13 @@ func compileSrc(src []byte, filename string) (*compiler.Bytecode, error) { 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", - }, + Func: &ast.Ident{Name: "__repl_println__"}, Args: []ast.Expr{s.Expr}, }, }) @@ -284,7 +296,7 @@ func addPrints(file *ast.File) *ast.File { stmts = append(stmts, &ast.ExprStmt{ Expr: &ast.CallExpr{ Func: &ast.Ident{ - Name: "print", + Name: "__repl_println__", }, Args: s.LHS, }, diff --git a/cmd/bench/main.go b/cmd/bench/main.go index 1824ce6c..4c247bbc 100644 --- a/cmd/bench/main.go +++ b/cmd/bench/main.go @@ -210,7 +210,7 @@ func runVM(bytecode *compiler.Bytecode) (time.Duration, objects.Object, error) { start := time.Now() - v := runtime.NewVM(bytecode, globals, nil, nil, -1) + v := runtime.NewVM(bytecode, globals, -1) if err := v.Run(); err != nil { return time.Since(start), nil, err } diff --git a/cmd/tengo/main.go b/cmd/tengo/main.go index c071ae01..66b0c46b 100644 --- a/cmd/tengo/main.go +++ b/cmd/tengo/main.go @@ -4,7 +4,6 @@ import ( "flag" "github.com/d5/tengo/cli" - "github.com/d5/tengo/objects" "github.com/d5/tengo/stdlib" ) @@ -23,17 +22,12 @@ func init() { } func main() { - builtinModules := make(map[string]objects.Object, len(stdlib.Modules)) - for k, mod := range stdlib.Modules { - builtinModules[k] = mod - } - cli.Run(&cli.Options{ - ShowHelp: showHelp, - ShowVersion: showVersion, - Version: version, - CompileOutput: compileOutput, - BuiltinModules: builtinModules, - InputFile: flag.Arg(0), + ShowHelp: showHelp, + ShowVersion: showVersion, + Version: version, + CompileOutput: compileOutput, + Modules: stdlib.GetModules(stdlib.AllModuleNames()...), + InputFile: flag.Arg(0), }) } diff --git a/compiler/bytecode_optimize.go b/compiler/bytecode_optimize.go index e2446522..11c8775a 100644 --- a/compiler/bytecode_optimize.go +++ b/compiler/bytecode_optimize.go @@ -16,6 +16,7 @@ func (b *Bytecode) RemoveDuplicates() { strings := make(map[string]int) floats := make(map[float64]int) chars := make(map[rune]int) + immutableMaps := make(map[string]int) // for modules for curIdx, c := range b.Constants { switch c := c.(type) { @@ -23,7 +24,17 @@ func (b *Bytecode) RemoveDuplicates() { // add to deduped list indexMap[curIdx] = len(deduped) deduped = append(deduped, c) - continue + case *objects.ImmutableMap: + modName := c.Value["__module_name__"].(*objects.String).Value + newIdx, ok := immutableMaps[modName] + if modName != "" && ok { + indexMap[curIdx] = newIdx + } else { + newIdx = len(deduped) + immutableMaps[modName] = newIdx + indexMap[curIdx] = newIdx + deduped = append(deduped, c) + } case *objects.Int: if newIdx, ok := ints[c.Value]; ok { indexMap[curIdx] = newIdx diff --git a/compiler/compiler.go b/compiler/compiler.go index ad09ad3d..a87c551e 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -3,7 +3,10 @@ package compiler import ( "fmt" "io" + "io/ioutil" + "path/filepath" "reflect" + "strings" "github.com/d5/tengo" "github.com/d5/tengo/compiler/ast" @@ -16,14 +19,14 @@ import ( type Compiler struct { file *source.File parent *Compiler - moduleName string + modulePath string constants []objects.Object symbolTable *SymbolTable scopes []CompilationScope scopeIndex int - moduleLoader ModuleLoader - builtinModules map[string]bool + importModules map[string]objects.Importable compiledModules map[string]*objects.CompiledFunction + allowFileImport bool loops []*Loop loopIndex int trace io.Writer @@ -31,12 +34,7 @@ type Compiler struct { } // NewCompiler creates a 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(file *source.File, symbolTable *SymbolTable, constants []objects.Object, builtinModules map[string]bool, trace io.Writer) *Compiler { +func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []objects.Object, importModules map[string]objects.Importable, trace io.Writer) *Compiler { mainScope := CompilationScope{ symbolInit: make(map[string]bool), sourceMap: make(map[int]source.Pos), @@ -45,15 +43,16 @@ func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []object // symbol table if symbolTable == nil { symbolTable = NewSymbolTable() + } - for idx, fn := range objects.Builtins { - symbolTable.DefineBuiltin(idx, fn.Name) - } + // add builtin functions to the symbol table + for idx, fn := range objects.Builtins { + symbolTable.DefineBuiltin(idx, fn.Name) } // builtin modules - if builtinModules == nil { - builtinModules = make(map[string]bool) + if importModules == nil { + importModules = make(map[string]objects.Importable) } return &Compiler{ @@ -64,7 +63,7 @@ func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []object scopeIndex: 0, loopIndex: -1, trace: trace, - builtinModules: builtinModules, + importModules: importModules, compiledModules: make(map[string]*objects.CompiledFunction), } } @@ -510,21 +509,53 @@ func (c *Compiler) Compile(node ast.Node) error { c.emit(node, OpCall, len(node.Args)) case *ast.ImportExpr: - if c.builtinModules[node.ModuleName] { - if len(node.ModuleName) > tengo.MaxStringLen { - return c.error(node, objects.ErrStringLimit) + if mod, ok := c.importModules[node.ModuleName]; ok { + v, err := mod.Import(node.ModuleName) + if err != nil { + return err } - c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.ModuleName})) - c.emit(node, OpGetBuiltinModule) - } else { - userMod, err := c.compileModule(node) + switch v := v.(type) { + case []byte: // module written in Tengo + compiled, err := c.compileModule(node, node.ModuleName, node.ModuleName, v) + if err != nil { + return err + } + c.emit(node, OpConstant, c.addConstant(compiled)) + c.emit(node, OpCall, 0) + case objects.Object: // builtin module + c.emit(node, OpConstant, c.addConstant(v)) + default: + panic(fmt.Errorf("invalid import value type: %T", v)) + } + } else if c.allowFileImport { + moduleName := node.ModuleName + if !strings.HasSuffix(moduleName, ".tengo") { + moduleName += ".tengo" + } + + modulePath, err := filepath.Abs(moduleName) if err != nil { + return c.errorf(node, "module file path error: %s", err.Error()) + } + + if err := c.checkCyclicImports(node, modulePath); err != nil { return err } - c.emit(node, OpConstant, c.addConstant(userMod)) + moduleSrc, err := ioutil.ReadFile(moduleName) + if err != nil { + return c.errorf(node, "module file read error: %s", err.Error()) + } + + compiled, err := c.compileModule(node, moduleName, modulePath, moduleSrc) + if err != nil { + return err + } + c.emit(node, OpConstant, c.addConstant(compiled)) c.emit(node, OpCall, 0) + } else { + return c.errorf(node, "module '%s' not found", node.ModuleName) } case *ast.ExportStmt: @@ -602,18 +633,16 @@ 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 +// EnableFileImport enables or disables module loading from local files. +// Local file modules are disabled by default. +func (c *Compiler) EnableFileImport(enable bool) { + c.allowFileImport = enable } -func (c *Compiler) fork(file *source.File, moduleName string, symbolTable *SymbolTable) *Compiler { - child := NewCompiler(file, symbolTable, nil, c.builtinModules, c.trace) - child.moduleName = moduleName // name of the module to compile - child.parent = c // parent to set to current compiler - child.moduleLoader = c.moduleLoader // share module loader +func (c *Compiler) fork(file *source.File, modulePath string, symbolTable *SymbolTable) *Compiler { + child := NewCompiler(file, symbolTable, nil, c.importModules, c.trace) + child.modulePath = modulePath // module file path + child.parent = c // parent to set to current compiler return child } diff --git a/compiler/compiler_error_report_test.go b/compiler/compiler_error_report_test.go index 63071953..c357c81d 100644 --- a/compiler/compiler_error_report_test.go +++ b/compiler/compiler_error_report_test.go @@ -3,7 +3,7 @@ package compiler_test import "testing" func TestCompilerErrorReport(t *testing.T) { - expectError(t, `import("user1")`, "Compile Error: module file read error: open user1.tengo: no such file or directory\n\tat test:1:1") + expectError(t, `import("user1")`, "Compile Error: module 'user1' not found\n\tat test:1:1") expectError(t, `a = 1`, "Compile Error: unresolved reference 'a'\n\tat test:1:1") expectError(t, `a, b := 1, 2`, "Compile Error: tuple assignment not allowed\n\tat test:1:1") diff --git a/compiler/compiler_module.go b/compiler/compiler_module.go index d069bfab..48c2ef26 100644 --- a/compiler/compiler_module.go +++ b/compiler/compiler_module.go @@ -1,72 +1,31 @@ package compiler import ( - "io/ioutil" - "strings" - "github.com/d5/tengo/compiler/ast" "github.com/d5/tengo/compiler/parser" "github.com/d5/tengo/objects" ) -func (c *Compiler) compileModule(expr *ast.ImportExpr) (*objects.CompiledFunction, error) { - compiledModule, exists := c.loadCompiledModule(expr.ModuleName) - if exists { - return compiledModule, nil +func (c *Compiler) checkCyclicImports(node ast.Node, modulePath string) error { + if c.modulePath == modulePath { + return c.errorf(node, "cyclic module import: %s", modulePath) + } else if c.parent != nil { + return c.parent.checkCyclicImports(node, modulePath) } - moduleName := expr.ModuleName - - // read module source from loader - var moduleSrc []byte - if c.moduleLoader == nil { - // default loader: read from local file - if !strings.HasSuffix(moduleName, ".tengo") { - moduleName += ".tengo" - } - - if err := c.checkCyclicImports(expr, moduleName); err != nil { - return nil, err - } - - var err error - moduleSrc, err = ioutil.ReadFile(moduleName) - if err != nil { - return nil, c.errorf(expr, "module file read error: %s", err.Error()) - } - } else { - if err := c.checkCyclicImports(expr, moduleName); err != nil { - return nil, err - } - - var err error - moduleSrc, err = c.moduleLoader(moduleName) - if err != nil { - return nil, err - } - } + return nil +} - compiledModule, err := c.doCompileModule(moduleName, moduleSrc) - if err != nil { +func (c *Compiler) compileModule(node ast.Node, moduleName, modulePath string, src []byte) (*objects.CompiledFunction, error) { + if err := c.checkCyclicImports(node, modulePath); err != nil { return nil, err } - c.storeCompiledModule(moduleName, compiledModule) - - return compiledModule, nil -} - -func (c *Compiler) checkCyclicImports(node ast.Node, moduleName string) error { - if c.moduleName == moduleName { - return c.errorf(node, "cyclic module import: %s", moduleName) - } else if c.parent != nil { - return c.parent.checkCyclicImports(node, moduleName) + compiledModule, exists := c.loadCompiledModule(modulePath) + if exists { + return compiledModule, nil } - return nil -} - -func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.CompiledFunction, error) { modFile := c.file.Set().AddFile(moduleName, -1, len(src)) p := parser.NewParser(modFile, src, nil) file, err := p.ParseFile() @@ -85,7 +44,7 @@ func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.Comp symbolTable = symbolTable.Fork(false) // compile module - moduleCompiler := c.fork(modFile, moduleName, symbolTable) + moduleCompiler := c.fork(modFile, modulePath, symbolTable) if err := moduleCompiler.Compile(file); err != nil { return nil, err } @@ -98,23 +57,25 @@ func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.Comp compiledFunc := moduleCompiler.Bytecode().MainFunction compiledFunc.NumLocals = symbolTable.MaxSymbols() + c.storeCompiledModule(modulePath, compiledFunc) + return compiledFunc, nil } -func (c *Compiler) loadCompiledModule(moduleName string) (mod *objects.CompiledFunction, ok bool) { +func (c *Compiler) loadCompiledModule(modulePath string) (mod *objects.CompiledFunction, ok bool) { if c.parent != nil { - return c.parent.loadCompiledModule(moduleName) + return c.parent.loadCompiledModule(modulePath) } - mod, ok = c.compiledModules[moduleName] + mod, ok = c.compiledModules[modulePath] return } -func (c *Compiler) storeCompiledModule(moduleName string, module *objects.CompiledFunction) { +func (c *Compiler) storeCompiledModule(modulePath string, module *objects.CompiledFunction) { if c.parent != nil { - c.parent.storeCompiledModule(moduleName, module) + c.parent.storeCompiledModule(modulePath, module) } - c.compiledModules[moduleName] = module + c.compiledModules[modulePath] = module } diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 51e32661..a6506812 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -695,7 +695,7 @@ func TestCompiler_Compile(t *testing.T) { expect(t, `len([]);`, bytecode( concat( - compiler.MakeInstruction(compiler.OpGetBuiltin, 4), + compiler.MakeInstruction(compiler.OpGetBuiltin, 0), compiler.MakeInstruction(compiler.OpArray, 0), compiler.MakeInstruction(compiler.OpCall, 1), compiler.MakeInstruction(compiler.OpPop)), @@ -708,7 +708,7 @@ func TestCompiler_Compile(t *testing.T) { compiler.MakeInstruction(compiler.OpPop)), objectsArray( compiledFunction(0, 0, - compiler.MakeInstruction(compiler.OpGetBuiltin, 4), + compiler.MakeInstruction(compiler.OpGetBuiltin, 0), compiler.MakeInstruction(compiler.OpArray, 0), compiler.MakeInstruction(compiler.OpCall, 1), compiler.MakeInstruction(compiler.OpReturnValue))))) @@ -874,7 +874,7 @@ func() { intObject(1), intObject(1)))) - expectError(t, `import("user1")`, "no such file or directory") // unknown module name + expectError(t, `import("user1")`, "module 'user1' not found") // unknown module name expectError(t, ` r["x"] = { diff --git a/compiler/opcodes.go b/compiler/opcodes.go index 2e5f43bf..aa5fb9d3 100644 --- a/compiler/opcodes.go +++ b/compiler/opcodes.go @@ -54,7 +54,6 @@ const ( OpGetLocalPtr // Get local variable as a pointer OpSetSelFree // Set free variables using selectors OpGetBuiltin // Get builtin function - OpGetBuiltinModule // Get builtin module OpClosure // Push closure OpIteratorInit // Iterator init OpIteratorNext // Iterator next @@ -108,7 +107,6 @@ var OpcodeNames = [...]string{ OpDefineLocal: "DEFL", OpSetSelLocal: "SETSL", OpGetBuiltin: "BUILTIN", - OpGetBuiltinModule: "BLTMOD", OpClosure: "CLOSURE", OpGetFreePtr: "GETFP", OpGetFree: "GETF", @@ -167,7 +165,6 @@ var OpcodeOperands = [...][]int{ OpDefineLocal: {1}, OpSetSelLocal: {1, 1}, OpGetBuiltin: {1}, - OpGetBuiltinModule: {}, OpClosure: {2, 1}, OpGetFreePtr: {1}, OpGetFree: {1}, diff --git a/compiler/parser/parser_map_test.go b/compiler/parser/parser_map_test.go index ae0a3c1d..147ccb09 100644 --- a/compiler/parser/parser_map_test.go +++ b/compiler/parser/parser_map_test.go @@ -65,6 +65,13 @@ func TestMap(t *testing.T) { mapElementLit("key3", p(5, 2), p(5, 6), boolLit(true, p(5, 8)))))) }) + expectError(t, ` +{ + key1: 1, + key2: "2", + key3: true, +}`) // unlike Go, trailing comma for the last element is illegal + expectError(t, `{ key1: 1, }`) expectError(t, `{ key1: 1, diff --git a/docs/builtins.md b/docs/builtins.md index 529d69f5..6a57d7dc 100644 --- a/docs/builtins.md +++ b/docs/builtins.md @@ -1,45 +1,5 @@ # Builtin Functions -## print - -Prints a string representation of the given variable to the standard output. No spaces are added between the operands. - -```golang -v := [1, 2, 3] -print(v) // "[1, 2, 3]" - -print(1, 2, 3) // "123" -``` - -## println - -Prints a string representation of the given variable to the standard output with a newline appended. No spaces are added between the operands. - -```golang -v := [1, 2, 3] -println(v) // "[1, 2, 3]" - -println(1, 2, 3) // "123" newline -``` - -## printf - -Prints a formatted string to the standard output. It does not append the newline character at the end. The first argument must a String object. It's same as Go's `fmt.Printf`. - -```golang -a := [1, 2, 3] -printf("foo %v", a) // "foo [1, 2, 3]" -``` - -## sprintf - -Returns a formatted string. The first argument must be a String object. It's the same as Go's `fmt.Sprintf`. - -```golang -a := [1, 2, 3] -b := sprintp("foo %v", a) // b == "foo [1, 2, 3]" -``` - ## len Returns the number of elements if the given variable is array, string, map, or module map. @@ -71,26 +31,6 @@ v := [1] v = append(v, 2, 3) // v == [1, 2, 3] ``` -## to_json - -Returns the JSON encoding of an object. - -```golang -print(to_json([1, 2, 3])) // [1, 2, 3] -print(to_json(4)) // 4 -print(to_json("five")) // "five" -``` - -## from_json - -Parses the JSON-encoded data and returns an object. - -```golang -arr := from_json(`[1, 2, 3]`) -four := from_json(`4`) -five := from_json(`"five"`) -``` - ## string Tries to convert an object to string object. See [Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more details on type conversion. diff --git a/docs/interoperability.md b/docs/interoperability.md index c6e13bde..51e2f5ff 100644 --- a/docs/interoperability.md +++ b/docs/interoperability.md @@ -119,68 +119,40 @@ Users can add and use a custom user type in Tengo code by implementing [Object]( To securely compile and execute _potentially_ unsafe script code, you can use the following Script functions. -#### Script.SetBuiltinFunctions(funcs []*objects.BuiltinFunction) +#### Script.SetImports(modules map[string]objects.Importable) -SetBuiltinFunctions resets all builtin functions in the compiler to the ones provided in the input parameter. Compiler will report a compile-time error if the a function not set is referenced. Passing `nil` will disable all builtin functions. All builtin functions **are included by default** unless `SetBuiltinFunctions` is called. - -```golang -s := script.New([]byte(`print([1, 2, 3])`)) - -_, err := s.Run() // prints [1, 2, 3] - -s.SetBuiltinFunctions(nil) - -_, err := s.Run() // compile error - -s.SetBuiltinFunctions([]*objects.BuiltinFunction{&objects.Builtins[0]}) - -_, err := s.Run() // prints [1, 2, 3] -``` - -#### Script.SetBuiltinModules(modules map[string]*objects.ImmutableMap) - -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. Passing `nil` will disable all builtin modules. No standard library modules are included by default unless `SetBuiltinModules` is called. +SetImports sets the import modules with corresponding names. Script **does not** include any modules by default. You can use this function to include the [Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md). ```golang s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`)) -_, err := s.Run() // compile error - -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 +s.SetImports(map[string]objects.Importable{ + "math": stdlib.BuiltinModules["math"], +}) +// or +s.SetImports(stdlib.GetModules("math")) +// or, to include all stdlib at once +s.SetImports(stdlib.GetModules(stdlib.AllModuleNames()...)) ``` -#### Script.SetUserModuleLoader(loader compiler.ModuleLoader) - -SetUserModuleLoader replaces the default user-module loader of the compiler, which tries to read the source from a local file. +You can also include Tengo's written module using `objects.SourceModule` (which implements `objects.Importable`). ```golang -s := script.New([]byte(`math := import("mod1"); a := math.foo()`)) - -s.SetUserModuleLoader(func(moduleName string) ([]byte, error) { - if moduleName == "mod1" { - return []byte(`foo := func() { return 5 }`), nil - } +s := script.New([]byte(`double := import("double"); a := double(20)`)) - return nil, errors.New("module not found") +s.SetImports(map[string]objects.Importable{ + "double": &objects.SourceModule{Src: []byte(`export func(x) { return x * 2 }`)}, }) ``` -Note that when a script is being added to another script as a module (via `Script.AddModule`), it does not inherit the module loader from the main script. #### Script.SetMaxAllocs(n int64) SetMaxAllocs sets the maximum number of object allocations. Note this is a cumulative metric that tracks only the object creations. Set this to a negative number (e.g. `-1`) if you don't need to limit the number of allocations. +#### Script.EnableFileImport(enable bool) + +EnableFileImport enables or disables module loading from the local files. It's disabled by default. #### tengo.MaxStringLen diff --git a/docs/stdlib-fmt.md b/docs/stdlib-fmt.md new file mode 100644 index 00000000..f9f13a0a --- /dev/null +++ b/docs/stdlib-fmt.md @@ -0,0 +1,12 @@ +# Module - "fmt" + +```golang +fmt := import("fmt") +``` + +## Functions + +- `print(args...)`: Prints a string representation of the given variable to the standard output. Unlike Go's `fmt.Print` function, no spaces are added between the operands. +- `println(args...)`: Prints a string representation of the given variable to the standard output with a newline appended. Unlike Go's `fmt.Println` function, no spaces are added between the operands. +- `printf(format, args...)`: Prints a formatted string to the standard output. It does not append the newline character at the end. The first argument must a String object. It's same as Go's `fmt.Printf`. +- `sprintf(format, args...)`: Returns a formatted string. The first argument must be a String object. It's the same as Go's `fmt.Sprintf`. diff --git a/docs/stdlib-json.md b/docs/stdlib-json.md new file mode 100644 index 00000000..f0633801 --- /dev/null +++ b/docs/stdlib-json.md @@ -0,0 +1,10 @@ +# Module - "json" + +```golang +json := import("json") +``` + +## Functions + +- `parse(v)`: Parses the JSON string and returns an object. +- `stringify(v)`: Returns the JSON string representation of the object. diff --git a/docs/stdlib.md b/docs/stdlib.md index 19707e02..e051f750 100644 --- a/docs/stdlib.md +++ b/docs/stdlib.md @@ -4,4 +4,6 @@ - [text](https://github.com/d5/tengo/blob/master/docs/stdlib-text.md): regular expressions, string conversion, and manipulation - [math](https://github.com/d5/tengo/blob/master/docs/stdlib-math.md): mathematical constants and functions - [times](https://github.com/d5/tengo/blob/master/docs/stdlib-times.md): time-related functions -- [rand](https://github.com/d5/tengo/blob/master/docs/stdlib-rand.md): random functions \ No newline at end of file +- [rand](https://github.com/d5/tengo/blob/master/docs/stdlib-rand.md): random functions +- [fmt](https://github.com/d5/tengo/blob/master/docs/stdlib-fmt.md): formatting functions +- [json](https://github.com/d5/tengo/blob/master/docs/stdlib-json.md): JSON functions \ No newline at end of file diff --git a/docs/tutorial.md b/docs/tutorial.md index 26e4b854..42af53a7 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -226,7 +226,7 @@ Main script: ```golang sum := import("./sum") // assuming sum.tengo file exists in the current directory // same as 'import("./sum.tengo")' or 'import("sum")' -print(sum(10)) // module function +fmt.print(sum(10)) // module function ``` `sum.tengo` file: diff --git a/objects/builtin_json.go b/objects/builtin_json.go deleted file mode 100644 index 411c50db..00000000 --- a/objects/builtin_json.go +++ /dev/null @@ -1,65 +0,0 @@ -package objects - -import ( - "encoding/json" - - "github.com/d5/tengo" -) - -// to_json(v object) => bytes -func builtinToJSON(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - v := ToInterface(args[0]) - if vErr, isErr := v.(error); isErr { - v = vErr.Error() - } - - res, err := json.Marshal(v) - if err != nil { - return &Error{Value: &String{Value: err.Error()}}, nil - } - - if len(res) > tengo.MaxBytesLen { - return nil, ErrBytesLimit - } - - return &Bytes{Value: res}, nil -} - -// from_json(data string/bytes) => object -func builtinFromJSON(args ...Object) (Object, error) { - if len(args) != 1 { - return nil, ErrWrongNumArguments - } - - var target interface{} - - switch o := args[0].(type) { - case *Bytes: - err := json.Unmarshal(o.Value, &target) - if err != nil { - return &Error{Value: &String{Value: err.Error()}}, nil - } - case *String: - err := json.Unmarshal([]byte(o.Value), &target) - if err != nil { - return &Error{Value: &String{Value: err.Error()}}, nil - } - default: - return nil, ErrInvalidArgumentType{ - Name: "first", - Expected: "bytes/string", - Found: args[0].TypeName(), - } - } - - res, err := FromInterface(target) - if err != nil { - return nil, err - } - - return res, nil -} diff --git a/objects/builtin_module.go b/objects/builtin_module.go new file mode 100644 index 00000000..54f359c8 --- /dev/null +++ b/objects/builtin_module.go @@ -0,0 +1,16 @@ +package objects + +// BuiltinModule is an importable module that's written in Go. +type BuiltinModule struct { + Attrs map[string]Object +} + +// Import returns an immutable map for the module. +func (m *BuiltinModule) Import(name string) (interface{}, error) { + attrs := make(map[string]Object, len(m.Attrs)) + for k, v := range m.Attrs { + attrs[k] = v.Copy() + } + attrs["__module_name__"] = &String{Value: name} + return &ImmutableMap{Value: attrs}, nil +} diff --git a/objects/builtins.go b/objects/builtins.go index 2fa998c8..67aec46d 100644 --- a/objects/builtins.go +++ b/objects/builtins.go @@ -2,23 +2,7 @@ package objects // Builtins contains all default builtin functions. // Use GetBuiltinFunctions instead of accessing Builtins directly. -var Builtins = []BuiltinFunction{ - { - Name: "print", - Value: builtinPrint, - }, - { - Name: "println", - Value: builtinPrintln, - }, - { - Name: "printf", - Value: builtinPrintf, - }, - { - Name: "sprintf", - Value: builtinSprintf, - }, +var Builtins = []*BuiltinFunction{ { Name: "len", Value: builtinLen, @@ -119,50 +103,8 @@ var Builtins = []BuiltinFunction{ Name: "is_callable", Value: builtinIsCallable, }, - { - Name: "to_json", - Value: builtinToJSON, - }, - { - Name: "from_json", - Value: builtinFromJSON, - }, { Name: "type_name", Value: builtinTypeName, }, } - -// AllBuiltinFunctionNames returns a list of all default builtin function names. -func AllBuiltinFunctionNames() []string { - var names []string - for _, bf := range Builtins { - names = append(names, bf.Name) - } - return names -} - -// GetBuiltinFunctions returns a slice of builtin function objects. -// GetBuiltinFunctions removes the duplicate names, and, the returned builtin functions -// are not guaranteed to be in the same order as names. -func GetBuiltinFunctions(names ...string) []*BuiltinFunction { - include := make(map[string]bool) - for _, name := range names { - include[name] = true - } - - var builtinFuncs []*BuiltinFunction - for _, bf := range Builtins { - if include[bf.Name] { - bf := bf - builtinFuncs = append(builtinFuncs, &bf) - } - } - - return builtinFuncs -} - -// GetAllBuiltinFunctions returns all builtin functions. -func GetAllBuiltinFunctions() []*BuiltinFunction { - return GetBuiltinFunctions(AllBuiltinFunctionNames()...) -} diff --git a/objects/builtins_test.go b/objects/builtins_test.go deleted file mode 100644 index 4dfe036b..00000000 --- a/objects/builtins_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package objects_test - -import ( - "testing" - - "github.com/d5/tengo/assert" - "github.com/d5/tengo/objects" -) - -func TestGetBuiltinFunctions(t *testing.T) { - testGetBuiltinFunctions(t) - testGetBuiltinFunctions(t, "print") - testGetBuiltinFunctions(t, "int", "float") - testGetBuiltinFunctions(t, "int", "float", "printf") - testGetBuiltinFunctions(t, "int", "int") // duplicate names ignored -} - -func TestGetAllBuiltinFunctions(t *testing.T) { - funcs := objects.GetAllBuiltinFunctions() - if !assert.Equal(t, len(objects.Builtins), len(funcs)) { - return - } - - namesM := make(map[string]bool) - for _, bf := range objects.Builtins { - namesM[bf.Name] = true - } - - for _, bf := range funcs { - assert.True(t, namesM[bf.Name], "name: %s", bf.Name) - } -} - -func TestAllBuiltinFunctionNames(t *testing.T) { - names := objects.AllBuiltinFunctionNames() - if !assert.Equal(t, len(objects.Builtins), len(names)) { - return - } - - namesM := make(map[string]bool) - for _, name := range names { - namesM[name] = true - } - - for _, bf := range objects.Builtins { - assert.True(t, namesM[bf.Name], "name: %s", bf.Name) - } -} - -func testGetBuiltinFunctions(t *testing.T, names ...string) { - // remove duplicates - namesM := make(map[string]bool) - for _, name := range names { - namesM[name] = true - } - - funcs := objects.GetBuiltinFunctions(names...) - if !assert.Equal(t, len(namesM), len(funcs)) { - return - } - - for _, bf := range funcs { - assert.True(t, namesM[bf.Name], "name: %s", bf.Name) - } -} diff --git a/objects/importable.go b/objects/importable.go new file mode 100644 index 00000000..a5395c0f --- /dev/null +++ b/objects/importable.go @@ -0,0 +1,7 @@ +package objects + +// Importable interface represents importable module instance. +type Importable interface { + // Import should return either an Object or module source code ([]byte). + Import(name string) (interface{}, error) +} diff --git a/objects/source_module.go b/objects/source_module.go new file mode 100644 index 00000000..577fddf2 --- /dev/null +++ b/objects/source_module.go @@ -0,0 +1,11 @@ +package objects + +// SourceModule is an importable module that's written in Tengo. +type SourceModule struct { + Src []byte +} + +// Import returns a module source code. +func (m *SourceModule) Import(_ string) (interface{}, error) { + return m.Src, nil +} diff --git a/runtime/vm.go b/runtime/vm.go index 10dadbb4..ac5e1e95 100644 --- a/runtime/vm.go +++ b/runtime/vm.go @@ -23,65 +23,47 @@ const ( // VM is a virtual machine that executes the bytecode compiled by Compiler. type VM struct { - constants []objects.Object - stack []objects.Object - sp int - globals []objects.Object - fileSet *source.FileSet - frames []Frame - framesIndex int - curFrame *Frame - curInsts []byte - curIPLimit int - ip int - aborting int64 - builtinFuncs []objects.Object - builtinModules map[string]objects.Object - maxAllocs int64 - allocs int64 - err error - errOffset int + constants []objects.Object + stack []objects.Object + sp int + globals []objects.Object + fileSet *source.FileSet + frames []Frame + framesIndex int + curFrame *Frame + curInsts []byte + curIPLimit int + ip int + aborting int64 + maxAllocs int64 + allocs int64 + err error + errOffset int } // NewVM creates a VM. -func NewVM(bytecode *compiler.Bytecode, globals []objects.Object, builtinFuncs []objects.Object, builtinModules map[string]objects.Object, maxAllocs int64) *VM { +func NewVM(bytecode *compiler.Bytecode, globals []objects.Object, maxAllocs int64) *VM { if globals == nil { globals = make([]objects.Object, GlobalsSize) } - if builtinModules == nil { - builtinModules = make(map[string]objects.Object) - } - - if builtinFuncs == nil { - builtinFuncs = make([]objects.Object, len(objects.Builtins)) - for idx, fn := range objects.Builtins { - builtinFuncs[idx] = &objects.BuiltinFunction{ - Name: fn.Name, - Value: fn.Value, - } - } - } - frames := make([]Frame, MaxFrames) frames[0].fn = bytecode.MainFunction frames[0].ip = -1 return &VM{ - constants: bytecode.Constants, - stack: make([]objects.Object, StackSize), - sp: 0, - globals: globals, - fileSet: bytecode.FileSet, - frames: frames, - framesIndex: 1, - curFrame: &(frames[0]), - curInsts: frames[0].fn.Instructions, - curIPLimit: len(frames[0].fn.Instructions) - 1, - ip: -1, - builtinFuncs: builtinFuncs, - builtinModules: builtinModules, - maxAllocs: maxAllocs, + constants: bytecode.Constants, + stack: make([]objects.Object, StackSize), + sp: 0, + globals: globals, + fileSet: bytecode.FileSet, + frames: frames, + framesIndex: 1, + curFrame: &(frames[0]), + curInsts: frames[0].fn.Instructions, + curIPLimit: len(frames[0].fn.Instructions) - 1, + ip: -1, + maxAllocs: maxAllocs, } } @@ -997,28 +979,7 @@ func (v *VM) run() { return } - v.stack[v.sp] = v.builtinFuncs[builtinIndex] - v.sp++ - - case compiler.OpGetBuiltinModule: - val := v.stack[v.sp-1] - v.sp-- - - moduleName := val.(*objects.String).Value - - module, ok := v.builtinModules[moduleName] - if !ok { - v.errOffset = 3 - v.err = fmt.Errorf("module '%s' not found", moduleName) - return - } - - if v.sp >= StackSize { - v.err = ErrStackOverflow - return - } - - v.stack[v.sp] = module + v.stack[v.sp] = objects.Builtins[builtinIndex] v.sp++ case compiler.OpClosure: diff --git a/runtime/vm_array_test.go b/runtime/vm_array_test.go index 56a59ca2..dcdf7b4b 100644 --- a/runtime/vm_array_test.go +++ b/runtime/vm_array_test.go @@ -8,48 +8,48 @@ import ( ) func TestArray(t *testing.T) { - expect(t, `out = [1, 2 * 2, 3 + 3]`, ARR{1, 4, 6}) + expect(t, `out = [1, 2 * 2, 3 + 3]`, nil, ARR{1, 4, 6}) // array copy-by-reference - expect(t, `a1 := [1, 2, 3]; a2 := a1; a1[0] = 5; out = a2`, ARR{5, 2, 3}) - expect(t, `func () { a1 := [1, 2, 3]; a2 := a1; a1[0] = 5; out = a2 }()`, ARR{5, 2, 3}) + expect(t, `a1 := [1, 2, 3]; a2 := a1; a1[0] = 5; out = a2`, nil, ARR{5, 2, 3}) + expect(t, `func () { a1 := [1, 2, 3]; a2 := a1; a1[0] = 5; out = a2 }()`, nil, ARR{5, 2, 3}) // array index set - expectError(t, `a1 := [1, 2, 3]; a1[3] = 5`, "index out of bounds") + expectError(t, `a1 := [1, 2, 3]; a1[3] = 5`, nil, "index out of bounds") // index operator arr := ARR{1, 2, 3, 4, 5, 6} arrStr := `[1, 2, 3, 4, 5, 6]` arrLen := 6 for idx := 0; idx < arrLen; idx++ { - expect(t, fmt.Sprintf("out = %s[%d]", arrStr, idx), arr[idx]) - expect(t, fmt.Sprintf("out = %s[0 + %d]", arrStr, idx), arr[idx]) - expect(t, fmt.Sprintf("out = %s[1 + %d - 1]", arrStr, idx), arr[idx]) - expect(t, fmt.Sprintf("idx := %d; out = %s[idx]", idx, arrStr), arr[idx]) + expect(t, fmt.Sprintf("out = %s[%d]", arrStr, idx), nil, arr[idx]) + expect(t, fmt.Sprintf("out = %s[0 + %d]", arrStr, idx), nil, arr[idx]) + expect(t, fmt.Sprintf("out = %s[1 + %d - 1]", arrStr, idx), nil, arr[idx]) + expect(t, fmt.Sprintf("idx := %d; out = %s[idx]", idx, arrStr), nil, arr[idx]) } - expect(t, fmt.Sprintf("%s[%d]", arrStr, -1), objects.UndefinedValue) - expect(t, fmt.Sprintf("%s[%d]", arrStr, arrLen), objects.UndefinedValue) + expect(t, fmt.Sprintf("%s[%d]", arrStr, -1), nil, objects.UndefinedValue) + expect(t, fmt.Sprintf("%s[%d]", arrStr, arrLen), nil, objects.UndefinedValue) // slice operator for low := 0; low < arrLen; low++ { - expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, low, low), ARR{}) + expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, low, low), nil, ARR{}) for high := low; high <= arrLen; high++ { - expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, low, high), arr[low:high]) - expect(t, fmt.Sprintf("out = %s[0 + %d : 0 + %d]", arrStr, low, high), arr[low:high]) - expect(t, fmt.Sprintf("out = %s[1 + %d - 1 : 1 + %d - 1]", arrStr, low, high), arr[low:high]) - expect(t, fmt.Sprintf("out = %s[:%d]", arrStr, high), arr[:high]) - expect(t, fmt.Sprintf("out = %s[%d:]", arrStr, low), arr[low:]) + expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, low, high), nil, arr[low:high]) + expect(t, fmt.Sprintf("out = %s[0 + %d : 0 + %d]", arrStr, low, high), nil, arr[low:high]) + expect(t, fmt.Sprintf("out = %s[1 + %d - 1 : 1 + %d - 1]", arrStr, low, high), nil, arr[low:high]) + expect(t, fmt.Sprintf("out = %s[:%d]", arrStr, high), nil, arr[:high]) + expect(t, fmt.Sprintf("out = %s[%d:]", arrStr, low), nil, arr[low:]) } } - expect(t, fmt.Sprintf("out = %s[:]", arrStr), arr) - expect(t, fmt.Sprintf("out = %s[%d:]", arrStr, -1), arr) - expect(t, fmt.Sprintf("out = %s[:%d]", arrStr, arrLen+1), arr) - expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, 2, 2), ARR{}) + expect(t, fmt.Sprintf("out = %s[:]", arrStr), nil, arr) + expect(t, fmt.Sprintf("out = %s[%d:]", arrStr, -1), nil, arr) + expect(t, fmt.Sprintf("out = %s[:%d]", arrStr, arrLen+1), nil, arr) + expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, 2, 2), nil, ARR{}) - expectError(t, fmt.Sprintf("%s[:%d]", arrStr, -1), "invalid slice index") - expectError(t, fmt.Sprintf("%s[%d:]", arrStr, arrLen+1), "invalid slice index") - expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 0, -1), "invalid slice index") - expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 2, 1), "invalid slice index") + expectError(t, fmt.Sprintf("%s[:%d]", arrStr, -1), nil, "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:]", arrStr, arrLen+1), nil, "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 0, -1), nil, "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 2, 1), nil, "invalid slice index") } diff --git a/runtime/vm_assignment_test.go b/runtime/vm_assignment_test.go index 43f51330..5e351f66 100644 --- a/runtime/vm_assignment_test.go +++ b/runtime/vm_assignment_test.go @@ -5,17 +5,17 @@ import ( ) func TestAssignment(t *testing.T) { - expect(t, `a := 1; a = 2; out = a`, 2) - expect(t, `a := 1; a = 2; out = a`, 2) - expect(t, `a := 1; a = a + 4; out = a`, 5) - expect(t, `a := 1; f1 := func() { a = 2; return a }; out = f1()`, 2) - expect(t, `a := 1; f1 := func() { a := 3; a = 2; return a }; out = f1()`, 2) + expect(t, `a := 1; a = 2; out = a`, nil, 2) + expect(t, `a := 1; a = 2; out = a`, nil, 2) + expect(t, `a := 1; a = a + 4; out = a`, nil, 5) + expect(t, `a := 1; f1 := func() { a = 2; return a }; out = f1()`, nil, 2) + expect(t, `a := 1; f1 := func() { a := 3; a = 2; return a }; out = f1()`, nil, 2) - expect(t, `a := 1; out = a`, 1) - expect(t, `a := 1; a = 2; out = a`, 2) - expect(t, `a := 1; func() { a = 2 }(); out = a`, 2) - expect(t, `a := 1; func() { a := 2 }(); out = a`, 1) // "a := 2" defines a new local variable 'a' - expect(t, `a := 1; func() { b := 2; out = b }()`, 2) + expect(t, `a := 1; out = a`, nil, 1) + expect(t, `a := 1; a = 2; out = a`, nil, 2) + expect(t, `a := 1; func() { a = 2 }(); out = a`, nil, 2) + expect(t, `a := 1; func() { a := 2 }(); out = a`, nil, 1) // "a := 2" defines a new local variable 'a' + expect(t, `a := 1; func() { b := 2; out = b }()`, nil, 2) expect(t, ` out = func() { a := 2 @@ -24,7 +24,7 @@ out = func() { }() return a }() -`, 3) +`, nil, 3) expect(t, ` func() { @@ -33,25 +33,25 @@ func() { a := 4 return a }() -}()`, 4) +}()`, nil, 4) - expectError(t, `a := 1; a := 2`, "redeclared") // redeclared in the same scope - expectError(t, `func() { a := 1; a := 2 }()`, "redeclared") // redeclared in the same scope + expectError(t, `a := 1; a := 2`, nil, "redeclared") // redeclared in the same scope + expectError(t, `func() { a := 1; a := 2 }()`, nil, "redeclared") // redeclared in the same scope - expect(t, `a := 1; a += 2; out = a`, 3) - expect(t, `a := 1; a += 4 - 2;; out = a`, 3) - expect(t, `a := 3; a -= 1;; out = a`, 2) - expect(t, `a := 3; a -= 5 - 4;; out = a`, 2) - expect(t, `a := 2; a *= 4;; out = a`, 8) - expect(t, `a := 2; a *= 1 + 3;; out = a`, 8) - expect(t, `a := 10; a /= 2;; out = a`, 5) - expect(t, `a := 10; a /= 5 - 3;; out = a`, 5) + expect(t, `a := 1; a += 2; out = a`, nil, 3) + expect(t, `a := 1; a += 4 - 2;; out = a`, nil, 3) + expect(t, `a := 3; a -= 1;; out = a`, nil, 2) + expect(t, `a := 3; a -= 5 - 4;; out = a`, nil, 2) + expect(t, `a := 2; a *= 4;; out = a`, nil, 8) + expect(t, `a := 2; a *= 1 + 3;; out = a`, nil, 8) + expect(t, `a := 10; a /= 2;; out = a`, nil, 5) + expect(t, `a := 10; a /= 5 - 3;; out = a`, nil, 5) // compound assignment operator does not define new variable - expectError(t, `a += 4`, "unresolved reference") - expectError(t, `a -= 4`, "unresolved reference") - expectError(t, `a *= 4`, "unresolved reference") - expectError(t, `a /= 4`, "unresolved reference") + expectError(t, `a += 4`, nil, "unresolved reference") + expectError(t, `a -= 4`, nil, "unresolved reference") + expectError(t, `a *= 4`, nil, "unresolved reference") + expectError(t, `a /= 4`, nil, "unresolved reference") expect(t, ` f1 := func() { @@ -64,16 +64,16 @@ f1 := func() { return f2(); }; -out = f1();`, 3) - expect(t, `f1 := func() { f2 := func() { a := 1; a += 4 - 2; return a }; return f2(); }; out = f1()`, 3) - expect(t, `f1 := func() { f2 := func() { a := 3; a -= 1; return a }; return f2(); }; out = f1()`, 2) - expect(t, `f1 := func() { f2 := func() { a := 3; a -= 5 - 4; return a }; return f2(); }; out = f1()`, 2) - expect(t, `f1 := func() { f2 := func() { a := 2; a *= 4; return a }; return f2(); }; out = f1()`, 8) - expect(t, `f1 := func() { f2 := func() { a := 2; a *= 1 + 3; return a }; return f2(); }; out = f1()`, 8) - expect(t, `f1 := func() { f2 := func() { a := 10; a /= 2; return a }; return f2(); }; out = f1()`, 5) - expect(t, `f1 := func() { f2 := func() { a := 10; a /= 5 - 3; return a }; return f2(); }; out = f1()`, 5) +out = f1();`, nil, 3) + expect(t, `f1 := func() { f2 := func() { a := 1; a += 4 - 2; return a }; return f2(); }; out = f1()`, nil, 3) + expect(t, `f1 := func() { f2 := func() { a := 3; a -= 1; return a }; return f2(); }; out = f1()`, nil, 2) + expect(t, `f1 := func() { f2 := func() { a := 3; a -= 5 - 4; return a }; return f2(); }; out = f1()`, nil, 2) + expect(t, `f1 := func() { f2 := func() { a := 2; a *= 4; return a }; return f2(); }; out = f1()`, nil, 8) + expect(t, `f1 := func() { f2 := func() { a := 2; a *= 1 + 3; return a }; return f2(); }; out = f1()`, nil, 8) + expect(t, `f1 := func() { f2 := func() { a := 10; a /= 2; return a }; return f2(); }; out = f1()`, nil, 5) + expect(t, `f1 := func() { f2 := func() { a := 10; a /= 5 - 3; return a }; return f2(); }; out = f1()`, nil, 5) - expect(t, `a := 1; f1 := func() { f2 := func() { a += 2; return a }; return f2(); }; out = f1()`, 3) + expect(t, `a := 1; f1 := func() { f2 := func() { a += 2; return a }; return f2(); }; out = f1()`, nil, 3) expect(t, ` f1 := func(a) { @@ -85,7 +85,7 @@ out = f1();`, 3) } out = f1(3)(4) - `, 11) + `, nil, 11) expect(t, ` out = func() { @@ -101,7 +101,7 @@ out = f1();`, 3) }() return a }() - `, 3) + `, nil, 3) // write on free variables expect(t, ` @@ -114,7 +114,7 @@ out = f1();`, 3) }() } out = f1() - `, 8) + `, nil, 8) expect(t, ` out = func() { @@ -127,7 +127,7 @@ out = f1();`, 3) } return f1() }()() - `, 20) + `, nil, 20) expect(t, ` it := func(seq, fn) { @@ -145,7 +145,7 @@ out = f1();`, 3) } out = foo(2) - `, 5) + `, nil, 5) expect(t, ` it := func(seq, fn) { @@ -163,7 +163,7 @@ out = f1();`, 3) } out = foo(2) - `, 12) + `, nil, 12) expect(t, ` out = func() { @@ -173,7 +173,7 @@ out = func() { }() return a }() -`, 2) +`, nil, 2) expect(t, ` f := func() { @@ -188,7 +188,7 @@ m := f() m.b() m.c() out = m.d() -`, 6) +`, nil, 6) expect(t, ` each := func(s, x) { for i:=0; i> 2`, 16>>2) + expect(t, `out = 1 & 1`, nil, 1) + expect(t, `out = 1 & 0`, nil, 0) + expect(t, `out = 0 & 1`, nil, 0) + expect(t, `out = 0 & 0`, nil, 0) + expect(t, `out = 1 | 1`, nil, 1) + expect(t, `out = 1 | 0`, nil, 1) + expect(t, `out = 0 | 1`, nil, 1) + expect(t, `out = 0 | 0`, nil, 0) + expect(t, `out = 1 ^ 1`, nil, 0) + expect(t, `out = 1 ^ 0`, nil, 1) + expect(t, `out = 0 ^ 1`, nil, 1) + expect(t, `out = 0 ^ 0`, nil, 0) + expect(t, `out = 1 &^ 1`, nil, 0) + expect(t, `out = 1 &^ 0`, nil, 1) + expect(t, `out = 0 &^ 1`, nil, 0) + expect(t, `out = 0 &^ 0`, nil, 0) + expect(t, `out = 1 << 2`, nil, 4) + expect(t, `out = 16 >> 2`, nil, 4) - expect(t, `out = 1; out &= 1`, 1) - expect(t, `out = 1; out |= 0`, 1) - expect(t, `out = 1; out ^= 0`, 1) - expect(t, `out = 1; out &^= 0`, 1) - expect(t, `out = 1; out <<= 2`, 4) - expect(t, `out = 16; out >>= 2`, 4) + expect(t, `out = 1; out &= 1`, nil, 1) + expect(t, `out = 1; out |= 0`, nil, 1) + expect(t, `out = 1; out ^= 0`, nil, 1) + expect(t, `out = 1; out &^= 0`, nil, 1) + expect(t, `out = 1; out <<= 2`, nil, 4) + expect(t, `out = 16; out >>= 2`, nil, 4) - expect(t, `out = ^0`, ^0) - expect(t, `out = ^1`, ^1) - expect(t, `out = ^55`, ^55) - expect(t, `out = ^-55`, ^-55) + expect(t, `out = ^0`, nil, ^0) + expect(t, `out = ^1`, nil, ^1) + expect(t, `out = ^55`, nil, ^55) + expect(t, `out = ^-55`, nil, ^-55) } diff --git a/runtime/vm_boolean_test.go b/runtime/vm_boolean_test.go index 0a6468b5..eafcbb88 100644 --- a/runtime/vm_boolean_test.go +++ b/runtime/vm_boolean_test.go @@ -5,38 +5,38 @@ import ( ) func TestBoolean(t *testing.T) { - expect(t, `out = true`, true) - expect(t, `out = false`, false) + expect(t, `out = true`, nil, true) + expect(t, `out = false`, nil, false) - expect(t, `out = 1 < 2`, true) - expect(t, `out = 1 > 2`, false) - expect(t, `out = 1 < 1`, false) - expect(t, `out = 1 > 2`, false) - expect(t, `out = 1 == 1`, true) - expect(t, `out = 1 != 1`, false) - expect(t, `out = 1 == 2`, false) - expect(t, `out = 1 != 2`, true) - expect(t, `out = 1 <= 2`, true) - expect(t, `out = 1 >= 2`, false) - expect(t, `out = 1 <= 1`, true) - expect(t, `out = 1 >= 2`, false) + expect(t, `out = 1 < 2`, nil, true) + expect(t, `out = 1 > 2`, nil, false) + expect(t, `out = 1 < 1`, nil, false) + expect(t, `out = 1 > 2`, nil, false) + expect(t, `out = 1 == 1`, nil, true) + expect(t, `out = 1 != 1`, nil, false) + expect(t, `out = 1 == 2`, nil, false) + expect(t, `out = 1 != 2`, nil, true) + expect(t, `out = 1 <= 2`, nil, true) + expect(t, `out = 1 >= 2`, nil, false) + expect(t, `out = 1 <= 1`, nil, true) + expect(t, `out = 1 >= 2`, nil, false) - expect(t, `out = true == true`, true) - expect(t, `out = false == false`, true) - expect(t, `out = true == false`, false) - expect(t, `out = true != false`, true) - expect(t, `out = false != true`, true) - expect(t, `out = (1 < 2) == true`, true) - expect(t, `out = (1 < 2) == false`, false) - expect(t, `out = (1 > 2) == true`, false) - expect(t, `out = (1 > 2) == false`, true) + expect(t, `out = true == true`, nil, true) + expect(t, `out = false == false`, nil, true) + expect(t, `out = true == false`, nil, false) + expect(t, `out = true != false`, nil, true) + expect(t, `out = false != true`, nil, true) + expect(t, `out = (1 < 2) == true`, nil, true) + expect(t, `out = (1 < 2) == false`, nil, false) + expect(t, `out = (1 > 2) == true`, nil, false) + expect(t, `out = (1 > 2) == false`, nil, true) - expectError(t, `5 + true`, "invalid operation") - expectError(t, `5 + true; 5`, "invalid operation") - expectError(t, `-true`, "invalid operation") - expectError(t, `true + false`, "invalid operation") - expectError(t, `5; true + false; 5`, "invalid operation") - expectError(t, `if (10 > 1) { true + false; }`, "invalid operation") + expectError(t, `5 + true`, nil, "invalid operation") + expectError(t, `5 + true; 5`, nil, "invalid operation") + expectError(t, `-true`, nil, "invalid operation") + expectError(t, `true + false`, nil, "invalid operation") + expectError(t, `5; true + false; 5`, nil, "invalid operation") + expectError(t, `if (10 > 1) { true + false; }`, nil, "invalid operation") expectError(t, ` func() { if (10 > 1) { @@ -47,9 +47,9 @@ func() { return 1; } }() -`, "invalid operation") - expectError(t, `if (true + false) { 10 }`, "invalid operation") - expectError(t, `10 + (true + false)`, "invalid operation") - expectError(t, `(true + false) + 20`, "invalid operation") - expectError(t, `!(true + false)`, "invalid operation") +`, nil, "invalid operation") + expectError(t, `if (true + false) { 10 }`, nil, "invalid operation") + expectError(t, `10 + (true + false)`, nil, "invalid operation") + expectError(t, `(true + false) + 20`, nil, "invalid operation") + expectError(t, `!(true + false)`, nil, "invalid operation") } diff --git a/runtime/vm_builtin_test.go b/runtime/vm_builtin_test.go index 1ae3311c..2fb09bab 100644 --- a/runtime/vm_builtin_test.go +++ b/runtime/vm_builtin_test.go @@ -8,198 +8,155 @@ import ( ) func TestBuiltinFunction(t *testing.T) { - expect(t, `out = len("")`, 0) - expect(t, `out = len("four")`, 4) - expect(t, `out = len("hello world")`, 11) - expect(t, `out = len([])`, 0) - expect(t, `out = len([1, 2, 3])`, 3) - expect(t, `out = len({})`, 0) - expect(t, `out = len({a:1, b:2})`, 2) - expect(t, `out = len(immutable([]))`, 0) - expect(t, `out = len(immutable([1, 2, 3]))`, 3) - expect(t, `out = len(immutable({}))`, 0) - expect(t, `out = len(immutable({a:1, b:2}))`, 2) - expectError(t, `len(1)`, "invalid type for argument") - expectError(t, `len("one", "two")`, "wrong number of arguments") - - expect(t, `out = copy(1)`, 1) - expectError(t, `copy(1, 2)`, "wrong number of arguments") - - expect(t, `out = append([1, 2, 3], 4)`, ARR{1, 2, 3, 4}) - expect(t, `out = append([1, 2, 3], 4, 5, 6)`, ARR{1, 2, 3, 4, 5, 6}) - expect(t, `out = append([1, 2, 3], "foo", false)`, ARR{1, 2, 3, "foo", false}) - - expect(t, `out = int(1)`, 1) - expect(t, `out = int(1.8)`, 1) - expect(t, `out = int("-522")`, -522) - expect(t, `out = int(true)`, 1) - expect(t, `out = int(false)`, 0) - expect(t, `out = int('8')`, 56) - expect(t, `out = int([1])`, objects.UndefinedValue) - expect(t, `out = int({a: 1})`, objects.UndefinedValue) - expect(t, `out = int(undefined)`, objects.UndefinedValue) - expect(t, `out = int("-522", 1)`, -522) - expect(t, `out = int(undefined, 1)`, 1) - expect(t, `out = int(undefined, 1.8)`, 1.8) - expect(t, `out = int(undefined, string(1))`, "1") - expect(t, `out = int(undefined, undefined)`, objects.UndefinedValue) - - expect(t, `out = string(1)`, "1") - expect(t, `out = string(1.8)`, "1.8") - expect(t, `out = string("-522")`, "-522") - expect(t, `out = string(true)`, "true") - expect(t, `out = string(false)`, "false") - expect(t, `out = string('8')`, "8") - expect(t, `out = string([1,8.1,true,3])`, "[1, 8.1, true, 3]") - expect(t, `out = string({b: "foo"})`, `{b: "foo"}`) - expect(t, `out = string(undefined)`, objects.UndefinedValue) // not "undefined" - expect(t, `out = string(1, "-522")`, "1") - expect(t, `out = string(undefined, "-522")`, "-522") // not "undefined" - - expect(t, `out = float(1)`, 1.0) - expect(t, `out = float(1.8)`, 1.8) - expect(t, `out = float("-52.2")`, -52.2) - expect(t, `out = float(true)`, objects.UndefinedValue) - expect(t, `out = float(false)`, objects.UndefinedValue) - expect(t, `out = float('8')`, objects.UndefinedValue) - expect(t, `out = float([1,8.1,true,3])`, objects.UndefinedValue) - expect(t, `out = float({a: 1, b: "foo"})`, objects.UndefinedValue) - expect(t, `out = float(undefined)`, objects.UndefinedValue) - expect(t, `out = float("-52.2", 1.8)`, -52.2) - expect(t, `out = float(undefined, 1)`, 1) - expect(t, `out = float(undefined, 1.8)`, 1.8) - expect(t, `out = float(undefined, "-52.2")`, "-52.2") - expect(t, `out = float(undefined, char(56))`, '8') - expect(t, `out = float(undefined, undefined)`, objects.UndefinedValue) - - expect(t, `out = char(56)`, '8') - expect(t, `out = char(1.8)`, objects.UndefinedValue) - expect(t, `out = char("-52.2")`, objects.UndefinedValue) - expect(t, `out = char(true)`, objects.UndefinedValue) - expect(t, `out = char(false)`, objects.UndefinedValue) - expect(t, `out = char('8')`, '8') - expect(t, `out = char([1,8.1,true,3])`, objects.UndefinedValue) - expect(t, `out = char({a: 1, b: "foo"})`, objects.UndefinedValue) - expect(t, `out = char(undefined)`, objects.UndefinedValue) - expect(t, `out = char(56, 'a')`, '8') - expect(t, `out = char(undefined, '8')`, '8') - expect(t, `out = char(undefined, 56)`, 56) - expect(t, `out = char(undefined, "-52.2")`, "-52.2") - expect(t, `out = char(undefined, undefined)`, objects.UndefinedValue) - - expect(t, `out = bool(1)`, true) // non-zero integer: true - expect(t, `out = bool(0)`, false) // zero: true - expect(t, `out = bool(1.8)`, true) // all floats (except for NaN): true - expect(t, `out = bool(0.0)`, true) // all floats (except for NaN): true - expect(t, `out = bool("false")`, true) // non-empty string: true - expect(t, `out = bool("")`, false) // empty string: false - expect(t, `out = bool(true)`, true) // true: true - expect(t, `out = bool(false)`, false) // false: false - expect(t, `out = bool('8')`, true) // non-zero chars: true - expect(t, `out = bool(char(0))`, false) // zero char: false - expect(t, `out = bool([1])`, true) // non-empty arrays: true - expect(t, `out = bool([])`, false) // empty array: false - expect(t, `out = bool({a: 1})`, true) // non-empty maps: true - expect(t, `out = bool({})`, false) // empty maps: false - expect(t, `out = bool(undefined)`, false) // undefined: false - - expect(t, `out = bytes(1)`, []byte{0}) - expect(t, `out = bytes(1.8)`, objects.UndefinedValue) - expect(t, `out = bytes("-522")`, []byte{'-', '5', '2', '2'}) - expect(t, `out = bytes(true)`, objects.UndefinedValue) - expect(t, `out = bytes(false)`, objects.UndefinedValue) - expect(t, `out = bytes('8')`, objects.UndefinedValue) - expect(t, `out = bytes([1])`, objects.UndefinedValue) - expect(t, `out = bytes({a: 1})`, objects.UndefinedValue) - expect(t, `out = bytes(undefined)`, objects.UndefinedValue) - expect(t, `out = bytes("-522", ['8'])`, []byte{'-', '5', '2', '2'}) - expect(t, `out = bytes(undefined, "-522")`, "-522") - expect(t, `out = bytes(undefined, 1)`, 1) - expect(t, `out = bytes(undefined, 1.8)`, 1.8) - expect(t, `out = bytes(undefined, int("-522"))`, -522) - expect(t, `out = bytes(undefined, undefined)`, objects.UndefinedValue) - - expect(t, `out = is_error(error(1))`, true) - expect(t, `out = is_error(1)`, false) - - expect(t, `out = is_undefined(undefined)`, true) - expect(t, `out = is_undefined(error(1))`, false) - - // to_json - expect(t, `out = to_json(5)`, []byte("5")) - expect(t, `out = to_json("foobar")`, []byte(`"foobar"`)) - expect(t, `out = to_json({foo: 5})`, []byte("{\"foo\":5}")) - expect(t, `out = to_json(immutable({foo: 5}))`, []byte("{\"foo\":5}")) - expect(t, `out = to_json([1,2,3])`, []byte("[1,2,3]")) - expect(t, `out = to_json(immutable([1,2,3]))`, []byte("[1,2,3]")) - expect(t, `out = to_json({foo: "bar"})`, []byte("{\"foo\":\"bar\"}")) - expect(t, `out = to_json({foo: 1.8})`, []byte("{\"foo\":1.8}")) - expect(t, `out = to_json({foo: true})`, []byte("{\"foo\":true}")) - expect(t, `out = to_json({foo: '8'})`, []byte("{\"foo\":56}")) - expect(t, `out = to_json({foo: bytes("foo")})`, []byte("{\"foo\":\"Zm9v\"}")) // json encoding returns []byte as base64 encoded string - expect(t, `out = to_json({foo: ["bar", 1, 1.8, '8', true]})`, []byte("{\"foo\":[\"bar\",1,1.8,56,true]}")) - expect(t, `out = to_json({foo: immutable(["bar", 1, 1.8, '8', true])})`, []byte("{\"foo\":[\"bar\",1,1.8,56,true]}")) - expect(t, `out = to_json({foo: [["bar", 1], ["bar", 1]]})`, []byte("{\"foo\":[[\"bar\",1],[\"bar\",1]]}")) - expect(t, `out = to_json({foo: {string: "bar", int: 1, float: 1.8, char: '8', bool: true}})`, []byte("{\"foo\":{\"bool\":true,\"char\":56,\"float\":1.8,\"int\":1,\"string\":\"bar\"}}")) - expect(t, `out = to_json({foo: immutable({string: "bar", int: 1, float: 1.8, char: '8', bool: true})})`, []byte("{\"foo\":{\"bool\":true,\"char\":56,\"float\":1.8,\"int\":1,\"string\":\"bar\"}}")) - expect(t, `out = to_json({foo: {map1: {string: "bar"}, map2: {int: "1"}}})`, []byte("{\"foo\":{\"map1\":{\"string\":\"bar\"},\"map2\":{\"int\":\"1\"}}}")) - expect(t, `out = to_json([["bar", 1], ["bar", 1]])`, []byte("[[\"bar\",1],[\"bar\",1]]")) - expect(t, `out = to_json(error("my error"))`, []byte(`"error: \"my error\""`)) - - // from_json - expect(t, `out = from_json("{\"foo\":5}").foo`, 5.0) - expect(t, `out = from_json("{\"foo\":\"bar\"}").foo`, "bar") - expect(t, `out = from_json("{\"foo\":1.8}").foo`, 1.8) - expect(t, `out = from_json("{\"foo\":true}").foo`, true) - expect(t, `out = from_json("{\"foo\":[\"bar\",1,1.8,56,true]}").foo`, ARR{"bar", 1.0, 1.8, 56.0, true}) - expect(t, `out = from_json("{\"foo\":[[\"bar\",1],[\"bar\",1]]}").foo[0]`, ARR{"bar", 1.0}) - expect(t, `out = from_json("{\"foo\":{\"bool\":true,\"char\":56,\"float\":1.8,\"int\":1,\"string\":\"bar\"}}").foo.bool`, true) - expect(t, `out = from_json("{\"foo\":{\"map1\":{\"string\":\"bar\"},\"map2\":{\"int\":\"1\"}}}").foo.map1.string`, "bar") - expect(t, `out = from_json("5")`, 5.0) - expect(t, `out = from_json("\"foobar\"")`, "foobar") - expect(t, `out = from_json("[\"bar\",1,1.8,56,true]")`, ARR{"bar", 1.0, 1.8, 56.0, true}) - - // sprintf - expect(t, `out = sprintf("")`, "") - expect(t, `out = sprintf("foo")`, "foo") - expect(t, `out = sprintf("foo %d %v %s", 1, 2, "bar")`, "foo 1 2 bar") - expect(t, `out = sprintf("foo %v", [1, "bar", true])`, "foo [1 bar true]") - expect(t, `out = sprintf("foo %v %d", [1, "bar", true], 19)`, "foo [1 bar true] 19") - expectError(t, `sprintf(1)`, "invalid type for argument") // format has to be String - expectError(t, `sprintf('c')`, "invalid type for argument") // format has to be String + expect(t, `out = len("")`, nil, 0) + expect(t, `out = len("four")`, nil, 4) + expect(t, `out = len("hello world")`, nil, 11) + expect(t, `out = len([])`, nil, 0) + expect(t, `out = len([1, 2, 3])`, nil, 3) + expect(t, `out = len({})`, nil, 0) + expect(t, `out = len({a:1, b:2})`, nil, 2) + expect(t, `out = len(immutable([]))`, nil, 0) + expect(t, `out = len(immutable([1, 2, 3]))`, nil, 3) + expect(t, `out = len(immutable({}))`, nil, 0) + expect(t, `out = len(immutable({a:1, b:2}))`, nil, 2) + expectError(t, `len(1)`, nil, "invalid type for argument") + expectError(t, `len("one", "two")`, nil, "wrong number of arguments") + + expect(t, `out = copy(1)`, nil, 1) + expectError(t, `copy(1, 2)`, nil, "wrong number of arguments") + + expect(t, `out = append([1, 2, 3], 4)`, nil, ARR{1, 2, 3, 4}) + expect(t, `out = append([1, 2, 3], 4, 5, 6)`, nil, ARR{1, 2, 3, 4, 5, 6}) + expect(t, `out = append([1, 2, 3], "foo", false)`, nil, ARR{1, 2, 3, "foo", false}) + + expect(t, `out = int(1)`, nil, 1) + expect(t, `out = int(1.8)`, nil, 1) + expect(t, `out = int("-522")`, nil, -522) + expect(t, `out = int(true)`, nil, 1) + expect(t, `out = int(false)`, nil, 0) + expect(t, `out = int('8')`, nil, 56) + expect(t, `out = int([1])`, nil, objects.UndefinedValue) + expect(t, `out = int({a: 1})`, nil, objects.UndefinedValue) + expect(t, `out = int(undefined)`, nil, objects.UndefinedValue) + expect(t, `out = int("-522", 1)`, nil, -522) + expect(t, `out = int(undefined, 1)`, nil, 1) + expect(t, `out = int(undefined, 1.8)`, nil, 1.8) + expect(t, `out = int(undefined, string(1))`, nil, "1") + expect(t, `out = int(undefined, undefined)`, nil, objects.UndefinedValue) + + expect(t, `out = string(1)`, nil, "1") + expect(t, `out = string(1.8)`, nil, "1.8") + expect(t, `out = string("-522")`, nil, "-522") + expect(t, `out = string(true)`, nil, "true") + expect(t, `out = string(false)`, nil, "false") + expect(t, `out = string('8')`, nil, "8") + expect(t, `out = string([1,8.1,true,3])`, nil, "[1, 8.1, true, 3]") + expect(t, `out = string({b: "foo"})`, nil, `{b: "foo"}`) + expect(t, `out = string(undefined)`, nil, objects.UndefinedValue) // not "undefined" + expect(t, `out = string(1, "-522")`, nil, "1") + expect(t, `out = string(undefined, "-522")`, nil, "-522") // not "undefined" + + expect(t, `out = float(1)`, nil, 1.0) + expect(t, `out = float(1.8)`, nil, 1.8) + expect(t, `out = float("-52.2")`, nil, -52.2) + expect(t, `out = float(true)`, nil, objects.UndefinedValue) + expect(t, `out = float(false)`, nil, objects.UndefinedValue) + expect(t, `out = float('8')`, nil, objects.UndefinedValue) + expect(t, `out = float([1,8.1,true,3])`, nil, objects.UndefinedValue) + expect(t, `out = float({a: 1, b: "foo"})`, nil, objects.UndefinedValue) + expect(t, `out = float(undefined)`, nil, objects.UndefinedValue) + expect(t, `out = float("-52.2", 1.8)`, nil, -52.2) + expect(t, `out = float(undefined, 1)`, nil, 1) + expect(t, `out = float(undefined, 1.8)`, nil, 1.8) + expect(t, `out = float(undefined, "-52.2")`, nil, "-52.2") + expect(t, `out = float(undefined, char(56))`, nil, '8') + expect(t, `out = float(undefined, undefined)`, nil, objects.UndefinedValue) + + expect(t, `out = char(56)`, nil, '8') + expect(t, `out = char(1.8)`, nil, objects.UndefinedValue) + expect(t, `out = char("-52.2")`, nil, objects.UndefinedValue) + expect(t, `out = char(true)`, nil, objects.UndefinedValue) + expect(t, `out = char(false)`, nil, objects.UndefinedValue) + expect(t, `out = char('8')`, nil, '8') + expect(t, `out = char([1,8.1,true,3])`, nil, objects.UndefinedValue) + expect(t, `out = char({a: 1, b: "foo"})`, nil, objects.UndefinedValue) + expect(t, `out = char(undefined)`, nil, objects.UndefinedValue) + expect(t, `out = char(56, 'a')`, nil, '8') + expect(t, `out = char(undefined, '8')`, nil, '8') + expect(t, `out = char(undefined, 56)`, nil, 56) + expect(t, `out = char(undefined, "-52.2")`, nil, "-52.2") + expect(t, `out = char(undefined, undefined)`, nil, objects.UndefinedValue) + + expect(t, `out = bool(1)`, nil, true) // non-zero integer: true + expect(t, `out = bool(0)`, nil, false) // zero: true + expect(t, `out = bool(1.8)`, nil, true) // all floats (except for NaN): true + expect(t, `out = bool(0.0)`, nil, true) // all floats (except for NaN): true + expect(t, `out = bool("false")`, nil, true) // non-empty string: true + expect(t, `out = bool("")`, nil, false) // empty string: false + expect(t, `out = bool(true)`, nil, true) // true: true + expect(t, `out = bool(false)`, nil, false) // false: false + expect(t, `out = bool('8')`, nil, true) // non-zero chars: true + expect(t, `out = bool(char(0))`, nil, false) // zero char: false + expect(t, `out = bool([1])`, nil, true) // non-empty arrays: true + expect(t, `out = bool([])`, nil, false) // empty array: false + expect(t, `out = bool({a: 1})`, nil, true) // non-empty maps: true + expect(t, `out = bool({})`, nil, false) // empty maps: false + expect(t, `out = bool(undefined)`, nil, false) // undefined: false + + expect(t, `out = bytes(1)`, nil, []byte{0}) + expect(t, `out = bytes(1.8)`, nil, objects.UndefinedValue) + expect(t, `out = bytes("-522")`, nil, []byte{'-', '5', '2', '2'}) + expect(t, `out = bytes(true)`, nil, objects.UndefinedValue) + expect(t, `out = bytes(false)`, nil, objects.UndefinedValue) + expect(t, `out = bytes('8')`, nil, objects.UndefinedValue) + expect(t, `out = bytes([1])`, nil, objects.UndefinedValue) + expect(t, `out = bytes({a: 1})`, nil, objects.UndefinedValue) + expect(t, `out = bytes(undefined)`, nil, objects.UndefinedValue) + expect(t, `out = bytes("-522", ['8'])`, nil, []byte{'-', '5', '2', '2'}) + expect(t, `out = bytes(undefined, "-522")`, nil, "-522") + expect(t, `out = bytes(undefined, 1)`, nil, 1) + expect(t, `out = bytes(undefined, 1.8)`, nil, 1.8) + expect(t, `out = bytes(undefined, int("-522"))`, nil, -522) + expect(t, `out = bytes(undefined, undefined)`, nil, objects.UndefinedValue) + + expect(t, `out = is_error(error(1))`, nil, true) + expect(t, `out = is_error(1)`, nil, false) + + expect(t, `out = is_undefined(undefined)`, nil, true) + expect(t, `out = is_undefined(error(1))`, nil, false) // type_name - expect(t, `out = type_name(1)`, "int") - expect(t, `out = type_name(1.1)`, "float") - expect(t, `out = type_name("a")`, "string") - expect(t, `out = type_name([1,2,3])`, "array") - expect(t, `out = type_name({k:1})`, "map") - expect(t, `out = type_name('a')`, "char") - expect(t, `out = type_name(true)`, "bool") - expect(t, `out = type_name(false)`, "bool") - expect(t, `out = type_name(bytes( 1))`, "bytes") - expect(t, `out = type_name(undefined)`, "undefined") - expect(t, `out = type_name(error("err"))`, "error") - expect(t, `out = type_name(func() {})`, "compiled-function") - expect(t, `a := func(x) { return func() { return x } }; out = type_name(a(5))`, "closure") // closure + expect(t, `out = type_name(1)`, nil, "int") + expect(t, `out = type_name(1.1)`, nil, "float") + expect(t, `out = type_name("a")`, nil, "string") + expect(t, `out = type_name([1,2,3])`, nil, "array") + expect(t, `out = type_name({k:1})`, nil, "map") + expect(t, `out = type_name('a')`, nil, "char") + expect(t, `out = type_name(true)`, nil, "bool") + expect(t, `out = type_name(false)`, nil, "bool") + expect(t, `out = type_name(bytes( 1))`, nil, "bytes") + expect(t, `out = type_name(undefined)`, nil, "undefined") + expect(t, `out = type_name(error("err"))`, nil, "error") + expect(t, `out = type_name(func() {})`, nil, "compiled-function") + expect(t, `a := func(x) { return func() { return x } }; out = type_name(a(5))`, nil, "closure") // closure // is_function - expect(t, `out = is_function(1)`, false) - expect(t, `out = is_function(func() {})`, true) - expect(t, `out = is_function(func(x) { return x })`, true) - expect(t, `out = is_function(len)`, false) // builtin function - expect(t, `a := func(x) { return func() { return x } }; out = is_function(a)`, true) // function - expect(t, `a := func(x) { return func() { return x } }; out = is_function(a(5))`, true) // closure - expectWithSymbols(t, `out = is_function(x)`, false, SYM{"x": &StringArray{Value: []string{"foo", "bar"}}}) // user object + expect(t, `out = is_function(1)`, nil, false) + expect(t, `out = is_function(func() {})`, nil, true) + expect(t, `out = is_function(func(x) { return x })`, nil, true) + expect(t, `out = is_function(len)`, nil, false) // builtin function + expect(t, `a := func(x) { return func() { return x } }; out = is_function(a)`, nil, true) // function + expect(t, `a := func(x) { return func() { return x } }; out = is_function(a(5))`, nil, true) // closure + expect(t, `out = is_function(x)`, Opts().Symbol("x", &StringArray{Value: []string{"foo", "bar"}}).Skip2ndPass(), false) // user object // is_callable - expect(t, `out = is_callable(1)`, false) - expect(t, `out = is_callable(func() {})`, true) - expect(t, `out = is_callable(func(x) { return x })`, true) - expect(t, `out = is_callable(len)`, true) // builtin function - expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a)`, true) // function - expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a(5))`, true) // closure - expectWithSymbols(t, `out = is_callable(x)`, true, SYM{"x": &StringArray{Value: []string{"foo", "bar"}}}) // user object + expect(t, `out = is_callable(1)`, nil, false) + expect(t, `out = is_callable(func() {})`, nil, true) + expect(t, `out = is_callable(func(x) { return x })`, nil, true) + expect(t, `out = is_callable(len)`, nil, true) // builtin function + expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a)`, nil, true) // function + expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a(5))`, nil, true) // closure + expect(t, `out = is_callable(x)`, Opts().Symbol("x", &StringArray{Value: []string{"foo", "bar"}}).Skip2ndPass(), true) // user object } func TestBytesN(t *testing.T) { @@ -207,11 +164,11 @@ func TestBytesN(t *testing.T) { defer func() { tengo.MaxBytesLen = curMaxBytesLen }() tengo.MaxBytesLen = 10 - expect(t, `out = bytes(0)`, make([]byte, 0)) - expect(t, `out = bytes(10)`, make([]byte, 10)) - expectError(t, `bytes(11)`, "bytes size limit") + expect(t, `out = bytes(0)`, nil, make([]byte, 0)) + expect(t, `out = bytes(10)`, nil, make([]byte, 10)) + expectError(t, `bytes(11)`, nil, "bytes size limit") tengo.MaxBytesLen = 1000 - expect(t, `out = bytes(1000)`, make([]byte, 1000)) - expectError(t, `bytes(1001)`, "bytes size limit") + expect(t, `out = bytes(1000)`, nil, make([]byte, 1000)) + expectError(t, `bytes(1001)`, nil, "bytes size limit") } diff --git a/runtime/vm_bytes_test.go b/runtime/vm_bytes_test.go index 3003f225..a17a3959 100644 --- a/runtime/vm_bytes_test.go +++ b/runtime/vm_bytes_test.go @@ -7,12 +7,12 @@ import ( ) 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!")) + expect(t, `out = bytes("Hello World!")`, nil, []byte("Hello World!")) + expect(t, `out = bytes("Hello") + bytes(" ") + bytes("World!")`, nil, []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) - expect(t, `out = bytes("abcde")[10]`, objects.UndefinedValue) + expect(t, `out = bytes("abcde")[0]`, nil, 97) + expect(t, `out = bytes("abcde")[1]`, nil, 98) + expect(t, `out = bytes("abcde")[4]`, nil, 101) + expect(t, `out = bytes("abcde")[10]`, nil, objects.UndefinedValue) } diff --git a/runtime/vm_call_test.go b/runtime/vm_call_test.go index c93e7737..81618c53 100644 --- a/runtime/vm_call_test.go +++ b/runtime/vm_call_test.go @@ -3,9 +3,9 @@ package runtime_test import "testing" func TestCall(t *testing.T) { - expect(t, `a := { b: func(x) { return x + 2 } }; out = a.b(5)`, 7) - expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a.b.c(5)`, 7) - expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a["b"].c(5)`, 7) + expect(t, `a := { b: func(x) { return x + 2 } }; out = a.b(5)`, nil, 7) + expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a.b.c(5)`, nil, 7) + expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a["b"].c(5)`, nil, 7) expectError(t, `a := 1 b := func(a, c) { c(a) @@ -15,5 +15,5 @@ c := func(a) { a() } b(a, c) -`, "Runtime Error: not callable: int\n\tat test:7:4\n\tat test:3:4\n\tat test:9:1") +`, nil, "Runtime Error: not callable: int\n\tat test:7:4\n\tat test:3:4\n\tat test:9:1") } diff --git a/runtime/vm_char_test.go b/runtime/vm_char_test.go index 59830ad5..42211bf4 100644 --- a/runtime/vm_char_test.go +++ b/runtime/vm_char_test.go @@ -3,21 +3,21 @@ package runtime_test import "testing" func TestChar(t *testing.T) { - expect(t, `out = 'a'`, 'a') - expect(t, `out = '九'`, rune(20061)) - expect(t, `out = 'Æ'`, rune(198)) + expect(t, `out = 'a'`, nil, 'a') + expect(t, `out = '九'`, nil, rune(20061)) + expect(t, `out = 'Æ'`, nil, rune(198)) - expect(t, `out = '0' + '9'`, rune(105)) - expect(t, `out = '0' + 9`, '9') - expect(t, `out = '9' - 4`, '5') - expect(t, `out = '0' == '0'`, true) - expect(t, `out = '0' != '0'`, false) - expect(t, `out = '2' < '4'`, true) - expect(t, `out = '2' > '4'`, false) - expect(t, `out = '2' <= '4'`, true) - expect(t, `out = '2' >= '4'`, false) - expect(t, `out = '4' < '4'`, false) - expect(t, `out = '4' > '4'`, false) - expect(t, `out = '4' <= '4'`, true) - expect(t, `out = '4' >= '4'`, true) + expect(t, `out = '0' + '9'`, nil, rune(105)) + expect(t, `out = '0' + 9`, nil, '9') + expect(t, `out = '9' - 4`, nil, '5') + expect(t, `out = '0' == '0'`, nil, true) + expect(t, `out = '0' != '0'`, nil, false) + expect(t, `out = '2' < '4'`, nil, true) + expect(t, `out = '2' > '4'`, nil, false) + expect(t, `out = '2' <= '4'`, nil, true) + expect(t, `out = '2' >= '4'`, nil, false) + expect(t, `out = '4' < '4'`, nil, false) + expect(t, `out = '4' > '4'`, nil, false) + expect(t, `out = '4' <= '4'`, nil, true) + expect(t, `out = '4' >= '4'`, nil, true) } diff --git a/runtime/vm_cond_test.go b/runtime/vm_cond_test.go index 37edaf04..56326ff3 100644 --- a/runtime/vm_cond_test.go +++ b/runtime/vm_cond_test.go @@ -3,25 +3,25 @@ package runtime_test import "testing" func TestCondExpr(t *testing.T) { - expect(t, `out = true ? 5 : 10`, 5) - expect(t, `out = false ? 5 : 10`, 10) - expect(t, `out = (1 == 1) ? 2 + 3 : 12 - 2`, 5) - expect(t, `out = (1 != 1) ? 2 + 3 : 12 - 2`, 10) - expect(t, `out = (1 == 1) ? true ? 10 - 8 : 1 + 3 : 12 - 2`, 2) - expect(t, `out = (1 == 1) ? false ? 10 - 8 : 1 + 3 : 12 - 2`, 4) + expect(t, `out = true ? 5 : 10`, nil, 5) + expect(t, `out = false ? 5 : 10`, nil, 10) + expect(t, `out = (1 == 1) ? 2 + 3 : 12 - 2`, nil, 5) + expect(t, `out = (1 != 1) ? 2 + 3 : 12 - 2`, nil, 10) + expect(t, `out = (1 == 1) ? true ? 10 - 8 : 1 + 3 : 12 - 2`, nil, 2) + expect(t, `out = (1 == 1) ? false ? 10 - 8 : 1 + 3 : 12 - 2`, nil, 4) expect(t, ` out = 0 f1 := func() { out += 10 } f2 := func() { out = -out } true ? f1() : f2() -`, 10) +`, nil, 10) expect(t, ` out = 5 f1 := func() { out += 10 } f2 := func() { out = -out } false ? f1() : f2() -`, -5) +`, nil, -5) expect(t, ` f1 := func(a) { return a + 2 } f2 := func(a) { return a - 2 } @@ -33,13 +33,13 @@ f := func(c) { } out = [f(0), f(1), f(2)] -`, ARR{2, 11, -2}) +`, nil, ARR{2, 11, -2}) - expect(t, `f := func(a) { return -a }; out = f(true ? 5 : 3)`, -5) - expect(t, `out = [false?5:10, true?1:2]`, ARR{10, 1}) + expect(t, `f := func(a) { return -a }; out = f(true ? 5 : 3)`, nil, -5) + expect(t, `out = [false?5:10, true?1:2]`, nil, ARR{10, 1}) expect(t, ` out = 1 > 2 ? 1 + 2 + 3 : - 10 - 5`, 5) + 10 - 5`, nil, 5) } diff --git a/runtime/vm_equality_test.go b/runtime/vm_equality_test.go index e61fab2b..0a3eacde 100644 --- a/runtime/vm_equality_test.go +++ b/runtime/vm_equality_test.go @@ -43,8 +43,8 @@ func TestEquality(t *testing.T) { func testEquality(t *testing.T, lhs, rhs string, expected bool) { // 1. equality is commutative // 2. equality and inequality must be always opposite - expect(t, fmt.Sprintf("out = %s == %s", lhs, rhs), expected) - expect(t, fmt.Sprintf("out = %s == %s", rhs, lhs), expected) - expect(t, fmt.Sprintf("out = %s != %s", lhs, rhs), !expected) - expect(t, fmt.Sprintf("out = %s != %s", rhs, lhs), !expected) + expect(t, fmt.Sprintf("out = %s == %s", lhs, rhs), nil, expected) + expect(t, fmt.Sprintf("out = %s == %s", rhs, lhs), nil, expected) + expect(t, fmt.Sprintf("out = %s != %s", lhs, rhs), nil, !expected) + expect(t, fmt.Sprintf("out = %s != %s", rhs, lhs), nil, !expected) } diff --git a/runtime/vm_error_report_test.go b/runtime/vm_error_report_test.go index a25219ae..72bb17a9 100644 --- a/runtime/vm_error_report_test.go +++ b/runtime/vm_error_report_test.go @@ -5,16 +5,16 @@ import "testing" func TestVMErrorInfo(t *testing.T) { expectError(t, `a := 5 a + "boo"`, - "Runtime Error: invalid operation: int + string\n\tat test:2:1") + nil, "Runtime Error: invalid operation: int + string\n\tat test:2:1") expectError(t, `a := 5 b := a(5)`, - "Runtime Error: not callable: int\n\tat test:2:6") + nil, "Runtime Error: not callable: int\n\tat test:2:6") expectError(t, `a := 5 b := {} b.x.y = 10`, - "Runtime Error: not index-assignable: undefined\n\tat test:3:1") + nil, "Runtime Error: not index-assignable: undefined\n\tat test:3:1") expectError(t, ` a := func() { @@ -22,27 +22,28 @@ a := func() { b += "foo" } a()`, - "Runtime Error: invalid operation: int + string\n\tat test:4:2") + nil, "Runtime Error: invalid operation: int + string\n\tat test:4:2") - expectErrorWithUserModules(t, `a := 5 - a + import("mod1")`, map[string]string{ - "mod1": `export "foo"`, - }, ": invalid operation: int + string\n\tat test:2:2") + expectError(t, `a := 5 +a + import("mod1")`, Opts().Module( + "mod1", `export "foo"`, + ), ": invalid operation: int + string\n\tat test:2:1") - expectErrorWithUserModules(t, `a := import("mod1")()`, map[string]string{ - "mod1": ` + expectError(t, `a := import("mod1")()`, + Opts().Module( + "mod1", ` export func() { b := 5 return b + "foo" -}`, - }, "Runtime Error: invalid operation: int + string\n\tat mod1:4:9") +}`), "Runtime Error: invalid operation: int + string\n\tat mod1:4:9") - expectErrorWithUserModules(t, `a := import("mod1")()`, map[string]string{ - "mod1": `export import("mod2")()`, - "mod2": ` + expectError(t, `a := import("mod1")()`, + Opts().Module( + "mod1", `export import("mod2")()`). + Module( + "mod2", ` export func() { b := 5 return b + "foo" -}`, - }, "Runtime Error: invalid operation: int + string\n\tat mod2:4:9") +}`), "Runtime Error: invalid operation: int + string\n\tat mod2:4:9") } diff --git a/runtime/vm_error_test.go b/runtime/vm_error_test.go index 3120e82c..8cefda1d 100644 --- a/runtime/vm_error_test.go +++ b/runtime/vm_error_test.go @@ -5,17 +5,17 @@ import ( ) func TestError(t *testing.T) { - expect(t, `out = error(1)`, errorObject(1)) - expect(t, `out = error(1).value`, 1) - expect(t, `out = error("some error")`, errorObject("some error")) - expect(t, `out = error("some" + " error")`, errorObject("some error")) - expect(t, `out = func() { return error(5) }()`, errorObject(5)) - expect(t, `out = error(error("foo"))`, errorObject(errorObject("foo"))) - expect(t, `out = error("some error")`, errorObject("some error")) - expect(t, `out = error("some error").value`, "some error") - expect(t, `out = error("some error")["value"]`, "some error") + expect(t, `out = error(1)`, nil, errorObject(1)) + expect(t, `out = error(1).value`, nil, 1) + expect(t, `out = error("some error")`, nil, errorObject("some error")) + expect(t, `out = error("some" + " error")`, nil, errorObject("some error")) + expect(t, `out = func() { return error(5) }()`, nil, errorObject(5)) + expect(t, `out = error(error("foo"))`, nil, errorObject(errorObject("foo"))) + expect(t, `out = error("some error")`, nil, errorObject("some error")) + expect(t, `out = error("some error").value`, nil, "some error") + expect(t, `out = error("some error")["value"]`, nil, "some error") - expectError(t, `error("error").err`, "invalid index on error") - expectError(t, `error("error").value_`, "invalid index on error") - expectError(t, `error([1,2,3])[1]`, "invalid index on error") + expectError(t, `error("error").err`, nil, "invalid index on error") + expectError(t, `error("error").value_`, nil, "invalid index on error") + expectError(t, `error([1,2,3])[1]`, nil, "invalid index on error") } diff --git a/runtime/vm_float_test.go b/runtime/vm_float_test.go index fa5abb51..0eb2f84a 100644 --- a/runtime/vm_float_test.go +++ b/runtime/vm_float_test.go @@ -5,11 +5,11 @@ import ( ) func TestFloat(t *testing.T) { - expect(t, `out = 0.0`, 0.0) - expect(t, `out = -10.3`, -10.3) - expect(t, `out = 3.2 + 2.0 * -4.0`, -4.8) - expect(t, `out = 4 + 2.3`, 6.3) - expect(t, `out = 2.3 + 4`, 6.3) - expect(t, `out = +5.0`, 5.0) - expect(t, `out = -5.0 + +5.0`, 0.0) + expect(t, `out = 0.0`, nil, 0.0) + expect(t, `out = -10.3`, nil, -10.3) + expect(t, `out = 3.2 + 2.0 * -4.0`, nil, -4.8) + expect(t, `out = 4 + 2.3`, nil, 6.3) + expect(t, `out = 2.3 + 4`, nil, 6.3) + expect(t, `out = +5.0`, nil, 5.0) + expect(t, `out = -5.0 + +5.0`, nil, 0.0) } diff --git a/runtime/vm_for_in_test.go b/runtime/vm_for_in_test.go index fe8fde42..21904ef9 100644 --- a/runtime/vm_for_in_test.go +++ b/runtime/vm_for_in_test.go @@ -6,20 +6,20 @@ import ( func TestForIn(t *testing.T) { // array - expect(t, `out = 0; for x in [1, 2, 3] { out += x }`, 6) // value - expect(t, `out = 0; for i, x in [1, 2, 3] { out += i + x }`, 9) // index, value - expect(t, `out = 0; func() { for i, x in [1, 2, 3] { out += i + x } }()`, 9) // index, value - expect(t, `out = 0; for i, _ in [1, 2, 3] { out += i }`, 3) // index, _ - expect(t, `out = 0; func() { for i, _ in [1, 2, 3] { out += i } }()`, 3) // index, _ + expect(t, `out = 0; for x in [1, 2, 3] { out += x }`, nil, 6) // value + expect(t, `out = 0; for i, x in [1, 2, 3] { out += i + x }`, nil, 9) // index, value + expect(t, `out = 0; func() { for i, x in [1, 2, 3] { out += i + x } }()`, nil, 9) // index, value + expect(t, `out = 0; for i, _ in [1, 2, 3] { out += i }`, nil, 3) // index, _ + expect(t, `out = 0; func() { for i, _ in [1, 2, 3] { out += i } }()`, nil, 3) // index, _ // map - expect(t, `out = 0; for v in {a:2,b:3,c:4} { out += v }`, 9) // value - expect(t, `out = ""; for k, v in {a:2,b:3,c:4} { out = k; if v==3 { break } }`, "b") // key, value - expect(t, `out = ""; for k, _ in {a:2} { out += k }`, "a") // key, _ - expect(t, `out = 0; for _, v in {a:2,b:3,c:4} { out += v }`, 9) // _, value - expect(t, `out = ""; func() { for k, v in {a:2,b:3,c:4} { out = k; if v==3 { break } } }()`, "b") // key, value + expect(t, `out = 0; for v in {a:2,b:3,c:4} { out += v }`, nil, 9) // value + expect(t, `out = ""; for k, v in {a:2,b:3,c:4} { out = k; if v==3 { break } }`, nil, "b") // key, value + expect(t, `out = ""; for k, _ in {a:2} { out += k }`, nil, "a") // key, _ + expect(t, `out = 0; for _, v in {a:2,b:3,c:4} { out += v }`, nil, 9) // _, value + expect(t, `out = ""; func() { for k, v in {a:2,b:3,c:4} { out = k; if v==3 { break } } }()`, nil, "b") // key, value // string - expect(t, `out = ""; for c in "abcde" { out += c }`, "abcde") - expect(t, `out = ""; for i, c in "abcde" { if i == 2 { continue }; out += c }`, "abde") + expect(t, `out = ""; for c in "abcde" { out += c }`, nil, "abcde") + expect(t, `out = ""; for i, c in "abcde" { if i == 2 { continue }; out += c }`, nil, "abde") } diff --git a/runtime/vm_for_test.go b/runtime/vm_for_test.go index 134aeccc..2dc492a6 100644 --- a/runtime/vm_for_test.go +++ b/runtime/vm_for_test.go @@ -12,7 +12,7 @@ func TestFor(t *testing.T) { if out == 5 { break } - }`, 5) + }`, nil, 5) expect(t, ` out = 0 @@ -21,7 +21,7 @@ func TestFor(t *testing.T) { if out == 5 { break } - }`, 5) + }`, nil, 5) expect(t, ` out = 0 @@ -31,7 +31,7 @@ func TestFor(t *testing.T) { if a == 3 { continue } if a == 5 { break } out += a - }`, 7) // 1 + 2 + 4 + }`, nil, 7) // 1 + 2 + 4 expect(t, ` out = 0 @@ -41,7 +41,7 @@ func TestFor(t *testing.T) { if a == 3 { continue } out += a if a == 5 { break } - }`, 12) // 1 + 2 + 4 + 5 + }`, nil, 12) // 1 + 2 + 4 + 5 expect(t, ` out = 0 @@ -50,7 +50,7 @@ func TestFor(t *testing.T) { if out == 5 { break } - }`, 5) + }`, nil, 5) expect(t, ` a := 0 @@ -60,7 +60,7 @@ func TestFor(t *testing.T) { break } } - out = a`, 5) + out = a`, nil, 5) expect(t, ` out = 0 @@ -70,7 +70,7 @@ func TestFor(t *testing.T) { if a == 3 { continue } if a == 5 { break } out += a - }`, 7) // 1 + 2 + 4 + }`, nil, 7) // 1 + 2 + 4 expect(t, ` out = 0 @@ -80,7 +80,7 @@ func TestFor(t *testing.T) { if a == 3 { continue } out += a if a == 5 { break } - }`, 12) // 1 + 2 + 4 + 5 + }`, nil, 12) // 1 + 2 + 4 + 5 expect(t, ` out = 0 @@ -91,13 +91,13 @@ func TestFor(t *testing.T) { return } } - }()`, 5) + }()`, nil, 5) expect(t, ` out = 0 for a:=1; a<=10; a++ { out += a - }`, 55) + }`, nil, 55) expect(t, ` out = 0 @@ -105,7 +105,7 @@ func TestFor(t *testing.T) { for b:=3; b<=6; b++ { out += b } - }`, 54) + }`, nil, 54) expect(t, ` out = 0 @@ -116,7 +116,7 @@ func TestFor(t *testing.T) { break } } - }()`, 5) + }()`, nil, 5) expect(t, ` out = 0 @@ -127,7 +127,7 @@ func TestFor(t *testing.T) { break } } - }()`, 5) + }()`, nil, 5) expect(t, ` out = func() { @@ -139,7 +139,7 @@ func TestFor(t *testing.T) { } } return a - }()`, 5) + }()`, nil, 5) expect(t, ` out = func() { @@ -151,7 +151,7 @@ func TestFor(t *testing.T) { } } return a - }()`, 5) + }()`, nil, 5) expect(t, ` out = func() { @@ -165,7 +165,7 @@ func TestFor(t *testing.T) { } }() return a - }()`, 5) + }()`, nil, 5) expect(t, ` out = func() { @@ -179,7 +179,7 @@ func TestFor(t *testing.T) { } }() return a - }()`, 5) + }()`, nil, 5) expect(t, ` out = func() { @@ -188,7 +188,7 @@ func TestFor(t *testing.T) { sum += a } return sum - }()`, 55) + }()`, nil, 55) expect(t, ` out = func() { @@ -199,7 +199,7 @@ func TestFor(t *testing.T) { } } return sum - }()`, 48) // (3+4+5) * 4 + }()`, nil, 48) // (3+4+5) * 4 expect(t, ` a := 1 @@ -208,7 +208,7 @@ func TestFor(t *testing.T) { break } } - out = a`, 5) + out = a`, nil, 5) expect(t, ` out = 0 @@ -220,7 +220,7 @@ func TestFor(t *testing.T) { if a == 5 { break } - }`, 12) // 1 + 2 + 4 + 5 + }`, nil, 12) // 1 + 2 + 4 + 5 expect(t, ` out = 0 @@ -234,5 +234,5 @@ func TestFor(t *testing.T) { break } a++ - }`, 12) // 1 + 2 + 4 + 5 + }`, nil, 12) // 1 + 2 + 4 + 5 } diff --git a/runtime/vm_function_test.go b/runtime/vm_function_test.go index 775943c4..f4c25f16 100644 --- a/runtime/vm_function_test.go +++ b/runtime/vm_function_test.go @@ -8,16 +8,16 @@ import ( func TestFunction(t *testing.T) { // function with no "return" statement returns "invalid" value. - expect(t, `f1 := func() {}; out = f1();`, objects.UndefinedValue) - expect(t, `f1 := func() {}; f2 := func() { return f1(); }; f1(); out = f2();`, objects.UndefinedValue) - expect(t, `f := func(x) { x; }; out = f(5);`, objects.UndefinedValue) + expect(t, `f1 := func() {}; out = f1();`, nil, objects.UndefinedValue) + expect(t, `f1 := func() {}; f2 := func() { return f1(); }; f1(); out = f2();`, nil, objects.UndefinedValue) + expect(t, `f := func(x) { x; }; out = f(5);`, nil, objects.UndefinedValue) - expect(t, `f := func(x) { return x; }; out = f(5);`, 5) - expect(t, `f := func(x) { return x * 2; }; out = f(5);`, 10) - expect(t, `f := func(x, y) { return x + y; }; out = f(5, 5);`, 10) - expect(t, `f := func(x, y) { return x + y; }; out = f(5 + 5, f(5, 5));`, 20) - expect(t, `out = func(x) { return x; }(5)`, 5) - expect(t, `x := 10; f := func(x) { return x; }; f(5); out = x;`, 10) + expect(t, `f := func(x) { return x; }; out = f(5);`, nil, 5) + expect(t, `f := func(x) { return x * 2; }; out = f(5);`, nil, 10) + expect(t, `f := func(x, y) { return x + y; }; out = f(5, 5);`, nil, 10) + expect(t, `f := func(x, y) { return x + y; }; out = f(5 + 5, f(5, 5));`, nil, 20) + expect(t, `out = func(x) { return x; }(5)`, nil, 5) + expect(t, `x := 10; f := func(x) { return x; }; f(5); out = x;`, nil, 10) expect(t, ` f2 := func(a) { @@ -29,7 +29,7 @@ func TestFunction(t *testing.T) { }; out = f2(10); - `, 60) + `, nil, 60) // closures expect(t, ` @@ -39,7 +39,7 @@ func TestFunction(t *testing.T) { add2 := newAdder(2); out = add2(5); - `, 7) + `, nil, 7) // function as a argument expect(t, ` @@ -48,17 +48,17 @@ func TestFunction(t *testing.T) { applyFunc := func(a, b, f) { return f(a, b) }; out = applyFunc(applyFunc(2, 2, add), 3, sub); - `, 1) + `, nil, 1) - expect(t, `f1 := func() { return 5 + 10; }; out = f1();`, 15) - expect(t, `f1 := func() { return 1 }; f2 := func() { return 2 }; out = f1() + f2()`, 3) - expect(t, `f1 := func() { return 1 }; f2 := func() { return f1() + 2 }; f3 := func() { return f2() + 3 }; out = f3()`, 6) - expect(t, `f1 := func() { return 99; 100 }; out = f1();`, 99) - expect(t, `f1 := func() { return 99; return 100 }; out = f1();`, 99) - expect(t, `f1 := func() { return 33; }; f2 := func() { return f1 }; out = f2()();`, 33) - expect(t, `one := func() { one = 1; return one }; out = one()`, 1) - expect(t, `three := func() { one := 1; two := 2; return one + two }; out = three()`, 3) - expect(t, `three := func() { one := 1; two := 2; return one + two }; seven := func() { three := 3; four := 4; return three + four }; out = three() + seven()`, 10) + expect(t, `f1 := func() { return 5 + 10; }; out = f1();`, nil, 15) + expect(t, `f1 := func() { return 1 }; f2 := func() { return 2 }; out = f1() + f2()`, nil, 3) + expect(t, `f1 := func() { return 1 }; f2 := func() { return f1() + 2 }; f3 := func() { return f2() + 3 }; out = f3()`, nil, 6) + expect(t, `f1 := func() { return 99; 100 }; out = f1();`, nil, 99) + expect(t, `f1 := func() { return 99; return 100 }; out = f1();`, nil, 99) + expect(t, `f1 := func() { return 33; }; f2 := func() { return f1 }; out = f2()();`, nil, 33) + expect(t, `one := func() { one = 1; return one }; out = one()`, nil, 1) + expect(t, `three := func() { one := 1; two := 2; return one + two }; out = three()`, nil, 3) + expect(t, `three := func() { one := 1; two := 2; return one + two }; seven := func() { three := 3; four := 4; return three + four }; out = three() + seven()`, nil, 10) expect(t, ` foo1 := func() { foo := 50 @@ -68,7 +68,7 @@ func TestFunction(t *testing.T) { foo := 100 return foo } - out = foo1() + foo2()`, 150) + out = foo1() + foo2()`, nil, 150) expect(t, ` g := 50; minusOne := func() { @@ -80,35 +80,35 @@ func TestFunction(t *testing.T) { return g - n; }; out = minusOne() + minusTwo() - `, 97) + `, nil, 97) expect(t, ` f1 := func() { f2 := func() { return 1; } return f2 }; out = f1()() - `, 1) + `, nil, 1) expect(t, ` f1 := func(a) { return a; }; - out = f1(4)`, 4) + out = f1(4)`, nil, 4) expect(t, ` f1 := func(a, b) { return a + b; }; - out = f1(1, 2)`, 3) + out = f1(1, 2)`, nil, 3) expect(t, ` sum := func(a, b) { c := a + b; return c; }; - out = sum(1, 2);`, 3) + out = sum(1, 2);`, nil, 3) expect(t, ` sum := func(a, b) { c := a + b; return c; }; - out = sum(1, 2) + sum(3, 4);`, 10) + out = sum(1, 2) + sum(3, 4);`, nil, 10) expect(t, ` sum := func(a, b) { @@ -118,7 +118,7 @@ func TestFunction(t *testing.T) { outer := func() { return sum(1, 2) + sum(3, 4) }; - out = outer();`, 10) + out = outer();`, nil, 10) expect(t, ` g := 10; @@ -133,11 +133,11 @@ func TestFunction(t *testing.T) { } out = outer() + g - `, 50) + `, nil, 50) - expectError(t, `func() { return 1; }(1)`, "wrong number of arguments") - expectError(t, `func(a) { return a; }()`, "wrong number of arguments") - expectError(t, `func(a, b) { return a + b; }(1)`, "wrong number of arguments") + expectError(t, `func() { return 1; }(1)`, nil, "wrong number of arguments") + expectError(t, `func(a) { return a; }()`, nil, "wrong number of arguments") + expectError(t, `func(a, b) { return a + b; }(1)`, nil, "wrong number of arguments") expect(t, ` f1 := func(a) { @@ -145,7 +145,7 @@ func TestFunction(t *testing.T) { }; f2 := f1(99); out = f2() - `, 99) + `, nil, 99) expect(t, ` f1 := func(a, b) { @@ -154,7 +154,7 @@ func TestFunction(t *testing.T) { f2 := f1(1, 2); out = f2(8); - `, 11) + `, nil, 11) expect(t, ` f1 := func(a, b) { c := a + b; @@ -162,7 +162,7 @@ func TestFunction(t *testing.T) { }; f2 := f1(1, 2); out = f2(8); - `, 11) + `, nil, 11) expect(t, ` f1 := func(a, b) { c := a + b; @@ -174,7 +174,7 @@ func TestFunction(t *testing.T) { f2 := f1(1, 2); f3 := f2(3); out = f3(8); - `, 14) + `, nil, 14) expect(t, ` a := 1; f1 := func(b) { @@ -185,7 +185,7 @@ func TestFunction(t *testing.T) { f2 := f1(2); f3 := f2(3); out = f3(8); - `, 14) + `, nil, 14) expect(t, ` f1 := func(a, b) { one := func() { return a; }; @@ -194,7 +194,7 @@ func TestFunction(t *testing.T) { }; f2 := f1(9, 90); out = f2(); - `, 99) + `, nil, 99) // global function recursion expect(t, ` @@ -207,7 +207,7 @@ func TestFunction(t *testing.T) { return fib(x-1) + fib(x-2) } } - out = fib(15)`, 610) + out = fib(15)`, nil, 610) // local function recursion expect(t, ` @@ -216,9 +216,9 @@ out = func() { return x == 0 ? 0 : x + sum(x-1) } return sum(5) -}()`, 15) +}()`, nil, 15) - expectError(t, `return 5`, "return not allowed outside function") + expectError(t, `return 5`, nil, "return not allowed outside function") // closure and block scopes expect(t, ` @@ -230,7 +230,7 @@ func() { out = a + 5 } }() -}()`, 15) +}()`, nil, 15) expect(t, ` func() { a := 10 @@ -240,7 +240,7 @@ func() { out = a + b() } }() -}()`, 15) +}()`, nil, 15) expect(t, ` func() { a := 10 @@ -252,5 +252,5 @@ func() { } }() }() -}()`, 15) +}()`, nil, 15) } diff --git a/runtime/vm_if_test.go b/runtime/vm_if_test.go index 648f0640..864d1de0 100644 --- a/runtime/vm_if_test.go +++ b/runtime/vm_if_test.go @@ -7,29 +7,29 @@ import ( ) func TestIf(t *testing.T) { - expect(t, `if (true) { out = 10 }`, 10) - expect(t, `if (false) { out = 10 }`, objects.UndefinedValue) - expect(t, `if (false) { out = 10 } else { out = 20 }`, 20) - expect(t, `if (1) { out = 10 }`, 10) - expect(t, `if (0) { out = 10 } else { out = 20 }`, 20) - expect(t, `if (1 < 2) { out = 10 }`, 10) - expect(t, `if (1 > 2) { out = 10 }`, objects.UndefinedValue) - expect(t, `if (1 < 2) { out = 10 } else { out = 20 }`, 10) - expect(t, `if (1 > 2) { out = 10 } else { out = 20 }`, 20) + expect(t, `if (true) { out = 10 }`, nil, 10) + expect(t, `if (false) { out = 10 }`, nil, objects.UndefinedValue) + expect(t, `if (false) { out = 10 } else { out = 20 }`, nil, 20) + expect(t, `if (1) { out = 10 }`, nil, 10) + expect(t, `if (0) { out = 10 } else { out = 20 }`, nil, 20) + expect(t, `if (1 < 2) { out = 10 }`, nil, 10) + expect(t, `if (1 > 2) { out = 10 }`, nil, objects.UndefinedValue) + expect(t, `if (1 < 2) { out = 10 } else { out = 20 }`, nil, 10) + expect(t, `if (1 > 2) { out = 10 } else { out = 20 }`, nil, 20) - expect(t, `if (1 < 2) { out = 10 } else if (1 > 2) { out = 20 } else { out = 30 }`, 10) - expect(t, `if (1 > 2) { out = 10 } else if (1 < 2) { out = 20 } else { out = 30 }`, 20) - expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else { out = 30 }`, 30) - expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else if (1 < 2) { out = 30 } else { out = 40 }`, 30) - expect(t, `if (1 > 2) { out = 10 } else if (1 < 2) { out = 20; out = 21; out = 22 } else { out = 30 }`, 22) - expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else { out = 30; out = 31; out = 32}`, 32) - expect(t, `if (1 > 2) { out = 10 } else if (1 < 2) { if (1 == 2) { out = 21 } else { out = 22 } } else { out = 30 }`, 22) - expect(t, `if (1 > 2) { out = 10 } else if (1 < 2) { if (1 == 2) { out = 21 } else if (2 == 3) { out = 22 } else { out = 23 } } else { out = 30 }`, 23) - expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { if (1 == 2) { out = 21 } else if (2 == 3) { out = 22 } else { out = 23 } } else { out = 30 }`, 30) - expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else { if (1 == 2) { out = 31 } else if (2 == 3) { out = 32 } else { out = 33 } }`, 33) + expect(t, `if (1 < 2) { out = 10 } else if (1 > 2) { out = 20 } else { out = 30 }`, nil, 10) + expect(t, `if (1 > 2) { out = 10 } else if (1 < 2) { out = 20 } else { out = 30 }`, nil, 20) + expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else { out = 30 }`, nil, 30) + expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else if (1 < 2) { out = 30 } else { out = 40 }`, nil, 30) + expect(t, `if (1 > 2) { out = 10 } else if (1 < 2) { out = 20; out = 21; out = 22 } else { out = 30 }`, nil, 22) + expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else { out = 30; out = 31; out = 32}`, nil, 32) + expect(t, `if (1 > 2) { out = 10 } else if (1 < 2) { if (1 == 2) { out = 21 } else { out = 22 } } else { out = 30 }`, nil, 22) + expect(t, `if (1 > 2) { out = 10 } else if (1 < 2) { if (1 == 2) { out = 21 } else if (2 == 3) { out = 22 } else { out = 23 } } else { out = 30 }`, nil, 23) + expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { if (1 == 2) { out = 21 } else if (2 == 3) { out = 22 } else { out = 23 } } else { out = 30 }`, nil, 30) + expect(t, `if (1 > 2) { out = 10 } else if (1 == 2) { out = 20 } else { if (1 == 2) { out = 31 } else if (2 == 3) { out = 32 } else { out = 33 } }`, nil, 33) - expect(t, `if a:=0; a<1 { out = 10 }`, 10) - expect(t, `a:=0; if a++; a==1 { out = 10 }`, 10) + expect(t, `if a:=0; a<1 { out = 10 }`, nil, 10) + expect(t, `a:=0; if a++; a==1 { out = 10 }`, nil, 10) expect(t, ` func() { a := 1 @@ -37,7 +37,7 @@ func() { out = a } }() -`, 2) +`, nil, 2) expect(t, ` func() { a := 1 @@ -47,7 +47,7 @@ func() { out = 20 } }() -`, 20) +`, nil, 20) expect(t, ` func() { a := 1 @@ -60,5 +60,5 @@ func() { out = a }() -`, 3) +`, nil, 3) } diff --git a/runtime/vm_immutable_test.go b/runtime/vm_immutable_test.go index 51568652..99ffd5b1 100644 --- a/runtime/vm_immutable_test.go +++ b/runtime/vm_immutable_test.go @@ -9,46 +9,46 @@ import ( func TestImmutable(t *testing.T) { // primitive types are already immutable values // immutable expression has no effects. - expect(t, `a := immutable(1); out = a`, 1) - expect(t, `a := 5; b := immutable(a); out = b`, 5) - expect(t, `a := immutable(1); a = 5; out = a`, 5) + expect(t, `a := immutable(1); out = a`, nil, 1) + expect(t, `a := 5; b := immutable(a); out = b`, nil, 5) + expect(t, `a := immutable(1); a = 5; out = a`, nil, 5) // array - expectError(t, `a := immutable([1, 2, 3]); a[1] = 5`, "not index-assignable") - expectError(t, `a := immutable(["foo", [1,2,3]]); a[1] = "bar"`, "not index-assignable") - expect(t, `a := immutable(["foo", [1,2,3]]); a[1][1] = "bar"; out = a`, IARR{"foo", ARR{1, "bar", 3}}) - expectError(t, `a := immutable(["foo", immutable([1,2,3])]); a[1][1] = "bar"`, "not index-assignable") - expectError(t, `a := ["foo", immutable([1,2,3])]; a[1][1] = "bar"`, "not index-assignable") - expect(t, `a := immutable([1,2,3]); b := copy(a); b[1] = 5; out = b`, ARR{1, 5, 3}) - expect(t, `a := immutable([1,2,3]); b := copy(a); b[1] = 5; out = a`, IARR{1, 2, 3}) - expect(t, `out = immutable([1,2,3]) == [1,2,3]`, true) - expect(t, `out = immutable([1,2,3]) == immutable([1,2,3])`, true) - expect(t, `out = [1,2,3] == immutable([1,2,3])`, true) - expect(t, `out = immutable([1,2,3]) == [1,2]`, false) - expect(t, `out = immutable([1,2,3]) == immutable([1,2])`, false) - expect(t, `out = [1,2,3] == immutable([1,2])`, false) - expect(t, `out = immutable([1, 2, 3, 4])[1]`, 2) - expect(t, `out = immutable([1, 2, 3, 4])[1:3]`, ARR{2, 3}) - expect(t, `a := immutable([1,2,3]); a = 5; out = a`, 5) - expect(t, `a := immutable([1, 2, 3]); out = a[5]`, objects.UndefinedValue) + expectError(t, `a := immutable([1, 2, 3]); a[1] = 5`, nil, "not index-assignable") + expectError(t, `a := immutable(["foo", [1,2,3]]); a[1] = "bar"`, nil, "not index-assignable") + expect(t, `a := immutable(["foo", [1,2,3]]); a[1][1] = "bar"; out = a`, nil, IARR{"foo", ARR{1, "bar", 3}}) + expectError(t, `a := immutable(["foo", immutable([1,2,3])]); a[1][1] = "bar"`, nil, "not index-assignable") + expectError(t, `a := ["foo", immutable([1,2,3])]; a[1][1] = "bar"`, nil, "not index-assignable") + expect(t, `a := immutable([1,2,3]); b := copy(a); b[1] = 5; out = b`, nil, ARR{1, 5, 3}) + expect(t, `a := immutable([1,2,3]); b := copy(a); b[1] = 5; out = a`, nil, IARR{1, 2, 3}) + expect(t, `out = immutable([1,2,3]) == [1,2,3]`, nil, true) + expect(t, `out = immutable([1,2,3]) == immutable([1,2,3])`, nil, true) + expect(t, `out = [1,2,3] == immutable([1,2,3])`, nil, true) + expect(t, `out = immutable([1,2,3]) == [1,2]`, nil, false) + expect(t, `out = immutable([1,2,3]) == immutable([1,2])`, nil, false) + expect(t, `out = [1,2,3] == immutable([1,2])`, nil, false) + expect(t, `out = immutable([1, 2, 3, 4])[1]`, nil, 2) + expect(t, `out = immutable([1, 2, 3, 4])[1:3]`, nil, ARR{2, 3}) + expect(t, `a := immutable([1,2,3]); a = 5; out = a`, nil, 5) + expect(t, `a := immutable([1, 2, 3]); out = a[5]`, nil, objects.UndefinedValue) // map - expectError(t, `a := immutable({b: 1, c: 2}); a.b = 5`, "not index-assignable") - expectError(t, `a := immutable({b: 1, c: 2}); a["b"] = "bar"`, "not index-assignable") - expect(t, `a := immutable({b: 1, c: [1,2,3]}); a.c[1] = "bar"; out = a`, IMAP{"b": 1, "c": ARR{1, "bar", 3}}) - expectError(t, `a := immutable({b: 1, c: immutable([1,2,3])}); a.c[1] = "bar"`, "not index-assignable") - expectError(t, `a := {b: 1, c: immutable([1,2,3])}; a.c[1] = "bar"`, "not index-assignable") - expect(t, `out = immutable({a:1,b:2}) == {a:1,b:2}`, true) - expect(t, `out = immutable({a:1,b:2}) == immutable({a:1,b:2})`, true) - expect(t, `out = {a:1,b:2} == immutable({a:1,b:2})`, true) - expect(t, `out = immutable({a:1,b:2}) == {a:1,b:3}`, false) - expect(t, `out = immutable({a:1,b:2}) == immutable({a:1,b:3})`, false) - expect(t, `out = {a:1,b:2} == immutable({a:1,b:3})`, false) - expect(t, `out = immutable({a:1,b:2}).b`, 2) - expect(t, `out = immutable({a:1,b:2})["b"]`, 2) - expect(t, `a := immutable({a:1,b:2}); a = 5; out = 5`, 5) - expect(t, `a := immutable({a:1,b:2}); out = a.c`, objects.UndefinedValue) + expectError(t, `a := immutable({b: 1, c: 2}); a.b = 5`, nil, "not index-assignable") + expectError(t, `a := immutable({b: 1, c: 2}); a["b"] = "bar"`, nil, "not index-assignable") + expect(t, `a := immutable({b: 1, c: [1,2,3]}); a.c[1] = "bar"; out = a`, nil, IMAP{"b": 1, "c": ARR{1, "bar", 3}}) + expectError(t, `a := immutable({b: 1, c: immutable([1,2,3])}); a.c[1] = "bar"`, nil, "not index-assignable") + expectError(t, `a := {b: 1, c: immutable([1,2,3])}; a.c[1] = "bar"`, nil, "not index-assignable") + expect(t, `out = immutable({a:1,b:2}) == {a:1,b:2}`, nil, true) + expect(t, `out = immutable({a:1,b:2}) == immutable({a:1,b:2})`, nil, true) + expect(t, `out = {a:1,b:2} == immutable({a:1,b:2})`, nil, true) + expect(t, `out = immutable({a:1,b:2}) == {a:1,b:3}`, nil, false) + expect(t, `out = immutable({a:1,b:2}) == immutable({a:1,b:3})`, nil, false) + expect(t, `out = {a:1,b:2} == immutable({a:1,b:3})`, nil, false) + expect(t, `out = immutable({a:1,b:2}).b`, nil, 2) + expect(t, `out = immutable({a:1,b:2})["b"]`, nil, 2) + expect(t, `a := immutable({a:1,b:2}); a = 5; out = 5`, nil, 5) + expect(t, `a := immutable({a:1,b:2}); out = a.c`, nil, objects.UndefinedValue) - expect(t, `a := immutable({b: 5, c: "foo"}); out = a.b`, 5) - expectError(t, `a := immutable({b: 5, c: "foo"}); a.b = 10`, "not index-assignable") + expect(t, `a := immutable({b: 5, c: "foo"}); out = a.b`, nil, 5) + expectError(t, `a := immutable({b: 5, c: "foo"}); a.b = 10`, nil, "not index-assignable") } diff --git a/runtime/vm_inc_dec_test.go b/runtime/vm_inc_dec_test.go index f01ad99f..0845e6f5 100644 --- a/runtime/vm_inc_dec_test.go +++ b/runtime/vm_inc_dec_test.go @@ -5,18 +5,17 @@ import ( ) func TestIncDec(t *testing.T) { - expect(t, `out = 0; out++`, 1) - expect(t, `out = 0; out--`, -1) - expect(t, `a := 0; a++; out = a`, 1) - expect(t, `a := 0; a++; a--; out = a`, 0) + expect(t, `out = 0; out++`, nil, 1) + expect(t, `out = 0; out--`, nil, -1) + expect(t, `a := 0; a++; out = a`, nil, 1) + expect(t, `a := 0; a++; a--; out = a`, nil, 0) // this seems strange but it works because 'a += b' is // translated into 'a = a + b' and string type takes other types for + operator. - expect(t, `a := "foo"; a++; out = a`, "foo1") - expectError(t, `a := "foo"; a--`, "invalid operation") + expect(t, `a := "foo"; a++; out = a`, nil, "foo1") + expectError(t, `a := "foo"; a--`, nil, "invalid operation") - expectError(t, `a++`, "unresolved reference") // not declared - expectError(t, `a--`, "unresolved reference") // not declared - //expectError(t, `a := 0; b := a++`) // inc-dec is statement not expression <- parser error - expectError(t, `4++`, "unresolved reference") + expectError(t, `a++`, nil, "unresolved reference") // not declared + expectError(t, `a--`, nil, "unresolved reference") // not declared + expectError(t, `4++`, nil, "unresolved reference") } diff --git a/runtime/vm_indexable_test.go b/runtime/vm_indexable_test.go index e7741909..77b69eb6 100644 --- a/runtime/vm_indexable_test.go +++ b/runtime/vm_indexable_test.go @@ -225,44 +225,44 @@ func (o *StringArray) Call(args ...objects.Object) (ret objects.Object, err erro func TestIndexable(t *testing.T) { dict := func() *StringDict { return &StringDict{Value: map[string]string{"a": "foo", "b": "bar"}} } - expectWithSymbols(t, `out = dict["a"]`, "foo", SYM{"dict": dict()}) - expectWithSymbols(t, `out = dict["B"]`, "bar", SYM{"dict": dict()}) - expectWithSymbols(t, `out = dict["x"]`, objects.UndefinedValue, SYM{"dict": dict()}) - expectErrorWithSymbols(t, `dict[0]`, SYM{"dict": dict()}, "invalid index type") + expect(t, `out = dict["a"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "foo") + expect(t, `out = dict["B"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "bar") + expect(t, `out = dict["x"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), objects.UndefinedValue) + expectError(t, `dict[0]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "invalid index type") strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} } - expectWithSymbols(t, `out = cir[0]`, "one", SYM{"cir": strCir()}) - expectWithSymbols(t, `out = cir[1]`, "two", SYM{"cir": strCir()}) - expectWithSymbols(t, `out = cir[-1]`, "three", SYM{"cir": strCir()}) - expectWithSymbols(t, `out = cir[-2]`, "two", SYM{"cir": strCir()}) - expectWithSymbols(t, `out = cir[3]`, "one", SYM{"cir": strCir()}) - expectErrorWithSymbols(t, `cir["a"]`, SYM{"cir": strCir()}, "invalid index type") + expect(t, `out = cir[0]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "one") + expect(t, `out = cir[1]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "two") + expect(t, `out = cir[-1]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "three") + expect(t, `out = cir[-2]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "two") + expect(t, `out = cir[3]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "one") + expectError(t, `cir["a"]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "invalid index type") strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} } - expectWithSymbols(t, `out = arr["one"]`, 0, SYM{"arr": strArr()}) - expectWithSymbols(t, `out = arr["three"]`, 2, SYM{"arr": strArr()}) - expectWithSymbols(t, `out = arr["four"]`, objects.UndefinedValue, SYM{"arr": strArr()}) - expectWithSymbols(t, `out = arr[0]`, "one", SYM{"arr": strArr()}) - expectWithSymbols(t, `out = arr[1]`, "two", SYM{"arr": strArr()}) - expectErrorWithSymbols(t, `arr[-1]`, SYM{"arr": strArr()}, "index out of bounds") + expect(t, `out = arr["one"]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), 0) + expect(t, `out = arr["three"]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), 2) + expect(t, `out = arr["four"]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), objects.UndefinedValue) + expect(t, `out = arr[0]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "one") + expect(t, `out = arr[1]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "two") + expectError(t, `arr[-1]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "index out of bounds") } func TestIndexAssignable(t *testing.T) { dict := func() *StringDict { return &StringDict{Value: map[string]string{"a": "foo", "b": "bar"}} } - expectWithSymbols(t, `dict["a"] = "1984"; out = dict["a"]`, "1984", SYM{"dict": dict()}) - expectWithSymbols(t, `dict["c"] = "1984"; out = dict["c"]`, "1984", SYM{"dict": dict()}) - expectWithSymbols(t, `dict["c"] = 1984; out = dict["C"]`, "1984", SYM{"dict": dict()}) - expectErrorWithSymbols(t, `dict[0] = "1984"`, SYM{"dict": dict()}, "invalid index type") + expect(t, `dict["a"] = "1984"; out = dict["a"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "1984") + expect(t, `dict["c"] = "1984"; out = dict["c"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "1984") + expect(t, `dict["c"] = 1984; out = dict["C"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "1984") + expectError(t, `dict[0] = "1984"`, Opts().Symbol("dict", dict()).Skip2ndPass(), "invalid index type") strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} } - expectWithSymbols(t, `cir[0] = "ONE"; out = cir[0]`, "ONE", SYM{"cir": strCir()}) - expectWithSymbols(t, `cir[1] = "TWO"; out = cir[1]`, "TWO", SYM{"cir": strCir()}) - expectWithSymbols(t, `cir[-1] = "THREE"; out = cir[2]`, "THREE", SYM{"cir": strCir()}) - expectWithSymbols(t, `cir[0] = "ONE"; out = cir[3]`, "ONE", SYM{"cir": strCir()}) - expectErrorWithSymbols(t, `cir["a"] = "ONE"`, SYM{"cir": strCir()}, "invalid index type") + expect(t, `cir[0] = "ONE"; out = cir[0]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "ONE") + expect(t, `cir[1] = "TWO"; out = cir[1]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "TWO") + expect(t, `cir[-1] = "THREE"; out = cir[2]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "THREE") + expect(t, `cir[0] = "ONE"; out = cir[3]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "ONE") + expectError(t, `cir["a"] = "ONE"`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "invalid index type") strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} } - expectWithSymbols(t, `arr[0] = "ONE"; out = arr[0]`, "ONE", SYM{"arr": strArr()}) - expectWithSymbols(t, `arr[1] = "TWO"; out = arr[1]`, "TWO", SYM{"arr": strArr()}) - expectErrorWithSymbols(t, `arr["one"] = "ONE"`, SYM{"arr": strArr()}, "invalid index type") + expect(t, `arr[0] = "ONE"; out = arr[0]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "ONE") + expect(t, `arr[1] = "TWO"; out = arr[1]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "TWO") + expectError(t, `arr["one"] = "ONE"`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "invalid index type") } diff --git a/runtime/vm_integer_test.go b/runtime/vm_integer_test.go index ca0462fb..4b1d78d9 100644 --- a/runtime/vm_integer_test.go +++ b/runtime/vm_integer_test.go @@ -5,26 +5,26 @@ import ( ) func TestInteger(t *testing.T) { - expect(t, `out = 5`, 5) - expect(t, `out = 10`, 10) - expect(t, `out = -5`, -5) - expect(t, `out = -10`, -10) - expect(t, `out = 5 + 5 + 5 + 5 - 10`, 10) - expect(t, `out = 2 * 2 * 2 * 2 * 2`, 32) - expect(t, `out = -50 + 100 + -50`, 0) - expect(t, `out = 5 * 2 + 10`, 20) - expect(t, `out = 5 + 2 * 10`, 25) - expect(t, `out = 20 + 2 * -10`, 0) - expect(t, `out = 50 / 2 * 2 + 10`, 60) - expect(t, `out = 2 * (5 + 10)`, 30) - expect(t, `out = 3 * 3 * 3 + 10`, 37) - expect(t, `out = 3 * (3 * 3) + 10`, 37) - expect(t, `out = (5 + 10 * 2 + 15 /3) * 2 + -10`, 50) - expect(t, `out = 5 % 3`, 2) - expect(t, `out = 5 % 3 + 4`, 6) - expect(t, `out = +5`, 5) - expect(t, `out = +5 + -5`, 0) + expect(t, `out = 5`, nil, 5) + expect(t, `out = 10`, nil, 10) + expect(t, `out = -5`, nil, -5) + expect(t, `out = -10`, nil, -10) + expect(t, `out = 5 + 5 + 5 + 5 - 10`, nil, 10) + expect(t, `out = 2 * 2 * 2 * 2 * 2`, nil, 32) + expect(t, `out = -50 + 100 + -50`, nil, 0) + expect(t, `out = 5 * 2 + 10`, nil, 20) + expect(t, `out = 5 + 2 * 10`, nil, 25) + expect(t, `out = 20 + 2 * -10`, nil, 0) + expect(t, `out = 50 / 2 * 2 + 10`, nil, 60) + expect(t, `out = 2 * (5 + 10)`, nil, 30) + expect(t, `out = 3 * 3 * 3 + 10`, nil, 37) + expect(t, `out = 3 * (3 * 3) + 10`, nil, 37) + expect(t, `out = (5 + 10 * 2 + 15 /3) * 2 + -10`, nil, 50) + expect(t, `out = 5 % 3`, nil, 2) + expect(t, `out = 5 % 3 + 4`, nil, 6) + expect(t, `out = +5`, nil, 5) + expect(t, `out = +5 + -5`, nil, 0) - expect(t, `out = 9 + '0'`, '9') - expect(t, `out = '9' - 5`, '4') + expect(t, `out = 9 + '0'`, nil, '9') + expect(t, `out = '9' - 5`, nil, '4') } diff --git a/runtime/vm_iterable_test.go b/runtime/vm_iterable_test.go index b7a43da8..bddbf2c6 100644 --- a/runtime/vm_iterable_test.go +++ b/runtime/vm_iterable_test.go @@ -38,7 +38,7 @@ func (o *StringArray) Iterate() objects.Iterator { func TestIterable(t *testing.T) { strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} } - expectWithSymbols(t, `for i, s in arr { out += i }`, 3, SYM{"arr": strArr()}) - expectWithSymbols(t, `for i, s in arr { out += s }`, "onetwothree", SYM{"arr": strArr()}) - expectWithSymbols(t, `for i, s in arr { out += s + i }`, "one0two1three2", SYM{"arr": strArr()}) + expect(t, `for i, s in arr { out += i }`, Opts().Symbol("arr", strArr()).Skip2ndPass(), 3) + expect(t, `for i, s in arr { out += s }`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "onetwothree") + expect(t, `for i, s in arr { out += s + i }`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "one0two1three2") } diff --git a/runtime/vm_logic_test.go b/runtime/vm_logic_test.go index 8bd053a6..24f98b0b 100644 --- a/runtime/vm_logic_test.go +++ b/runtime/vm_logic_test.go @@ -3,39 +3,39 @@ package runtime_test import "testing" func TestLogical(t *testing.T) { - expect(t, `out = true && true`, true) - expect(t, `out = true && false`, false) - expect(t, `out = false && true`, false) - expect(t, `out = false && false`, false) - expect(t, `out = !true && true`, false) - expect(t, `out = !true && false`, false) - expect(t, `out = !false && true`, true) - expect(t, `out = !false && false`, false) + expect(t, `out = true && true`, nil, true) + expect(t, `out = true && false`, nil, false) + expect(t, `out = false && true`, nil, false) + expect(t, `out = false && false`, nil, false) + expect(t, `out = !true && true`, nil, false) + expect(t, `out = !true && false`, nil, false) + expect(t, `out = !false && true`, nil, true) + expect(t, `out = !false && false`, nil, false) - expect(t, `out = true || true`, true) - expect(t, `out = true || false`, true) - expect(t, `out = false || true`, true) - expect(t, `out = false || false`, false) - expect(t, `out = !true || true`, true) - expect(t, `out = !true || false`, false) - expect(t, `out = !false || true`, true) - expect(t, `out = !false || false`, true) + expect(t, `out = true || true`, nil, true) + expect(t, `out = true || false`, nil, true) + expect(t, `out = false || true`, nil, true) + expect(t, `out = false || false`, nil, false) + expect(t, `out = !true || true`, nil, true) + expect(t, `out = !true || false`, nil, false) + expect(t, `out = !false || true`, nil, true) + expect(t, `out = !false || false`, nil, true) - expect(t, `out = 1 && 2`, 2) - expect(t, `out = 1 || 2`, 1) - expect(t, `out = 1 && 0`, 0) - expect(t, `out = 1 || 0`, 1) - expect(t, `out = 1 && (0 || 2)`, 2) - expect(t, `out = 0 || (0 || 2)`, 2) - expect(t, `out = 0 || (0 && 2)`, 0) - expect(t, `out = 0 || (2 && 0)`, 0) + expect(t, `out = 1 && 2`, nil, 2) + expect(t, `out = 1 || 2`, nil, 1) + expect(t, `out = 1 && 0`, nil, 0) + expect(t, `out = 1 || 0`, nil, 1) + expect(t, `out = 1 && (0 || 2)`, nil, 2) + expect(t, `out = 0 || (0 || 2)`, nil, 2) + expect(t, `out = 0 || (0 && 2)`, nil, 0) + expect(t, `out = 0 || (2 && 0)`, nil, 0) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; t() && f()`, 7) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; f() && t()`, 7) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; f() || t()`, 3) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; t() || f()`, 3) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !t() && f()`, 3) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !f() && t()`, 3) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !f() || t()`, 7) - expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !t() || f()`, 7) + expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; t() && f()`, nil, 7) + expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; f() && t()`, nil, 7) + expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; f() || t()`, nil, 3) + expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; t() || f()`, nil, 3) + expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !t() && f()`, nil, 3) + expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !f() && t()`, nil, 3) + expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !f() || t()`, nil, 7) + expect(t, `t:=func() {out = 3; return true}; f:=func() {out = 7; return false}; !t() || f()`, nil, 7) } diff --git a/runtime/vm_map_test.go b/runtime/vm_map_test.go index d3914078..4ade6764 100644 --- a/runtime/vm_map_test.go +++ b/runtime/vm_map_test.go @@ -12,7 +12,7 @@ out = { one: 10 - 9, two: 1 + 1, three: 6 / 2 -}`, MAP{ +}`, nil, MAP{ "one": 1, "two": 2, "three": 3, @@ -23,16 +23,16 @@ out = { "one": 10 - 9, "two": 1 + 1, "three": 6 / 2 -}`, MAP{ +}`, nil, MAP{ "one": 1, "two": 2, "three": 3, }) - expect(t, `out = {foo: 5}["foo"]`, 5) - expect(t, `out = {foo: 5}["bar"]`, objects.UndefinedValue) - expect(t, `key := "foo"; out = {foo: 5}[key]`, 5) - expect(t, `out = {}["foo"]`, objects.UndefinedValue) + expect(t, `out = {foo: 5}["foo"]`, nil, 5) + expect(t, `out = {foo: 5}["bar"]`, nil, objects.UndefinedValue) + expect(t, `key := "foo"; out = {foo: 5}[key]`, nil, 5) + expect(t, `out = {}["foo"]`, nil, objects.UndefinedValue) expect(t, ` m := { @@ -41,11 +41,11 @@ m := { } } out = m["foo"](2) + m["foo"](3) -`, 10) +`, nil, 10) // map assignment is copy-by-reference - expect(t, `m1 := {k1: 1, k2: "foo"}; m2 := m1; m1.k1 = 5; out = m2.k1`, 5) - expect(t, `m1 := {k1: 1, k2: "foo"}; m2 := m1; m2.k1 = 3; out = m1.k1`, 3) - expect(t, `func() { m1 := {k1: 1, k2: "foo"}; m2 := m1; m1.k1 = 5; out = m2.k1 }()`, 5) - expect(t, `func() { m1 := {k1: 1, k2: "foo"}; m2 := m1; m2.k1 = 3; out = m1.k1 }()`, 3) + expect(t, `m1 := {k1: 1, k2: "foo"}; m2 := m1; m1.k1 = 5; out = m2.k1`, nil, 5) + expect(t, `m1 := {k1: 1, k2: "foo"}; m2 := m1; m2.k1 = 3; out = m1.k1`, nil, 3) + expect(t, `func() { m1 := {k1: 1, k2: "foo"}; m2 := m1; m1.k1 = 5; out = m2.k1 }()`, nil, 5) + expect(t, `func() { m1 := {k1: 1, k2: "foo"}; m2 := m1; m2.k1 = 3; out = m1.k1 }()`, nil, 3) } diff --git a/runtime/vm_module_test.go b/runtime/vm_module_test.go index 46b59392..6310ac15 100644 --- a/runtime/vm_module_test.go +++ b/runtime/vm_module_test.go @@ -9,89 +9,66 @@ import ( ) func TestBuiltin(t *testing.T) { + m := Opts().Module("math", + &objects.BuiltinModule{ + Attrs: 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 + }, + }, + }, + }) - mathModule := map[string]objects.Object{ - "math": &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 - }}, - }}, - } // 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) + expect(t, `math := import("math"); out = math.abs(1)`, m, 1.0) + expect(t, `math := import("math"); out = math.abs(-1)`, m, 1.0) + expect(t, `math := import("math"); out = math.abs(1.0)`, m, 1.0) + expect(t, `math := import("math"); out = math.abs(-1.0)`, m, 1.0) } func TestUserModules(t *testing.T) { - // user modules - // export none - expectWithUserModules(t, `out = import("mod1")`, objects.UndefinedValue, map[string]string{ - "mod1": `fn := func() { return 5.0 }; a := 2`, - }) + expect(t, `out = import("mod1")`, Opts().Module("mod1", `fn := func() { return 5.0 }; a := 2`), objects.UndefinedValue) // export values - expectWithUserModules(t, `out = import("mod1")`, 5, map[string]string{ - "mod1": `export 5`, - }) - expectWithUserModules(t, `out = import("mod1")`, "foo", map[string]string{ - "mod1": `export "foo"`, - }) + expect(t, `out = import("mod1")`, Opts().Module("mod1", `export 5`), 5) + expect(t, `out = import("mod1")`, Opts().Module("mod1", `export "foo"`), "foo") // export compound types - expectWithUserModules(t, `out = import("mod1")`, IARR{1, 2, 3}, map[string]string{ - "mod1": `export [1, 2, 3]`, - }) - expectWithUserModules(t, `out = import("mod1")`, IMAP{"a": 1, "b": 2}, map[string]string{ - "mod1": `export {a: 1, b: 2}`, - }) + expect(t, `out = import("mod1")`, Opts().Module("mod1", `export [1, 2, 3]`), IARR{1, 2, 3}) + expect(t, `out = import("mod1")`, Opts().Module("mod1", `export {a: 1, b: 2}`), IMAP{"a": 1, "b": 2}) + // export value is immutable - expectErrorWithUserModules(t, `m1 := import("mod1"); m1.a = 5`, map[string]string{ - "mod1": `export {a: 1, b: 2}`, - }, "not index-assignable") - expectErrorWithUserModules(t, `m1 := import("mod1"); m1[1] = 5`, map[string]string{ - "mod1": `export [1, 2, 3]`, - }, "not index-assignable") + expectError(t, `m1 := import("mod1"); m1.a = 5`, Opts().Module("mod1", `export {a: 1, b: 2}`), "not index-assignable") + expectError(t, `m1 := import("mod1"); m1[1] = 5`, Opts().Module("mod1", `export [1, 2, 3]`), "not index-assignable") // code after export statement will not be executed - expectWithUserModules(t, `out = import("mod1")`, 10, map[string]string{ - "mod1": `a := 10; export a; a = 20`, - }) - expectWithUserModules(t, `out = import("mod1")`, 10, map[string]string{ - "mod1": `a := 10; export a; a = 20; export a`, - }) + expect(t, `out = import("mod1")`, Opts().Module("mod1", `a := 10; export a; a = 20`), 10) + expect(t, `out = import("mod1")`, Opts().Module("mod1", `a := 10; export a; a = 20; export a`), 10) // export function - expectWithUserModules(t, `out = import("mod1")()`, 5.0, map[string]string{ - "mod1": `export func() { return 5.0 }`, - }) + expect(t, `out = import("mod1")()`, Opts().Module("mod1", `export func() { return 5.0 }`), 5.0) // export function that reads module-global variable - expectWithUserModules(t, `out = import("mod1")()`, 6.5, map[string]string{ - "mod1": `a := 1.5; export func() { return a + 5.0 }`, - }) + expect(t, `out = import("mod1")()`, Opts().Module("mod1", `a := 1.5; export func() { return a + 5.0 }`), 6.5) // export function that read local variable - expectWithUserModules(t, `out = import("mod1")()`, 6.5, map[string]string{ - "mod1": `export func() { a := 1.5; return a + 5.0 }`, - }) + expect(t, `out = import("mod1")()`, Opts().Module("mod1", `export func() { a := 1.5; return a + 5.0 }`), 6.5) // export function that read free variables - expectWithUserModules(t, `out = import("mod1")()`, 6.5, map[string]string{ - "mod1": `export func() { a := 1.5; return func() { return a + 5.0 }() }`, - }) + expect(t, `out = import("mod1")()`, Opts().Module("mod1", `export func() { a := 1.5; return func() { return a + 5.0 }() }`), 6.5) // recursive function in module - expectWithUserModules(t, `out = import("mod1")`, 15, map[string]string{ - "mod1": ` + expect(t, `out = import("mod1")`, Opts().Module( + "mod1", ` a := func(x) { return x == 0 ? 0 : x + a(x-1) } export a(5) -`}) - expectWithUserModules(t, `out = import("mod1")`, 15, map[string]string{ - "mod1": ` +`), 15) + expect(t, `out = import("mod1")`, Opts().Module( + "mod1", ` export func() { a := func(x) { return x == 0 ? 0 : x + a(x-1) @@ -99,117 +76,114 @@ export func() { return a(5) }() -`}) +`), 15) // (main) -> mod1 -> mod2 - expectWithUserModules(t, `out = import("mod1")()`, 5.0, map[string]string{ - "mod1": `export import("mod2")`, - "mod2": `export func() { return 5.0 }`, - }) + expect(t, `out = import("mod1")()`, + Opts().Module("mod1", `export import("mod2")`). + Module("mod2", `export func() { return 5.0 }`), + 5.0) // (main) -> mod1 -> mod2 // -> mod2 - expectWithUserModules(t, `import("mod1"); out = import("mod2")()`, 5.0, map[string]string{ - "mod1": `export import("mod2")`, - "mod2": `export func() { return 5.0 }`, - }) + expect(t, `import("mod1"); out = import("mod2")()`, + Opts().Module("mod1", `export import("mod2")`). + Module("mod2", `export func() { return 5.0 }`), + 5.0) // (main) -> mod1 -> mod2 -> mod3 // -> mod2 -> mod3 - expectWithUserModules(t, `import("mod1"); out = import("mod2")()`, 5.0, map[string]string{ - "mod1": `export import("mod2")`, - "mod2": `export import("mod3")`, - "mod3": `export func() { return 5.0 }`, - }) + expect(t, `import("mod1"); out = import("mod2")()`, + Opts().Module("mod1", `export import("mod2")`). + Module("mod2", `export import("mod3")`). + Module("mod3", `export func() { return 5.0 }`), + 5.0) // cyclic imports // (main) -> mod1 -> mod2 -> mod1 - expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ - "mod1": `import("mod2")`, - "mod2": `import("mod1")`, - }, "Compile Error: cyclic module import: mod1\n\tat mod2:1:1") + expectError(t, `import("mod1")`, + Opts().Module("mod1", `import("mod2")`). + Module("mod2", `import("mod1")`), + "Compile Error: cyclic module import: mod1\n\tat mod2:1:1") // (main) -> mod1 -> mod2 -> mod3 -> mod1 - expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ - "mod1": `import("mod2")`, - "mod2": `import("mod3")`, - "mod3": `import("mod1")`, - }, "Compile Error: cyclic module import: mod1\n\tat mod3:1:1") + expectError(t, `import("mod1")`, + Opts().Module("mod1", `import("mod2")`). + Module("mod2", `import("mod3")`). + Module("mod3", `import("mod1")`), + "Compile Error: cyclic module import: mod1\n\tat mod3:1:1") // (main) -> mod1 -> mod2 -> mod3 -> mod2 - expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ - "mod1": `import("mod2")`, - "mod2": `import("mod3")`, - "mod3": `import("mod2")`, - }, "Compile Error: cyclic module import: mod2\n\tat mod3:1:1") + expectError(t, `import("mod1")`, + Opts().Module("mod1", `import("mod2")`). + Module("mod2", `import("mod3")`). + Module("mod3", `import("mod2")`), + "Compile Error: cyclic module import: mod2\n\tat mod3:1:1") // unknown modules - expectErrorWithUserModules(t, `import("mod0")`, map[string]string{ - "mod1": `a := 5`, - }, "module 'mod0' not found") - expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ - "mod1": `import("mod2")`, - }, "module 'mod2' not found") + expectError(t, `import("mod0")`, Opts().Module("mod1", `a := 5`), "module 'mod0' not found") + expectError(t, `import("mod1")`, Opts().Module("mod1", `import("mod2")`), "module 'mod2' not found") // module is immutable but its variables is not necessarily immutable. - expectWithUserModules(t, `m1 := import("mod1"); m1.a.b = 5; out = m1.a.b`, 5, map[string]string{ - "mod1": `export {a: {b: 3}}`, - }) + expect(t, `m1 := import("mod1"); m1.a.b = 5; out = m1.a.b`, + Opts().Module("mod1", `export {a: {b: 3}}`), + 5) // make sure module has same builtin functions - expectWithUserModules(t, `out = import("mod1")`, "int", map[string]string{ - "mod1": `export func() { return type_name(0) }()`, - }) + expect(t, `out = import("mod1")`, + Opts().Module("mod1", `export func() { return type_name(0) }()`), + "int") // 'export' statement is ignored outside module - expectNoMod(t, `a := 5; export func() { a = 10 }(); out = a`, 5) + expect(t, `a := 5; export func() { a = 10 }(); out = a`, Opts().Skip2ndPass(), 5) // 'export' must be in the top-level - expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ - "mod1": `func() { export 5 }()`, - }, "Compile Error: export not allowed inside function\n\tat mod1:1:10") - expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ - "mod1": `func() { func() { export 5 }() }()`, - }, "Compile Error: export not allowed inside function\n\tat mod1:1:19") + expectError(t, `import("mod1")`, + Opts().Module("mod1", `func() { export 5 }()`), + "Compile Error: export not allowed inside function\n\tat mod1:1:10") + expectError(t, `import("mod1")`, + Opts().Module("mod1", `func() { func() { export 5 }() }()`), + "Compile Error: export not allowed inside function\n\tat mod1:1:19") // module cannot access outer scope - expectErrorWithUserModules(t, `a := 5; import("mod1")`, map[string]string{ - "mod1": `export a`, - }, "Compile Error: unresolved reference 'a'\n\tat mod1:1:8") + expectError(t, `a := 5; import("mod1")`, + Opts().Module("mod1", `export a`), + "Compile Error: unresolved reference 'a'\n\tat mod1:1:8") // runtime error within modules - expectErrorWithUserModules(t, ` + expectError(t, ` a := 1; b := import("mod1"); b(a)`, - map[string]string{"mod1": ` + Opts().Module("mod1", ` export func(a) { a() } -`, - }, "Runtime Error: not callable: int\n\tat mod1:3:4\n\tat test:4:1") +`), "Runtime Error: not callable: int\n\tat mod1:3:4\n\tat test:4:1") } func TestModuleBlockScopes(t *testing.T) { - randModule := map[string]objects.Object{ - "rand": &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 - }}, - }}, - } + m := Opts().Module("rand", + &objects.BuiltinModule{ + Attrs: 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 - expectWithUserAndBuiltinModules(t, `out = import("mod1")()`, 1, map[string]string{ - "mod1": ` + expect(t, `out = import("mod1")()`, m.Module( + "mod1", ` rand := import("rand") foo := func() { return 1 } export func() { rand.intn(3) return foo() - } - `, - }, randModule) + }`), 1) - expectWithUserAndBuiltinModules(t, `out = import("mod1")()`, 10, map[string]string{ - "mod1": ` + expect(t, `out = import("mod1")()`, m.Module( + "mod1", ` rand := import("rand") foo := func() { return 1 } export func() { @@ -217,11 +191,10 @@ export func() { if foo() {} return 10 } -`, - }, randModule) +`), 10) - expectWithUserAndBuiltinModules(t, `out = import("mod1")()`, 10, map[string]string{ - "mod1": ` + expect(t, `out = import("mod1")()`, m.Module( + "mod1", ` rand := import("rand") foo := func() { return 1 } export func() { @@ -229,6 +202,5 @@ export func() { if true { foo() } return 10 } - `, - }, randModule) + `), 10) } diff --git a/runtime/vm_not_operator_test.go b/runtime/vm_not_operator_test.go index b7028c96..9b6123ba 100644 --- a/runtime/vm_not_operator_test.go +++ b/runtime/vm_not_operator_test.go @@ -5,11 +5,11 @@ import ( ) func TestBangOperator(t *testing.T) { - expect(t, `out = !true`, false) - expect(t, `out = !false`, true) - expect(t, `out = !0`, true) - expect(t, `out = !5`, false) - expect(t, `out = !!true`, true) - expect(t, `out = !!false`, false) - expect(t, `out = !!5`, true) + expect(t, `out = !true`, nil, false) + expect(t, `out = !false`, nil, true) + expect(t, `out = !0`, nil, true) + expect(t, `out = !5`, nil, false) + expect(t, `out = !!true`, nil, true) + expect(t, `out = !!false`, nil, false) + expect(t, `out = !!5`, nil, true) } diff --git a/runtime/vm_objects_limit_test.go b/runtime/vm_objects_limit_test.go index 3e6f2491..877c9418 100644 --- a/runtime/vm_objects_limit_test.go +++ b/runtime/vm_objects_limit_test.go @@ -37,13 +37,13 @@ f() } func testAllocsLimit(t *testing.T, src string, limit int64) { - expectAllocsLimit(t, src, -1, objects.UndefinedValue) // no limit - expectAllocsLimit(t, src, limit, objects.UndefinedValue) - expectAllocsLimit(t, src, limit+1, objects.UndefinedValue) + expect(t, src, Opts().Skip2ndPass(), objects.UndefinedValue) // no limit + expect(t, src, Opts().MaxAllocs(limit).Skip2ndPass(), objects.UndefinedValue) + expect(t, src, Opts().MaxAllocs(limit+1).Skip2ndPass(), objects.UndefinedValue) if limit > 1 { - expectErrorAllocsLimit(t, src, limit-1, "allocation limit exceeded") + expectError(t, src, Opts().MaxAllocs(limit-1).Skip2ndPass(), "allocation limit exceeded") } if limit > 2 { - expectErrorAllocsLimit(t, src, limit-2, "allocation limit exceeded") + expectError(t, src, Opts().MaxAllocs(limit-2).Skip2ndPass(), "allocation limit exceeded") } } diff --git a/runtime/vm_return_test.go b/runtime/vm_return_test.go index 9e6d686d..52514295 100644 --- a/runtime/vm_return_test.go +++ b/runtime/vm_return_test.go @@ -5,10 +5,10 @@ import ( ) func TestReturn(t *testing.T) { - expect(t, `out = func() { return 10; }()`, 10) - expect(t, `out = func() { return 10; return 9; }()`, 10) - expect(t, `out = func() { return 2 * 5; return 9 }()`, 10) - expect(t, `out = func() { 9; return 2 * 5; return 9 }()`, 10) + expect(t, `out = func() { return 10; }()`, nil, 10) + expect(t, `out = func() { return 10; return 9; }()`, nil, 10) + expect(t, `out = func() { return 2 * 5; return 9 }()`, nil, 10) + expect(t, `out = func() { 9; return 2 * 5; return 9 }()`, nil, 10) expect(t, ` out = func() { if (10 > 1) { @@ -18,7 +18,7 @@ func TestReturn(t *testing.T) { return 1; } - }()`, 10) + }()`, nil, 10) - expect(t, `f1 := func() { return 2 * 5; }; out = f1()`, 10) + expect(t, `f1 := func() { return 2 * 5; }; out = f1()`, nil, 10) } diff --git a/runtime/vm_selector_test.go b/runtime/vm_selector_test.go index c024282c..9461cd9c 100644 --- a/runtime/vm_selector_test.go +++ b/runtime/vm_selector_test.go @@ -7,9 +7,9 @@ import ( ) func TestSelector(t *testing.T) { - expect(t, `a := {k1: 5, k2: "foo"}; out = a.k1`, 5) - expect(t, `a := {k1: 5, k2: "foo"}; out = a.k2`, "foo") - expect(t, `a := {k1: 5, k2: "foo"}; out = a.k3`, objects.UndefinedValue) + expect(t, `a := {k1: 5, k2: "foo"}; out = a.k1`, nil, 5) + expect(t, `a := {k1: 5, k2: "foo"}; out = a.k2`, nil, "foo") + expect(t, `a := {k1: 5, k2: "foo"}; out = a.k3`, nil, objects.UndefinedValue) expect(t, ` a := { @@ -19,7 +19,7 @@ a := { }, c: "foo bar" } -out = a.b.c`, 4) +out = a.b.c`, nil, 4) expect(t, ` a := { @@ -29,7 +29,7 @@ a := { }, c: "foo bar" } -b := a.x.c`, objects.UndefinedValue) +b := a.x.c`, nil, objects.UndefinedValue) expect(t, ` a := { @@ -39,25 +39,25 @@ a := { }, c: "foo bar" } -b := a.x.y`, objects.UndefinedValue) +b := a.x.y`, nil, objects.UndefinedValue) - expect(t, `a := {b: 1, c: "foo"}; a.b = 2; out = a.b`, 2) - expect(t, `a := {b: 1, c: "foo"}; a.c = 2; out = a.c`, 2) // type not checked on sub-field - expect(t, `a := {b: {c: 1}}; a.b.c = 2; out = a.b.c`, 2) - expect(t, `a := {b: 1}; a.c = 2; out = a`, MAP{"b": 1, "c": 2}) - expect(t, `a := {b: {c: 1}}; a.b.d = 2; out = a`, MAP{"b": MAP{"c": 1, "d": 2}}) + expect(t, `a := {b: 1, c: "foo"}; a.b = 2; out = a.b`, nil, 2) + expect(t, `a := {b: 1, c: "foo"}; a.c = 2; out = a.c`, nil, 2) // type not checked on sub-field + expect(t, `a := {b: {c: 1}}; a.b.c = 2; out = a.b.c`, nil, 2) + expect(t, `a := {b: 1}; a.c = 2; out = a`, nil, MAP{"b": 1, "c": 2}) + expect(t, `a := {b: {c: 1}}; a.b.d = 2; out = a`, nil, MAP{"b": MAP{"c": 1, "d": 2}}) - expect(t, `func() { a := {b: 1, c: "foo"}; a.b = 2; out = a.b }()`, 2) - expect(t, `func() { a := {b: 1, c: "foo"}; a.c = 2; out = a.c }()`, 2) // type not checked on sub-field - expect(t, `func() { a := {b: {c: 1}}; a.b.c = 2; out = a.b.c }()`, 2) - expect(t, `func() { a := {b: 1}; a.c = 2; out = a }()`, MAP{"b": 1, "c": 2}) - expect(t, `func() { a := {b: {c: 1}}; a.b.d = 2; out = a }()`, MAP{"b": MAP{"c": 1, "d": 2}}) + expect(t, `func() { a := {b: 1, c: "foo"}; a.b = 2; out = a.b }()`, nil, 2) + expect(t, `func() { a := {b: 1, c: "foo"}; a.c = 2; out = a.c }()`, nil, 2) // type not checked on sub-field + expect(t, `func() { a := {b: {c: 1}}; a.b.c = 2; out = a.b.c }()`, nil, 2) + expect(t, `func() { a := {b: 1}; a.c = 2; out = a }()`, nil, MAP{"b": 1, "c": 2}) + expect(t, `func() { a := {b: {c: 1}}; a.b.d = 2; out = a }()`, nil, MAP{"b": MAP{"c": 1, "d": 2}}) - expect(t, `func() { a := {b: 1, c: "foo"}; func() { a.b = 2 }(); out = a.b }()`, 2) - expect(t, `func() { a := {b: 1, c: "foo"}; func() { a.c = 2 }(); out = a.c }()`, 2) // type not checked on sub-field - expect(t, `func() { a := {b: {c: 1}}; func() { a.b.c = 2 }(); out = a.b.c }()`, 2) - expect(t, `func() { a := {b: 1}; func() { a.c = 2 }(); out = a }()`, MAP{"b": 1, "c": 2}) - expect(t, `func() { a := {b: {c: 1}}; func() { a.b.d = 2 }(); out = a }()`, MAP{"b": MAP{"c": 1, "d": 2}}) + expect(t, `func() { a := {b: 1, c: "foo"}; func() { a.b = 2 }(); out = a.b }()`, nil, 2) + expect(t, `func() { a := {b: 1, c: "foo"}; func() { a.c = 2 }(); out = a.c }()`, nil, 2) // type not checked on sub-field + expect(t, `func() { a := {b: {c: 1}}; func() { a.b.c = 2 }(); out = a.b.c }()`, nil, 2) + expect(t, `func() { a := {b: 1}; func() { a.c = 2 }(); out = a }()`, nil, MAP{"b": 1, "c": 2}) + expect(t, `func() { a := {b: {c: 1}}; func() { a.b.d = 2 }(); out = a }()`, nil, MAP{"b": MAP{"c": 1, "d": 2}}) expect(t, ` a := { @@ -69,7 +69,7 @@ a := { } } out = [a.b[2], a.c.d, a.c.e, a.c.f[1]] -`, ARR{3, 8, "foo", 8}) +`, nil, ARR{3, 8, "foo", 8}) expect(t, ` func() { @@ -79,12 +79,12 @@ func() { b = 7 // make sure a[1] has a COPY of value of 'b' out = a[1] }() -`, 9) +`, nil, 9) - expectError(t, `a := {b: {c: 1}}; a.d.c = 2`, "not index-assignable") - expectError(t, `a := [1, 2, 3]; a.b = 2`, "invalid index type") - expectError(t, `a := "foo"; a.b = 2`, "not index-assignable") - expectError(t, `func() { a := {b: {c: 1}}; a.d.c = 2 }()`, "not index-assignable") - expectError(t, `func() { a := [1, 2, 3]; a.b = 2 }()`, "invalid index type") - expectError(t, `func() { a := "foo"; a.b = 2 }()`, "not index-assignable") + expectError(t, `a := {b: {c: 1}}; a.d.c = 2`, nil, "not index-assignable") + expectError(t, `a := [1, 2, 3]; a.b = 2`, nil, "invalid index type") + expectError(t, `a := "foo"; a.b = 2`, nil, "not index-assignable") + expectError(t, `func() { a := {b: {c: 1}}; a.d.c = 2 }()`, nil, "not index-assignable") + expectError(t, `func() { a := [1, 2, 3]; a.b = 2 }()`, nil, "invalid index type") + expectError(t, `func() { a := "foo"; a.b = 2 }()`, nil, "not index-assignable") } diff --git a/runtime/vm_source_modules_test.go b/runtime/vm_source_modules_test.go new file mode 100644 index 00000000..ab5ca3a0 --- /dev/null +++ b/runtime/vm_source_modules_test.go @@ -0,0 +1,13 @@ +package runtime_test + +import ( + "testing" + + "github.com/d5/tengo/stdlib" +) + +func TestSourceModules(t *testing.T) { + expect(t, `enum := import("enum"); out = enum.any([1,2,3], func(i, v) { return v == 2 })`, + Opts().Module("enum", stdlib.SourceModules["enum"]), + true) +} diff --git a/runtime/vm_string_test.go b/runtime/vm_string_test.go index 61406347..f1b9c463 100644 --- a/runtime/vm_string_test.go +++ b/runtime/vm_string_test.go @@ -8,67 +8,66 @@ import ( ) func TestString(t *testing.T) { - expect(t, `out = "Hello World!"`, "Hello World!") - expect(t, `out = "Hello" + " " + "World!"`, "Hello World!") + expect(t, `out = "Hello World!"`, nil, "Hello World!") + expect(t, `out = "Hello" + " " + "World!"`, nil, "Hello World!") - expect(t, `out = "Hello" == "Hello"`, true) - expect(t, `out = "Hello" == "World"`, false) - expect(t, `out = "Hello" != "Hello"`, false) - expect(t, `out = "Hello" != "World"`, true) + expect(t, `out = "Hello" == "Hello"`, nil, true) + expect(t, `out = "Hello" == "World"`, nil, false) + expect(t, `out = "Hello" != "Hello"`, nil, false) + expect(t, `out = "Hello" != "World"`, nil, true) // index operator str := "abcdef" strStr := `"abcdef"` strLen := 6 for idx := 0; idx < strLen; idx++ { - expect(t, fmt.Sprintf("out = %s[%d]", strStr, idx), str[idx]) - expect(t, fmt.Sprintf("out = %s[0 + %d]", strStr, idx), str[idx]) - expect(t, fmt.Sprintf("out = %s[1 + %d - 1]", strStr, idx), str[idx]) - expect(t, fmt.Sprintf("idx := %d; out = %s[idx]", idx, strStr), str[idx]) + expect(t, fmt.Sprintf("out = %s[%d]", strStr, idx), nil, str[idx]) + expect(t, fmt.Sprintf("out = %s[0 + %d]", strStr, idx), nil, str[idx]) + expect(t, fmt.Sprintf("out = %s[1 + %d - 1]", strStr, idx), nil, str[idx]) + expect(t, fmt.Sprintf("idx := %d; out = %s[idx]", idx, strStr), nil, str[idx]) } - expect(t, fmt.Sprintf("%s[%d]", strStr, -1), objects.UndefinedValue) - expect(t, fmt.Sprintf("%s[%d]", strStr, strLen), objects.UndefinedValue) + expect(t, fmt.Sprintf("%s[%d]", strStr, -1), nil, objects.UndefinedValue) + expect(t, fmt.Sprintf("%s[%d]", strStr, strLen), nil, objects.UndefinedValue) // slice operator for low := 0; low <= strLen; low++ { - expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, low, low), "") + expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, low, low), nil, "") for high := low; high <= strLen; high++ { - expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, low, high), str[low:high]) - expect(t, fmt.Sprintf("out = %s[0 + %d : 0 + %d]", strStr, low, high), str[low:high]) - expect(t, fmt.Sprintf("out = %s[1 + %d - 1 : 1 + %d - 1]", strStr, low, high), str[low:high]) - expect(t, fmt.Sprintf("out = %s[:%d]", strStr, high), str[:high]) - expect(t, fmt.Sprintf("out = %s[%d:]", strStr, low), str[low:]) + expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, low, high), nil, str[low:high]) + expect(t, fmt.Sprintf("out = %s[0 + %d : 0 + %d]", strStr, low, high), nil, str[low:high]) + expect(t, fmt.Sprintf("out = %s[1 + %d - 1 : 1 + %d - 1]", strStr, low, high), nil, str[low:high]) + expect(t, fmt.Sprintf("out = %s[:%d]", strStr, high), nil, str[:high]) + expect(t, fmt.Sprintf("out = %s[%d:]", strStr, low), nil, str[low:]) } } - expect(t, fmt.Sprintf("out = %s[:]", strStr), str[:]) - expect(t, fmt.Sprintf("out = %s[:]", strStr), str) - expect(t, fmt.Sprintf("out = %s[%d:]", strStr, -1), str) - expect(t, fmt.Sprintf("out = %s[:%d]", strStr, strLen+1), str) - expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, 2, 2), "") + expect(t, fmt.Sprintf("out = %s[:]", strStr), nil, str[:]) + expect(t, fmt.Sprintf("out = %s[:]", strStr), nil, str) + expect(t, fmt.Sprintf("out = %s[%d:]", strStr, -1), nil, str) + expect(t, fmt.Sprintf("out = %s[:%d]", strStr, strLen+1), nil, str) + expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, 2, 2), nil, "") - expectError(t, fmt.Sprintf("%s[:%d]", strStr, -1), "invalid slice index") - expectError(t, fmt.Sprintf("%s[%d:]", strStr, strLen+1), "invalid slice index") - expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 0, -1), "invalid slice index") - expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 2, 1), "invalid slice index") + expectError(t, fmt.Sprintf("%s[:%d]", strStr, -1), nil, "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:]", strStr, strLen+1), nil, "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 0, -1), nil, "invalid slice index") + expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 2, 1), nil, "invalid slice index") // string concatenation with other types - expect(t, `out = "foo" + 1`, "foo1") + expect(t, `out = "foo" + 1`, nil, "foo1") // Float.String() returns the smallest number of digits // necessary such that ParseFloat will return f exactly. - expect(t, `out = "foo" + 1.0`, "foo1") // <- note '1' instead of '1.0' - expect(t, `out = "foo" + 1.5`, "foo1.5") - expect(t, `out = "foo" + true`, "footrue") - expect(t, `out = "foo" + 'X'`, "fooX") - expect(t, `out = "foo" + error(5)`, "fooerror: 5") - expect(t, `out = "foo" + undefined`, "foo") - expect(t, `out = "foo" + [1,2,3]`, "foo[1, 2, 3]") - //expect(t, `out = "foo" + {a: 1, b: 2}`, "foo{a: 1, b: 2}") // TODO: commented because order of key is not consistent + expect(t, `out = "foo" + 1.0`, nil, "foo1") // <- note '1' instead of '1.0' + expect(t, `out = "foo" + 1.5`, nil, "foo1.5") + expect(t, `out = "foo" + true`, nil, "footrue") + expect(t, `out = "foo" + 'X'`, nil, "fooX") + expect(t, `out = "foo" + error(5)`, nil, "fooerror: 5") + expect(t, `out = "foo" + undefined`, nil, "foo") + expect(t, `out = "foo" + [1,2,3]`, nil, "foo[1, 2, 3]") // also works with "+=" operator - expect(t, `out = "foo"; out += 1.5`, "foo1.5") + expect(t, `out = "foo"; out += 1.5`, nil, "foo1.5") // string concats works only when string is LHS - expectError(t, `1 + "foo"`, "invalid operation") + expectError(t, `1 + "foo"`, nil, "invalid operation") - expectError(t, `"foo" - "bar"`, "invalid operation") + expectError(t, `"foo" - "bar"`, nil, "invalid operation") } diff --git a/runtime/vm_tail_call_test.go b/runtime/vm_tail_call_test.go index 449231a0..c310b6bd 100644 --- a/runtime/vm_tail_call_test.go +++ b/runtime/vm_tail_call_test.go @@ -10,7 +10,7 @@ func TestTailCall(t *testing.T) { } return fac(n-1, n*a) } - out = fac(5, 1)`, 120) + out = fac(5, 1)`, nil, 120) expect(t, ` fac := func(n, a) { @@ -20,7 +20,7 @@ func TestTailCall(t *testing.T) { x := {foo: fac} // indirection for test return x.foo(n-1, n*a) } - out = fac(5, 1)`, 120) + out = fac(5, 1)`, nil, 120) expect(t, ` fib := func(x, s) { @@ -31,7 +31,7 @@ func TestTailCall(t *testing.T) { } return fib(x-1, fib(x-2, s)) } - out = fib(15, 0)`, 610) + out = fib(15, 0)`, nil, 610) expect(t, ` fib := func(n, a, b) { @@ -42,7 +42,7 @@ func TestTailCall(t *testing.T) { } return fib(n-1, b, a + b) } - out = fib(15, 0, 1)`, 610) + out = fib(15, 0, 1)`, nil, 610) // global variable and no return value expect(t, ` @@ -54,7 +54,7 @@ func TestTailCall(t *testing.T) { out += a foo(a-1) } - foo(10)`, 55) + foo(10)`, nil, 55) expect(t, ` f1 := func() { @@ -65,7 +65,7 @@ func TestTailCall(t *testing.T) { } return f2(5, 0) } - out = f1()`, 15) + out = f1()`, nil, 15) // tail-call replacing loop // without tail-call optimization, this code will cause stack overflow @@ -78,7 +78,7 @@ iter := func(n, max) { return iter(n+1, max) } out = iter(0, 9999) -`, 9999) +`, nil, 9999) expect(t, ` c := 0 iter := func(n, max) { @@ -91,7 +91,7 @@ iter := func(n, max) { } iter(0, 9999) out = c -`, 9999) +`, nil, 9999) } // tail call with free vars @@ -107,5 +107,5 @@ func() { return f2(n-1, n+s) } out = f2(5, 0) -}()`, 25) +}()`, nil, 25) } diff --git a/runtime/vm_test.go b/runtime/vm_test.go index c9a3ef22..8fa4c05c 100644 --- a/runtime/vm_test.go +++ b/runtime/vm_test.go @@ -22,53 +22,81 @@ type IARR []interface{} type IMAP map[string]interface{} type MAP = map[string]interface{} type ARR = []interface{} -type SYM = map[string]objects.Object -func expect(t *testing.T, input string, expected interface{}) { - runVM(t, input, expected, nil, nil, nil, -1, false) +type testopts struct { + modules map[string]objects.Importable + symbols map[string]objects.Object + maxAllocs int64 + skip2ndPass bool } -func expectAllocsLimit(t *testing.T, input string, maxAllocs int64, expected interface{}) { - runVM(t, input, expected, nil, nil, nil, maxAllocs, true) -} - -func expectNoMod(t *testing.T, input string, expected interface{}) { - runVM(t, input, expected, nil, nil, nil, -1, true) -} - -func expectWithSymbols(t *testing.T, input string, expected interface{}, symbols map[string]objects.Object) { - runVM(t, input, expected, symbols, nil, nil, -1, true) +func Opts() *testopts { + return &testopts{ + modules: make(map[string]objects.Importable), + symbols: make(map[string]objects.Object), + maxAllocs: -1, + skip2ndPass: false, + } } -func expectWithUserModules(t *testing.T, input string, expected interface{}, userModules map[string]string) { - runVM(t, input, expected, nil, userModules, nil, -1, false) +func (o *testopts) copy() *testopts { + c := &testopts{ + modules: make(map[string]objects.Importable), + symbols: make(map[string]objects.Object), + maxAllocs: o.maxAllocs, + skip2ndPass: o.skip2ndPass, + } + for k, v := range o.modules { + c.modules[k] = v + } + for k, v := range o.symbols { + c.symbols[k] = v + } + return c } -func expectWithBuiltinModules(t *testing.T, input string, expected interface{}, builtinModules map[string]objects.Object) { - runVM(t, input, expected, nil, nil, builtinModules, -1, false) +func (o *testopts) Module(name string, mod interface{}) *testopts { + c := o.copy() + switch mod := mod.(type) { + case objects.Importable: + c.modules[name] = mod + case string: + c.modules[name] = &objects.SourceModule{Src: []byte(mod)} + case []byte: + c.modules[name] = &objects.SourceModule{Src: mod} + default: + panic(fmt.Errorf("invalid module type: %T", mod)) + } + return c } -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, -1, false) +func (o *testopts) Symbol(name string, value objects.Object) *testopts { + c := o.copy() + c.symbols[name] = value + return c } -func expectError(t *testing.T, input, expected string) { - runVMError(t, input, nil, nil, nil, -1, expected) +func (o *testopts) MaxAllocs(limit int64) *testopts { + c := o.copy() + c.maxAllocs = limit + return c } -func expectErrorAllocsLimit(t *testing.T, input string, maxAllocs int64, expected string) { - runVMError(t, input, nil, nil, nil, maxAllocs, expected) +func (o *testopts) Skip2ndPass() *testopts { + c := o.copy() + c.skip2ndPass = true + return c } -func expectErrorWithUserModules(t *testing.T, input string, userModules map[string]string, expected string) { - runVMError(t, input, nil, userModules, nil, -1, expected) -} +func expect(t *testing.T, input string, opts *testopts, expected interface{}) { + if opts == nil { + opts = Opts() + } -func expectErrorWithSymbols(t *testing.T, input string, symbols map[string]objects.Object, expected string) { - runVMError(t, input, symbols, nil, nil, -1, expected) -} + symbols := opts.symbols + modules := opts.modules + maxAllocs := opts.maxAllocs -func runVM(t *testing.T, input string, expected interface{}, symbols map[string]objects.Object, userModules map[string]string, builtinModules map[string]objects.Object, maxAllocs int64, skipModuleTest bool) { expectedObj := toObject(expected) if symbols == nil { @@ -85,7 +113,7 @@ func runVM(t *testing.T, input string, expected interface{}, symbols map[string] } // compiler/VM - res, trace, err := traceCompileRun(file, symbols, userModules, builtinModules, maxAllocs) + res, trace, err := traceCompileRun(file, symbols, modules, maxAllocs) if !assert.NoError(t, err) || !assert.Equal(t, expectedObj, res[testOut]) { t.Log("\n" + strings.Join(trace, "\n")) @@ -93,7 +121,7 @@ func runVM(t *testing.T, input string, expected interface{}, symbols map[string] } // second pass: run the code as import module - if !skipModuleTest { + if !opts.skip2ndPass { file := parse(t, `out = import("__code__")`) if file == nil { return @@ -107,12 +135,11 @@ func runVM(t *testing.T, input string, expected interface{}, symbols map[string] expectedObj = &objects.ImmutableMap{Value: eo.Value} } - if userModules == nil { - userModules = make(map[string]string) + modules["__code__"] = &objects.SourceModule{ + Src: []byte(fmt.Sprintf("out := undefined; %s; export out", input)), } - userModules["__code__"] = fmt.Sprintf("out := undefined; %s; export out", input) - res, trace, err := traceCompileRun(file, symbols, userModules, builtinModules, maxAllocs) + res, trace, err := traceCompileRun(file, symbols, modules, maxAllocs) if !assert.NoError(t, err) || !assert.Equal(t, expectedObj, res[testOut]) { t.Log("\n" + strings.Join(trace, "\n")) @@ -120,7 +147,15 @@ 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, builtinModules map[string]objects.Object, maxAllocs int64, expected string) { +func expectError(t *testing.T, input string, opts *testopts, expected string) { + if opts == nil { + opts = Opts() + } + + symbols := opts.symbols + modules := opts.modules + maxAllocs := opts.maxAllocs + expected = strings.TrimSpace(expected) if expected == "" { panic("expected must not be empty") @@ -133,7 +168,7 @@ func runVMError(t *testing.T, input string, symbols map[string]objects.Object, u } // compiler/VM - _, trace, err := traceCompileRun(program, symbols, userModules, builtinModules, maxAllocs) + _, trace, err := traceCompileRun(program, symbols, modules, maxAllocs) 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")) @@ -149,7 +184,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, builtinModules map[string]objects.Object, maxAllocs int64) (res map[string]objects.Object, trace []string, err error) { +func traceCompileRun(file *ast.File, symbols map[string]objects.Object, modules map[string]objects.Importable, maxAllocs int64) (res map[string]objects.Object, trace []string, err error) { var v *runtime.VM defer func() { @@ -185,20 +220,8 @@ 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, bm, tr) - c.SetModuleLoader(func(moduleName string) ([]byte, error) { - if src, ok := userModules[moduleName]; ok { - return []byte(src), nil - } - - return nil, fmt.Errorf("module '%s' not found", moduleName) - }) + c := compiler.NewCompiler(file.InputFile, symTable, nil, modules, tr) err = c.Compile(file) trace = append(trace, fmt.Sprintf("\n[Compiler Trace]\n\n%s", strings.Join(tr.Out, ""))) if err != nil { @@ -210,7 +233,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, builtinModules, maxAllocs) + v = runtime.NewVM(bytecode, globals, maxAllocs) err = v.Run() { diff --git a/runtime/vm_undefined_test.go b/runtime/vm_undefined_test.go index 460a9a93..6796459f 100644 --- a/runtime/vm_undefined_test.go +++ b/runtime/vm_undefined_test.go @@ -7,15 +7,15 @@ import ( ) func TestUndefined(t *testing.T) { - expect(t, `out = undefined`, objects.UndefinedValue) - expect(t, `out = undefined.a`, objects.UndefinedValue) - expect(t, `out = undefined[1]`, objects.UndefinedValue) - expect(t, `out = undefined.a.b`, objects.UndefinedValue) - expect(t, `out = undefined[1][2]`, objects.UndefinedValue) - expect(t, `out = undefined ? 1 : 2`, 2) - expect(t, `out = undefined == undefined`, true) - expect(t, `out = undefined == 1`, false) - expect(t, `out = 1 == undefined`, false) - expect(t, `out = undefined == float([])`, true) - expect(t, `out = float([]) == undefined`, true) + expect(t, `out = undefined`, nil, objects.UndefinedValue) + expect(t, `out = undefined.a`, nil, objects.UndefinedValue) + expect(t, `out = undefined[1]`, nil, objects.UndefinedValue) + expect(t, `out = undefined.a.b`, nil, objects.UndefinedValue) + expect(t, `out = undefined[1][2]`, nil, objects.UndefinedValue) + expect(t, `out = undefined ? 1 : 2`, nil, 2) + expect(t, `out = undefined == undefined`, nil, true) + expect(t, `out = undefined == 1`, nil, false) + expect(t, `out = 1 == undefined`, nil, false) + expect(t, `out = undefined == float([])`, nil, true) + expect(t, `out = float([]) == undefined`, nil, true) } diff --git a/script/compiled.go b/script/compiled.go index 2aee47ba..ce50f498 100644 --- a/script/compiled.go +++ b/script/compiled.go @@ -13,13 +13,11 @@ import ( // Compiled is a compiled instance of the user script. // Use Script.Compile() to create Compiled object. type Compiled struct { - globalIndexes map[string]int // global symbol name to index - bytecode *compiler.Bytecode - globals []objects.Object - builtinFunctions []objects.Object - builtinModules map[string]objects.Object - maxAllocs int64 - lock sync.RWMutex + globalIndexes map[string]int // global symbol name to index + bytecode *compiler.Bytecode + globals []objects.Object + maxAllocs int64 + lock sync.RWMutex } // Run executes the compiled script in the virtual machine. @@ -27,7 +25,7 @@ func (c *Compiled) Run() error { c.lock.Lock() defer c.lock.Unlock() - v := runtime.NewVM(c.bytecode, c.globals, c.builtinFunctions, c.builtinModules, c.maxAllocs) + v := runtime.NewVM(c.bytecode, c.globals, c.maxAllocs) return v.Run() } @@ -37,7 +35,7 @@ func (c *Compiled) RunContext(ctx context.Context) (err error) { c.lock.Lock() defer c.lock.Unlock() - v := runtime.NewVM(c.bytecode, c.globals, c.builtinFunctions, c.builtinModules, c.maxAllocs) + v := runtime.NewVM(c.bytecode, c.globals, c.maxAllocs) ch := make(chan error, 1) @@ -63,12 +61,10 @@ func (c *Compiled) Clone() *Compiled { defer c.lock.Unlock() clone := &Compiled{ - globalIndexes: c.globalIndexes, - bytecode: c.bytecode, - globals: make([]objects.Object, len(c.globals)), - builtinFunctions: c.builtinFunctions, - builtinModules: c.builtinModules, - maxAllocs: c.maxAllocs, + globalIndexes: c.globalIndexes, + bytecode: c.bytecode, + globals: make([]objects.Object, len(c.globals)), + maxAllocs: c.maxAllocs, } // copy global objects diff --git a/script/script.go b/script/script.go index a0c57d51..91ffe563 100644 --- a/script/script.go +++ b/script/script.go @@ -14,12 +14,11 @@ import ( // Script can simplify compilation and execution of embedded scripts. type Script struct { variables map[string]*Variable - builtinFuncs []objects.Object - builtinModules map[string]objects.Object - userModuleLoader compiler.ModuleLoader + importModules map[string]objects.Importable input []byte maxAllocs int64 maxConstObjects int + enableFileImport bool } // New creates a Script instance with an input script. @@ -59,33 +58,9 @@ func (s *Script) Remove(name string) bool { return true } -// SetBuiltinFunctions allows to define builtin functions. -func (s *Script) SetBuiltinFunctions(funcs []*objects.BuiltinFunction) { - if funcs != nil { - s.builtinFuncs = make([]objects.Object, len(funcs)) - for idx, fn := range funcs { - s.builtinFuncs[idx] = fn - } - } else { - s.builtinFuncs = []objects.Object{} - } -} - -// SetBuiltinModules allows to define builtin modules. -func (s *Script) SetBuiltinModules(modules map[string]*objects.ImmutableMap) { - if modules != nil { - s.builtinModules = make(map[string]objects.Object, len(modules)) - for k, mod := range modules { - s.builtinModules[k] = mod - } - } else { - s.builtinModules = map[string]objects.Object{} - } -} - -// SetUserModuleLoader sets the user module loader for the compiler. -func (s *Script) SetUserModuleLoader(loader compiler.ModuleLoader) { - s.userModuleLoader = loader +// SetImports sets import modules. +func (s *Script) SetImports(modules map[string]objects.Importable) { + s.importModules = modules } // SetMaxAllocs sets the maximum number of objects allocations during the run time. @@ -99,9 +74,15 @@ func (s *Script) SetMaxConstObjects(n int) { s.maxConstObjects = n } +// EnableFileImport enables or disables module loading from local files. +// Local file modules are disabled by default. +func (s *Script) EnableFileImport(enable bool) { + s.enableFileImport = enable +} + // Compile compiles the script with all the defined variables, and, returns Compiled object. func (s *Script) Compile() (*Compiled, error) { - symbolTable, builtinModules, globals, err := s.prepCompile() + symbolTable, globals, err := s.prepCompile() if err != nil { return nil, err } @@ -115,12 +96,8 @@ func (s *Script) Compile() (*Compiled, error) { return nil, err } - c := compiler.NewCompiler(srcFile, symbolTable, nil, builtinModules, nil) - - if s.userModuleLoader != nil { - c.SetModuleLoader(s.userModuleLoader) - } - + c := compiler.NewCompiler(srcFile, symbolTable, nil, s.importModules, nil) + c.EnableFileImport(s.enableFileImport) if err := c.Compile(file); err != nil { return nil, err } @@ -150,12 +127,10 @@ func (s *Script) Compile() (*Compiled, error) { } return &Compiled{ - globalIndexes: globalIndexes, - bytecode: bytecode, - globals: globals, - builtinFunctions: s.builtinFuncs, - builtinModules: s.builtinModules, - maxAllocs: s.maxAllocs, + globalIndexes: globalIndexes, + bytecode: bytecode, + globals: globals, + maxAllocs: s.maxAllocs, }, nil } @@ -184,36 +159,15 @@ func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error) return } -func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, builtinModules map[string]bool, globals []objects.Object, err error) { +func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, globals []objects.Object, err error) { var names []string for name := range s.variables { names = append(names, name) } symbolTable = compiler.NewSymbolTable() - - if s.builtinFuncs == nil { - s.builtinFuncs = make([]objects.Object, len(objects.Builtins)) - for idx, fn := range objects.Builtins { - s.builtinFuncs[idx] = &objects.BuiltinFunction{ - Name: fn.Name, - Value: fn.Value, - } - } - } - - if s.builtinModules == nil { - s.builtinModules = make(map[string]objects.Object) - } - - for idx, fn := range s.builtinFuncs { - f := fn.(*objects.BuiltinFunction) - symbolTable.DefineBuiltin(idx, f.Name) - } - - builtinModules = make(map[string]bool) - for name := range s.builtinModules { - builtinModules[name] = true + for idx, fn := range objects.Builtins { + symbolTable.DefineBuiltin(idx, fn.Name) } globals = make([]objects.Object, runtime.GlobalsSize) diff --git a/script/script_concurrency_test.go b/script/script_concurrency_test.go index 559300ff..aba765b6 100644 --- a/script/script_concurrency_test.go +++ b/script/script_concurrency_test.go @@ -33,7 +33,7 @@ b += c a += b * 2 arr := [a, b, c] -arrstr := stringify(arr) +arrstr := string(arr) map := {a: a, b: b, c: c} d := a + b + c @@ -45,8 +45,8 @@ for i:=1; i<=d; i++ { e := mod1.double(s) `) - mod1 := &objects.ImmutableMap{ - Value: map[string]objects.Object{ + mod1 := &objects.BuiltinModule{ + Attrs: map[string]objects.Object{ "double": &objects.UserFunction{ Value: func(args ...objects.Object) (ret objects.Object, err error) { arg0, _ := objects.ToInt64(args[0]) @@ -61,18 +61,9 @@ e := mod1.double(s) _ = scr.Add("a", 0) _ = scr.Add("b", 0) _ = scr.Add("c", 0) - scr.SetBuiltinModules(map[string]*objects.ImmutableMap{ + scr.SetImports(map[string]objects.Importable{ "mod1": mod1, }) - scr.SetBuiltinFunctions([]*objects.BuiltinFunction{ - { - Name: "stringify", - Value: func(args ...objects.Object) (ret objects.Object, err error) { - ret = &objects.String{Value: args[0].String()} - return - }, - }, - }) compiled, err := scr.Compile() assert.NoError(t, err) diff --git a/script/script_module_test.go b/script/script_module_test.go index 8e0338fa..23174031 100644 --- a/script/script_module_test.go +++ b/script/script_module_test.go @@ -1,7 +1,6 @@ package script_test import ( - "errors" "strings" "testing" @@ -10,57 +9,40 @@ import ( "github.com/d5/tengo/script" ) -func TestScript_SetUserModuleLoader(t *testing.T) { +func TestScriptSourceModule(t *testing.T) { // script1 imports "mod1" scr := script.New([]byte(`out := import("mod")`)) - scr.SetUserModuleLoader(func(name string) ([]byte, error) { - return []byte(`export 5`), nil + scr.SetImports(map[string]objects.Importable{ + "mod": &objects.SourceModule{Src: []byte(`export 5`)}, }) c, err := scr.Run() assert.Equal(t, int64(5), c.Get("out").Value()) // executing module function scr = script.New([]byte(`fn := import("mod"); out := fn()`)) - scr.SetUserModuleLoader(func(name string) ([]byte, error) { - return []byte(`a := 3; export func() { return a + 5 }`), nil + scr.SetImports(map[string]objects.Importable{ + "mod": &objects.SourceModule{Src: []byte(`a := 3; export func() { return a + 5 }`)}, }) c, err = scr.Run() assert.NoError(t, err) assert.Equal(t, int64(8), c.Get("out").Value()) - // disabled builtin function scr = script.New([]byte(`out := import("mod")`)) - scr.SetUserModuleLoader(func(name string) ([]byte, error) { - return []byte(`export len([1, 2, 3])`), nil - }) - c, err = scr.Run() - assert.NoError(t, err) - assert.Equal(t, int64(3), c.Get("out").Value()) - scr.SetBuiltinFunctions(nil) - _, err = scr.Run() - assert.Error(t, err) - - scr = script.New([]byte(`out := import("mod")`)) - scr.SetBuiltinModules(map[string]*objects.ImmutableMap{ - "text": objectPtr(&objects.ImmutableMap{ - Value: map[string]objects.Object{ + scr.SetImports(map[string]objects.Importable{ + "text": &objects.BuiltinModule{ + Attrs: 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 - } - return nil, errors.New("module not found") + }, + "mod": &objects.SourceModule{Src: []byte(`text := import("text"); export text.title("foo")`)}, }) c, err = scr.Run() assert.NoError(t, err) assert.Equal(t, "Foo", c.Get("out").Value()) - scr.SetBuiltinModules(nil) + scr.SetImports(nil) _, err = scr.Run() assert.Error(t, err) diff --git a/script/script_test.go b/script/script_test.go index 86e7e8ac..5b90aec2 100644 --- a/script/script_test.go +++ b/script/script_test.go @@ -1,13 +1,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) { @@ -52,71 +51,42 @@ func TestScript_Run(t *testing.T) { compiledGet(t, c, "a", int64(5)) } -func TestScript_SetBuiltinFunctions(t *testing.T) { - s := script.New([]byte(`a := len([1, 2, 3])`)) +func TestScript_BuiltinModules(t *testing.T) { + s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`)) + s.SetImports(map[string]objects.Importable{"math": stdlib.BuiltinModules["math"]}) c, err := s.Run() assert.NoError(t, err) assert.NotNil(t, c) - compiledGet(t, c, "a", int64(3)) + compiledGet(t, c, "a", 19.84) - s = script.New([]byte(`a := len([1, 2, 3])`)) - s.SetBuiltinFunctions([]*objects.BuiltinFunction{&objects.Builtins[4]}) c, err = s.Run() assert.NoError(t, err) assert.NotNil(t, c) - compiledGet(t, c, "a", int64(3)) + compiledGet(t, c, "a", 19.84) - s.SetBuiltinFunctions([]*objects.BuiltinFunction{&objects.Builtins[0]}) + s.SetImports(map[string]objects.Importable{"os": &objects.BuiltinModule{Attrs: map[string]objects.Object{}}}) _, err = s.Run() assert.Error(t, err) - s.SetBuiltinFunctions(nil) + s.SetImports(nil) _, err = s.Run() assert.Error(t, err) - - s = script.New([]byte(`a := import("b")`)) - s.SetUserModuleLoader(func(name string) ([]byte, error) { - if name == "b" { - return []byte(`export import("c")`), nil - } else if name == "c" { - return []byte("export len([1, 2, 3])"), nil - } - return nil, errors.New("module not found") - }) - s.SetBuiltinFunctions([]*objects.BuiltinFunction{&objects.Builtins[4]}) - c, err = s.Run() - assert.NoError(t, err) - assert.NotNil(t, c) - compiledGet(t, c, "a", int64(3)) } -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 - }}, - }, - }), - }) +func TestScript_SourceModules(t *testing.T) { + s := script.New([]byte(` +enum := import("enum") +a := enum.all([1,2,3], func(_, v) { + return v > 0 +}) +`)) + s.SetImports(stdlib.GetModules("enum")) c, err := s.Run() assert.NoError(t, err) assert.NotNil(t, c) - compiledGet(t, c, "a", 19.84) - - c, err = s.Run() - assert.NoError(t, err) - assert.NotNil(t, c) - compiledGet(t, c, "a", 19.84) + compiledGet(t, c, "a", true) - s.SetBuiltinModules(map[string]*objects.ImmutableMap{"os": objectPtr(&objects.ImmutableMap{Value: map[string]objects.Object{}})}) - _, err = s.Run() - assert.Error(t, err) - - s.SetBuiltinModules(nil) + s.SetImports(nil) _, err = s.Run() assert.Error(t, err) } @@ -154,7 +124,3 @@ func TestScript_SetMaxConstObjects(t *testing.T) { _, err = s.Compile() assert.NoError(t, err) } - -func objectPtr(o objects.Object) *objects.ImmutableMap { - return o.(*objects.ImmutableMap) -} diff --git a/stdlib/builtin_modules.go b/stdlib/builtin_modules.go new file mode 100644 index 00000000..e840d331 --- /dev/null +++ b/stdlib/builtin_modules.go @@ -0,0 +1,14 @@ +package stdlib + +import "github.com/d5/tengo/objects" + +// BuiltinModules are builtin type standard library modules. +var BuiltinModules = map[string]*objects.BuiltinModule{ + "math": {Attrs: mathModule}, + "os": {Attrs: osModule}, + "text": {Attrs: textModule}, + "times": {Attrs: timesModule}, + "rand": {Attrs: randModule}, + "fmt": {Attrs: fmtModule}, + "json": {Attrs: jsonModule}, +} diff --git a/stdlib/enum_test.go b/stdlib/enum_test.go new file mode 100644 index 00000000..393399f1 --- /dev/null +++ b/stdlib/enum_test.go @@ -0,0 +1,7 @@ +package stdlib_test + +import "testing" + +func TestEnum(t *testing.T) { + +} diff --git a/objects/builtin_print.go b/stdlib/fmt.go similarity index 52% rename from objects/builtin_print.go rename to stdlib/fmt.go index a05d5477..9c75fc33 100644 --- a/objects/builtin_print.go +++ b/stdlib/fmt.go @@ -1,30 +1,20 @@ -package objects +package stdlib import ( "fmt" "github.com/d5/tengo" + "github.com/d5/tengo/objects" ) -func getPrintArgs(args ...Object) ([]interface{}, error) { - var printArgs []interface{} - l := 0 - for _, arg := range args { - s, _ := ToString(arg) - slen := len(s) - if l+slen > tengo.MaxStringLen { // make sure length does not exceed the limit - return nil, ErrStringLimit - } - l += slen - - printArgs = append(printArgs, s) - } - - return printArgs, nil +var fmtModule = map[string]objects.Object{ + "print": &objects.UserFunction{Name: "print", Value: fmtPrint}, + "printf": &objects.UserFunction{Name: "printf", Value: fmtPrintf}, + "println": &objects.UserFunction{Name: "println", Value: fmtPrintln}, + "sprintf": &objects.UserFunction{Name: "sprintf", Value: fmtSprintf}, } -// print(args...) -func builtinPrint(args ...Object) (Object, error) { +func fmtPrint(args ...objects.Object) (ret objects.Object, err error) { printArgs, err := getPrintArgs(args...) if err != nil { return nil, err @@ -35,29 +25,15 @@ func builtinPrint(args ...Object) (Object, error) { return nil, nil } -// println(args...) -func builtinPrintln(args ...Object) (Object, error) { - printArgs, err := getPrintArgs(args...) - if err != nil { - return nil, err - } - - printArgs = append(printArgs, "\n") - _, _ = fmt.Print(printArgs...) - - return nil, nil -} - -// printf("format", args...) -func builtinPrintf(args ...Object) (Object, error) { +func fmtPrintf(args ...objects.Object) (ret objects.Object, err error) { numArgs := len(args) if numArgs == 0 { - return nil, ErrWrongNumArguments + return nil, objects.ErrWrongNumArguments } - format, ok := args[0].(*String) + format, ok := args[0].(*objects.String) if !ok { - return nil, ErrInvalidArgumentType{ + return nil, objects.ErrInvalidArgumentType{ Name: "format", Expected: "string", Found: args[0].TypeName(), @@ -70,7 +46,7 @@ func builtinPrintf(args ...Object) (Object, error) { formatArgs := make([]interface{}, numArgs-1, numArgs-1) for idx, arg := range args[1:] { - formatArgs[idx] = ToInterface(arg) + formatArgs[idx] = objects.ToInterface(arg) } fmt.Printf(format.Value, formatArgs...) @@ -78,16 +54,27 @@ func builtinPrintf(args ...Object) (Object, error) { return nil, nil } -// sprintf("format", args...) -func builtinSprintf(args ...Object) (Object, error) { +func fmtPrintln(args ...objects.Object) (ret objects.Object, err error) { + printArgs, err := getPrintArgs(args...) + if err != nil { + return nil, err + } + + printArgs = append(printArgs, "\n") + _, _ = fmt.Print(printArgs...) + + return nil, nil +} + +func fmtSprintf(args ...objects.Object) (ret objects.Object, err error) { numArgs := len(args) if numArgs == 0 { - return nil, ErrWrongNumArguments + return nil, objects.ErrWrongNumArguments } - format, ok := args[0].(*String) + format, ok := args[0].(*objects.String) if !ok { - return nil, ErrInvalidArgumentType{ + return nil, objects.ErrInvalidArgumentType{ Name: "format", Expected: "string", Found: args[0].TypeName(), @@ -99,14 +86,31 @@ func builtinSprintf(args ...Object) (Object, error) { formatArgs := make([]interface{}, numArgs-1, numArgs-1) for idx, arg := range args[1:] { - formatArgs[idx] = ToInterface(arg) + formatArgs[idx] = objects.ToInterface(arg) } s := fmt.Sprintf(format.Value, formatArgs...) if len(s) > tengo.MaxStringLen { - return nil, ErrStringLimit + return nil, objects.ErrStringLimit + } + + return &objects.String{Value: s}, nil +} + +func getPrintArgs(args ...objects.Object) ([]interface{}, error) { + var printArgs []interface{} + l := 0 + for _, arg := range args { + s, _ := objects.ToString(arg) + slen := len(s) + if l+slen > tengo.MaxStringLen { // make sure length does not exceed the limit + return nil, objects.ErrStringLimit + } + l += slen + + printArgs = append(printArgs, s) } - return &String{Value: s}, nil + return printArgs, nil } diff --git a/stdlib/fmt_test.go b/stdlib/fmt_test.go new file mode 100644 index 00000000..68086f60 --- /dev/null +++ b/stdlib/fmt_test.go @@ -0,0 +1,11 @@ +package stdlib_test + +import "testing" + +func TestFmtSprintf(t *testing.T) { + module(t, `fmt`).call("sprintf", "").expect("") + module(t, `fmt`).call("sprintf", "foo").expect("foo") + module(t, `fmt`).call("sprintf", `foo %d %v %s`, 1, 2, "bar").expect("foo 1 2 bar") + module(t, `fmt`).call("sprintf", "foo %v", `[1, "bar", true]`).expect(`foo [1, "bar", true]`) + module(t, `fmt`).call("sprintf", "foo %v %d", `[1, "bar", true]`, 19).expect(`foo [1, "bar", true] 19`) +} diff --git a/stdlib/gensrcmods.go b/stdlib/gensrcmods.go new file mode 100644 index 00000000..0c3b3dba --- /dev/null +++ b/stdlib/gensrcmods.go @@ -0,0 +1,54 @@ +// +build ignore + +package main + +import ( + "bytes" + "io/ioutil" + "log" + "regexp" +) + +var tengoModFileRE = regexp.MustCompile(`^srcmod_(\w+).tengo$`) + +func main() { + modules := make(map[string]string) + + // enumerate all Tengo module files + files, err := ioutil.ReadDir(".") + if err != nil { + log.Fatal(err) + } + for _, file := range files { + m := tengoModFileRE.FindStringSubmatch(file.Name()) + if m != nil { + modName := m[1] + + src, err := ioutil.ReadFile(file.Name()) + if err != nil { + log.Fatalf("file '%s' read error: %s", file.Name(), err.Error()) + } + + modules[modName] = string(src) + } + } + + var out bytes.Buffer + out.WriteString(`// Code generated using gensrcmods.go; DO NOT EDIT. + +package stdlib + +import "github.com/d5/tengo/objects" + +// SourceModules are source type standard library modules. +var SourceModules = map[string]*objects.SourceModule{` + "\n") + for modName, modSrc := range modules { + out.WriteString("\t\"" + modName + "\": {Src: []byte(`" + modSrc + "`)},\n") + } + out.WriteString("}\n") + + const target = "source_modules.go" + if err := ioutil.WriteFile(target, out.Bytes(), 0644); err != nil { + log.Fatal(err) + } +} diff --git a/stdlib/json.go b/stdlib/json.go new file mode 100644 index 00000000..5650027f --- /dev/null +++ b/stdlib/json.go @@ -0,0 +1,69 @@ +package stdlib + +import ( + "encoding/json" + + "github.com/d5/tengo" + "github.com/d5/tengo/objects" +) + +var jsonModule = map[string]objects.Object{ + "parse": &objects.UserFunction{Name: "parse", Value: jsonParse}, + "stringify": &objects.UserFunction{Name: "stringify", Value: jsonStringify}, +} + +func jsonParse(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + var target interface{} + + switch o := args[0].(type) { + case *objects.Bytes: + err := json.Unmarshal(o.Value, &target) + if err != nil { + return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil + } + case *objects.String: + err := json.Unmarshal([]byte(o.Value), &target) + if err != nil { + return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil + } + default: + return nil, objects.ErrInvalidArgumentType{ + Name: "first", + Expected: "bytes/string", + Found: args[0].TypeName(), + } + } + + res, err := objects.FromInterface(target) + if err != nil { + return nil, err + } + + return res, nil +} + +func jsonStringify(args ...objects.Object) (ret objects.Object, err error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + v := objects.ToInterface(args[0]) + if vErr, isErr := v.(error); isErr { + v = vErr.Error() + } + + res, err := json.Marshal(v) + if err != nil { + return &objects.Error{Value: &objects.String{Value: err.Error()}}, nil + } + + if len(res) > tengo.MaxBytesLen { + return nil, objects.ErrBytesLimit + } + + return &objects.String{Value: string(res)}, nil +} diff --git a/stdlib/json_test.go b/stdlib/json_test.go new file mode 100644 index 00000000..337f5fea --- /dev/null +++ b/stdlib/json_test.go @@ -0,0 +1,33 @@ +package stdlib_test + +import "testing" + +func TestJSON(t *testing.T) { + module(t, "json").call("stringify", 5).expect("5") + module(t, "json").call("stringify", "foobar").expect(`"foobar"`) + module(t, "json").call("stringify", MAP{"foo": 5}).expect("{\"foo\":5}") + module(t, "json").call("stringify", IMAP{"foo": 5}).expect("{\"foo\":5}") + module(t, "json").call("stringify", ARR{1, 2, 3}).expect("[1,2,3]") + module(t, "json").call("stringify", IARR{1, 2, 3}).expect("[1,2,3]") + module(t, "json").call("stringify", MAP{"foo": "bar"}).expect("{\"foo\":\"bar\"}") + module(t, "json").call("stringify", MAP{"foo": 1.8}).expect("{\"foo\":1.8}") + module(t, "json").call("stringify", MAP{"foo": true}).expect("{\"foo\":true}") + module(t, "json").call("stringify", MAP{"foo": '8'}).expect("{\"foo\":56}") + module(t, "json").call("stringify", MAP{"foo": []byte("foo")}).expect("{\"foo\":\"Zm9v\"}") // json encoding returns []byte as base64 encoded string + module(t, "json").call("stringify", MAP{"foo": ARR{"bar", 1, 1.8, '8', true}}).expect("{\"foo\":[\"bar\",1,1.8,56,true]}") + module(t, "json").call("stringify", MAP{"foo": IARR{"bar", 1, 1.8, '8', true}}).expect("{\"foo\":[\"bar\",1,1.8,56,true]}") + module(t, "json").call("stringify", MAP{"foo": ARR{ARR{"bar", 1}, ARR{"bar", 1}}}).expect("{\"foo\":[[\"bar\",1],[\"bar\",1]]}") + module(t, "json").call("stringify", MAP{"foo": MAP{"string": "bar", "int": 1, "float": 1.8, "char": '8', "bool": true}}).expect("{\"foo\":{\"bool\":true,\"char\":56,\"float\":1.8,\"int\":1,\"string\":\"bar\"}}") + module(t, "json").call("stringify", MAP{"foo": IMAP{"string": "bar", "int": 1, "float": 1.8, "char": '8', "bool": true}}).expect("{\"foo\":{\"bool\":true,\"char\":56,\"float\":1.8,\"int\":1,\"string\":\"bar\"}}") + module(t, "json").call("stringify", MAP{"foo": MAP{"map1": MAP{"string": "bar"}, "map2": MAP{"int": "1"}}}).expect("{\"foo\":{\"map1\":{\"string\":\"bar\"},\"map2\":{\"int\":\"1\"}}}") + module(t, "json").call("stringify", ARR{ARR{"bar", 1}, ARR{"bar", 1}}).expect("[[\"bar\",1],[\"bar\",1]]") + + module(t, "json").call("parse", `5`).expect(5.0) + module(t, "json").call("parse", `"foo"`).expect("foo") + module(t, "json").call("parse", `[1,2,3,"bar"]`).expect(ARR{1.0, 2.0, 3.0, "bar"}) + module(t, "json").call("parse", `{"foo":5}`).expect(MAP{"foo": 5.0}) + module(t, "json").call("parse", `{"foo":2.5}`).expect(MAP{"foo": 2.5}) + module(t, "json").call("parse", `{"foo":true}`).expect(MAP{"foo": true}) + module(t, "json").call("parse", `{"foo":"bar"}`).expect(MAP{"foo": "bar"}) + module(t, "json").call("parse", `{"foo":[1,2,3,"bar"]}`).expect(MAP{"foo": ARR{1.0, 2.0, 3.0, "bar"}}) +} diff --git a/stdlib/source_modules.go b/stdlib/source_modules.go new file mode 100644 index 00000000..a55b0a9d --- /dev/null +++ b/stdlib/source_modules.go @@ -0,0 +1,50 @@ +// Code generated using gensrcmods.go; DO NOT EDIT. + +package stdlib + +import "github.com/d5/tengo/objects" + +// SourceModules are source type standard library modules. +var SourceModules = map[string]*objects.SourceModule{ + "enum": {Src: []byte(`export { + // all returns true if the given function fn evaluates to a truthy value on + // all of the items in the enumerable. + all: func(enumerable, fn) { + for k, v in enumerable { + if !fn(k, v) { + return false + } + } + return true + }, + // any returns true if the given function fn evaluates to a truthy value on + // any of the items in the enumerable. + any: func(enumerable, fn) { + for k, v in enumerable { + if fn(k, v) { + return true + } + } + return false + }, + // chunk returns an array of elements split into groups the length of size. + // If the enumerable can't be split evenly, the final chunk will be the + // remaining elements. + chunk: func(enumerable, size) { + numElements := len(enumerable) + + if !numElements { + return [] + } + + res := [] + idx := 0 + for idx < numElements { + res = append(res, enumerable[idx:idx+size]) + idx += size + } + return res + } +} +`)}, +} diff --git a/stdlib/srcmod_enum.tengo b/stdlib/srcmod_enum.tengo new file mode 100644 index 00000000..914d78a0 --- /dev/null +++ b/stdlib/srcmod_enum.tengo @@ -0,0 +1,40 @@ +export { + // all returns true if the given function fn evaluates to a truthy value on + // all of the items in the enumerable. + all: func(enumerable, fn) { + for k, v in enumerable { + if !fn(k, v) { + return false + } + } + return true + }, + // any returns true if the given function fn evaluates to a truthy value on + // any of the items in the enumerable. + any: func(enumerable, fn) { + for k, v in enumerable { + if fn(k, v) { + return true + } + } + return false + }, + // chunk returns an array of elements split into groups the length of size. + // If the enumerable can't be split evenly, the final chunk will be the + // remaining elements. + chunk: func(enumerable, size) { + numElements := len(enumerable) + + if !numElements { + return [] + } + + res := [] + idx := 0 + for idx < numElements { + res = append(res, enumerable[idx:idx+size]) + idx += size + } + return res + } +} diff --git a/stdlib/stdlib.go b/stdlib/stdlib.go index 359c3a89..3e7f64f8 100644 --- a/stdlib/stdlib.go +++ b/stdlib/stdlib.go @@ -1,20 +1,16 @@ package stdlib -import "github.com/d5/tengo/objects" +//go:generate go run gensrcmods.go -// Modules contain the standard modules. -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}, -} +import "github.com/d5/tengo/objects" // AllModuleNames returns a list of all default module names. func AllModuleNames() []string { var names []string - for name := range Modules { + for name := range BuiltinModules { + names = append(names, name) + } + for name := range SourceModules { names = append(names, name) } return names @@ -22,12 +18,16 @@ func AllModuleNames() []string { // GetModules returns the modules for the given names. // Duplicate names and invalid names are ignore. -func GetModules(names ...string) map[string]*objects.ImmutableMap { - modules := make(map[string]*objects.ImmutableMap) +func GetModules(names ...string) map[string]objects.Importable { + modules := make(map[string]objects.Importable) for _, name := range names { - if mod := Modules[name]; mod != nil { + if mod := BuiltinModules[name]; mod != nil { + modules[name] = mod + } + if mod := SourceModules[name]; mod != nil { modules[name] = mod } } + return modules } diff --git a/stdlib/stdlib_test.go b/stdlib/stdlib_test.go index 304adc98..a6d28eaa 100644 --- a/stdlib/stdlib_test.go +++ b/stdlib/stdlib_test.go @@ -18,12 +18,9 @@ type IMAP map[string]interface{} func TestAllModuleNames(t *testing.T) { names := stdlib.AllModuleNames() - if !assert.Equal(t, len(stdlib.Modules), len(names)) { + if !assert.Equal(t, len(stdlib.BuiltinModules)+len(stdlib.SourceModules), len(names)) { return } - for _, name := range names { - assert.NotNil(t, stdlib.Modules[name], "name: %s", name) - } } func TestModulesRun(t *testing.T) { @@ -100,7 +97,7 @@ func TestGetModules(t *testing.T) { type callres struct { t *testing.T - o objects.Object + o interface{} e error } @@ -109,29 +106,44 @@ func (c callres) call(funcName string, args ...interface{}) callres { return c } - imap, ok := c.o.(*objects.ImmutableMap) - if !ok { - return c - } - - m, ok := imap.Value[funcName] - if !ok { - return callres{t: c.t, e: fmt.Errorf("function not found: %s", funcName)} - } - - f, ok := m.(*objects.UserFunction) - if !ok { - return callres{t: c.t, e: fmt.Errorf("non-callable: %s", funcName)} - } - var oargs []objects.Object for _, v := range args { oargs = append(oargs, object(v)) } - res, err := f.Value(oargs...) + switch o := c.o.(type) { + case *objects.BuiltinModule: + m, ok := o.Attrs[funcName] + if !ok { + return callres{t: c.t, e: fmt.Errorf("function not found: %s", funcName)} + } - return callres{t: c.t, o: res, e: err} + f, ok := m.(*objects.UserFunction) + if !ok { + return callres{t: c.t, e: fmt.Errorf("non-callable: %s", funcName)} + } + + res, err := f.Value(oargs...) + return callres{t: c.t, o: res, e: err} + case *objects.UserFunction: + res, err := o.Value(oargs...) + return callres{t: c.t, o: res, e: err} + case *objects.ImmutableMap: + m, ok := o.Value[funcName] + if !ok { + return callres{t: c.t, e: fmt.Errorf("function not found: %s", funcName)} + } + + f, ok := m.(*objects.UserFunction) + if !ok { + return callres{t: c.t, e: fmt.Errorf("non-callable: %s", funcName)} + } + + res, err := f.Value(oargs...) + return callres{t: c.t, o: res, e: err} + default: + panic(fmt.Errorf("unexpected object: %v (%T)", o, o)) + } } func (c callres) expect(expected interface{}, msgAndArgs ...interface{}) bool { @@ -144,7 +156,7 @@ func (c callres) expectError() bool { } func module(t *testing.T, moduleName string) callres { - mod, ok := stdlib.Modules[moduleName] + mod, ok := stdlib.BuiltinModules[moduleName] if !ok { return callres{t: t, e: fmt.Errorf("module not found: %s", moduleName)} } @@ -219,7 +231,7 @@ func object(v interface{}) objects.Object { func expect(t *testing.T, input string, expected interface{}) { s := script.New([]byte(input)) - s.SetBuiltinModules(stdlib.Modules) + s.SetImports(stdlib.GetModules(stdlib.AllModuleNames()...)) c, err := s.Run() assert.NoError(t, err) assert.NotNil(t, c)