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
This commit is contained in:
Daniel 2019-03-18 08:15:26 -07:00 committed by GitHub
parent 052ae5906b
commit 61890b15cb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 1625 additions and 1725 deletions

View file

@ -1,10 +1,13 @@
vet: vet:
go vet ./... go vet ./...
generate:
go generate ./...
lint: lint:
golint -set_exit_status ./... golint -set_exit_status ./...
test: vet lint test: generate vet lint
go test -race -cover ./... go test -race -cover ./...
fmt: fmt:

View file

@ -23,33 +23,28 @@ const (
replPrompt = ">> " replPrompt = ">> "
) )
//Options represent REPL options // Options represent CLI options
type Options struct { type Options struct {
//Compile output file // Compile output file
CompileOutput string CompileOutput string
//Show help flag // Show help flag
ShowHelp bool ShowHelp bool
//Show version flag // Show version flag
ShowVersion bool ShowVersion bool
//Input file // Input file
InputFile string InputFile string
//Version // Version
Version string Version string
//Builtin modules // Import modules
BuiltinModules map[string]objects.Object Modules map[string]objects.Importable
} }
var ( // Run CLI
bm map[string]bool
builtinModules map[string]objects.Object
)
//Run REPL
func Run(options *Options) { func Run(options *Options) {
if options.ShowHelp { if options.ShowHelp {
doHelp() doHelp()
@ -59,15 +54,9 @@ func Run(options *Options) {
return return
} }
builtinModules = options.BuiltinModules
bm = make(map[string]bool, len(builtinModules))
for k := range builtinModules {
bm[k] = true
}
if options.InputFile == "" { if options.InputFile == "" {
// REPL // REPL
runREPL(os.Stdin, os.Stdout) runREPL(options.Modules, os.Stdin, os.Stdout)
return return
} }
@ -78,12 +67,12 @@ func Run(options *Options) {
} }
if options.CompileOutput != "" { 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()) _, _ = fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
} else if filepath.Ext(options.InputFile) == sourceFileExt { } 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()) _, _ = fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1) os.Exit(1)
} }
@ -127,8 +116,8 @@ func doHelp() {
fmt.Println() fmt.Println()
} }
func compileOnly(data []byte, inputFile, outputFile string) (err error) { func compileOnly(modules map[string]objects.Importable, data []byte, inputFile, outputFile string) (err error) {
bytecode, err := compileSrc(data, filepath.Base(inputFile)) bytecode, err := compileSrc(modules, data, filepath.Base(inputFile))
if err != nil { if err != nil {
return return
} }
@ -159,13 +148,13 @@ func compileOnly(data []byte, inputFile, outputFile string) (err error) {
return return
} }
func compileAndRun(data []byte, inputFile string) (err error) { func compileAndRun(modules map[string]objects.Importable, data []byte, inputFile string) (err error) {
bytecode, err := compileSrc(data, filepath.Base(inputFile)) bytecode, err := compileSrc(modules, data, filepath.Base(inputFile))
if err != nil { if err != nil {
return return
} }
machine := runtime.NewVM(bytecode, nil, nil, builtinModules, -1) machine := runtime.NewVM(bytecode, nil, -1)
err = machine.Run() err = machine.Run()
if err != nil { if err != nil {
@ -182,7 +171,7 @@ func runCompiled(data []byte) (err error) {
return return
} }
machine := runtime.NewVM(bytecode, nil, nil, builtinModules, -1) machine := runtime.NewVM(bytecode, nil, -1)
err = machine.Run() err = machine.Run()
if err != nil { if err != nil {
@ -192,7 +181,7 @@ func runCompiled(data []byte) (err error) {
return 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) stdin := bufio.NewScanner(in)
fileSet := source.NewFileSet() fileSet := source.NewFileSet()
@ -203,6 +192,28 @@ func runREPL(in io.Reader, out io.Writer) {
symbolTable.DefineBuiltin(idx, fn.Name) 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, "<undefined>")
} else {
s, _ := objects.ToString(arg)
printArgs = append(printArgs, s)
}
}
printArgs = append(printArgs, "\n")
_, _ = fmt.Print(printArgs...)
return
},
}
var constants []objects.Object var constants []objects.Object
for { for {
@ -225,7 +236,7 @@ func runREPL(in io.Reader, out io.Writer) {
file = addPrints(file) 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 { if err := c.Compile(file); err != nil {
_, _ = fmt.Fprintln(out, err.Error()) _, _ = fmt.Fprintln(out, err.Error())
continue continue
@ -233,7 +244,7 @@ func runREPL(in io.Reader, out io.Writer) {
bytecode := c.Bytecode() bytecode := c.Bytecode()
machine := runtime.NewVM(bytecode, globals, nil, builtinModules, -1) machine := runtime.NewVM(bytecode, globals, -1)
if err := machine.Run(); err != nil { if err := machine.Run(); err != nil {
_, _ = fmt.Fprintln(out, err.Error()) _, _ = fmt.Fprintln(out, err.Error())
continue 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() fileSet := source.NewFileSet()
srcFile := fileSet.AddFile(filename, -1, len(src)) srcFile := fileSet.AddFile(filename, -1, len(src))
@ -253,7 +264,9 @@ func compileSrc(src []byte, filename string) (*compiler.Bytecode, error) {
return nil, err 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 { if err := c.Compile(file); err != nil {
return nil, err return nil, err
} }
@ -266,14 +279,13 @@ func compileSrc(src []byte, filename string) (*compiler.Bytecode, error) {
func addPrints(file *ast.File) *ast.File { func addPrints(file *ast.File) *ast.File {
var stmts []ast.Stmt var stmts []ast.Stmt
for _, s := range file.Stmts { for _, s := range file.Stmts {
switch s := s.(type) { switch s := s.(type) {
case *ast.ExprStmt: case *ast.ExprStmt:
stmts = append(stmts, &ast.ExprStmt{ stmts = append(stmts, &ast.ExprStmt{
Expr: &ast.CallExpr{ Expr: &ast.CallExpr{
Func: &ast.Ident{ Func: &ast.Ident{Name: "__repl_println__"},
Name: "print",
},
Args: []ast.Expr{s.Expr}, Args: []ast.Expr{s.Expr},
}, },
}) })
@ -284,7 +296,7 @@ func addPrints(file *ast.File) *ast.File {
stmts = append(stmts, &ast.ExprStmt{ stmts = append(stmts, &ast.ExprStmt{
Expr: &ast.CallExpr{ Expr: &ast.CallExpr{
Func: &ast.Ident{ Func: &ast.Ident{
Name: "print", Name: "__repl_println__",
}, },
Args: s.LHS, Args: s.LHS,
}, },

View file

@ -210,7 +210,7 @@ func runVM(bytecode *compiler.Bytecode) (time.Duration, objects.Object, error) {
start := time.Now() start := time.Now()
v := runtime.NewVM(bytecode, globals, nil, nil, -1) v := runtime.NewVM(bytecode, globals, -1)
if err := v.Run(); err != nil { if err := v.Run(); err != nil {
return time.Since(start), nil, err return time.Since(start), nil, err
} }

View file

@ -4,7 +4,6 @@ import (
"flag" "flag"
"github.com/d5/tengo/cli" "github.com/d5/tengo/cli"
"github.com/d5/tengo/objects"
"github.com/d5/tengo/stdlib" "github.com/d5/tengo/stdlib"
) )
@ -23,17 +22,12 @@ func init() {
} }
func main() { func main() {
builtinModules := make(map[string]objects.Object, len(stdlib.Modules))
for k, mod := range stdlib.Modules {
builtinModules[k] = mod
}
cli.Run(&cli.Options{ cli.Run(&cli.Options{
ShowHelp: showHelp, ShowHelp: showHelp,
ShowVersion: showVersion, ShowVersion: showVersion,
Version: version, Version: version,
CompileOutput: compileOutput, CompileOutput: compileOutput,
BuiltinModules: builtinModules, Modules: stdlib.GetModules(stdlib.AllModuleNames()...),
InputFile: flag.Arg(0), InputFile: flag.Arg(0),
}) })
} }

View file

@ -16,6 +16,7 @@ func (b *Bytecode) RemoveDuplicates() {
strings := make(map[string]int) strings := make(map[string]int)
floats := make(map[float64]int) floats := make(map[float64]int)
chars := make(map[rune]int) chars := make(map[rune]int)
immutableMaps := make(map[string]int) // for modules
for curIdx, c := range b.Constants { for curIdx, c := range b.Constants {
switch c := c.(type) { switch c := c.(type) {
@ -23,7 +24,17 @@ func (b *Bytecode) RemoveDuplicates() {
// add to deduped list // add to deduped list
indexMap[curIdx] = len(deduped) indexMap[curIdx] = len(deduped)
deduped = append(deduped, c) 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: case *objects.Int:
if newIdx, ok := ints[c.Value]; ok { if newIdx, ok := ints[c.Value]; ok {
indexMap[curIdx] = newIdx indexMap[curIdx] = newIdx

View file

@ -3,7 +3,10 @@ package compiler
import ( import (
"fmt" "fmt"
"io" "io"
"io/ioutil"
"path/filepath"
"reflect" "reflect"
"strings"
"github.com/d5/tengo" "github.com/d5/tengo"
"github.com/d5/tengo/compiler/ast" "github.com/d5/tengo/compiler/ast"
@ -16,14 +19,14 @@ import (
type Compiler struct { type Compiler struct {
file *source.File file *source.File
parent *Compiler parent *Compiler
moduleName string modulePath string
constants []objects.Object constants []objects.Object
symbolTable *SymbolTable symbolTable *SymbolTable
scopes []CompilationScope scopes []CompilationScope
scopeIndex int scopeIndex int
moduleLoader ModuleLoader importModules map[string]objects.Importable
builtinModules map[string]bool
compiledModules map[string]*objects.CompiledFunction compiledModules map[string]*objects.CompiledFunction
allowFileImport bool
loops []*Loop loops []*Loop
loopIndex int loopIndex int
trace io.Writer trace io.Writer
@ -31,12 +34,7 @@ type Compiler struct {
} }
// NewCompiler creates a Compiler. // NewCompiler creates a Compiler.
// User can optionally provide the symbol table if one wants to add or remove func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []objects.Object, importModules map[string]objects.Importable, trace io.Writer) *Compiler {
// 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 {
mainScope := CompilationScope{ mainScope := CompilationScope{
symbolInit: make(map[string]bool), symbolInit: make(map[string]bool),
sourceMap: make(map[int]source.Pos), sourceMap: make(map[int]source.Pos),
@ -45,15 +43,16 @@ func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []object
// symbol table // symbol table
if symbolTable == nil { if symbolTable == nil {
symbolTable = NewSymbolTable() symbolTable = NewSymbolTable()
}
// add builtin functions to the symbol table
for idx, fn := range objects.Builtins { for idx, fn := range objects.Builtins {
symbolTable.DefineBuiltin(idx, fn.Name) symbolTable.DefineBuiltin(idx, fn.Name)
} }
}
// builtin modules // builtin modules
if builtinModules == nil { if importModules == nil {
builtinModules = make(map[string]bool) importModules = make(map[string]objects.Importable)
} }
return &Compiler{ return &Compiler{
@ -64,7 +63,7 @@ func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []object
scopeIndex: 0, scopeIndex: 0,
loopIndex: -1, loopIndex: -1,
trace: trace, trace: trace,
builtinModules: builtinModules, importModules: importModules,
compiledModules: make(map[string]*objects.CompiledFunction), 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)) c.emit(node, OpCall, len(node.Args))
case *ast.ImportExpr: case *ast.ImportExpr:
if c.builtinModules[node.ModuleName] { if mod, ok := c.importModules[node.ModuleName]; ok {
if len(node.ModuleName) > tengo.MaxStringLen { v, err := mod.Import(node.ModuleName)
return c.error(node, objects.ErrStringLimit)
}
c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.ModuleName}))
c.emit(node, OpGetBuiltinModule)
} else {
userMod, err := c.compileModule(node)
if err != nil { if err != nil {
return err return err
} }
c.emit(node, OpConstant, c.addConstant(userMod)) 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) 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
}
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: case *ast.ExportStmt:
@ -602,18 +633,16 @@ func (c *Compiler) Bytecode() *Bytecode {
} }
} }
// SetModuleLoader sets or replaces the current module loader. // EnableFileImport enables or disables module loading from local files.
// Note that the module loader is used for user modules, // Local file modules are disabled by default.
// not for the standard modules. func (c *Compiler) EnableFileImport(enable bool) {
func (c *Compiler) SetModuleLoader(moduleLoader ModuleLoader) { c.allowFileImport = enable
c.moduleLoader = moduleLoader
} }
func (c *Compiler) fork(file *source.File, moduleName string, symbolTable *SymbolTable) *Compiler { func (c *Compiler) fork(file *source.File, modulePath string, symbolTable *SymbolTable) *Compiler {
child := NewCompiler(file, symbolTable, nil, c.builtinModules, c.trace) child := NewCompiler(file, symbolTable, nil, c.importModules, c.trace)
child.moduleName = moduleName // name of the module to compile child.modulePath = modulePath // module file path
child.parent = c // parent to set to current compiler child.parent = c // parent to set to current compiler
child.moduleLoader = c.moduleLoader // share module loader
return child return child
} }

View file

@ -3,7 +3,7 @@ package compiler_test
import "testing" import "testing"
func TestCompilerErrorReport(t *testing.T) { 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 = 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") expectError(t, `a, b := 1, 2`, "Compile Error: tuple assignment not allowed\n\tat test:1:1")

View file

@ -1,72 +1,31 @@
package compiler package compiler
import ( import (
"io/ioutil"
"strings"
"github.com/d5/tengo/compiler/ast" "github.com/d5/tengo/compiler/ast"
"github.com/d5/tengo/compiler/parser" "github.com/d5/tengo/compiler/parser"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
) )
func (c *Compiler) compileModule(expr *ast.ImportExpr) (*objects.CompiledFunction, error) { func (c *Compiler) checkCyclicImports(node ast.Node, modulePath string) error {
compiledModule, exists := c.loadCompiledModule(expr.ModuleName) if c.modulePath == modulePath {
if exists { return c.errorf(node, "cyclic module import: %s", modulePath)
return compiledModule, nil
}
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
}
}
compiledModule, err := c.doCompileModule(moduleName, moduleSrc)
if 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 { } else if c.parent != nil {
return c.parent.checkCyclicImports(node, moduleName) return c.parent.checkCyclicImports(node, modulePath)
} }
return nil return nil
} }
func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.CompiledFunction, error) { 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
}
compiledModule, exists := c.loadCompiledModule(modulePath)
if exists {
return compiledModule, nil
}
modFile := c.file.Set().AddFile(moduleName, -1, len(src)) modFile := c.file.Set().AddFile(moduleName, -1, len(src))
p := parser.NewParser(modFile, src, nil) p := parser.NewParser(modFile, src, nil)
file, err := p.ParseFile() file, err := p.ParseFile()
@ -85,7 +44,7 @@ func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.Comp
symbolTable = symbolTable.Fork(false) symbolTable = symbolTable.Fork(false)
// compile module // compile module
moduleCompiler := c.fork(modFile, moduleName, symbolTable) moduleCompiler := c.fork(modFile, modulePath, symbolTable)
if err := moduleCompiler.Compile(file); err != nil { if err := moduleCompiler.Compile(file); err != nil {
return nil, err return nil, err
} }
@ -98,23 +57,25 @@ func (c *Compiler) doCompileModule(moduleName string, src []byte) (*objects.Comp
compiledFunc := moduleCompiler.Bytecode().MainFunction compiledFunc := moduleCompiler.Bytecode().MainFunction
compiledFunc.NumLocals = symbolTable.MaxSymbols() compiledFunc.NumLocals = symbolTable.MaxSymbols()
c.storeCompiledModule(modulePath, compiledFunc)
return compiledFunc, nil 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 { 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 return
} }
func (c *Compiler) storeCompiledModule(moduleName string, module *objects.CompiledFunction) { func (c *Compiler) storeCompiledModule(modulePath string, module *objects.CompiledFunction) {
if c.parent != nil { if c.parent != nil {
c.parent.storeCompiledModule(moduleName, module) c.parent.storeCompiledModule(modulePath, module)
} }
c.compiledModules[moduleName] = module c.compiledModules[modulePath] = module
} }

View file

@ -695,7 +695,7 @@ func TestCompiler_Compile(t *testing.T) {
expect(t, `len([]);`, expect(t, `len([]);`,
bytecode( bytecode(
concat( concat(
compiler.MakeInstruction(compiler.OpGetBuiltin, 4), compiler.MakeInstruction(compiler.OpGetBuiltin, 0),
compiler.MakeInstruction(compiler.OpArray, 0), compiler.MakeInstruction(compiler.OpArray, 0),
compiler.MakeInstruction(compiler.OpCall, 1), compiler.MakeInstruction(compiler.OpCall, 1),
compiler.MakeInstruction(compiler.OpPop)), compiler.MakeInstruction(compiler.OpPop)),
@ -708,7 +708,7 @@ func TestCompiler_Compile(t *testing.T) {
compiler.MakeInstruction(compiler.OpPop)), compiler.MakeInstruction(compiler.OpPop)),
objectsArray( objectsArray(
compiledFunction(0, 0, compiledFunction(0, 0,
compiler.MakeInstruction(compiler.OpGetBuiltin, 4), compiler.MakeInstruction(compiler.OpGetBuiltin, 0),
compiler.MakeInstruction(compiler.OpArray, 0), compiler.MakeInstruction(compiler.OpArray, 0),
compiler.MakeInstruction(compiler.OpCall, 1), compiler.MakeInstruction(compiler.OpCall, 1),
compiler.MakeInstruction(compiler.OpReturnValue))))) compiler.MakeInstruction(compiler.OpReturnValue)))))
@ -874,7 +874,7 @@ func() {
intObject(1), intObject(1),
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, ` expectError(t, `
r["x"] = { r["x"] = {

View file

@ -54,7 +54,6 @@ const (
OpGetLocalPtr // Get local variable as a pointer OpGetLocalPtr // Get local variable as a pointer
OpSetSelFree // Set free variables using selectors OpSetSelFree // Set free variables using selectors
OpGetBuiltin // Get builtin function OpGetBuiltin // Get builtin function
OpGetBuiltinModule // Get builtin module
OpClosure // Push closure OpClosure // Push closure
OpIteratorInit // Iterator init OpIteratorInit // Iterator init
OpIteratorNext // Iterator next OpIteratorNext // Iterator next
@ -108,7 +107,6 @@ var OpcodeNames = [...]string{
OpDefineLocal: "DEFL", OpDefineLocal: "DEFL",
OpSetSelLocal: "SETSL", OpSetSelLocal: "SETSL",
OpGetBuiltin: "BUILTIN", OpGetBuiltin: "BUILTIN",
OpGetBuiltinModule: "BLTMOD",
OpClosure: "CLOSURE", OpClosure: "CLOSURE",
OpGetFreePtr: "GETFP", OpGetFreePtr: "GETFP",
OpGetFree: "GETF", OpGetFree: "GETF",
@ -167,7 +165,6 @@ var OpcodeOperands = [...][]int{
OpDefineLocal: {1}, OpDefineLocal: {1},
OpSetSelLocal: {1, 1}, OpSetSelLocal: {1, 1},
OpGetBuiltin: {1}, OpGetBuiltin: {1},
OpGetBuiltinModule: {},
OpClosure: {2, 1}, OpClosure: {2, 1},
OpGetFreePtr: {1}, OpGetFreePtr: {1},
OpGetFree: {1}, OpGetFree: {1},

View file

@ -65,6 +65,13 @@ func TestMap(t *testing.T) {
mapElementLit("key3", p(5, 2), p(5, 6), boolLit(true, p(5, 8)))))) 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, }`)
expectError(t, `{ expectError(t, `{
key1: 1, key1: 1,

View file

@ -1,45 +1,5 @@
# Builtin Functions # 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 ## len
Returns the number of elements if the given variable is array, string, map, or module map. 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] 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 ## 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. 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.

View file

@ -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. 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. 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(`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.
```golang ```golang
s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`)) s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`))
_, err := s.Run() // compile error s.SetImports(map[string]objects.Importable{
"math": stdlib.BuiltinModules["math"],
s.SetBuiltinModules(stdlib.Modules) })
// or
_, err := s.Run() // a = 19.84 s.SetImports(stdlib.GetModules("math"))
// or, to include all stdlib at once
s.SetBuiltinModules(nil) s.SetImports(stdlib.GetModules(stdlib.AllModuleNames()...))
_, err := s.Run() // compile error
s.SetBuiltinModules(map[string]*objects.ImmutableMap{"math": stdlib.Modules["math"]})
_, err := s.Run() // a = 19.84
``` ```
#### Script.SetUserModuleLoader(loader compiler.ModuleLoader) You can also include Tengo's written module using `objects.SourceModule` (which implements `objects.Importable`).
SetUserModuleLoader replaces the default user-module loader of the compiler, which tries to read the source from a local file.
```golang ```golang
s := script.New([]byte(`math := import("mod1"); a := math.foo()`)) s := script.New([]byte(`double := import("double"); a := double(20)`))
s.SetUserModuleLoader(func(moduleName string) ([]byte, error) { s.SetImports(map[string]objects.Importable{
if moduleName == "mod1" { "double": &objects.SourceModule{Src: []byte(`export func(x) { return x * 2 }`)},
return []byte(`foo := func() { return 5 }`), nil
}
return nil, errors.New("module not found")
}) })
``` ```
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) #### 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. 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 #### tengo.MaxStringLen

12
docs/stdlib-fmt.md Normal file
View file

@ -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`.

10
docs/stdlib-json.md Normal file
View file

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

View file

@ -5,3 +5,5 @@
- [math](https://github.com/d5/tengo/blob/master/docs/stdlib-math.md): mathematical constants and functions - [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 - [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 - [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

View file

@ -226,7 +226,7 @@ Main script:
```golang ```golang
sum := import("./sum") // assuming sum.tengo file exists in the current directory sum := import("./sum") // assuming sum.tengo file exists in the current directory
// same as 'import("./sum.tengo")' or 'import("sum")' // same as 'import("./sum.tengo")' or 'import("sum")'
print(sum(10)) // module function fmt.print(sum(10)) // module function
``` ```
`sum.tengo` file: `sum.tengo` file:

View file

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

16
objects/builtin_module.go Normal file
View file

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

View file

@ -2,23 +2,7 @@ package objects
// Builtins contains all default builtin functions. // Builtins contains all default builtin functions.
// Use GetBuiltinFunctions instead of accessing Builtins directly. // Use GetBuiltinFunctions instead of accessing Builtins directly.
var Builtins = []BuiltinFunction{ var Builtins = []*BuiltinFunction{
{
Name: "print",
Value: builtinPrint,
},
{
Name: "println",
Value: builtinPrintln,
},
{
Name: "printf",
Value: builtinPrintf,
},
{
Name: "sprintf",
Value: builtinSprintf,
},
{ {
Name: "len", Name: "len",
Value: builtinLen, Value: builtinLen,
@ -119,50 +103,8 @@ var Builtins = []BuiltinFunction{
Name: "is_callable", Name: "is_callable",
Value: builtinIsCallable, Value: builtinIsCallable,
}, },
{
Name: "to_json",
Value: builtinToJSON,
},
{
Name: "from_json",
Value: builtinFromJSON,
},
{ {
Name: "type_name", Name: "type_name",
Value: builtinTypeName, 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()...)
}

View file

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

7
objects/importable.go Normal file
View file

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

11
objects/source_module.go Normal file
View file

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

View file

@ -35,8 +35,6 @@ type VM struct {
curIPLimit int curIPLimit int
ip int ip int
aborting int64 aborting int64
builtinFuncs []objects.Object
builtinModules map[string]objects.Object
maxAllocs int64 maxAllocs int64
allocs int64 allocs int64
err error err error
@ -44,25 +42,11 @@ type VM struct {
} }
// NewVM creates a VM. // 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 { if globals == nil {
globals = make([]objects.Object, GlobalsSize) 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 := make([]Frame, MaxFrames)
frames[0].fn = bytecode.MainFunction frames[0].fn = bytecode.MainFunction
frames[0].ip = -1 frames[0].ip = -1
@ -79,8 +63,6 @@ func NewVM(bytecode *compiler.Bytecode, globals []objects.Object, builtinFuncs [
curInsts: frames[0].fn.Instructions, curInsts: frames[0].fn.Instructions,
curIPLimit: len(frames[0].fn.Instructions) - 1, curIPLimit: len(frames[0].fn.Instructions) - 1,
ip: -1, ip: -1,
builtinFuncs: builtinFuncs,
builtinModules: builtinModules,
maxAllocs: maxAllocs, maxAllocs: maxAllocs,
} }
} }
@ -997,28 +979,7 @@ func (v *VM) run() {
return return
} }
v.stack[v.sp] = v.builtinFuncs[builtinIndex] v.stack[v.sp] = objects.Builtins[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.sp++ v.sp++
case compiler.OpClosure: case compiler.OpClosure:

View file

@ -8,48 +8,48 @@ import (
) )
func TestArray(t *testing.T) { 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 // array copy-by-reference
expect(t, `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 }()`, 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 // 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 // index operator
arr := ARR{1, 2, 3, 4, 5, 6} arr := ARR{1, 2, 3, 4, 5, 6}
arrStr := `[1, 2, 3, 4, 5, 6]` arrStr := `[1, 2, 3, 4, 5, 6]`
arrLen := 6 arrLen := 6
for idx := 0; idx < arrLen; idx++ { for idx := 0; idx < arrLen; idx++ {
expect(t, fmt.Sprintf("out = %s[%d]", arrStr, idx), arr[idx]) expect(t, fmt.Sprintf("out = %s[%d]", arrStr, idx), nil, arr[idx])
expect(t, fmt.Sprintf("out = %s[0 + %d]", arrStr, idx), 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), 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), 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, -1), nil, objects.UndefinedValue)
expect(t, fmt.Sprintf("%s[%d]", arrStr, arrLen), objects.UndefinedValue) expect(t, fmt.Sprintf("%s[%d]", arrStr, arrLen), nil, objects.UndefinedValue)
// slice operator // slice operator
for low := 0; low < arrLen; low++ { 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++ { 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[%d:%d]", arrStr, low, high), nil, 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[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), 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), arr[:high]) expect(t, fmt.Sprintf("out = %s[:%d]", arrStr, high), nil, arr[:high])
expect(t, fmt.Sprintf("out = %s[%d:]", arrStr, low), arr[low:]) 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[:]", arrStr), nil, arr)
expect(t, fmt.Sprintf("out = %s[%d:]", arrStr, -1), arr) expect(t, fmt.Sprintf("out = %s[%d:]", arrStr, -1), nil, arr)
expect(t, fmt.Sprintf("out = %s[:%d]", arrStr, arrLen+1), arr) expect(t, fmt.Sprintf("out = %s[:%d]", arrStr, arrLen+1), nil, arr)
expect(t, fmt.Sprintf("out = %s[%d:%d]", arrStr, 2, 2), 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, -1), nil, "invalid slice index")
expectError(t, fmt.Sprintf("%s[%d:]", arrStr, arrLen+1), "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), "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), "invalid slice index") expectError(t, fmt.Sprintf("%s[%d:%d]", arrStr, 2, 1), nil, "invalid slice index")
} }

View file

@ -5,17 +5,17 @@ import (
) )
func TestAssignment(t *testing.T) { func TestAssignment(t *testing.T) {
expect(t, `a := 1; a = 2; out = a`, 2) expect(t, `a := 1; a = 2; out = a`, nil, 2)
expect(t, `a := 1; a = 2; out = a`, 2) expect(t, `a := 1; a = 2; out = a`, nil, 2)
expect(t, `a := 1; a = a + 4; out = a`, 5) expect(t, `a := 1; a = a + 4; out = a`, nil, 5)
expect(t, `a := 1; f1 := func() { a = 2; return a }; out = f1()`, 2) 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()`, 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; out = a`, nil, 1)
expect(t, `a := 1; a = 2; out = a`, 2) expect(t, `a := 1; a = 2; out = a`, nil, 2)
expect(t, `a := 1; func() { a = 2 }(); out = a`, 2) expect(t, `a := 1; func() { a = 2 }(); out = a`, nil, 2)
expect(t, `a := 1; func() { a := 2 }(); out = a`, 1) // "a := 2" defines a new local variable 'a' 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 }()`, 2) expect(t, `a := 1; func() { b := 2; out = b }()`, nil, 2)
expect(t, ` expect(t, `
out = func() { out = func() {
a := 2 a := 2
@ -24,7 +24,7 @@ out = func() {
}() }()
return a return a
}() }()
`, 3) `, nil, 3)
expect(t, ` expect(t, `
func() { func() {
@ -33,25 +33,25 @@ func() {
a := 4 a := 4
return a return a
}() }()
}()`, 4) }()`, nil, 4)
expectError(t, `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 }()`, "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 += 2; out = a`, nil, 3)
expect(t, `a := 1; a += 4 - 2;; out = a`, 3) expect(t, `a := 1; a += 4 - 2;; out = a`, nil, 3)
expect(t, `a := 3; a -= 1;; out = a`, 2) expect(t, `a := 3; a -= 1;; out = a`, nil, 2)
expect(t, `a := 3; a -= 5 - 4;; out = a`, 2) expect(t, `a := 3; a -= 5 - 4;; out = a`, nil, 2)
expect(t, `a := 2; a *= 4;; out = a`, 8) expect(t, `a := 2; a *= 4;; out = a`, nil, 8)
expect(t, `a := 2; a *= 1 + 3;; out = a`, 8) expect(t, `a := 2; a *= 1 + 3;; out = a`, nil, 8)
expect(t, `a := 10; a /= 2;; out = a`, 5) expect(t, `a := 10; a /= 2;; out = a`, nil, 5)
expect(t, `a := 10; a /= 5 - 3;; out = a`, 5) expect(t, `a := 10; a /= 5 - 3;; out = a`, nil, 5)
// compound assignment operator does not define new variable // compound assignment operator does not define new variable
expectError(t, `a += 4`, "unresolved reference") expectError(t, `a += 4`, nil, "unresolved reference")
expectError(t, `a -= 4`, "unresolved reference") expectError(t, `a -= 4`, nil, "unresolved reference")
expectError(t, `a *= 4`, "unresolved reference") expectError(t, `a *= 4`, nil, "unresolved reference")
expectError(t, `a /= 4`, "unresolved reference") expectError(t, `a /= 4`, nil, "unresolved reference")
expect(t, ` expect(t, `
f1 := func() { f1 := func() {
@ -64,16 +64,16 @@ f1 := func() {
return f2(); return f2();
}; };
out = f1();`, 3) out = f1();`, nil, 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 := 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()`, 2) 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()`, 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()`, 8) 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()`, 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()`, 5) 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()`, 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, ` expect(t, `
f1 := func(a) { f1 := func(a) {
@ -85,7 +85,7 @@ out = f1();`, 3)
} }
out = f1(3)(4) out = f1(3)(4)
`, 11) `, nil, 11)
expect(t, ` expect(t, `
out = func() { out = func() {
@ -101,7 +101,7 @@ out = f1();`, 3)
}() }()
return a return a
}() }()
`, 3) `, nil, 3)
// write on free variables // write on free variables
expect(t, ` expect(t, `
@ -114,7 +114,7 @@ out = f1();`, 3)
}() }()
} }
out = f1() out = f1()
`, 8) `, nil, 8)
expect(t, ` expect(t, `
out = func() { out = func() {
@ -127,7 +127,7 @@ out = f1();`, 3)
} }
return f1() return f1()
}()() }()()
`, 20) `, nil, 20)
expect(t, ` expect(t, `
it := func(seq, fn) { it := func(seq, fn) {
@ -145,7 +145,7 @@ out = f1();`, 3)
} }
out = foo(2) out = foo(2)
`, 5) `, nil, 5)
expect(t, ` expect(t, `
it := func(seq, fn) { it := func(seq, fn) {
@ -163,7 +163,7 @@ out = f1();`, 3)
} }
out = foo(2) out = foo(2)
`, 12) `, nil, 12)
expect(t, ` expect(t, `
out = func() { out = func() {
@ -173,7 +173,7 @@ out = func() {
}() }()
return a return a
}() }()
`, 2) `, nil, 2)
expect(t, ` expect(t, `
f := func() { f := func() {
@ -188,7 +188,7 @@ m := f()
m.b() m.b()
m.c() m.c()
out = m.d() out = m.d()
`, 6) `, nil, 6)
expect(t, ` expect(t, `
each := func(s, x) { for i:=0; i<len(s); i++ { x(s[i]) } } each := func(s, x) { for i:=0; i<len(s); i++ { x(s[i]) } }
@ -203,11 +203,11 @@ out = func() {
return a + b return a + b
} }
}()(20) }()(20)
`, 136) `, nil, 136)
// assigning different type value // assigning different type value
expect(t, `a := 1; a = "foo"; out = a`, "foo") // global expect(t, `a := 1; a = "foo"; out = a`, nil, "foo") // global
expect(t, `func() { a := 1; a = "foo"; out = a }()`, "foo") // local expect(t, `func() { a := 1; a = "foo"; out = a }()`, nil, "foo") // local
expect(t, ` expect(t, `
out = func() { out = func() {
a := 5 a := 5
@ -215,19 +215,19 @@ out = func() {
a = "foo" a = "foo"
return a return a
}() }()
}()`, "foo") // free }()`, nil, "foo") // free
// variables declared in if/for blocks // variables declared in if/for blocks
expect(t, `for a:=0; a<5; a++ {}; a := "foo"; out = a`, "foo") expect(t, `for a:=0; a<5; a++ {}; a := "foo"; out = a`, nil, "foo")
expect(t, `func() { for a:=0; a<5; a++ {}; a := "foo"; out = a }()`, "foo") expect(t, `func() { for a:=0; a<5; a++ {}; a := "foo"; out = a }()`, nil, "foo")
// selectors // selectors
expect(t, `a:=[1,2,3]; a[1] = 5; out = a[1]`, 5) expect(t, `a:=[1,2,3]; a[1] = 5; out = a[1]`, nil, 5)
expect(t, `a:=[1,2,3]; a[1] += 5; out = a[1]`, 7) expect(t, `a:=[1,2,3]; a[1] += 5; out = a[1]`, nil, 7)
expect(t, `a:={b:1,c:2}; a.b = 5; out = a.b`, 5) expect(t, `a:={b:1,c:2}; a.b = 5; out = a.b`, nil, 5)
expect(t, `a:={b:1,c:2}; a.b += 5; out = a.b`, 6) expect(t, `a:={b:1,c:2}; a.b += 5; out = a.b`, nil, 6)
expect(t, `a:={b:1,c:2}; a.b += a.c; out = a.b`, 3) expect(t, `a:={b:1,c:2}; a.b += a.c; out = a.b`, nil, 3)
expect(t, `a:={b:1,c:2}; a.b += a.c; out = a.c`, 2) expect(t, `a:={b:1,c:2}; a.b += a.c; out = a.c`, nil, 2)
expect(t, ` expect(t, `
a := { a := {
b: [1, 2, 3], b: [1, 2, 3],
@ -239,7 +239,7 @@ a := {
} }
a.c.f[1] += 2 a.c.f[1] += 2
out = a["c"]["f"][1] out = a["c"]["f"][1]
`, 10) `, nil, 10)
expect(t, ` expect(t, `
a := { a := {
@ -252,7 +252,7 @@ a := {
} }
a.c.h = "bar" a.c.h = "bar"
out = a.c.h out = a.c.h
`, "bar") `, nil, "bar")
expectError(t, ` expectError(t, `
a := { a := {
@ -263,9 +263,5 @@ a := {
f: [9, 8] f: [9, 8]
} }
} }
a.x.e = "bar"`, "not index-assignable") a.x.e = "bar"`, nil, "not index-assignable")
// multi-variables
//expect(t, `a, b = 1, 2; out = a + b`, 3)
//expect(t, `a, b = 1, 2; a, b = 2, 3; out = a + b`, 5)
} }

View file

@ -3,34 +3,34 @@ package runtime_test
import "testing" import "testing"
func TestBitwise(t *testing.T) { func TestBitwise(t *testing.T) {
expect(t, `out = 1 & 1`, 1&1) expect(t, `out = 1 & 1`, nil, 1)
expect(t, `out = 1 & 0`, 1&0) expect(t, `out = 1 & 0`, nil, 0)
expect(t, `out = 0 & 1`, 0&1) expect(t, `out = 0 & 1`, nil, 0)
expect(t, `out = 0 & 0`, 0&0) expect(t, `out = 0 & 0`, nil, 0)
expect(t, `out = 1 | 1`, 1|1) expect(t, `out = 1 | 1`, nil, 1)
expect(t, `out = 1 | 0`, 1|0) expect(t, `out = 1 | 0`, nil, 1)
expect(t, `out = 0 | 1`, 0|1) expect(t, `out = 0 | 1`, nil, 1)
expect(t, `out = 0 | 0`, 0|0) expect(t, `out = 0 | 0`, nil, 0)
expect(t, `out = 1 ^ 1`, 1^1) expect(t, `out = 1 ^ 1`, nil, 0)
expect(t, `out = 1 ^ 0`, 1^0) expect(t, `out = 1 ^ 0`, nil, 1)
expect(t, `out = 0 ^ 1`, 0^1) expect(t, `out = 0 ^ 1`, nil, 1)
expect(t, `out = 0 ^ 0`, 0^0) expect(t, `out = 0 ^ 0`, nil, 0)
expect(t, `out = 1 &^ 1`, 1&^1) expect(t, `out = 1 &^ 1`, nil, 0)
expect(t, `out = 1 &^ 0`, 1&^0) expect(t, `out = 1 &^ 0`, nil, 1)
expect(t, `out = 0 &^ 1`, 0&^1) expect(t, `out = 0 &^ 1`, nil, 0)
expect(t, `out = 0 &^ 0`, 0&^0) expect(t, `out = 0 &^ 0`, nil, 0)
expect(t, `out = 1 << 2`, 1<<2) expect(t, `out = 1 << 2`, nil, 4)
expect(t, `out = 16 >> 2`, 16>>2) expect(t, `out = 16 >> 2`, nil, 4)
expect(t, `out = 1; out &= 1`, 1) expect(t, `out = 1; out &= 1`, nil, 1)
expect(t, `out = 1; out |= 0`, 1) expect(t, `out = 1; out |= 0`, nil, 1)
expect(t, `out = 1; out ^= 0`, 1) expect(t, `out = 1; out ^= 0`, nil, 1)
expect(t, `out = 1; out &^= 0`, 1) expect(t, `out = 1; out &^= 0`, nil, 1)
expect(t, `out = 1; out <<= 2`, 4) expect(t, `out = 1; out <<= 2`, nil, 4)
expect(t, `out = 16; out >>= 2`, 4) expect(t, `out = 16; out >>= 2`, nil, 4)
expect(t, `out = ^0`, ^0) expect(t, `out = ^0`, nil, ^0)
expect(t, `out = ^1`, ^1) expect(t, `out = ^1`, nil, ^1)
expect(t, `out = ^55`, ^55) expect(t, `out = ^55`, nil, ^55)
expect(t, `out = ^-55`, ^-55) expect(t, `out = ^-55`, nil, ^-55)
} }

View file

@ -5,38 +5,38 @@ import (
) )
func TestBoolean(t *testing.T) { func TestBoolean(t *testing.T) {
expect(t, `out = true`, true) expect(t, `out = true`, nil, true)
expect(t, `out = false`, false) expect(t, `out = false`, nil, false)
expect(t, `out = 1 < 2`, true) expect(t, `out = 1 < 2`, nil, true)
expect(t, `out = 1 > 2`, false) expect(t, `out = 1 > 2`, nil, false)
expect(t, `out = 1 < 1`, false) expect(t, `out = 1 < 1`, nil, false)
expect(t, `out = 1 > 2`, false) expect(t, `out = 1 > 2`, nil, false)
expect(t, `out = 1 == 1`, true) expect(t, `out = 1 == 1`, nil, true)
expect(t, `out = 1 != 1`, false) expect(t, `out = 1 != 1`, nil, false)
expect(t, `out = 1 == 2`, false) expect(t, `out = 1 == 2`, nil, false)
expect(t, `out = 1 != 2`, true) expect(t, `out = 1 != 2`, nil, true)
expect(t, `out = 1 <= 2`, true) expect(t, `out = 1 <= 2`, nil, true)
expect(t, `out = 1 >= 2`, false) expect(t, `out = 1 >= 2`, nil, false)
expect(t, `out = 1 <= 1`, true) expect(t, `out = 1 <= 1`, nil, true)
expect(t, `out = 1 >= 2`, false) expect(t, `out = 1 >= 2`, nil, false)
expect(t, `out = true == true`, true) expect(t, `out = true == true`, nil, true)
expect(t, `out = false == false`, true) expect(t, `out = false == false`, nil, true)
expect(t, `out = true == false`, false) expect(t, `out = true == false`, nil, false)
expect(t, `out = true != false`, true) expect(t, `out = true != false`, nil, true)
expect(t, `out = false != true`, true) expect(t, `out = false != true`, nil, true)
expect(t, `out = (1 < 2) == true`, true) expect(t, `out = (1 < 2) == true`, nil, true)
expect(t, `out = (1 < 2) == false`, false) expect(t, `out = (1 < 2) == false`, nil, false)
expect(t, `out = (1 > 2) == true`, false) expect(t, `out = (1 > 2) == true`, nil, false)
expect(t, `out = (1 > 2) == false`, true) expect(t, `out = (1 > 2) == false`, nil, true)
expectError(t, `5 + true`, "invalid operation") expectError(t, `5 + true`, nil, "invalid operation")
expectError(t, `5 + true; 5`, "invalid operation") expectError(t, `5 + true; 5`, nil, "invalid operation")
expectError(t, `-true`, "invalid operation") expectError(t, `-true`, nil, "invalid operation")
expectError(t, `true + false`, "invalid operation") expectError(t, `true + false`, nil, "invalid operation")
expectError(t, `5; true + false; 5`, "invalid operation") expectError(t, `5; true + false; 5`, nil, "invalid operation")
expectError(t, `if (10 > 1) { true + false; }`, "invalid operation") expectError(t, `if (10 > 1) { true + false; }`, nil, "invalid operation")
expectError(t, ` expectError(t, `
func() { func() {
if (10 > 1) { if (10 > 1) {
@ -47,9 +47,9 @@ func() {
return 1; return 1;
} }
}() }()
`, "invalid operation") `, nil, "invalid operation")
expectError(t, `if (true + false) { 10 }`, "invalid operation") expectError(t, `if (true + false) { 10 }`, nil, "invalid operation")
expectError(t, `10 + (true + false)`, "invalid operation") expectError(t, `10 + (true + false)`, nil, "invalid operation")
expectError(t, `(true + false) + 20`, "invalid operation") expectError(t, `(true + false) + 20`, nil, "invalid operation")
expectError(t, `!(true + false)`, "invalid operation") expectError(t, `!(true + false)`, nil, "invalid operation")
} }

View file

@ -8,198 +8,155 @@ import (
) )
func TestBuiltinFunction(t *testing.T) { func TestBuiltinFunction(t *testing.T) {
expect(t, `out = len("")`, 0) expect(t, `out = len("")`, nil, 0)
expect(t, `out = len("four")`, 4) expect(t, `out = len("four")`, nil, 4)
expect(t, `out = len("hello world")`, 11) expect(t, `out = len("hello world")`, nil, 11)
expect(t, `out = len([])`, 0) expect(t, `out = len([])`, nil, 0)
expect(t, `out = len([1, 2, 3])`, 3) expect(t, `out = len([1, 2, 3])`, nil, 3)
expect(t, `out = len({})`, 0) expect(t, `out = len({})`, nil, 0)
expect(t, `out = len({a:1, b:2})`, 2) expect(t, `out = len({a:1, b:2})`, nil, 2)
expect(t, `out = len(immutable([]))`, 0) expect(t, `out = len(immutable([]))`, nil, 0)
expect(t, `out = len(immutable([1, 2, 3]))`, 3) expect(t, `out = len(immutable([1, 2, 3]))`, nil, 3)
expect(t, `out = len(immutable({}))`, 0) expect(t, `out = len(immutable({}))`, nil, 0)
expect(t, `out = len(immutable({a:1, b:2}))`, 2) expect(t, `out = len(immutable({a:1, b:2}))`, nil, 2)
expectError(t, `len(1)`, "invalid type for argument") expectError(t, `len(1)`, nil, "invalid type for argument")
expectError(t, `len("one", "two")`, "wrong number of arguments") expectError(t, `len("one", "two")`, nil, "wrong number of arguments")
expect(t, `out = copy(1)`, 1) expect(t, `out = copy(1)`, nil, 1)
expectError(t, `copy(1, 2)`, "wrong number of arguments") expectError(t, `copy(1, 2)`, nil, "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)`, nil, ARR{1, 2, 3, 4})
expect(t, `out = append([1, 2, 3], 4, 5, 6)`, ARR{1, 2, 3, 4, 5, 6}) expect(t, `out = append([1, 2, 3], 4, 5, 6)`, nil, 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 = append([1, 2, 3], "foo", false)`, nil, ARR{1, 2, 3, "foo", false})
expect(t, `out = int(1)`, 1) expect(t, `out = int(1)`, nil, 1)
expect(t, `out = int(1.8)`, 1) expect(t, `out = int(1.8)`, nil, 1)
expect(t, `out = int("-522")`, -522) expect(t, `out = int("-522")`, nil, -522)
expect(t, `out = int(true)`, 1) expect(t, `out = int(true)`, nil, 1)
expect(t, `out = int(false)`, 0) expect(t, `out = int(false)`, nil, 0)
expect(t, `out = int('8')`, 56) expect(t, `out = int('8')`, nil, 56)
expect(t, `out = int([1])`, objects.UndefinedValue) expect(t, `out = int([1])`, nil, objects.UndefinedValue)
expect(t, `out = int({a: 1})`, objects.UndefinedValue) expect(t, `out = int({a: 1})`, nil, objects.UndefinedValue)
expect(t, `out = int(undefined)`, objects.UndefinedValue) expect(t, `out = int(undefined)`, nil, objects.UndefinedValue)
expect(t, `out = int("-522", 1)`, -522) expect(t, `out = int("-522", 1)`, nil, -522)
expect(t, `out = int(undefined, 1)`, 1) expect(t, `out = int(undefined, 1)`, nil, 1)
expect(t, `out = int(undefined, 1.8)`, 1.8) expect(t, `out = int(undefined, 1.8)`, nil, 1.8)
expect(t, `out = int(undefined, string(1))`, "1") expect(t, `out = int(undefined, string(1))`, nil, "1")
expect(t, `out = int(undefined, undefined)`, objects.UndefinedValue) expect(t, `out = int(undefined, undefined)`, nil, objects.UndefinedValue)
expect(t, `out = string(1)`, "1") expect(t, `out = string(1)`, nil, "1")
expect(t, `out = string(1.8)`, "1.8") expect(t, `out = string(1.8)`, nil, "1.8")
expect(t, `out = string("-522")`, "-522") expect(t, `out = string("-522")`, nil, "-522")
expect(t, `out = string(true)`, "true") expect(t, `out = string(true)`, nil, "true")
expect(t, `out = string(false)`, "false") expect(t, `out = string(false)`, nil, "false")
expect(t, `out = string('8')`, "8") expect(t, `out = string('8')`, nil, "8")
expect(t, `out = string([1,8.1,true,3])`, "[1, 8.1, true, 3]") expect(t, `out = string([1,8.1,true,3])`, nil, "[1, 8.1, true, 3]")
expect(t, `out = string({b: "foo"})`, `{b: "foo"}`) expect(t, `out = string({b: "foo"})`, nil, `{b: "foo"}`)
expect(t, `out = string(undefined)`, objects.UndefinedValue) // not "undefined" expect(t, `out = string(undefined)`, nil, objects.UndefinedValue) // not "undefined"
expect(t, `out = string(1, "-522")`, "1") expect(t, `out = string(1, "-522")`, nil, "1")
expect(t, `out = string(undefined, "-522")`, "-522") // not "undefined" expect(t, `out = string(undefined, "-522")`, nil, "-522") // not "undefined"
expect(t, `out = float(1)`, 1.0) expect(t, `out = float(1)`, nil, 1.0)
expect(t, `out = float(1.8)`, 1.8) expect(t, `out = float(1.8)`, nil, 1.8)
expect(t, `out = float("-52.2")`, -52.2) expect(t, `out = float("-52.2")`, nil, -52.2)
expect(t, `out = float(true)`, objects.UndefinedValue) expect(t, `out = float(true)`, nil, objects.UndefinedValue)
expect(t, `out = float(false)`, objects.UndefinedValue) expect(t, `out = float(false)`, nil, objects.UndefinedValue)
expect(t, `out = float('8')`, objects.UndefinedValue) expect(t, `out = float('8')`, nil, objects.UndefinedValue)
expect(t, `out = float([1,8.1,true,3])`, objects.UndefinedValue) expect(t, `out = float([1,8.1,true,3])`, nil, objects.UndefinedValue)
expect(t, `out = float({a: 1, b: "foo"})`, objects.UndefinedValue) expect(t, `out = float({a: 1, b: "foo"})`, nil, objects.UndefinedValue)
expect(t, `out = float(undefined)`, objects.UndefinedValue) expect(t, `out = float(undefined)`, nil, objects.UndefinedValue)
expect(t, `out = float("-52.2", 1.8)`, -52.2) expect(t, `out = float("-52.2", 1.8)`, nil, -52.2)
expect(t, `out = float(undefined, 1)`, 1) expect(t, `out = float(undefined, 1)`, nil, 1)
expect(t, `out = float(undefined, 1.8)`, 1.8) expect(t, `out = float(undefined, 1.8)`, nil, 1.8)
expect(t, `out = float(undefined, "-52.2")`, "-52.2") expect(t, `out = float(undefined, "-52.2")`, nil, "-52.2")
expect(t, `out = float(undefined, char(56))`, '8') expect(t, `out = float(undefined, char(56))`, nil, '8')
expect(t, `out = float(undefined, undefined)`, objects.UndefinedValue) expect(t, `out = float(undefined, undefined)`, nil, objects.UndefinedValue)
expect(t, `out = char(56)`, '8') expect(t, `out = char(56)`, nil, '8')
expect(t, `out = char(1.8)`, objects.UndefinedValue) expect(t, `out = char(1.8)`, nil, objects.UndefinedValue)
expect(t, `out = char("-52.2")`, objects.UndefinedValue) expect(t, `out = char("-52.2")`, nil, objects.UndefinedValue)
expect(t, `out = char(true)`, objects.UndefinedValue) expect(t, `out = char(true)`, nil, objects.UndefinedValue)
expect(t, `out = char(false)`, objects.UndefinedValue) expect(t, `out = char(false)`, nil, objects.UndefinedValue)
expect(t, `out = char('8')`, '8') expect(t, `out = char('8')`, nil, '8')
expect(t, `out = char([1,8.1,true,3])`, objects.UndefinedValue) expect(t, `out = char([1,8.1,true,3])`, nil, objects.UndefinedValue)
expect(t, `out = char({a: 1, b: "foo"})`, objects.UndefinedValue) expect(t, `out = char({a: 1, b: "foo"})`, nil, objects.UndefinedValue)
expect(t, `out = char(undefined)`, objects.UndefinedValue) expect(t, `out = char(undefined)`, nil, objects.UndefinedValue)
expect(t, `out = char(56, 'a')`, '8') expect(t, `out = char(56, 'a')`, nil, '8')
expect(t, `out = char(undefined, '8')`, '8') expect(t, `out = char(undefined, '8')`, nil, '8')
expect(t, `out = char(undefined, 56)`, 56) expect(t, `out = char(undefined, 56)`, nil, 56)
expect(t, `out = char(undefined, "-52.2")`, "-52.2") expect(t, `out = char(undefined, "-52.2")`, nil, "-52.2")
expect(t, `out = char(undefined, undefined)`, objects.UndefinedValue) expect(t, `out = char(undefined, undefined)`, nil, objects.UndefinedValue)
expect(t, `out = bool(1)`, true) // non-zero integer: true expect(t, `out = bool(1)`, nil, true) // non-zero integer: true
expect(t, `out = bool(0)`, false) // zero: true expect(t, `out = bool(0)`, nil, false) // zero: true
expect(t, `out = bool(1.8)`, true) // all floats (except for NaN): true expect(t, `out = bool(1.8)`, nil, true) // all floats (except for NaN): true
expect(t, `out = bool(0.0)`, 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")`, true) // non-empty string: true expect(t, `out = bool("false")`, nil, true) // non-empty string: true
expect(t, `out = bool("")`, false) // empty string: false expect(t, `out = bool("")`, nil, false) // empty string: false
expect(t, `out = bool(true)`, true) // true: true expect(t, `out = bool(true)`, nil, true) // true: true
expect(t, `out = bool(false)`, false) // false: false expect(t, `out = bool(false)`, nil, false) // false: false
expect(t, `out = bool('8')`, true) // non-zero chars: true expect(t, `out = bool('8')`, nil, true) // non-zero chars: true
expect(t, `out = bool(char(0))`, false) // zero char: false expect(t, `out = bool(char(0))`, nil, false) // zero char: false
expect(t, `out = bool([1])`, true) // non-empty arrays: true expect(t, `out = bool([1])`, nil, true) // non-empty arrays: true
expect(t, `out = bool([])`, false) // empty array: false expect(t, `out = bool([])`, nil, false) // empty array: false
expect(t, `out = bool({a: 1})`, true) // non-empty maps: true expect(t, `out = bool({a: 1})`, nil, true) // non-empty maps: true
expect(t, `out = bool({})`, false) // empty maps: false expect(t, `out = bool({})`, nil, false) // empty maps: false
expect(t, `out = bool(undefined)`, false) // undefined: false expect(t, `out = bool(undefined)`, nil, false) // undefined: false
expect(t, `out = bytes(1)`, []byte{0}) expect(t, `out = bytes(1)`, nil, []byte{0})
expect(t, `out = bytes(1.8)`, objects.UndefinedValue) expect(t, `out = bytes(1.8)`, nil, objects.UndefinedValue)
expect(t, `out = bytes("-522")`, []byte{'-', '5', '2', '2'}) expect(t, `out = bytes("-522")`, nil, []byte{'-', '5', '2', '2'})
expect(t, `out = bytes(true)`, objects.UndefinedValue) expect(t, `out = bytes(true)`, nil, objects.UndefinedValue)
expect(t, `out = bytes(false)`, objects.UndefinedValue) expect(t, `out = bytes(false)`, nil, objects.UndefinedValue)
expect(t, `out = bytes('8')`, objects.UndefinedValue) expect(t, `out = bytes('8')`, nil, objects.UndefinedValue)
expect(t, `out = bytes([1])`, objects.UndefinedValue) expect(t, `out = bytes([1])`, nil, objects.UndefinedValue)
expect(t, `out = bytes({a: 1})`, objects.UndefinedValue) expect(t, `out = bytes({a: 1})`, nil, objects.UndefinedValue)
expect(t, `out = bytes(undefined)`, objects.UndefinedValue) expect(t, `out = bytes(undefined)`, nil, objects.UndefinedValue)
expect(t, `out = bytes("-522", ['8'])`, []byte{'-', '5', '2', '2'}) expect(t, `out = bytes("-522", ['8'])`, nil, []byte{'-', '5', '2', '2'})
expect(t, `out = bytes(undefined, "-522")`, "-522") expect(t, `out = bytes(undefined, "-522")`, nil, "-522")
expect(t, `out = bytes(undefined, 1)`, 1) expect(t, `out = bytes(undefined, 1)`, nil, 1)
expect(t, `out = bytes(undefined, 1.8)`, 1.8) expect(t, `out = bytes(undefined, 1.8)`, nil, 1.8)
expect(t, `out = bytes(undefined, int("-522"))`, -522) expect(t, `out = bytes(undefined, int("-522"))`, nil, -522)
expect(t, `out = bytes(undefined, undefined)`, objects.UndefinedValue) expect(t, `out = bytes(undefined, undefined)`, nil, objects.UndefinedValue)
expect(t, `out = is_error(error(1))`, true) expect(t, `out = is_error(error(1))`, nil, true)
expect(t, `out = is_error(1)`, false) expect(t, `out = is_error(1)`, nil, false)
expect(t, `out = is_undefined(undefined)`, true) expect(t, `out = is_undefined(undefined)`, nil, true)
expect(t, `out = is_undefined(error(1))`, false) expect(t, `out = is_undefined(error(1))`, nil, 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
// type_name // type_name
expect(t, `out = type_name(1)`, "int") expect(t, `out = type_name(1)`, nil, "int")
expect(t, `out = type_name(1.1)`, "float") expect(t, `out = type_name(1.1)`, nil, "float")
expect(t, `out = type_name("a")`, "string") expect(t, `out = type_name("a")`, nil, "string")
expect(t, `out = type_name([1,2,3])`, "array") expect(t, `out = type_name([1,2,3])`, nil, "array")
expect(t, `out = type_name({k:1})`, "map") expect(t, `out = type_name({k:1})`, nil, "map")
expect(t, `out = type_name('a')`, "char") expect(t, `out = type_name('a')`, nil, "char")
expect(t, `out = type_name(true)`, "bool") expect(t, `out = type_name(true)`, nil, "bool")
expect(t, `out = type_name(false)`, "bool") expect(t, `out = type_name(false)`, nil, "bool")
expect(t, `out = type_name(bytes( 1))`, "bytes") expect(t, `out = type_name(bytes( 1))`, nil, "bytes")
expect(t, `out = type_name(undefined)`, "undefined") expect(t, `out = type_name(undefined)`, nil, "undefined")
expect(t, `out = type_name(error("err"))`, "error") expect(t, `out = type_name(error("err"))`, nil, "error")
expect(t, `out = type_name(func() {})`, "compiled-function") expect(t, `out = type_name(func() {})`, nil, "compiled-function")
expect(t, `a := func(x) { return func() { return x } }; out = type_name(a(5))`, "closure") // closure expect(t, `a := func(x) { return func() { return x } }; out = type_name(a(5))`, nil, "closure") // closure
// is_function // is_function
expect(t, `out = is_function(1)`, false) expect(t, `out = is_function(1)`, nil, false)
expect(t, `out = is_function(func() {})`, true) expect(t, `out = is_function(func() {})`, nil, true)
expect(t, `out = is_function(func(x) { return x })`, true) expect(t, `out = is_function(func(x) { return x })`, nil, true)
expect(t, `out = is_function(len)`, false) // builtin function expect(t, `out = is_function(len)`, nil, 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)`, nil, true) // function
expect(t, `a := func(x) { return func() { return x } }; out = is_function(a(5))`, true) // closure expect(t, `a := func(x) { return func() { return x } }; out = is_function(a(5))`, nil, true) // closure
expectWithSymbols(t, `out = is_function(x)`, false, SYM{"x": &StringArray{Value: []string{"foo", "bar"}}}) // user object expect(t, `out = is_function(x)`, Opts().Symbol("x", &StringArray{Value: []string{"foo", "bar"}}).Skip2ndPass(), false) // user object
// is_callable // is_callable
expect(t, `out = is_callable(1)`, false) expect(t, `out = is_callable(1)`, nil, false)
expect(t, `out = is_callable(func() {})`, true) expect(t, `out = is_callable(func() {})`, nil, true)
expect(t, `out = is_callable(func(x) { return x })`, true) expect(t, `out = is_callable(func(x) { return x })`, nil, true)
expect(t, `out = is_callable(len)`, true) // builtin function expect(t, `out = is_callable(len)`, nil, 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)`, nil, true) // function
expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a(5))`, true) // closure expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a(5))`, nil, true) // closure
expectWithSymbols(t, `out = is_callable(x)`, true, SYM{"x": &StringArray{Value: []string{"foo", "bar"}}}) // user object expect(t, `out = is_callable(x)`, Opts().Symbol("x", &StringArray{Value: []string{"foo", "bar"}}).Skip2ndPass(), true) // user object
} }
func TestBytesN(t *testing.T) { func TestBytesN(t *testing.T) {
@ -207,11 +164,11 @@ func TestBytesN(t *testing.T) {
defer func() { tengo.MaxBytesLen = curMaxBytesLen }() defer func() { tengo.MaxBytesLen = curMaxBytesLen }()
tengo.MaxBytesLen = 10 tengo.MaxBytesLen = 10
expect(t, `out = bytes(0)`, make([]byte, 0)) expect(t, `out = bytes(0)`, nil, make([]byte, 0))
expect(t, `out = bytes(10)`, make([]byte, 10)) expect(t, `out = bytes(10)`, nil, make([]byte, 10))
expectError(t, `bytes(11)`, "bytes size limit") expectError(t, `bytes(11)`, nil, "bytes size limit")
tengo.MaxBytesLen = 1000 tengo.MaxBytesLen = 1000
expect(t, `out = bytes(1000)`, make([]byte, 1000)) expect(t, `out = bytes(1000)`, nil, make([]byte, 1000))
expectError(t, `bytes(1001)`, "bytes size limit") expectError(t, `bytes(1001)`, nil, "bytes size limit")
} }

View file

@ -7,12 +7,12 @@ import (
) )
func TestBytes(t *testing.T) { func TestBytes(t *testing.T) {
expect(t, `out = bytes("Hello World!")`, []byte("Hello World!")) expect(t, `out = bytes("Hello World!")`, nil, []byte("Hello World!"))
expect(t, `out = bytes("Hello") + bytes(" ") + bytes("World!")`, []byte("Hello World!")) expect(t, `out = bytes("Hello") + bytes(" ") + bytes("World!")`, nil, []byte("Hello World!"))
// bytes[] -> int // bytes[] -> int
expect(t, `out = bytes("abcde")[0]`, 97) expect(t, `out = bytes("abcde")[0]`, nil, 97)
expect(t, `out = bytes("abcde")[1]`, 98) expect(t, `out = bytes("abcde")[1]`, nil, 98)
expect(t, `out = bytes("abcde")[4]`, 101) expect(t, `out = bytes("abcde")[4]`, nil, 101)
expect(t, `out = bytes("abcde")[10]`, objects.UndefinedValue) expect(t, `out = bytes("abcde")[10]`, nil, objects.UndefinedValue)
} }

View file

@ -3,9 +3,9 @@ package runtime_test
import "testing" import "testing"
func TestCall(t *testing.T) { func TestCall(t *testing.T) {
expect(t, `a := { b: func(x) { return x + 2 } }; out = a.b(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)`, 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)`, 7) expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a["b"].c(5)`, nil, 7)
expectError(t, `a := 1 expectError(t, `a := 1
b := func(a, c) { b := func(a, c) {
c(a) c(a)
@ -15,5 +15,5 @@ c := func(a) {
a() a()
} }
b(a, c) 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")
} }

View file

@ -3,21 +3,21 @@ package runtime_test
import "testing" import "testing"
func TestChar(t *testing.T) { func TestChar(t *testing.T) {
expect(t, `out = 'a'`, 'a') expect(t, `out = 'a'`, nil, 'a')
expect(t, `out = '九'`, rune(20061)) expect(t, `out = '九'`, nil, rune(20061))
expect(t, `out = 'Æ'`, rune(198)) expect(t, `out = 'Æ'`, nil, rune(198))
expect(t, `out = '0' + '9'`, rune(105)) expect(t, `out = '0' + '9'`, nil, rune(105))
expect(t, `out = '0' + 9`, '9') expect(t, `out = '0' + 9`, nil, '9')
expect(t, `out = '9' - 4`, '5') expect(t, `out = '9' - 4`, nil, '5')
expect(t, `out = '0' == '0'`, true) expect(t, `out = '0' == '0'`, nil, true)
expect(t, `out = '0' != '0'`, false) expect(t, `out = '0' != '0'`, nil, false)
expect(t, `out = '2' < '4'`, true) expect(t, `out = '2' < '4'`, nil, true)
expect(t, `out = '2' > '4'`, false) expect(t, `out = '2' > '4'`, nil, false)
expect(t, `out = '2' <= '4'`, true) expect(t, `out = '2' <= '4'`, nil, true)
expect(t, `out = '2' >= '4'`, false) expect(t, `out = '2' >= '4'`, nil, false)
expect(t, `out = '4' < '4'`, false) expect(t, `out = '4' < '4'`, nil, false)
expect(t, `out = '4' > '4'`, false) expect(t, `out = '4' > '4'`, nil, false)
expect(t, `out = '4' <= '4'`, true) expect(t, `out = '4' <= '4'`, nil, true)
expect(t, `out = '4' >= '4'`, true) expect(t, `out = '4' >= '4'`, nil, true)
} }

View file

@ -3,25 +3,25 @@ package runtime_test
import "testing" import "testing"
func TestCondExpr(t *testing.T) { func TestCondExpr(t *testing.T) {
expect(t, `out = true ? 5 : 10`, 5) expect(t, `out = true ? 5 : 10`, nil, 5)
expect(t, `out = false ? 5 : 10`, 10) expect(t, `out = false ? 5 : 10`, nil, 10)
expect(t, `out = (1 == 1) ? 2 + 3 : 12 - 2`, 5) expect(t, `out = (1 == 1) ? 2 + 3 : 12 - 2`, nil, 5)
expect(t, `out = (1 != 1) ? 2 + 3 : 12 - 2`, 10) expect(t, `out = (1 != 1) ? 2 + 3 : 12 - 2`, nil, 10)
expect(t, `out = (1 == 1) ? true ? 10 - 8 : 1 + 3 : 12 - 2`, 2) 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`, 4) expect(t, `out = (1 == 1) ? false ? 10 - 8 : 1 + 3 : 12 - 2`, nil, 4)
expect(t, ` expect(t, `
out = 0 out = 0
f1 := func() { out += 10 } f1 := func() { out += 10 }
f2 := func() { out = -out } f2 := func() { out = -out }
true ? f1() : f2() true ? f1() : f2()
`, 10) `, nil, 10)
expect(t, ` expect(t, `
out = 5 out = 5
f1 := func() { out += 10 } f1 := func() { out += 10 }
f2 := func() { out = -out } f2 := func() { out = -out }
false ? f1() : f2() false ? f1() : f2()
`, -5) `, nil, -5)
expect(t, ` expect(t, `
f1 := func(a) { return a + 2 } f1 := func(a) { return a + 2 }
f2 := 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)] 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, `f := func(a) { return -a }; out = f(true ? 5 : 3)`, nil, -5)
expect(t, `out = [false?5:10, true?1:2]`, ARR{10, 1}) expect(t, `out = [false?5:10, true?1:2]`, nil, ARR{10, 1})
expect(t, ` expect(t, `
out = 1 > 2 ? out = 1 > 2 ?
1 + 2 + 3 : 1 + 2 + 3 :
10 - 5`, 5) 10 - 5`, nil, 5)
} }

View file

@ -43,8 +43,8 @@ func TestEquality(t *testing.T) {
func testEquality(t *testing.T, lhs, rhs string, expected bool) { func testEquality(t *testing.T, lhs, rhs string, expected bool) {
// 1. equality is commutative // 1. equality is commutative
// 2. equality and inequality must be always opposite // 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", lhs, rhs), nil, expected)
expect(t, fmt.Sprintf("out = %s == %s", rhs, lhs), expected) expect(t, fmt.Sprintf("out = %s == %s", rhs, lhs), nil, expected)
expect(t, fmt.Sprintf("out = %s != %s", lhs, rhs), !expected) expect(t, fmt.Sprintf("out = %s != %s", lhs, rhs), nil, !expected)
expect(t, fmt.Sprintf("out = %s != %s", rhs, lhs), !expected) expect(t, fmt.Sprintf("out = %s != %s", rhs, lhs), nil, !expected)
} }

View file

@ -5,16 +5,16 @@ import "testing"
func TestVMErrorInfo(t *testing.T) { func TestVMErrorInfo(t *testing.T) {
expectError(t, `a := 5 expectError(t, `a := 5
a + "boo"`, 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 expectError(t, `a := 5
b := 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 expectError(t, `a := 5
b := {} b := {}
b.x.y = 10`, 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, ` expectError(t, `
a := func() { a := func() {
@ -22,27 +22,28 @@ a := func() {
b += "foo" b += "foo"
} }
a()`, 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 expectError(t, `a := 5
a + import("mod1")`, map[string]string{ a + import("mod1")`, Opts().Module(
"mod1": `export "foo"`, "mod1", `export "foo"`,
}, ": invalid operation: int + string\n\tat test:2:2") ), ": invalid operation: int + string\n\tat test:2:1")
expectErrorWithUserModules(t, `a := import("mod1")()`, map[string]string{ expectError(t, `a := import("mod1")()`,
"mod1": ` Opts().Module(
"mod1", `
export func() { export func() {
b := 5 b := 5
return b + "foo" 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{ expectError(t, `a := import("mod1")()`,
"mod1": `export import("mod2")()`, Opts().Module(
"mod2": ` "mod1", `export import("mod2")()`).
Module(
"mod2", `
export func() { export func() {
b := 5 b := 5
return b + "foo" 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")
} }

View file

@ -5,17 +5,17 @@ import (
) )
func TestError(t *testing.T) { func TestError(t *testing.T) {
expect(t, `out = error(1)`, errorObject(1)) expect(t, `out = error(1)`, nil, errorObject(1))
expect(t, `out = error(1).value`, 1) expect(t, `out = error(1).value`, nil, 1)
expect(t, `out = error("some error")`, errorObject("some error")) expect(t, `out = error("some error")`, nil, errorObject("some error"))
expect(t, `out = error("some" + " error")`, errorObject("some error")) expect(t, `out = error("some" + " error")`, nil, errorObject("some error"))
expect(t, `out = func() { return error(5) }()`, errorObject(5)) expect(t, `out = func() { return error(5) }()`, nil, errorObject(5))
expect(t, `out = error(error("foo"))`, errorObject(errorObject("foo"))) expect(t, `out = error(error("foo"))`, nil, errorObject(errorObject("foo")))
expect(t, `out = error("some error")`, errorObject("some error")) expect(t, `out = error("some error")`, nil, errorObject("some error"))
expect(t, `out = error("some error").value`, "some error") expect(t, `out = error("some error").value`, nil, "some error")
expect(t, `out = error("some error")["value"]`, "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").err`, nil, "invalid index on error")
expectError(t, `error("error").value_`, "invalid index on error") expectError(t, `error("error").value_`, nil, "invalid index on error")
expectError(t, `error([1,2,3])[1]`, "invalid index on error") expectError(t, `error([1,2,3])[1]`, nil, "invalid index on error")
} }

View file

@ -5,11 +5,11 @@ import (
) )
func TestFloat(t *testing.T) { func TestFloat(t *testing.T) {
expect(t, `out = 0.0`, 0.0) expect(t, `out = 0.0`, nil, 0.0)
expect(t, `out = -10.3`, -10.3) expect(t, `out = -10.3`, nil, -10.3)
expect(t, `out = 3.2 + 2.0 * -4.0`, -4.8) expect(t, `out = 3.2 + 2.0 * -4.0`, nil, -4.8)
expect(t, `out = 4 + 2.3`, 6.3) expect(t, `out = 4 + 2.3`, nil, 6.3)
expect(t, `out = 2.3 + 4`, 6.3) expect(t, `out = 2.3 + 4`, nil, 6.3)
expect(t, `out = +5.0`, 5.0) expect(t, `out = +5.0`, nil, 5.0)
expect(t, `out = -5.0 + +5.0`, 0.0) expect(t, `out = -5.0 + +5.0`, nil, 0.0)
} }

View file

@ -6,20 +6,20 @@ import (
func TestForIn(t *testing.T) { func TestForIn(t *testing.T) {
// array // array
expect(t, `out = 0; for x in [1, 2, 3] { out += x }`, 6) // value 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 }`, 9) // index, 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 } }()`, 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 }`, 3) // index, _ 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 } }()`, 3) // index, _ expect(t, `out = 0; func() { for i, _ in [1, 2, 3] { out += i } }()`, nil, 3) // index, _
// map // map
expect(t, `out = 0; for v in {a:2,b:3,c:4} { out += v }`, 9) // 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 } }`, "b") // key, 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 }`, "a") // key, _ 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 }`, 9) // _, value 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 } } }()`, "b") // key, 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 // string
expect(t, `out = ""; for c in "abcde" { out += c }`, "abcde") 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 }`, "abde") expect(t, `out = ""; for i, c in "abcde" { if i == 2 { continue }; out += c }`, nil, "abde")
} }

View file

@ -12,7 +12,7 @@ func TestFor(t *testing.T) {
if out == 5 { if out == 5 {
break break
} }
}`, 5) }`, nil, 5)
expect(t, ` expect(t, `
out = 0 out = 0
@ -21,7 +21,7 @@ func TestFor(t *testing.T) {
if out == 5 { if out == 5 {
break break
} }
}`, 5) }`, nil, 5)
expect(t, ` expect(t, `
out = 0 out = 0
@ -31,7 +31,7 @@ func TestFor(t *testing.T) {
if a == 3 { continue } if a == 3 { continue }
if a == 5 { break } if a == 5 { break }
out += a out += a
}`, 7) // 1 + 2 + 4 }`, nil, 7) // 1 + 2 + 4
expect(t, ` expect(t, `
out = 0 out = 0
@ -41,7 +41,7 @@ func TestFor(t *testing.T) {
if a == 3 { continue } if a == 3 { continue }
out += a out += a
if a == 5 { break } if a == 5 { break }
}`, 12) // 1 + 2 + 4 + 5 }`, nil, 12) // 1 + 2 + 4 + 5
expect(t, ` expect(t, `
out = 0 out = 0
@ -50,7 +50,7 @@ func TestFor(t *testing.T) {
if out == 5 { if out == 5 {
break break
} }
}`, 5) }`, nil, 5)
expect(t, ` expect(t, `
a := 0 a := 0
@ -60,7 +60,7 @@ func TestFor(t *testing.T) {
break break
} }
} }
out = a`, 5) out = a`, nil, 5)
expect(t, ` expect(t, `
out = 0 out = 0
@ -70,7 +70,7 @@ func TestFor(t *testing.T) {
if a == 3 { continue } if a == 3 { continue }
if a == 5 { break } if a == 5 { break }
out += a out += a
}`, 7) // 1 + 2 + 4 }`, nil, 7) // 1 + 2 + 4
expect(t, ` expect(t, `
out = 0 out = 0
@ -80,7 +80,7 @@ func TestFor(t *testing.T) {
if a == 3 { continue } if a == 3 { continue }
out += a out += a
if a == 5 { break } if a == 5 { break }
}`, 12) // 1 + 2 + 4 + 5 }`, nil, 12) // 1 + 2 + 4 + 5
expect(t, ` expect(t, `
out = 0 out = 0
@ -91,13 +91,13 @@ func TestFor(t *testing.T) {
return return
} }
} }
}()`, 5) }()`, nil, 5)
expect(t, ` expect(t, `
out = 0 out = 0
for a:=1; a<=10; a++ { for a:=1; a<=10; a++ {
out += a out += a
}`, 55) }`, nil, 55)
expect(t, ` expect(t, `
out = 0 out = 0
@ -105,7 +105,7 @@ func TestFor(t *testing.T) {
for b:=3; b<=6; b++ { for b:=3; b<=6; b++ {
out += b out += b
} }
}`, 54) }`, nil, 54)
expect(t, ` expect(t, `
out = 0 out = 0
@ -116,7 +116,7 @@ func TestFor(t *testing.T) {
break break
} }
} }
}()`, 5) }()`, nil, 5)
expect(t, ` expect(t, `
out = 0 out = 0
@ -127,7 +127,7 @@ func TestFor(t *testing.T) {
break break
} }
} }
}()`, 5) }()`, nil, 5)
expect(t, ` expect(t, `
out = func() { out = func() {
@ -139,7 +139,7 @@ func TestFor(t *testing.T) {
} }
} }
return a return a
}()`, 5) }()`, nil, 5)
expect(t, ` expect(t, `
out = func() { out = func() {
@ -151,7 +151,7 @@ func TestFor(t *testing.T) {
} }
} }
return a return a
}()`, 5) }()`, nil, 5)
expect(t, ` expect(t, `
out = func() { out = func() {
@ -165,7 +165,7 @@ func TestFor(t *testing.T) {
} }
}() }()
return a return a
}()`, 5) }()`, nil, 5)
expect(t, ` expect(t, `
out = func() { out = func() {
@ -179,7 +179,7 @@ func TestFor(t *testing.T) {
} }
}() }()
return a return a
}()`, 5) }()`, nil, 5)
expect(t, ` expect(t, `
out = func() { out = func() {
@ -188,7 +188,7 @@ func TestFor(t *testing.T) {
sum += a sum += a
} }
return sum return sum
}()`, 55) }()`, nil, 55)
expect(t, ` expect(t, `
out = func() { out = func() {
@ -199,7 +199,7 @@ func TestFor(t *testing.T) {
} }
} }
return sum return sum
}()`, 48) // (3+4+5) * 4 }()`, nil, 48) // (3+4+5) * 4
expect(t, ` expect(t, `
a := 1 a := 1
@ -208,7 +208,7 @@ func TestFor(t *testing.T) {
break break
} }
} }
out = a`, 5) out = a`, nil, 5)
expect(t, ` expect(t, `
out = 0 out = 0
@ -220,7 +220,7 @@ func TestFor(t *testing.T) {
if a == 5 { if a == 5 {
break break
} }
}`, 12) // 1 + 2 + 4 + 5 }`, nil, 12) // 1 + 2 + 4 + 5
expect(t, ` expect(t, `
out = 0 out = 0
@ -234,5 +234,5 @@ func TestFor(t *testing.T) {
break break
} }
a++ a++
}`, 12) // 1 + 2 + 4 + 5 }`, nil, 12) // 1 + 2 + 4 + 5
} }

View file

@ -8,16 +8,16 @@ import (
func TestFunction(t *testing.T) { func TestFunction(t *testing.T) {
// function with no "return" statement returns "invalid" value. // function with no "return" statement returns "invalid" value.
expect(t, `f1 := func() {}; out = f1();`, objects.UndefinedValue) expect(t, `f1 := func() {}; out = f1();`, nil, objects.UndefinedValue)
expect(t, `f1 := func() {}; f2 := func() { return f1(); }; f1(); out = f2();`, 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);`, 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; }; out = f(5);`, nil, 5)
expect(t, `f := func(x) { return x * 2; }; out = f(5);`, 10) 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);`, 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));`, 20) 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)`, 5) expect(t, `out = func(x) { return x; }(5)`, nil, 5)
expect(t, `x := 10; f := func(x) { return x; }; f(5); out = x;`, 10) expect(t, `x := 10; f := func(x) { return x; }; f(5); out = x;`, nil, 10)
expect(t, ` expect(t, `
f2 := func(a) { f2 := func(a) {
@ -29,7 +29,7 @@ func TestFunction(t *testing.T) {
}; };
out = f2(10); out = f2(10);
`, 60) `, nil, 60)
// closures // closures
expect(t, ` expect(t, `
@ -39,7 +39,7 @@ func TestFunction(t *testing.T) {
add2 := newAdder(2); add2 := newAdder(2);
out = add2(5); out = add2(5);
`, 7) `, nil, 7)
// function as a argument // function as a argument
expect(t, ` expect(t, `
@ -48,17 +48,17 @@ func TestFunction(t *testing.T) {
applyFunc := func(a, b, f) { return f(a, b) }; applyFunc := func(a, b, f) { return f(a, b) };
out = applyFunc(applyFunc(2, 2, add), 3, sub); 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 5 + 10; }; out = f1();`, nil, 15)
expect(t, `f1 := func() { return 1 }; f2 := func() { return 2 }; out = f1() + f2()`, 3) 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()`, 6) 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();`, 99) expect(t, `f1 := func() { return 99; 100 }; out = f1();`, nil, 99)
expect(t, `f1 := func() { return 99; return 100 }; out = f1();`, 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()();`, 33) expect(t, `f1 := func() { return 33; }; f2 := func() { return f1 }; out = f2()();`, nil, 33)
expect(t, `one := func() { one = 1; return one }; out = one()`, 1) 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()`, 3) 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()`, 10) 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, ` expect(t, `
foo1 := func() { foo1 := func() {
foo := 50 foo := 50
@ -68,7 +68,7 @@ func TestFunction(t *testing.T) {
foo := 100 foo := 100
return foo return foo
} }
out = foo1() + foo2()`, 150) out = foo1() + foo2()`, nil, 150)
expect(t, ` expect(t, `
g := 50; g := 50;
minusOne := func() { minusOne := func() {
@ -80,35 +80,35 @@ func TestFunction(t *testing.T) {
return g - n; return g - n;
}; };
out = minusOne() + minusTwo() out = minusOne() + minusTwo()
`, 97) `, nil, 97)
expect(t, ` expect(t, `
f1 := func() { f1 := func() {
f2 := func() { return 1; } f2 := func() { return 1; }
return f2 return f2
}; };
out = f1()() out = f1()()
`, 1) `, nil, 1)
expect(t, ` expect(t, `
f1 := func(a) { return a; }; f1 := func(a) { return a; };
out = f1(4)`, 4) out = f1(4)`, nil, 4)
expect(t, ` expect(t, `
f1 := func(a, b) { return a + b; }; f1 := func(a, b) { return a + b; };
out = f1(1, 2)`, 3) out = f1(1, 2)`, nil, 3)
expect(t, ` expect(t, `
sum := func(a, b) { sum := func(a, b) {
c := a + b; c := a + b;
return c; return c;
}; };
out = sum(1, 2);`, 3) out = sum(1, 2);`, nil, 3)
expect(t, ` expect(t, `
sum := func(a, b) { sum := func(a, b) {
c := a + b; c := a + b;
return c; return c;
}; };
out = sum(1, 2) + sum(3, 4);`, 10) out = sum(1, 2) + sum(3, 4);`, nil, 10)
expect(t, ` expect(t, `
sum := func(a, b) { sum := func(a, b) {
@ -118,7 +118,7 @@ func TestFunction(t *testing.T) {
outer := func() { outer := func() {
return sum(1, 2) + sum(3, 4) return sum(1, 2) + sum(3, 4)
}; };
out = outer();`, 10) out = outer();`, nil, 10)
expect(t, ` expect(t, `
g := 10; g := 10;
@ -133,11 +133,11 @@ func TestFunction(t *testing.T) {
} }
out = outer() + g out = outer() + g
`, 50) `, nil, 50)
expectError(t, `func() { return 1; }(1)`, "wrong number of arguments") expectError(t, `func() { return 1; }(1)`, nil, "wrong number of arguments")
expectError(t, `func(a) { return a; }()`, "wrong number of arguments") expectError(t, `func(a) { return a; }()`, nil, "wrong number of arguments")
expectError(t, `func(a, b) { return a + b; }(1)`, "wrong number of arguments") expectError(t, `func(a, b) { return a + b; }(1)`, nil, "wrong number of arguments")
expect(t, ` expect(t, `
f1 := func(a) { f1 := func(a) {
@ -145,7 +145,7 @@ func TestFunction(t *testing.T) {
}; };
f2 := f1(99); f2 := f1(99);
out = f2() out = f2()
`, 99) `, nil, 99)
expect(t, ` expect(t, `
f1 := func(a, b) { f1 := func(a, b) {
@ -154,7 +154,7 @@ func TestFunction(t *testing.T) {
f2 := f1(1, 2); f2 := f1(1, 2);
out = f2(8); out = f2(8);
`, 11) `, nil, 11)
expect(t, ` expect(t, `
f1 := func(a, b) { f1 := func(a, b) {
c := a + b; c := a + b;
@ -162,7 +162,7 @@ func TestFunction(t *testing.T) {
}; };
f2 := f1(1, 2); f2 := f1(1, 2);
out = f2(8); out = f2(8);
`, 11) `, nil, 11)
expect(t, ` expect(t, `
f1 := func(a, b) { f1 := func(a, b) {
c := a + b; c := a + b;
@ -174,7 +174,7 @@ func TestFunction(t *testing.T) {
f2 := f1(1, 2); f2 := f1(1, 2);
f3 := f2(3); f3 := f2(3);
out = f3(8); out = f3(8);
`, 14) `, nil, 14)
expect(t, ` expect(t, `
a := 1; a := 1;
f1 := func(b) { f1 := func(b) {
@ -185,7 +185,7 @@ func TestFunction(t *testing.T) {
f2 := f1(2); f2 := f1(2);
f3 := f2(3); f3 := f2(3);
out = f3(8); out = f3(8);
`, 14) `, nil, 14)
expect(t, ` expect(t, `
f1 := func(a, b) { f1 := func(a, b) {
one := func() { return a; }; one := func() { return a; };
@ -194,7 +194,7 @@ func TestFunction(t *testing.T) {
}; };
f2 := f1(9, 90); f2 := f1(9, 90);
out = f2(); out = f2();
`, 99) `, nil, 99)
// global function recursion // global function recursion
expect(t, ` expect(t, `
@ -207,7 +207,7 @@ func TestFunction(t *testing.T) {
return fib(x-1) + fib(x-2) return fib(x-1) + fib(x-2)
} }
} }
out = fib(15)`, 610) out = fib(15)`, nil, 610)
// local function recursion // local function recursion
expect(t, ` expect(t, `
@ -216,9 +216,9 @@ out = func() {
return x == 0 ? 0 : x + sum(x-1) return x == 0 ? 0 : x + sum(x-1)
} }
return sum(5) 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 // closure and block scopes
expect(t, ` expect(t, `
@ -230,7 +230,7 @@ func() {
out = a + 5 out = a + 5
} }
}() }()
}()`, 15) }()`, nil, 15)
expect(t, ` expect(t, `
func() { func() {
a := 10 a := 10
@ -240,7 +240,7 @@ func() {
out = a + b() out = a + b()
} }
}() }()
}()`, 15) }()`, nil, 15)
expect(t, ` expect(t, `
func() { func() {
a := 10 a := 10
@ -252,5 +252,5 @@ func() {
} }
}() }()
}() }()
}()`, 15) }()`, nil, 15)
} }

View file

@ -7,29 +7,29 @@ import (
) )
func TestIf(t *testing.T) { func TestIf(t *testing.T) {
expect(t, `if (true) { out = 10 }`, 10) expect(t, `if (true) { out = 10 }`, nil, 10)
expect(t, `if (false) { out = 10 }`, objects.UndefinedValue) expect(t, `if (false) { out = 10 }`, nil, objects.UndefinedValue)
expect(t, `if (false) { out = 10 } else { out = 20 }`, 20) expect(t, `if (false) { out = 10 } else { out = 20 }`, nil, 20)
expect(t, `if (1) { out = 10 }`, 10) expect(t, `if (1) { out = 10 }`, nil, 10)
expect(t, `if (0) { out = 10 } else { out = 20 }`, 20) expect(t, `if (0) { out = 10 } else { out = 20 }`, nil, 20)
expect(t, `if (1 < 2) { out = 10 }`, 10) expect(t, `if (1 < 2) { out = 10 }`, nil, 10)
expect(t, `if (1 > 2) { out = 10 }`, objects.UndefinedValue) expect(t, `if (1 > 2) { out = 10 }`, nil, objects.UndefinedValue)
expect(t, `if (1 < 2) { out = 10 } else { out = 20 }`, 10) expect(t, `if (1 < 2) { out = 10 } else { out = 20 }`, nil, 10)
expect(t, `if (1 > 2) { out = 10 } else { out = 20 }`, 20) 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 }`, nil, 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 }`, nil, 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 { 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 }`, 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 }`, 22) 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}`, 32) 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 }`, 22) 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 }`, 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, 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) { 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 } }`, 33) 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, `if a:=0; a<1 { out = 10 }`, nil, 10)
expect(t, `a:=0; if a++; a==1 { out = 10 }`, 10) expect(t, `a:=0; if a++; a==1 { out = 10 }`, nil, 10)
expect(t, ` expect(t, `
func() { func() {
a := 1 a := 1
@ -37,7 +37,7 @@ func() {
out = a out = a
} }
}() }()
`, 2) `, nil, 2)
expect(t, ` expect(t, `
func() { func() {
a := 1 a := 1
@ -47,7 +47,7 @@ func() {
out = 20 out = 20
} }
}() }()
`, 20) `, nil, 20)
expect(t, ` expect(t, `
func() { func() {
a := 1 a := 1
@ -60,5 +60,5 @@ func() {
out = a out = a
}() }()
`, 3) `, nil, 3)
} }

View file

@ -9,46 +9,46 @@ import (
func TestImmutable(t *testing.T) { func TestImmutable(t *testing.T) {
// primitive types are already immutable values // primitive types are already immutable values
// immutable expression has no effects. // immutable expression has no effects.
expect(t, `a := immutable(1); out = a`, 1) expect(t, `a := immutable(1); out = a`, nil, 1)
expect(t, `a := 5; b := immutable(a); out = b`, 5) expect(t, `a := 5; b := immutable(a); out = b`, nil, 5)
expect(t, `a := immutable(1); a = 5; out = a`, 5) expect(t, `a := immutable(1); a = 5; out = a`, nil, 5)
// array // array
expectError(t, `a := immutable([1, 2, 3]); a[1] = 5`, "not index-assignable") 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"`, "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`, IARR{"foo", ARR{1, "bar", 3}}) 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"`, "not index-assignable") 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"`, "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`, ARR{1, 5, 3}) 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`, IARR{1, 2, 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]`, true) expect(t, `out = immutable([1,2,3]) == [1,2,3]`, nil, true)
expect(t, `out = immutable([1,2,3]) == immutable([1,2,3])`, true) expect(t, `out = immutable([1,2,3]) == immutable([1,2,3])`, nil, true)
expect(t, `out = [1,2,3] == immutable([1,2,3])`, true) expect(t, `out = [1,2,3] == immutable([1,2,3])`, nil, true)
expect(t, `out = immutable([1,2,3]) == [1,2]`, false) expect(t, `out = immutable([1,2,3]) == [1,2]`, nil, false)
expect(t, `out = immutable([1,2,3]) == immutable([1,2])`, false) expect(t, `out = immutable([1,2,3]) == immutable([1,2])`, nil, false)
expect(t, `out = [1,2,3] == immutable([1,2])`, false) expect(t, `out = [1,2,3] == immutable([1,2])`, nil, false)
expect(t, `out = immutable([1, 2, 3, 4])[1]`, 2) expect(t, `out = immutable([1, 2, 3, 4])[1]`, nil, 2)
expect(t, `out = immutable([1, 2, 3, 4])[1:3]`, ARR{2, 3}) 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`, 5) expect(t, `a := immutable([1,2,3]); a = 5; out = a`, nil, 5)
expect(t, `a := immutable([1, 2, 3]); out = a[5]`, objects.UndefinedValue) expect(t, `a := immutable([1, 2, 3]); out = a[5]`, nil, objects.UndefinedValue)
// map // 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 = 5`, nil, "not index-assignable")
expectError(t, `a := immutable({b: 1, c: 2}); a["b"] = "bar"`, "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`, IMAP{"b": 1, "c": ARR{1, "bar", 3}}) 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"`, "not index-assignable") 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"`, "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}`, true) 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})`, 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})`, 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}`, false) 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})`, 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})`, false) expect(t, `out = {a:1,b:2} == immutable({a:1,b:3})`, nil, false)
expect(t, `out = immutable({a:1,b:2}).b`, 2) expect(t, `out = immutable({a:1,b:2}).b`, nil, 2)
expect(t, `out = immutable({a:1,b:2})["b"]`, 2) expect(t, `out = immutable({a:1,b:2})["b"]`, nil, 2)
expect(t, `a := immutable({a:1,b:2}); a = 5; out = 5`, 5) 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`, objects.UndefinedValue) 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) expect(t, `a := immutable({b: 5, c: "foo"}); out = a.b`, nil, 5)
expectError(t, `a := immutable({b: 5, c: "foo"}); a.b = 10`, "not index-assignable") expectError(t, `a := immutable({b: 5, c: "foo"}); a.b = 10`, nil, "not index-assignable")
} }

View file

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

View file

@ -225,44 +225,44 @@ func (o *StringArray) Call(args ...objects.Object) (ret objects.Object, err erro
func TestIndexable(t *testing.T) { func TestIndexable(t *testing.T) {
dict := func() *StringDict { return &StringDict{Value: map[string]string{"a": "foo", "b": "bar"}} } dict := func() *StringDict { return &StringDict{Value: map[string]string{"a": "foo", "b": "bar"}} }
expectWithSymbols(t, `out = dict["a"]`, "foo", SYM{"dict": dict()}) expect(t, `out = dict["a"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "foo")
expectWithSymbols(t, `out = dict["B"]`, "bar", SYM{"dict": dict()}) expect(t, `out = dict["B"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "bar")
expectWithSymbols(t, `out = dict["x"]`, objects.UndefinedValue, SYM{"dict": dict()}) expect(t, `out = dict["x"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), objects.UndefinedValue)
expectErrorWithSymbols(t, `dict[0]`, SYM{"dict": dict()}, "invalid index type") expectError(t, `dict[0]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "invalid index type")
strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} } strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} }
expectWithSymbols(t, `out = cir[0]`, "one", SYM{"cir": strCir()}) expect(t, `out = cir[0]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "one")
expectWithSymbols(t, `out = cir[1]`, "two", SYM{"cir": strCir()}) expect(t, `out = cir[1]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "two")
expectWithSymbols(t, `out = cir[-1]`, "three", SYM{"cir": strCir()}) expect(t, `out = cir[-1]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "three")
expectWithSymbols(t, `out = cir[-2]`, "two", SYM{"cir": strCir()}) expect(t, `out = cir[-2]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "two")
expectWithSymbols(t, `out = cir[3]`, "one", SYM{"cir": strCir()}) expect(t, `out = cir[3]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "one")
expectErrorWithSymbols(t, `cir["a"]`, SYM{"cir": strCir()}, "invalid index type") expectError(t, `cir["a"]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "invalid index type")
strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} } strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} }
expectWithSymbols(t, `out = arr["one"]`, 0, SYM{"arr": strArr()}) expect(t, `out = arr["one"]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), 0)
expectWithSymbols(t, `out = arr["three"]`, 2, SYM{"arr": strArr()}) expect(t, `out = arr["three"]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), 2)
expectWithSymbols(t, `out = arr["four"]`, objects.UndefinedValue, SYM{"arr": strArr()}) expect(t, `out = arr["four"]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), objects.UndefinedValue)
expectWithSymbols(t, `out = arr[0]`, "one", SYM{"arr": strArr()}) expect(t, `out = arr[0]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "one")
expectWithSymbols(t, `out = arr[1]`, "two", SYM{"arr": strArr()}) expect(t, `out = arr[1]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "two")
expectErrorWithSymbols(t, `arr[-1]`, SYM{"arr": strArr()}, "index out of bounds") expectError(t, `arr[-1]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "index out of bounds")
} }
func TestIndexAssignable(t *testing.T) { func TestIndexAssignable(t *testing.T) {
dict := func() *StringDict { return &StringDict{Value: map[string]string{"a": "foo", "b": "bar"}} } 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()}) expect(t, `dict["a"] = "1984"; out = dict["a"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "1984")
expectWithSymbols(t, `dict["c"] = "1984"; out = dict["c"]`, "1984", SYM{"dict": dict()}) expect(t, `dict["c"] = "1984"; out = dict["c"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "1984")
expectWithSymbols(t, `dict["c"] = 1984; out = dict["C"]`, "1984", SYM{"dict": dict()}) expect(t, `dict["c"] = 1984; out = dict["C"]`, Opts().Symbol("dict", dict()).Skip2ndPass(), "1984")
expectErrorWithSymbols(t, `dict[0] = "1984"`, SYM{"dict": dict()}, "invalid index type") expectError(t, `dict[0] = "1984"`, Opts().Symbol("dict", dict()).Skip2ndPass(), "invalid index type")
strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} } strCir := func() *StringCircle { return &StringCircle{Value: []string{"one", "two", "three"}} }
expectWithSymbols(t, `cir[0] = "ONE"; out = cir[0]`, "ONE", SYM{"cir": strCir()}) expect(t, `cir[0] = "ONE"; out = cir[0]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "ONE")
expectWithSymbols(t, `cir[1] = "TWO"; out = cir[1]`, "TWO", SYM{"cir": strCir()}) expect(t, `cir[1] = "TWO"; out = cir[1]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "TWO")
expectWithSymbols(t, `cir[-1] = "THREE"; out = cir[2]`, "THREE", SYM{"cir": strCir()}) expect(t, `cir[-1] = "THREE"; out = cir[2]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "THREE")
expectWithSymbols(t, `cir[0] = "ONE"; out = cir[3]`, "ONE", SYM{"cir": strCir()}) expect(t, `cir[0] = "ONE"; out = cir[3]`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "ONE")
expectErrorWithSymbols(t, `cir["a"] = "ONE"`, SYM{"cir": strCir()}, "invalid index type") expectError(t, `cir["a"] = "ONE"`, Opts().Symbol("cir", strCir()).Skip2ndPass(), "invalid index type")
strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} } strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} }
expectWithSymbols(t, `arr[0] = "ONE"; out = arr[0]`, "ONE", SYM{"arr": strArr()}) expect(t, `arr[0] = "ONE"; out = arr[0]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "ONE")
expectWithSymbols(t, `arr[1] = "TWO"; out = arr[1]`, "TWO", SYM{"arr": strArr()}) expect(t, `arr[1] = "TWO"; out = arr[1]`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "TWO")
expectErrorWithSymbols(t, `arr["one"] = "ONE"`, SYM{"arr": strArr()}, "invalid index type") expectError(t, `arr["one"] = "ONE"`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "invalid index type")
} }

View file

@ -5,26 +5,26 @@ import (
) )
func TestInteger(t *testing.T) { func TestInteger(t *testing.T) {
expect(t, `out = 5`, 5) expect(t, `out = 5`, nil, 5)
expect(t, `out = 10`, 10) expect(t, `out = 10`, nil, 10)
expect(t, `out = -5`, -5) expect(t, `out = -5`, nil, -5)
expect(t, `out = -10`, -10) expect(t, `out = -10`, nil, -10)
expect(t, `out = 5 + 5 + 5 + 5 - 10`, 10) expect(t, `out = 5 + 5 + 5 + 5 - 10`, nil, 10)
expect(t, `out = 2 * 2 * 2 * 2 * 2`, 32) expect(t, `out = 2 * 2 * 2 * 2 * 2`, nil, 32)
expect(t, `out = -50 + 100 + -50`, 0) expect(t, `out = -50 + 100 + -50`, nil, 0)
expect(t, `out = 5 * 2 + 10`, 20) expect(t, `out = 5 * 2 + 10`, nil, 20)
expect(t, `out = 5 + 2 * 10`, 25) expect(t, `out = 5 + 2 * 10`, nil, 25)
expect(t, `out = 20 + 2 * -10`, 0) expect(t, `out = 20 + 2 * -10`, nil, 0)
expect(t, `out = 50 / 2 * 2 + 10`, 60) expect(t, `out = 50 / 2 * 2 + 10`, nil, 60)
expect(t, `out = 2 * (5 + 10)`, 30) expect(t, `out = 2 * (5 + 10)`, nil, 30)
expect(t, `out = 3 * 3 * 3 + 10`, 37) expect(t, `out = 3 * 3 * 3 + 10`, nil, 37)
expect(t, `out = 3 * (3 * 3) + 10`, 37) expect(t, `out = 3 * (3 * 3) + 10`, nil, 37)
expect(t, `out = (5 + 10 * 2 + 15 /3) * 2 + -10`, 50) expect(t, `out = (5 + 10 * 2 + 15 /3) * 2 + -10`, nil, 50)
expect(t, `out = 5 % 3`, 2) expect(t, `out = 5 % 3`, nil, 2)
expect(t, `out = 5 % 3 + 4`, 6) expect(t, `out = 5 % 3 + 4`, nil, 6)
expect(t, `out = +5`, 5) expect(t, `out = +5`, nil, 5)
expect(t, `out = +5 + -5`, 0) expect(t, `out = +5 + -5`, nil, 0)
expect(t, `out = 9 + '0'`, '9') expect(t, `out = 9 + '0'`, nil, '9')
expect(t, `out = '9' - 5`, '4') expect(t, `out = '9' - 5`, nil, '4')
} }

View file

@ -38,7 +38,7 @@ func (o *StringArray) Iterate() objects.Iterator {
func TestIterable(t *testing.T) { func TestIterable(t *testing.T) {
strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} } strArr := func() *StringArray { return &StringArray{Value: []string{"one", "two", "three"}} }
expectWithSymbols(t, `for i, s in arr { out += i }`, 3, SYM{"arr": strArr()}) expect(t, `for i, s in arr { out += i }`, Opts().Symbol("arr", strArr()).Skip2ndPass(), 3)
expectWithSymbols(t, `for i, s in arr { out += s }`, "onetwothree", SYM{"arr": strArr()}) expect(t, `for i, s in arr { out += s }`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "onetwothree")
expectWithSymbols(t, `for i, s in arr { out += s + i }`, "one0two1three2", SYM{"arr": strArr()}) expect(t, `for i, s in arr { out += s + i }`, Opts().Symbol("arr", strArr()).Skip2ndPass(), "one0two1three2")
} }

View file

@ -3,39 +3,39 @@ package runtime_test
import "testing" import "testing"
func TestLogical(t *testing.T) { func TestLogical(t *testing.T) {
expect(t, `out = true && true`, true) expect(t, `out = true && true`, nil, true)
expect(t, `out = true && false`, false) expect(t, `out = true && false`, nil, false)
expect(t, `out = false && true`, false) expect(t, `out = false && true`, nil, false)
expect(t, `out = false && false`, false) expect(t, `out = false && false`, nil, false)
expect(t, `out = !true && true`, false) expect(t, `out = !true && true`, nil, false)
expect(t, `out = !true && false`, false) expect(t, `out = !true && false`, nil, false)
expect(t, `out = !false && true`, true) expect(t, `out = !false && true`, nil, true)
expect(t, `out = !false && false`, false) expect(t, `out = !false && false`, nil, false)
expect(t, `out = true || true`, true) expect(t, `out = true || true`, nil, true)
expect(t, `out = true || false`, true) expect(t, `out = true || false`, nil, true)
expect(t, `out = false || true`, true) expect(t, `out = false || true`, nil, true)
expect(t, `out = false || false`, false) expect(t, `out = false || false`, nil, false)
expect(t, `out = !true || true`, true) expect(t, `out = !true || true`, nil, true)
expect(t, `out = !true || false`, false) expect(t, `out = !true || false`, nil, false)
expect(t, `out = !false || true`, true) expect(t, `out = !false || true`, nil, true)
expect(t, `out = !false || false`, true) expect(t, `out = !false || false`, nil, true)
expect(t, `out = 1 && 2`, 2) expect(t, `out = 1 && 2`, nil, 2)
expect(t, `out = 1 || 2`, 1) expect(t, `out = 1 || 2`, nil, 1)
expect(t, `out = 1 && 0`, 0) expect(t, `out = 1 && 0`, nil, 0)
expect(t, `out = 1 || 0`, 1) expect(t, `out = 1 || 0`, nil, 1)
expect(t, `out = 1 && (0 || 2)`, 2) expect(t, `out = 1 && (0 || 2)`, nil, 2)
expect(t, `out = 0 || (0 || 2)`, 2) expect(t, `out = 0 || (0 || 2)`, nil, 2)
expect(t, `out = 0 || (0 && 2)`, 0) expect(t, `out = 0 || (0 && 2)`, nil, 0)
expect(t, `out = 0 || (2 && 0)`, 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}; t() && f()`, nil, 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()`, nil, 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}; f() || t()`, nil, 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()`, nil, 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()`, nil, 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()`, nil, 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}; !f() || t()`, nil, 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)
} }

View file

@ -12,7 +12,7 @@ out = {
one: 10 - 9, one: 10 - 9,
two: 1 + 1, two: 1 + 1,
three: 6 / 2 three: 6 / 2
}`, MAP{ }`, nil, MAP{
"one": 1, "one": 1,
"two": 2, "two": 2,
"three": 3, "three": 3,
@ -23,16 +23,16 @@ out = {
"one": 10 - 9, "one": 10 - 9,
"two": 1 + 1, "two": 1 + 1,
"three": 6 / 2 "three": 6 / 2
}`, MAP{ }`, nil, MAP{
"one": 1, "one": 1,
"two": 2, "two": 2,
"three": 3, "three": 3,
}) })
expect(t, `out = {foo: 5}["foo"]`, 5) expect(t, `out = {foo: 5}["foo"]`, nil, 5)
expect(t, `out = {foo: 5}["bar"]`, objects.UndefinedValue) expect(t, `out = {foo: 5}["bar"]`, nil, objects.UndefinedValue)
expect(t, `key := "foo"; out = {foo: 5}[key]`, 5) expect(t, `key := "foo"; out = {foo: 5}[key]`, nil, 5)
expect(t, `out = {}["foo"]`, objects.UndefinedValue) expect(t, `out = {}["foo"]`, nil, objects.UndefinedValue)
expect(t, ` expect(t, `
m := { m := {
@ -41,11 +41,11 @@ m := {
} }
} }
out = m["foo"](2) + m["foo"](3) out = m["foo"](2) + m["foo"](3)
`, 10) `, nil, 10)
// map assignment is copy-by-reference // 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; m1.k1 = 5; out = m2.k1`, nil, 5)
expect(t, `m1 := {k1: 1, k2: "foo"}; m2 := m1; m2.k1 = 3; out = m1.k1`, 3) 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 }()`, 5) 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 }()`, 3) expect(t, `func() { m1 := {k1: 1, k2: "foo"}; m2 := m1; m2.k1 = 3; out = m1.k1 }()`, nil, 3)
} }

View file

@ -9,89 +9,66 @@ import (
) )
func TestBuiltin(t *testing.T) { func TestBuiltin(t *testing.T) {
m := Opts().Module("math",
mathModule := map[string]objects.Object{ &objects.BuiltinModule{
"math": &objects.ImmutableMap{Value: map[string]objects.Object{ Attrs: map[string]objects.Object{
"abs": &objects.UserFunction{Name: "abs", Value: func(args ...objects.Object) (ret objects.Object, err error) { "abs": &objects.UserFunction{
Name: "abs",
Value: func(args ...objects.Object) (ret objects.Object, err error) {
v, _ := objects.ToFloat64(args[0]) v, _ := objects.ToFloat64(args[0])
return &objects.Float{Value: math.Abs(v)}, nil return &objects.Float{Value: math.Abs(v)}, nil
}}, },
}}, },
} },
})
// builtin // builtin
expectWithBuiltinModules(t, `math := import("math"); out = math.abs(1)`, 1.0, mathModule) expect(t, `math := import("math"); out = math.abs(1)`, m, 1.0)
expectWithBuiltinModules(t, `math := import("math"); out = math.abs(-1)`, 1.0, mathModule) expect(t, `math := import("math"); out = math.abs(-1)`, m, 1.0)
expectWithBuiltinModules(t, `math := import("math"); out = math.abs(1.0)`, 1.0, mathModule) expect(t, `math := import("math"); out = math.abs(1.0)`, m, 1.0)
expectWithBuiltinModules(t, `math := import("math"); out = math.abs(-1.0)`, 1.0, mathModule) expect(t, `math := import("math"); out = math.abs(-1.0)`, m, 1.0)
} }
func TestUserModules(t *testing.T) { func TestUserModules(t *testing.T) {
// user modules
// export none // export none
expectWithUserModules(t, `out = import("mod1")`, objects.UndefinedValue, map[string]string{ expect(t, `out = import("mod1")`, Opts().Module("mod1", `fn := func() { return 5.0 }; a := 2`), objects.UndefinedValue)
"mod1": `fn := func() { return 5.0 }; a := 2`,
})
// export values // export values
expectWithUserModules(t, `out = import("mod1")`, 5, map[string]string{ expect(t, `out = import("mod1")`, Opts().Module("mod1", `export 5`), 5)
"mod1": `export 5`, expect(t, `out = import("mod1")`, Opts().Module("mod1", `export "foo"`), "foo")
})
expectWithUserModules(t, `out = import("mod1")`, "foo", map[string]string{
"mod1": `export "foo"`,
})
// export compound types // export compound types
expectWithUserModules(t, `out = import("mod1")`, IARR{1, 2, 3}, map[string]string{ expect(t, `out = import("mod1")`, Opts().Module("mod1", `export [1, 2, 3]`), IARR{1, 2, 3})
"mod1": `export [1, 2, 3]`, expect(t, `out = import("mod1")`, Opts().Module("mod1", `export {a: 1, b: 2}`), IMAP{"a": 1, "b": 2})
})
expectWithUserModules(t, `out = import("mod1")`, IMAP{"a": 1, "b": 2}, map[string]string{
"mod1": `export {a: 1, b: 2}`,
})
// export value is immutable // export value is immutable
expectErrorWithUserModules(t, `m1 := import("mod1"); m1.a = 5`, map[string]string{ expectError(t, `m1 := import("mod1"); m1.a = 5`, Opts().Module("mod1", `export {a: 1, b: 2}`), "not index-assignable")
"mod1": `export {a: 1, b: 2}`, expectError(t, `m1 := import("mod1"); m1[1] = 5`, Opts().Module("mod1", `export [1, 2, 3]`), "not index-assignable")
}, "not index-assignable")
expectErrorWithUserModules(t, `m1 := import("mod1"); m1[1] = 5`, map[string]string{
"mod1": `export [1, 2, 3]`,
}, "not index-assignable")
// code after export statement will not be executed // code after export statement will not be executed
expectWithUserModules(t, `out = import("mod1")`, 10, map[string]string{ expect(t, `out = import("mod1")`, Opts().Module("mod1", `a := 10; export a; a = 20`), 10)
"mod1": `a := 10; export a; a = 20`, expect(t, `out = import("mod1")`, Opts().Module("mod1", `a := 10; export a; a = 20; export a`), 10)
})
expectWithUserModules(t, `out = import("mod1")`, 10, map[string]string{
"mod1": `a := 10; export a; a = 20; export a`,
})
// export function // export function
expectWithUserModules(t, `out = import("mod1")()`, 5.0, map[string]string{ expect(t, `out = import("mod1")()`, Opts().Module("mod1", `export func() { return 5.0 }`), 5.0)
"mod1": `export func() { return 5.0 }`,
})
// export function that reads module-global variable // export function that reads module-global variable
expectWithUserModules(t, `out = import("mod1")()`, 6.5, map[string]string{ expect(t, `out = import("mod1")()`, Opts().Module("mod1", `a := 1.5; export func() { return a + 5.0 }`), 6.5)
"mod1": `a := 1.5; export func() { return a + 5.0 }`,
})
// export function that read local variable // export function that read local variable
expectWithUserModules(t, `out = import("mod1")()`, 6.5, map[string]string{ expect(t, `out = import("mod1")()`, Opts().Module("mod1", `export func() { a := 1.5; return a + 5.0 }`), 6.5)
"mod1": `export func() { a := 1.5; return a + 5.0 }`,
})
// export function that read free variables // export function that read free variables
expectWithUserModules(t, `out = import("mod1")()`, 6.5, map[string]string{ expect(t, `out = import("mod1")()`, Opts().Module("mod1", `export func() { a := 1.5; return func() { return a + 5.0 }() }`), 6.5)
"mod1": `export func() { a := 1.5; return func() { return a + 5.0 }() }`,
})
// recursive function in module // recursive function in module
expectWithUserModules(t, `out = import("mod1")`, 15, map[string]string{ expect(t, `out = import("mod1")`, Opts().Module(
"mod1": ` "mod1", `
a := func(x) { a := func(x) {
return x == 0 ? 0 : x + a(x-1) return x == 0 ? 0 : x + a(x-1)
} }
export a(5) export a(5)
`}) `), 15)
expectWithUserModules(t, `out = import("mod1")`, 15, map[string]string{ expect(t, `out = import("mod1")`, Opts().Module(
"mod1": ` "mod1", `
export func() { export func() {
a := func(x) { a := func(x) {
return x == 0 ? 0 : x + a(x-1) return x == 0 ? 0 : x + a(x-1)
@ -99,117 +76,114 @@ export func() {
return a(5) return a(5)
}() }()
`}) `), 15)
// (main) -> mod1 -> mod2 // (main) -> mod1 -> mod2
expectWithUserModules(t, `out = import("mod1")()`, 5.0, map[string]string{ expect(t, `out = import("mod1")()`,
"mod1": `export import("mod2")`, Opts().Module("mod1", `export import("mod2")`).
"mod2": `export func() { return 5.0 }`, Module("mod2", `export func() { return 5.0 }`),
}) 5.0)
// (main) -> mod1 -> mod2 // (main) -> mod1 -> mod2
// -> mod2 // -> mod2
expectWithUserModules(t, `import("mod1"); out = import("mod2")()`, 5.0, map[string]string{ expect(t, `import("mod1"); out = import("mod2")()`,
"mod1": `export import("mod2")`, Opts().Module("mod1", `export import("mod2")`).
"mod2": `export func() { return 5.0 }`, Module("mod2", `export func() { return 5.0 }`),
}) 5.0)
// (main) -> mod1 -> mod2 -> mod3 // (main) -> mod1 -> mod2 -> mod3
// -> mod2 -> mod3 // -> mod2 -> mod3
expectWithUserModules(t, `import("mod1"); out = import("mod2")()`, 5.0, map[string]string{ expect(t, `import("mod1"); out = import("mod2")()`,
"mod1": `export import("mod2")`, Opts().Module("mod1", `export import("mod2")`).
"mod2": `export import("mod3")`, Module("mod2", `export import("mod3")`).
"mod3": `export func() { return 5.0 }`, Module("mod3", `export func() { return 5.0 }`),
}) 5.0)
// cyclic imports // cyclic imports
// (main) -> mod1 -> mod2 -> mod1 // (main) -> mod1 -> mod2 -> mod1
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ expectError(t, `import("mod1")`,
"mod1": `import("mod2")`, Opts().Module("mod1", `import("mod2")`).
"mod2": `import("mod1")`, Module("mod2", `import("mod1")`),
}, "Compile Error: cyclic module import: mod1\n\tat mod2:1:1") "Compile Error: cyclic module import: mod1\n\tat mod2:1:1")
// (main) -> mod1 -> mod2 -> mod3 -> mod1 // (main) -> mod1 -> mod2 -> mod3 -> mod1
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ expectError(t, `import("mod1")`,
"mod1": `import("mod2")`, Opts().Module("mod1", `import("mod2")`).
"mod2": `import("mod3")`, Module("mod2", `import("mod3")`).
"mod3": `import("mod1")`, Module("mod3", `import("mod1")`),
}, "Compile Error: cyclic module import: mod1\n\tat mod3:1:1") "Compile Error: cyclic module import: mod1\n\tat mod3:1:1")
// (main) -> mod1 -> mod2 -> mod3 -> mod2 // (main) -> mod1 -> mod2 -> mod3 -> mod2
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ expectError(t, `import("mod1")`,
"mod1": `import("mod2")`, Opts().Module("mod1", `import("mod2")`).
"mod2": `import("mod3")`, Module("mod2", `import("mod3")`).
"mod3": `import("mod2")`, Module("mod3", `import("mod2")`),
}, "Compile Error: cyclic module import: mod2\n\tat mod3:1:1") "Compile Error: cyclic module import: mod2\n\tat mod3:1:1")
// unknown modules // unknown modules
expectErrorWithUserModules(t, `import("mod0")`, map[string]string{ expectError(t, `import("mod0")`, Opts().Module("mod1", `a := 5`), "module 'mod0' not found")
"mod1": `a := 5`, expectError(t, `import("mod1")`, Opts().Module("mod1", `import("mod2")`), "module 'mod2' not found")
}, "module 'mod0' not found")
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{
"mod1": `import("mod2")`,
}, "module 'mod2' not found")
// module is immutable but its variables is not necessarily immutable. // 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{ expect(t, `m1 := import("mod1"); m1.a.b = 5; out = m1.a.b`,
"mod1": `export {a: {b: 3}}`, Opts().Module("mod1", `export {a: {b: 3}}`),
}) 5)
// make sure module has same builtin functions // make sure module has same builtin functions
expectWithUserModules(t, `out = import("mod1")`, "int", map[string]string{ expect(t, `out = import("mod1")`,
"mod1": `export func() { return type_name(0) }()`, Opts().Module("mod1", `export func() { return type_name(0) }()`),
}) "int")
// 'export' statement is ignored outside module // '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 // 'export' must be in the top-level
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ expectError(t, `import("mod1")`,
"mod1": `func() { export 5 }()`, Opts().Module("mod1", `func() { export 5 }()`),
}, "Compile Error: export not allowed inside function\n\tat mod1:1:10") "Compile Error: export not allowed inside function\n\tat mod1:1:10")
expectErrorWithUserModules(t, `import("mod1")`, map[string]string{ expectError(t, `import("mod1")`,
"mod1": `func() { func() { export 5 }() }()`, Opts().Module("mod1", `func() { func() { export 5 }() }()`),
}, "Compile Error: export not allowed inside function\n\tat mod1:1:19") "Compile Error: export not allowed inside function\n\tat mod1:1:19")
// module cannot access outer scope // module cannot access outer scope
expectErrorWithUserModules(t, `a := 5; import("mod1")`, map[string]string{ expectError(t, `a := 5; import("mod1")`,
"mod1": `export a`, Opts().Module("mod1", `export a`),
}, "Compile Error: unresolved reference 'a'\n\tat mod1:1:8") "Compile Error: unresolved reference 'a'\n\tat mod1:1:8")
// runtime error within modules // runtime error within modules
expectErrorWithUserModules(t, ` expectError(t, `
a := 1; a := 1;
b := import("mod1"); b := import("mod1");
b(a)`, b(a)`,
map[string]string{"mod1": ` Opts().Module("mod1", `
export func(a) { export func(a) {
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) { func TestModuleBlockScopes(t *testing.T) {
randModule := map[string]objects.Object{ m := Opts().Module("rand",
"rand": &objects.ImmutableMap{Value: map[string]objects.Object{ &objects.BuiltinModule{
"intn": &objects.UserFunction{Name: "abs", Value: func(args ...objects.Object) (ret objects.Object, err error) { 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]) v, _ := objects.ToInt64(args[0])
return &objects.Int{Value: rand.Int63n(v)}, nil return &objects.Int{Value: rand.Int63n(v)}, nil
}}, },
}}, },
} },
})
// block scopes in module // block scopes in module
expectWithUserAndBuiltinModules(t, `out = import("mod1")()`, 1, map[string]string{ expect(t, `out = import("mod1")()`, m.Module(
"mod1": ` "mod1", `
rand := import("rand") rand := import("rand")
foo := func() { return 1 } foo := func() { return 1 }
export func() { export func() {
rand.intn(3) rand.intn(3)
return foo() return foo()
} }`), 1)
`,
}, randModule)
expectWithUserAndBuiltinModules(t, `out = import("mod1")()`, 10, map[string]string{ expect(t, `out = import("mod1")()`, m.Module(
"mod1": ` "mod1", `
rand := import("rand") rand := import("rand")
foo := func() { return 1 } foo := func() { return 1 }
export func() { export func() {
@ -217,11 +191,10 @@ export func() {
if foo() {} if foo() {}
return 10 return 10
} }
`, `), 10)
}, randModule)
expectWithUserAndBuiltinModules(t, `out = import("mod1")()`, 10, map[string]string{ expect(t, `out = import("mod1")()`, m.Module(
"mod1": ` "mod1", `
rand := import("rand") rand := import("rand")
foo := func() { return 1 } foo := func() { return 1 }
export func() { export func() {
@ -229,6 +202,5 @@ export func() {
if true { foo() } if true { foo() }
return 10 return 10
} }
`, `), 10)
}, randModule)
} }

View file

@ -5,11 +5,11 @@ import (
) )
func TestBangOperator(t *testing.T) { func TestBangOperator(t *testing.T) {
expect(t, `out = !true`, false) expect(t, `out = !true`, nil, false)
expect(t, `out = !false`, true) expect(t, `out = !false`, nil, true)
expect(t, `out = !0`, true) expect(t, `out = !0`, nil, true)
expect(t, `out = !5`, false) expect(t, `out = !5`, nil, false)
expect(t, `out = !!true`, true) expect(t, `out = !!true`, nil, true)
expect(t, `out = !!false`, false) expect(t, `out = !!false`, nil, false)
expect(t, `out = !!5`, true) expect(t, `out = !!5`, nil, true)
} }

View file

@ -37,13 +37,13 @@ f()
} }
func testAllocsLimit(t *testing.T, src string, limit int64) { func testAllocsLimit(t *testing.T, src string, limit int64) {
expectAllocsLimit(t, src, -1, objects.UndefinedValue) // no limit expect(t, src, Opts().Skip2ndPass(), objects.UndefinedValue) // no limit
expectAllocsLimit(t, src, limit, objects.UndefinedValue) expect(t, src, Opts().MaxAllocs(limit).Skip2ndPass(), objects.UndefinedValue)
expectAllocsLimit(t, src, limit+1, objects.UndefinedValue) expect(t, src, Opts().MaxAllocs(limit+1).Skip2ndPass(), objects.UndefinedValue)
if limit > 1 { 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 { if limit > 2 {
expectErrorAllocsLimit(t, src, limit-2, "allocation limit exceeded") expectError(t, src, Opts().MaxAllocs(limit-2).Skip2ndPass(), "allocation limit exceeded")
} }
} }

View file

@ -5,10 +5,10 @@ import (
) )
func TestReturn(t *testing.T) { func TestReturn(t *testing.T) {
expect(t, `out = func() { return 10; }()`, 10) expect(t, `out = func() { return 10; }()`, nil, 10)
expect(t, `out = func() { return 10; return 9; }()`, 10) expect(t, `out = func() { return 10; return 9; }()`, nil, 10)
expect(t, `out = func() { return 2 * 5; return 9 }()`, 10) expect(t, `out = func() { return 2 * 5; return 9 }()`, nil, 10)
expect(t, `out = func() { 9; return 2 * 5; return 9 }()`, 10) expect(t, `out = func() { 9; return 2 * 5; return 9 }()`, nil, 10)
expect(t, ` expect(t, `
out = func() { out = func() {
if (10 > 1) { if (10 > 1) {
@ -18,7 +18,7 @@ func TestReturn(t *testing.T) {
return 1; 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)
} }

View file

@ -7,9 +7,9 @@ import (
) )
func TestSelector(t *testing.T) { 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.k1`, nil, 5)
expect(t, `a := {k1: 5, k2: "foo"}; out = a.k2`, "foo") expect(t, `a := {k1: 5, k2: "foo"}; out = a.k2`, nil, "foo")
expect(t, `a := {k1: 5, k2: "foo"}; out = a.k3`, objects.UndefinedValue) expect(t, `a := {k1: 5, k2: "foo"}; out = a.k3`, nil, objects.UndefinedValue)
expect(t, ` expect(t, `
a := { a := {
@ -19,7 +19,7 @@ a := {
}, },
c: "foo bar" c: "foo bar"
} }
out = a.b.c`, 4) out = a.b.c`, nil, 4)
expect(t, ` expect(t, `
a := { a := {
@ -29,7 +29,7 @@ a := {
}, },
c: "foo bar" c: "foo bar"
} }
b := a.x.c`, objects.UndefinedValue) b := a.x.c`, nil, objects.UndefinedValue)
expect(t, ` expect(t, `
a := { a := {
@ -39,25 +39,25 @@ a := {
}, },
c: "foo bar" 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.b = 2; out = a.b`, nil, 2)
expect(t, `a := {b: 1, c: "foo"}; a.c = 2; out = a.c`, 2) // type not checked on sub-field expect(t, `a := {b: 1, c: "foo"}; a.c = 2; out = a.c`, nil, 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: {c: 1}}; a.b.c = 2; out = a.b.c`, nil, 2)
expect(t, `a := {b: 1}; a.c = 2; out = a`, MAP{"b": 1, "c": 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`, MAP{"b": MAP{"c": 1, "d": 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.b = 2; out = a.b }()`, nil, 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: 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 }()`, 2) 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 }()`, MAP{"b": 1, "c": 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 }()`, MAP{"b": MAP{"c": 1, "d": 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.b = 2 }(); out = a.b }()`, nil, 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: 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 }()`, 2) 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 }()`, MAP{"b": 1, "c": 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 }()`, MAP{"b": MAP{"c": 1, "d": 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, ` expect(t, `
a := { a := {
@ -69,7 +69,7 @@ a := {
} }
} }
out = [a.b[2], a.c.d, a.c.e, a.c.f[1]] 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, ` expect(t, `
func() { func() {
@ -79,12 +79,12 @@ func() {
b = 7 // make sure a[1] has a COPY of value of 'b' b = 7 // make sure a[1] has a COPY of value of 'b'
out = a[1] out = a[1]
}() }()
`, 9) `, nil, 9)
expectError(t, `a := {b: {c: 1}}; a.d.c = 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`, "invalid index type") expectError(t, `a := [1, 2, 3]; a.b = 2`, nil, "invalid index type")
expectError(t, `a := "foo"; a.b = 2`, "not index-assignable") expectError(t, `a := "foo"; a.b = 2`, nil, "not index-assignable")
expectError(t, `func() { a := {b: {c: 1}}; a.d.c = 2 }()`, "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 }()`, "invalid index type") expectError(t, `func() { a := [1, 2, 3]; a.b = 2 }()`, nil, "invalid index type")
expectError(t, `func() { a := "foo"; a.b = 2 }()`, "not index-assignable") expectError(t, `func() { a := "foo"; a.b = 2 }()`, nil, "not index-assignable")
} }

View file

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

View file

@ -8,67 +8,66 @@ import (
) )
func TestString(t *testing.T) { func TestString(t *testing.T) {
expect(t, `out = "Hello World!"`, "Hello World!") expect(t, `out = "Hello World!"`, nil, "Hello World!")
expect(t, `out = "Hello" + " " + "World!"`, "Hello World!") expect(t, `out = "Hello" + " " + "World!"`, nil, "Hello World!")
expect(t, `out = "Hello" == "Hello"`, true) expect(t, `out = "Hello" == "Hello"`, nil, true)
expect(t, `out = "Hello" == "World"`, false) expect(t, `out = "Hello" == "World"`, nil, false)
expect(t, `out = "Hello" != "Hello"`, false) expect(t, `out = "Hello" != "Hello"`, nil, false)
expect(t, `out = "Hello" != "World"`, true) expect(t, `out = "Hello" != "World"`, nil, true)
// index operator // index operator
str := "abcdef" str := "abcdef"
strStr := `"abcdef"` strStr := `"abcdef"`
strLen := 6 strLen := 6
for idx := 0; idx < strLen; idx++ { for idx := 0; idx < strLen; idx++ {
expect(t, fmt.Sprintf("out = %s[%d]", strStr, idx), str[idx]) expect(t, fmt.Sprintf("out = %s[%d]", strStr, idx), nil, str[idx])
expect(t, fmt.Sprintf("out = %s[0 + %d]", strStr, idx), 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), 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), 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, -1), nil, objects.UndefinedValue)
expect(t, fmt.Sprintf("%s[%d]", strStr, strLen), objects.UndefinedValue) expect(t, fmt.Sprintf("%s[%d]", strStr, strLen), nil, objects.UndefinedValue)
// slice operator // slice operator
for low := 0; low <= strLen; low++ { 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++ { 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[%d:%d]", strStr, low, high), nil, 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[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), 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), str[:high]) expect(t, fmt.Sprintf("out = %s[:%d]", strStr, high), nil, str[:high])
expect(t, fmt.Sprintf("out = %s[%d:]", strStr, low), str[low:]) 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), nil, str[:])
expect(t, fmt.Sprintf("out = %s[:]", strStr), str) expect(t, fmt.Sprintf("out = %s[:]", strStr), nil, str)
expect(t, fmt.Sprintf("out = %s[%d:]", strStr, -1), str) expect(t, fmt.Sprintf("out = %s[%d:]", strStr, -1), nil, str)
expect(t, fmt.Sprintf("out = %s[:%d]", strStr, strLen+1), str) expect(t, fmt.Sprintf("out = %s[:%d]", strStr, strLen+1), nil, str)
expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, 2, 2), "") 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, -1), nil, "invalid slice index")
expectError(t, fmt.Sprintf("%s[%d:]", strStr, strLen+1), "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), "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), "invalid slice index") expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 2, 1), nil, "invalid slice index")
// string concatenation with other types // 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 // Float.String() returns the smallest number of digits
// necessary such that ParseFloat will return f exactly. // 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.0`, nil, "foo1") // <- note '1' instead of '1.0'
expect(t, `out = "foo" + 1.5`, "foo1.5") expect(t, `out = "foo" + 1.5`, nil, "foo1.5")
expect(t, `out = "foo" + true`, "footrue") expect(t, `out = "foo" + true`, nil, "footrue")
expect(t, `out = "foo" + 'X'`, "fooX") expect(t, `out = "foo" + 'X'`, nil, "fooX")
expect(t, `out = "foo" + error(5)`, "fooerror: 5") expect(t, `out = "foo" + error(5)`, nil, "fooerror: 5")
expect(t, `out = "foo" + undefined`, "foo<undefined>") expect(t, `out = "foo" + undefined`, nil, "foo<undefined>")
expect(t, `out = "foo" + [1,2,3]`, "foo[1, 2, 3]") expect(t, `out = "foo" + [1,2,3]`, nil, "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
// also works with "+=" operator // 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 // 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")
} }

View file

@ -10,7 +10,7 @@ func TestTailCall(t *testing.T) {
} }
return fac(n-1, n*a) return fac(n-1, n*a)
} }
out = fac(5, 1)`, 120) out = fac(5, 1)`, nil, 120)
expect(t, ` expect(t, `
fac := func(n, a) { fac := func(n, a) {
@ -20,7 +20,7 @@ func TestTailCall(t *testing.T) {
x := {foo: fac} // indirection for test x := {foo: fac} // indirection for test
return x.foo(n-1, n*a) return x.foo(n-1, n*a)
} }
out = fac(5, 1)`, 120) out = fac(5, 1)`, nil, 120)
expect(t, ` expect(t, `
fib := func(x, s) { fib := func(x, s) {
@ -31,7 +31,7 @@ func TestTailCall(t *testing.T) {
} }
return fib(x-1, fib(x-2, s)) return fib(x-1, fib(x-2, s))
} }
out = fib(15, 0)`, 610) out = fib(15, 0)`, nil, 610)
expect(t, ` expect(t, `
fib := func(n, a, b) { fib := func(n, a, b) {
@ -42,7 +42,7 @@ func TestTailCall(t *testing.T) {
} }
return fib(n-1, b, a + b) 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 // global variable and no return value
expect(t, ` expect(t, `
@ -54,7 +54,7 @@ func TestTailCall(t *testing.T) {
out += a out += a
foo(a-1) foo(a-1)
} }
foo(10)`, 55) foo(10)`, nil, 55)
expect(t, ` expect(t, `
f1 := func() { f1 := func() {
@ -65,7 +65,7 @@ func TestTailCall(t *testing.T) {
} }
return f2(5, 0) return f2(5, 0)
} }
out = f1()`, 15) out = f1()`, nil, 15)
// tail-call replacing loop // tail-call replacing loop
// without tail-call optimization, this code will cause stack overflow // without tail-call optimization, this code will cause stack overflow
@ -78,7 +78,7 @@ iter := func(n, max) {
return iter(n+1, max) return iter(n+1, max)
} }
out = iter(0, 9999) out = iter(0, 9999)
`, 9999) `, nil, 9999)
expect(t, ` expect(t, `
c := 0 c := 0
iter := func(n, max) { iter := func(n, max) {
@ -91,7 +91,7 @@ iter := func(n, max) {
} }
iter(0, 9999) iter(0, 9999)
out = c out = c
`, 9999) `, nil, 9999)
} }
// tail call with free vars // tail call with free vars
@ -107,5 +107,5 @@ func() {
return f2(n-1, n+s) return f2(n-1, n+s)
} }
out = f2(5, 0) out = f2(5, 0)
}()`, 25) }()`, nil, 25)
} }

View file

@ -22,53 +22,81 @@ type IARR []interface{}
type IMAP map[string]interface{} type IMAP map[string]interface{}
type MAP = map[string]interface{} type MAP = map[string]interface{}
type ARR = []interface{} type ARR = []interface{}
type SYM = map[string]objects.Object
func expect(t *testing.T, input string, expected interface{}) { type testopts struct {
runVM(t, input, expected, nil, nil, nil, -1, false) 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{}) { func Opts() *testopts {
runVM(t, input, expected, nil, nil, nil, maxAllocs, true) return &testopts{
modules: make(map[string]objects.Importable),
symbols: make(map[string]objects.Object),
maxAllocs: -1,
skip2ndPass: false,
}
} }
func expectNoMod(t *testing.T, input string, expected interface{}) { func (o *testopts) copy() *testopts {
runVM(t, input, expected, nil, nil, nil, -1, true) 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 expectWithSymbols(t *testing.T, input string, expected interface{}, symbols map[string]objects.Object) { func (o *testopts) Module(name string, mod interface{}) *testopts {
runVM(t, input, expected, symbols, nil, nil, -1, true) 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 expectWithUserModules(t *testing.T, input string, expected interface{}, userModules map[string]string) { func (o *testopts) Symbol(name string, value objects.Object) *testopts {
runVM(t, input, expected, nil, userModules, nil, -1, false) c := o.copy()
c.symbols[name] = value
return c
} }
func expectWithBuiltinModules(t *testing.T, input string, expected interface{}, builtinModules map[string]objects.Object) { func (o *testopts) MaxAllocs(limit int64) *testopts {
runVM(t, input, expected, nil, nil, builtinModules, -1, false) c := o.copy()
c.maxAllocs = limit
return c
} }
func expectWithUserAndBuiltinModules(t *testing.T, input string, expected interface{}, userModules map[string]string, builtinModules map[string]objects.Object) { func (o *testopts) Skip2ndPass() *testopts {
runVM(t, input, expected, nil, userModules, builtinModules, -1, false) c := o.copy()
c.skip2ndPass = true
return c
} }
func expectError(t *testing.T, input, expected string) { func expect(t *testing.T, input string, opts *testopts, expected interface{}) {
runVMError(t, input, nil, nil, nil, -1, expected) if opts == nil {
} opts = Opts()
}
func expectErrorAllocsLimit(t *testing.T, input string, maxAllocs int64, expected string) { symbols := opts.symbols
runVMError(t, input, nil, nil, nil, maxAllocs, expected) modules := opts.modules
} maxAllocs := opts.maxAllocs
func expectErrorWithUserModules(t *testing.T, input string, userModules map[string]string, expected string) {
runVMError(t, input, nil, userModules, nil, -1, expected)
}
func expectErrorWithSymbols(t *testing.T, input string, symbols map[string]objects.Object, expected string) {
runVMError(t, input, symbols, nil, nil, -1, expected)
}
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) expectedObj := toObject(expected)
if symbols == nil { if symbols == nil {
@ -85,7 +113,7 @@ func runVM(t *testing.T, input string, expected interface{}, symbols map[string]
} }
// compiler/VM // compiler/VM
res, trace, err := traceCompileRun(file, symbols, userModules, builtinModules, maxAllocs) res, trace, err := traceCompileRun(file, symbols, modules, maxAllocs)
if !assert.NoError(t, err) || if !assert.NoError(t, err) ||
!assert.Equal(t, expectedObj, res[testOut]) { !assert.Equal(t, expectedObj, res[testOut]) {
t.Log("\n" + strings.Join(trace, "\n")) 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 // second pass: run the code as import module
if !skipModuleTest { if !opts.skip2ndPass {
file := parse(t, `out = import("__code__")`) file := parse(t, `out = import("__code__")`)
if file == nil { if file == nil {
return return
@ -107,12 +135,11 @@ func runVM(t *testing.T, input string, expected interface{}, symbols map[string]
expectedObj = &objects.ImmutableMap{Value: eo.Value} expectedObj = &objects.ImmutableMap{Value: eo.Value}
} }
if userModules == nil { modules["__code__"] = &objects.SourceModule{
userModules = make(map[string]string) 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) || if !assert.NoError(t, err) ||
!assert.Equal(t, expectedObj, res[testOut]) { !assert.Equal(t, expectedObj, res[testOut]) {
t.Log("\n" + strings.Join(trace, "\n")) 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) expected = strings.TrimSpace(expected)
if expected == "" { if expected == "" {
panic("expected must not be empty") 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 // compiler/VM
_, trace, err := traceCompileRun(program, symbols, userModules, builtinModules, maxAllocs) _, trace, err := traceCompileRun(program, symbols, modules, maxAllocs)
if !assert.Error(t, err) || if !assert.Error(t, err) ||
!assert.True(t, strings.Contains(err.Error(), expected), "expected error string: %s, got: %s", expected, err.Error()) { !assert.True(t, strings.Contains(err.Error(), expected), "expected error string: %s, got: %s", expected, err.Error()) {
t.Log("\n" + strings.Join(trace, "\n")) 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 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 var v *runtime.VM
defer func() { defer func() {
@ -185,20 +220,8 @@ func traceCompileRun(file *ast.File, symbols map[string]objects.Object, userModu
symTable.DefineBuiltin(idx, fn.Name) symTable.DefineBuiltin(idx, fn.Name)
} }
bm := make(map[string]bool)
for k := range builtinModules {
bm[k] = true
}
tr := &tracer{} tr := &tracer{}
c := compiler.NewCompiler(file.InputFile, symTable, nil, bm, tr) c := compiler.NewCompiler(file.InputFile, symTable, nil, modules, 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)
})
err = c.Compile(file) err = c.Compile(file)
trace = append(trace, fmt.Sprintf("\n[Compiler Trace]\n\n%s", strings.Join(tr.Out, ""))) trace = append(trace, fmt.Sprintf("\n[Compiler Trace]\n\n%s", strings.Join(tr.Out, "")))
if err != nil { 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 Constants]\n\n%s", strings.Join(bytecode.FormatConstants(), "\n")))
trace = append(trace, fmt.Sprintf("\n[Compiled Instructions]\n\n%s\n", strings.Join(bytecode.FormatInstructions(), "\n"))) trace = append(trace, fmt.Sprintf("\n[Compiled Instructions]\n\n%s\n", strings.Join(bytecode.FormatInstructions(), "\n")))
v = runtime.NewVM(bytecode, globals, nil, builtinModules, maxAllocs) v = runtime.NewVM(bytecode, globals, maxAllocs)
err = v.Run() err = v.Run()
{ {

View file

@ -7,15 +7,15 @@ import (
) )
func TestUndefined(t *testing.T) { func TestUndefined(t *testing.T) {
expect(t, `out = undefined`, objects.UndefinedValue) expect(t, `out = undefined`, nil, objects.UndefinedValue)
expect(t, `out = undefined.a`, objects.UndefinedValue) expect(t, `out = undefined.a`, nil, objects.UndefinedValue)
expect(t, `out = undefined[1]`, objects.UndefinedValue) expect(t, `out = undefined[1]`, nil, objects.UndefinedValue)
expect(t, `out = undefined.a.b`, objects.UndefinedValue) expect(t, `out = undefined.a.b`, nil, objects.UndefinedValue)
expect(t, `out = undefined[1][2]`, objects.UndefinedValue) expect(t, `out = undefined[1][2]`, nil, objects.UndefinedValue)
expect(t, `out = undefined ? 1 : 2`, 2) expect(t, `out = undefined ? 1 : 2`, nil, 2)
expect(t, `out = undefined == undefined`, true) expect(t, `out = undefined == undefined`, nil, true)
expect(t, `out = undefined == 1`, false) expect(t, `out = undefined == 1`, nil, false)
expect(t, `out = 1 == undefined`, false) expect(t, `out = 1 == undefined`, nil, false)
expect(t, `out = undefined == float([])`, true) expect(t, `out = undefined == float([])`, nil, true)
expect(t, `out = float([]) == undefined`, true) expect(t, `out = float([]) == undefined`, nil, true)
} }

View file

@ -16,8 +16,6 @@ type Compiled struct {
globalIndexes map[string]int // global symbol name to index globalIndexes map[string]int // global symbol name to index
bytecode *compiler.Bytecode bytecode *compiler.Bytecode
globals []objects.Object globals []objects.Object
builtinFunctions []objects.Object
builtinModules map[string]objects.Object
maxAllocs int64 maxAllocs int64
lock sync.RWMutex lock sync.RWMutex
} }
@ -27,7 +25,7 @@ func (c *Compiled) Run() error {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() 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() return v.Run()
} }
@ -37,7 +35,7 @@ func (c *Compiled) RunContext(ctx context.Context) (err error) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() 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) ch := make(chan error, 1)
@ -66,8 +64,6 @@ func (c *Compiled) Clone() *Compiled {
globalIndexes: c.globalIndexes, globalIndexes: c.globalIndexes,
bytecode: c.bytecode, bytecode: c.bytecode,
globals: make([]objects.Object, len(c.globals)), globals: make([]objects.Object, len(c.globals)),
builtinFunctions: c.builtinFunctions,
builtinModules: c.builtinModules,
maxAllocs: c.maxAllocs, maxAllocs: c.maxAllocs,
} }

View file

@ -14,12 +14,11 @@ import (
// Script can simplify compilation and execution of embedded scripts. // Script can simplify compilation and execution of embedded scripts.
type Script struct { type Script struct {
variables map[string]*Variable variables map[string]*Variable
builtinFuncs []objects.Object importModules map[string]objects.Importable
builtinModules map[string]objects.Object
userModuleLoader compiler.ModuleLoader
input []byte input []byte
maxAllocs int64 maxAllocs int64
maxConstObjects int maxConstObjects int
enableFileImport bool
} }
// New creates a Script instance with an input script. // New creates a Script instance with an input script.
@ -59,33 +58,9 @@ func (s *Script) Remove(name string) bool {
return true return true
} }
// SetBuiltinFunctions allows to define builtin functions. // SetImports sets import modules.
func (s *Script) SetBuiltinFunctions(funcs []*objects.BuiltinFunction) { func (s *Script) SetImports(modules map[string]objects.Importable) {
if funcs != nil { s.importModules = modules
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
} }
// SetMaxAllocs sets the maximum number of objects allocations during the run time. // 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 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. // Compile compiles the script with all the defined variables, and, returns Compiled object.
func (s *Script) Compile() (*Compiled, error) { func (s *Script) Compile() (*Compiled, error) {
symbolTable, builtinModules, globals, err := s.prepCompile() symbolTable, globals, err := s.prepCompile()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -115,12 +96,8 @@ func (s *Script) Compile() (*Compiled, error) {
return nil, err return nil, err
} }
c := compiler.NewCompiler(srcFile, symbolTable, nil, builtinModules, nil) c := compiler.NewCompiler(srcFile, symbolTable, nil, s.importModules, nil)
c.EnableFileImport(s.enableFileImport)
if s.userModuleLoader != nil {
c.SetModuleLoader(s.userModuleLoader)
}
if err := c.Compile(file); err != nil { if err := c.Compile(file); err != nil {
return nil, err return nil, err
} }
@ -153,8 +130,6 @@ func (s *Script) Compile() (*Compiled, error) {
globalIndexes: globalIndexes, globalIndexes: globalIndexes,
bytecode: bytecode, bytecode: bytecode,
globals: globals, globals: globals,
builtinFunctions: s.builtinFuncs,
builtinModules: s.builtinModules,
maxAllocs: s.maxAllocs, maxAllocs: s.maxAllocs,
}, nil }, nil
} }
@ -184,36 +159,15 @@ func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error)
return 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 var names []string
for name := range s.variables { for name := range s.variables {
names = append(names, name) names = append(names, name)
} }
symbolTable = compiler.NewSymbolTable() symbolTable = compiler.NewSymbolTable()
if s.builtinFuncs == nil {
s.builtinFuncs = make([]objects.Object, len(objects.Builtins))
for idx, fn := range objects.Builtins { for idx, fn := range objects.Builtins {
s.builtinFuncs[idx] = &objects.BuiltinFunction{ symbolTable.DefineBuiltin(idx, fn.Name)
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
} }
globals = make([]objects.Object, runtime.GlobalsSize) globals = make([]objects.Object, runtime.GlobalsSize)

View file

@ -33,7 +33,7 @@ b += c
a += b * 2 a += b * 2
arr := [a, b, c] arr := [a, b, c]
arrstr := stringify(arr) arrstr := string(arr)
map := {a: a, b: b, c: c} map := {a: a, b: b, c: c}
d := a + b + c d := a + b + c
@ -45,8 +45,8 @@ for i:=1; i<=d; i++ {
e := mod1.double(s) e := mod1.double(s)
`) `)
mod1 := &objects.ImmutableMap{ mod1 := &objects.BuiltinModule{
Value: map[string]objects.Object{ Attrs: map[string]objects.Object{
"double": &objects.UserFunction{ "double": &objects.UserFunction{
Value: func(args ...objects.Object) (ret objects.Object, err error) { Value: func(args ...objects.Object) (ret objects.Object, err error) {
arg0, _ := objects.ToInt64(args[0]) arg0, _ := objects.ToInt64(args[0])
@ -61,18 +61,9 @@ e := mod1.double(s)
_ = scr.Add("a", 0) _ = scr.Add("a", 0)
_ = scr.Add("b", 0) _ = scr.Add("b", 0)
_ = scr.Add("c", 0) _ = scr.Add("c", 0)
scr.SetBuiltinModules(map[string]*objects.ImmutableMap{ scr.SetImports(map[string]objects.Importable{
"mod1": mod1, "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() compiled, err := scr.Compile()
assert.NoError(t, err) assert.NoError(t, err)

View file

@ -1,7 +1,6 @@
package script_test package script_test
import ( import (
"errors"
"strings" "strings"
"testing" "testing"
@ -10,57 +9,40 @@ import (
"github.com/d5/tengo/script" "github.com/d5/tengo/script"
) )
func TestScript_SetUserModuleLoader(t *testing.T) { func TestScriptSourceModule(t *testing.T) {
// script1 imports "mod1" // script1 imports "mod1"
scr := script.New([]byte(`out := import("mod")`)) scr := script.New([]byte(`out := import("mod")`))
scr.SetUserModuleLoader(func(name string) ([]byte, error) { scr.SetImports(map[string]objects.Importable{
return []byte(`export 5`), nil "mod": &objects.SourceModule{Src: []byte(`export 5`)},
}) })
c, err := scr.Run() c, err := scr.Run()
assert.Equal(t, int64(5), c.Get("out").Value()) assert.Equal(t, int64(5), c.Get("out").Value())
// executing module function // executing module function
scr = script.New([]byte(`fn := import("mod"); out := fn()`)) scr = script.New([]byte(`fn := import("mod"); out := fn()`))
scr.SetUserModuleLoader(func(name string) ([]byte, error) { scr.SetImports(map[string]objects.Importable{
return []byte(`a := 3; export func() { return a + 5 }`), nil "mod": &objects.SourceModule{Src: []byte(`a := 3; export func() { return a + 5 }`)},
}) })
c, err = scr.Run() c, err = scr.Run()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, int64(8), c.Get("out").Value()) assert.Equal(t, int64(8), c.Get("out").Value())
// disabled builtin function
scr = script.New([]byte(`out := import("mod")`)) scr = script.New([]byte(`out := import("mod")`))
scr.SetUserModuleLoader(func(name string) ([]byte, error) { scr.SetImports(map[string]objects.Importable{
return []byte(`export len([1, 2, 3])`), nil "text": &objects.BuiltinModule{
}) Attrs: map[string]objects.Object{
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{
"title": &objects.UserFunction{Name: "title", Value: func(args ...objects.Object) (ret objects.Object, err error) { "title": &objects.UserFunction{Name: "title", Value: func(args ...objects.Object) (ret objects.Object, err error) {
s, _ := objects.ToString(args[0]) s, _ := objects.ToString(args[0])
return &objects.String{Value: strings.Title(s)}, nil return &objects.String{Value: strings.Title(s)}, nil
}}, }},
}, },
}), },
}) "mod": &objects.SourceModule{Src: []byte(`text := import("text"); export text.title("foo")`)},
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")
}) })
c, err = scr.Run() c, err = scr.Run()
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "Foo", c.Get("out").Value()) assert.Equal(t, "Foo", c.Get("out").Value())
scr.SetBuiltinModules(nil) scr.SetImports(nil)
_, err = scr.Run() _, err = scr.Run()
assert.Error(t, err) assert.Error(t, err)

View file

@ -1,13 +1,12 @@
package script_test package script_test
import ( import (
"errors"
"math"
"testing" "testing"
"github.com/d5/tengo/assert" "github.com/d5/tengo/assert"
"github.com/d5/tengo/objects" "github.com/d5/tengo/objects"
"github.com/d5/tengo/script" "github.com/d5/tengo/script"
"github.com/d5/tengo/stdlib"
) )
func TestScript_Add(t *testing.T) { func TestScript_Add(t *testing.T) {
@ -52,71 +51,42 @@ func TestScript_Run(t *testing.T) {
compiledGet(t, c, "a", int64(5)) compiledGet(t, c, "a", int64(5))
} }
func TestScript_SetBuiltinFunctions(t *testing.T) { func TestScript_BuiltinModules(t *testing.T) {
s := script.New([]byte(`a := len([1, 2, 3])`)) 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() c, err := s.Run()
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, c) 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() c, err = s.Run()
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, c) 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() _, err = s.Run()
assert.Error(t, err) assert.Error(t, err)
s.SetBuiltinFunctions(nil) s.SetImports(nil)
_, err = s.Run() _, err = s.Run()
assert.Error(t, err) 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) { func TestScript_SourceModules(t *testing.T) {
s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`)) s := script.New([]byte(`
s.SetBuiltinModules(map[string]*objects.ImmutableMap{ enum := import("enum")
"math": objectPtr(&objects.ImmutableMap{ a := enum.all([1,2,3], func(_, v) {
Value: map[string]objects.Object{ return v > 0
"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 s.SetImports(stdlib.GetModules("enum"))
}},
},
}),
})
c, err := s.Run() c, err := s.Run()
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, c) assert.NotNil(t, c)
compiledGet(t, c, "a", 19.84) compiledGet(t, c, "a", true)
c, err = s.Run() s.SetImports(nil)
assert.NoError(t, err)
assert.NotNil(t, c)
compiledGet(t, c, "a", 19.84)
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)
_, err = s.Run() _, err = s.Run()
assert.Error(t, err) assert.Error(t, err)
} }
@ -154,7 +124,3 @@ func TestScript_SetMaxConstObjects(t *testing.T) {
_, err = s.Compile() _, err = s.Compile()
assert.NoError(t, err) assert.NoError(t, err)
} }
func objectPtr(o objects.Object) *objects.ImmutableMap {
return o.(*objects.ImmutableMap)
}

14
stdlib/builtin_modules.go Normal file
View file

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

7
stdlib/enum_test.go Normal file
View file

@ -0,0 +1,7 @@
package stdlib_test
import "testing"
func TestEnum(t *testing.T) {
}

View file

@ -1,30 +1,20 @@
package objects package stdlib
import ( import (
"fmt" "fmt"
"github.com/d5/tengo" "github.com/d5/tengo"
"github.com/d5/tengo/objects"
) )
func getPrintArgs(args ...Object) ([]interface{}, error) { var fmtModule = map[string]objects.Object{
var printArgs []interface{} "print": &objects.UserFunction{Name: "print", Value: fmtPrint},
l := 0 "printf": &objects.UserFunction{Name: "printf", Value: fmtPrintf},
for _, arg := range args { "println": &objects.UserFunction{Name: "println", Value: fmtPrintln},
s, _ := ToString(arg) "sprintf": &objects.UserFunction{Name: "sprintf", Value: fmtSprintf},
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
} }
// print(args...) func fmtPrint(args ...objects.Object) (ret objects.Object, err error) {
func builtinPrint(args ...Object) (Object, error) {
printArgs, err := getPrintArgs(args...) printArgs, err := getPrintArgs(args...)
if err != nil { if err != nil {
return nil, err return nil, err
@ -35,29 +25,15 @@ func builtinPrint(args ...Object) (Object, error) {
return nil, nil return nil, nil
} }
// println(args...) func fmtPrintf(args ...objects.Object) (ret objects.Object, err error) {
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) {
numArgs := len(args) numArgs := len(args)
if numArgs == 0 { if numArgs == 0 {
return nil, ErrWrongNumArguments return nil, objects.ErrWrongNumArguments
} }
format, ok := args[0].(*String) format, ok := args[0].(*objects.String)
if !ok { if !ok {
return nil, ErrInvalidArgumentType{ return nil, objects.ErrInvalidArgumentType{
Name: "format", Name: "format",
Expected: "string", Expected: "string",
Found: args[0].TypeName(), Found: args[0].TypeName(),
@ -70,7 +46,7 @@ func builtinPrintf(args ...Object) (Object, error) {
formatArgs := make([]interface{}, numArgs-1, numArgs-1) formatArgs := make([]interface{}, numArgs-1, numArgs-1)
for idx, arg := range args[1:] { for idx, arg := range args[1:] {
formatArgs[idx] = ToInterface(arg) formatArgs[idx] = objects.ToInterface(arg)
} }
fmt.Printf(format.Value, formatArgs...) fmt.Printf(format.Value, formatArgs...)
@ -78,16 +54,27 @@ func builtinPrintf(args ...Object) (Object, error) {
return nil, nil return nil, nil
} }
// sprintf("format", args...) func fmtPrintln(args ...objects.Object) (ret objects.Object, err error) {
func builtinSprintf(args ...Object) (Object, error) { printArgs, err := getPrintArgs(args...)
numArgs := len(args) if err != nil {
if numArgs == 0 { return nil, err
return nil, ErrWrongNumArguments
} }
format, ok := args[0].(*String) 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, objects.ErrWrongNumArguments
}
format, ok := args[0].(*objects.String)
if !ok { if !ok {
return nil, ErrInvalidArgumentType{ return nil, objects.ErrInvalidArgumentType{
Name: "format", Name: "format",
Expected: "string", Expected: "string",
Found: args[0].TypeName(), Found: args[0].TypeName(),
@ -99,14 +86,31 @@ func builtinSprintf(args ...Object) (Object, error) {
formatArgs := make([]interface{}, numArgs-1, numArgs-1) formatArgs := make([]interface{}, numArgs-1, numArgs-1)
for idx, arg := range args[1:] { for idx, arg := range args[1:] {
formatArgs[idx] = ToInterface(arg) formatArgs[idx] = objects.ToInterface(arg)
} }
s := fmt.Sprintf(format.Value, formatArgs...) s := fmt.Sprintf(format.Value, formatArgs...)
if len(s) > tengo.MaxStringLen { if len(s) > tengo.MaxStringLen {
return nil, ErrStringLimit return nil, objects.ErrStringLimit
} }
return &String{Value: s}, nil 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 printArgs, nil
} }

11
stdlib/fmt_test.go Normal file
View file

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

54
stdlib/gensrcmods.go Normal file
View file

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

69
stdlib/json.go Normal file
View file

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

33
stdlib/json_test.go Normal file
View file

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

50
stdlib/source_modules.go Normal file
View file

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

40
stdlib/srcmod_enum.tengo Normal file
View file

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

View file

@ -1,20 +1,16 @@
package stdlib package stdlib
import "github.com/d5/tengo/objects" //go:generate go run gensrcmods.go
// Modules contain the standard modules. import "github.com/d5/tengo/objects"
var Modules = map[string]*objects.ImmutableMap{
"math": &objects.ImmutableMap{Value: mathModule},
"os": &objects.ImmutableMap{Value: osModule},
"text": &objects.ImmutableMap{Value: textModule},
"times": &objects.ImmutableMap{Value: timesModule},
"rand": &objects.ImmutableMap{Value: randModule},
}
// AllModuleNames returns a list of all default module names. // AllModuleNames returns a list of all default module names.
func AllModuleNames() []string { func AllModuleNames() []string {
var names []string var names []string
for name := range Modules { for name := range BuiltinModules {
names = append(names, name)
}
for name := range SourceModules {
names = append(names, name) names = append(names, name)
} }
return names return names
@ -22,12 +18,16 @@ func AllModuleNames() []string {
// GetModules returns the modules for the given names. // GetModules returns the modules for the given names.
// Duplicate names and invalid names are ignore. // Duplicate names and invalid names are ignore.
func GetModules(names ...string) map[string]*objects.ImmutableMap { func GetModules(names ...string) map[string]objects.Importable {
modules := make(map[string]*objects.ImmutableMap) modules := make(map[string]objects.Importable)
for _, name := range names { 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 modules[name] = mod
} }
} }
return modules return modules
} }

View file

@ -18,12 +18,9 @@ type IMAP map[string]interface{}
func TestAllModuleNames(t *testing.T) { func TestAllModuleNames(t *testing.T) {
names := stdlib.AllModuleNames() 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 return
} }
for _, name := range names {
assert.NotNil(t, stdlib.Modules[name], "name: %s", name)
}
} }
func TestModulesRun(t *testing.T) { func TestModulesRun(t *testing.T) {
@ -100,7 +97,7 @@ func TestGetModules(t *testing.T) {
type callres struct { type callres struct {
t *testing.T t *testing.T
o objects.Object o interface{}
e error e error
} }
@ -109,12 +106,14 @@ func (c callres) call(funcName string, args ...interface{}) callres {
return c return c
} }
imap, ok := c.o.(*objects.ImmutableMap) var oargs []objects.Object
if !ok { for _, v := range args {
return c oargs = append(oargs, object(v))
} }
m, ok := imap.Value[funcName] switch o := c.o.(type) {
case *objects.BuiltinModule:
m, ok := o.Attrs[funcName]
if !ok { if !ok {
return callres{t: c.t, e: fmt.Errorf("function not found: %s", funcName)} return callres{t: c.t, e: fmt.Errorf("function not found: %s", funcName)}
} }
@ -124,14 +123,27 @@ func (c callres) call(funcName string, args ...interface{}) callres {
return callres{t: c.t, e: fmt.Errorf("non-callable: %s", funcName)} return callres{t: c.t, e: fmt.Errorf("non-callable: %s", funcName)}
} }
var oargs []objects.Object res, err := f.Value(oargs...)
for _, v := range args { return callres{t: c.t, o: res, e: err}
oargs = append(oargs, object(v)) 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...) res, err := f.Value(oargs...)
return callres{t: c.t, o: res, e: err} 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 { 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 { func module(t *testing.T, moduleName string) callres {
mod, ok := stdlib.Modules[moduleName] mod, ok := stdlib.BuiltinModules[moduleName]
if !ok { if !ok {
return callres{t: t, e: fmt.Errorf("module not found: %s", moduleName)} 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{}) { func expect(t *testing.T, input string, expected interface{}) {
s := script.New([]byte(input)) s := script.New([]byte(input))
s.SetBuiltinModules(stdlib.Modules) s.SetImports(stdlib.GetModules(stdlib.AllModuleNames()...))
c, err := s.Run() c, err := s.Run()
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, c) assert.NotNil(t, c)