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:
go vet ./...
generate:
go generate ./...
lint:
golint -set_exit_status ./...
test: vet lint
test: generate vet lint
go test -race -cover ./...
fmt:

View file

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

View file

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

View file

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

View file

@ -16,6 +16,7 @@ func (b *Bytecode) RemoveDuplicates() {
strings := make(map[string]int)
floats := make(map[float64]int)
chars := make(map[rune]int)
immutableMaps := make(map[string]int) // for modules
for curIdx, c := range b.Constants {
switch c := c.(type) {
@ -23,7 +24,17 @@ func (b *Bytecode) RemoveDuplicates() {
// add to deduped list
indexMap[curIdx] = len(deduped)
deduped = append(deduped, c)
continue
case *objects.ImmutableMap:
modName := c.Value["__module_name__"].(*objects.String).Value
newIdx, ok := immutableMaps[modName]
if modName != "" && ok {
indexMap[curIdx] = newIdx
} else {
newIdx = len(deduped)
immutableMaps[modName] = newIdx
indexMap[curIdx] = newIdx
deduped = append(deduped, c)
}
case *objects.Int:
if newIdx, ok := ints[c.Value]; ok {
indexMap[curIdx] = newIdx

View file

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

View file

@ -3,7 +3,7 @@ package compiler_test
import "testing"
func TestCompilerErrorReport(t *testing.T) {
expectError(t, `import("user1")`, "Compile Error: module file read error: open user1.tengo: no such file or directory\n\tat test:1:1")
expectError(t, `import("user1")`, "Compile Error: module 'user1' not found\n\tat test:1:1")
expectError(t, `a = 1`, "Compile Error: unresolved reference 'a'\n\tat test:1:1")
expectError(t, `a, b := 1, 2`, "Compile Error: tuple assignment not allowed\n\tat test:1:1")

View file

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

View file

@ -695,7 +695,7 @@ func TestCompiler_Compile(t *testing.T) {
expect(t, `len([]);`,
bytecode(
concat(
compiler.MakeInstruction(compiler.OpGetBuiltin, 4),
compiler.MakeInstruction(compiler.OpGetBuiltin, 0),
compiler.MakeInstruction(compiler.OpArray, 0),
compiler.MakeInstruction(compiler.OpCall, 1),
compiler.MakeInstruction(compiler.OpPop)),
@ -708,7 +708,7 @@ func TestCompiler_Compile(t *testing.T) {
compiler.MakeInstruction(compiler.OpPop)),
objectsArray(
compiledFunction(0, 0,
compiler.MakeInstruction(compiler.OpGetBuiltin, 4),
compiler.MakeInstruction(compiler.OpGetBuiltin, 0),
compiler.MakeInstruction(compiler.OpArray, 0),
compiler.MakeInstruction(compiler.OpCall, 1),
compiler.MakeInstruction(compiler.OpReturnValue)))))
@ -874,7 +874,7 @@ func() {
intObject(1),
intObject(1))))
expectError(t, `import("user1")`, "no such file or directory") // unknown module name
expectError(t, `import("user1")`, "module 'user1' not found") // unknown module name
expectError(t, `
r["x"] = {

View file

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

View file

@ -65,6 +65,13 @@ func TestMap(t *testing.T) {
mapElementLit("key3", p(5, 2), p(5, 6), boolLit(true, p(5, 8))))))
})
expectError(t, `
{
key1: 1,
key2: "2",
key3: true,
}`) // unlike Go, trailing comma for the last element is illegal
expectError(t, `{ key1: 1, }`)
expectError(t, `{
key1: 1,

View file

@ -1,45 +1,5 @@
# Builtin Functions
## print
Prints a string representation of the given variable to the standard output. No spaces are added between the operands.
```golang
v := [1, 2, 3]
print(v) // "[1, 2, 3]"
print(1, 2, 3) // "123"
```
## println
Prints a string representation of the given variable to the standard output with a newline appended. No spaces are added between the operands.
```golang
v := [1, 2, 3]
println(v) // "[1, 2, 3]"
println(1, 2, 3) // "123" newline
```
## printf
Prints a formatted string to the standard output. It does not append the newline character at the end. The first argument must a String object. It's same as Go's `fmt.Printf`.
```golang
a := [1, 2, 3]
printf("foo %v", a) // "foo [1, 2, 3]"
```
## sprintf
Returns a formatted string. The first argument must be a String object. It's the same as Go's `fmt.Sprintf`.
```golang
a := [1, 2, 3]
b := sprintp("foo %v", a) // b == "foo [1, 2, 3]"
```
## len
Returns the number of elements if the given variable is array, string, map, or module map.
@ -71,26 +31,6 @@ v := [1]
v = append(v, 2, 3) // v == [1, 2, 3]
```
## to_json
Returns the JSON encoding of an object.
```golang
print(to_json([1, 2, 3])) // [1, 2, 3]
print(to_json(4)) // 4
print(to_json("five")) // "five"
```
## from_json
Parses the JSON-encoded data and returns an object.
```golang
arr := from_json(`[1, 2, 3]`)
four := from_json(`4`)
five := from_json(`"five"`)
```
## string
Tries to convert an object to string object. See [Runtime Types](https://github.com/d5/tengo/blob/master/docs/runtime-types.md) for more details on type conversion.

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.
#### Script.SetBuiltinFunctions(funcs []*objects.BuiltinFunction)
#### Script.SetImports(modules map[string]objects.Importable)
SetBuiltinFunctions resets all builtin functions in the compiler to the ones provided in the input parameter. Compiler will report a compile-time error if the a function not set is referenced. Passing `nil` will disable all builtin functions. All builtin functions **are included by default** unless `SetBuiltinFunctions` is called.
```golang
s := script.New([]byte(`print([1, 2, 3])`))
_, err := s.Run() // prints [1, 2, 3]
s.SetBuiltinFunctions(nil)
_, err := s.Run() // compile error
s.SetBuiltinFunctions([]*objects.BuiltinFunction{&objects.Builtins[0]})
_, err := s.Run() // prints [1, 2, 3]
```
#### Script.SetBuiltinModules(modules map[string]*objects.ImmutableMap)
SetBuiltinModules adds builtin modules provided in the input parameter. This can be used to add [standard library](https://github.com/d5/tengo/blob/master/docs/stdlib.md) modules into the compiler and VM. Compiler will report a compile-time error if the code tries to import a module that hasn't been included. Passing `nil` will disable all builtin modules. No standard library modules are included by default unless `SetBuiltinModules` is called.
SetImports sets the import modules with corresponding names. Script **does not** include any modules by default. You can use this function to include the [Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md).
```golang
s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`))
_, err := s.Run() // compile error
s.SetBuiltinModules(stdlib.Modules)
_, err := s.Run() // a = 19.84
s.SetBuiltinModules(nil)
_, err := s.Run() // compile error
s.SetBuiltinModules(map[string]*objects.ImmutableMap{"math": stdlib.Modules["math"]})
_, err := s.Run() // a = 19.84
s.SetImports(map[string]objects.Importable{
"math": stdlib.BuiltinModules["math"],
})
// or
s.SetImports(stdlib.GetModules("math"))
// or, to include all stdlib at once
s.SetImports(stdlib.GetModules(stdlib.AllModuleNames()...))
```
#### Script.SetUserModuleLoader(loader compiler.ModuleLoader)
SetUserModuleLoader replaces the default user-module loader of the compiler, which tries to read the source from a local file.
You can also include Tengo's written module using `objects.SourceModule` (which implements `objects.Importable`).
```golang
s := script.New([]byte(`math := import("mod1"); a := math.foo()`))
s := script.New([]byte(`double := import("double"); a := double(20)`))
s.SetUserModuleLoader(func(moduleName string) ([]byte, error) {
if moduleName == "mod1" {
return []byte(`foo := func() { return 5 }`), nil
}
return nil, errors.New("module not found")
s.SetImports(map[string]objects.Importable{
"double": &objects.SourceModule{Src: []byte(`export func(x) { return x * 2 }`)},
})
```
Note that when a script is being added to another script as a module (via `Script.AddModule`), it does not inherit the module loader from the main script.
#### Script.SetMaxAllocs(n int64)
SetMaxAllocs sets the maximum number of object allocations. Note this is a cumulative metric that tracks only the object creations. Set this to a negative number (e.g. `-1`) if you don't need to limit the number of allocations.
#### Script.EnableFileImport(enable bool)
EnableFileImport enables or disables module loading from the local files. It's disabled by default.
#### tengo.MaxStringLen

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
- [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
- [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
sum := import("./sum") // assuming sum.tengo file exists in the current directory
// same as 'import("./sum.tengo")' or 'import("sum")'
print(sum(10)) // module function
fmt.print(sum(10)) // module function
```
`sum.tengo` file:

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.
// Use GetBuiltinFunctions instead of accessing Builtins directly.
var Builtins = []BuiltinFunction{
{
Name: "print",
Value: builtinPrint,
},
{
Name: "println",
Value: builtinPrintln,
},
{
Name: "printf",
Value: builtinPrintf,
},
{
Name: "sprintf",
Value: builtinSprintf,
},
var Builtins = []*BuiltinFunction{
{
Name: "len",
Value: builtinLen,
@ -119,50 +103,8 @@ var Builtins = []BuiltinFunction{
Name: "is_callable",
Value: builtinIsCallable,
},
{
Name: "to_json",
Value: builtinToJSON,
},
{
Name: "from_json",
Value: builtinFromJSON,
},
{
Name: "type_name",
Value: builtinTypeName,
},
}
// AllBuiltinFunctionNames returns a list of all default builtin function names.
func AllBuiltinFunctionNames() []string {
var names []string
for _, bf := range Builtins {
names = append(names, bf.Name)
}
return names
}
// GetBuiltinFunctions returns a slice of builtin function objects.
// GetBuiltinFunctions removes the duplicate names, and, the returned builtin functions
// are not guaranteed to be in the same order as names.
func GetBuiltinFunctions(names ...string) []*BuiltinFunction {
include := make(map[string]bool)
for _, name := range names {
include[name] = true
}
var builtinFuncs []*BuiltinFunction
for _, bf := range Builtins {
if include[bf.Name] {
bf := bf
builtinFuncs = append(builtinFuncs, &bf)
}
}
return builtinFuncs
}
// GetAllBuiltinFunctions returns all builtin functions.
func GetAllBuiltinFunctions() []*BuiltinFunction {
return GetBuiltinFunctions(AllBuiltinFunctionNames()...)
}

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

@ -23,65 +23,47 @@ const (
// VM is a virtual machine that executes the bytecode compiled by Compiler.
type VM struct {
constants []objects.Object
stack []objects.Object
sp int
globals []objects.Object
fileSet *source.FileSet
frames []Frame
framesIndex int
curFrame *Frame
curInsts []byte
curIPLimit int
ip int
aborting int64
builtinFuncs []objects.Object
builtinModules map[string]objects.Object
maxAllocs int64
allocs int64
err error
errOffset int
constants []objects.Object
stack []objects.Object
sp int
globals []objects.Object
fileSet *source.FileSet
frames []Frame
framesIndex int
curFrame *Frame
curInsts []byte
curIPLimit int
ip int
aborting int64
maxAllocs int64
allocs int64
err error
errOffset int
}
// NewVM creates a VM.
func NewVM(bytecode *compiler.Bytecode, globals []objects.Object, builtinFuncs []objects.Object, builtinModules map[string]objects.Object, maxAllocs int64) *VM {
func NewVM(bytecode *compiler.Bytecode, globals []objects.Object, maxAllocs int64) *VM {
if globals == nil {
globals = make([]objects.Object, GlobalsSize)
}
if builtinModules == nil {
builtinModules = make(map[string]objects.Object)
}
if builtinFuncs == nil {
builtinFuncs = make([]objects.Object, len(objects.Builtins))
for idx, fn := range objects.Builtins {
builtinFuncs[idx] = &objects.BuiltinFunction{
Name: fn.Name,
Value: fn.Value,
}
}
}
frames := make([]Frame, MaxFrames)
frames[0].fn = bytecode.MainFunction
frames[0].ip = -1
return &VM{
constants: bytecode.Constants,
stack: make([]objects.Object, StackSize),
sp: 0,
globals: globals,
fileSet: bytecode.FileSet,
frames: frames,
framesIndex: 1,
curFrame: &(frames[0]),
curInsts: frames[0].fn.Instructions,
curIPLimit: len(frames[0].fn.Instructions) - 1,
ip: -1,
builtinFuncs: builtinFuncs,
builtinModules: builtinModules,
maxAllocs: maxAllocs,
constants: bytecode.Constants,
stack: make([]objects.Object, StackSize),
sp: 0,
globals: globals,
fileSet: bytecode.FileSet,
frames: frames,
framesIndex: 1,
curFrame: &(frames[0]),
curInsts: frames[0].fn.Instructions,
curIPLimit: len(frames[0].fn.Instructions) - 1,
ip: -1,
maxAllocs: maxAllocs,
}
}
@ -997,28 +979,7 @@ func (v *VM) run() {
return
}
v.stack[v.sp] = v.builtinFuncs[builtinIndex]
v.sp++
case compiler.OpGetBuiltinModule:
val := v.stack[v.sp-1]
v.sp--
moduleName := val.(*objects.String).Value
module, ok := v.builtinModules[moduleName]
if !ok {
v.errOffset = 3
v.err = fmt.Errorf("module '%s' not found", moduleName)
return
}
if v.sp >= StackSize {
v.err = ErrStackOverflow
return
}
v.stack[v.sp] = module
v.stack[v.sp] = objects.Builtins[builtinIndex]
v.sp++
case compiler.OpClosure:

View file

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

View file

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

View file

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

View file

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

View file

@ -8,198 +8,155 @@ import (
)
func TestBuiltinFunction(t *testing.T) {
expect(t, `out = len("")`, 0)
expect(t, `out = len("four")`, 4)
expect(t, `out = len("hello world")`, 11)
expect(t, `out = len([])`, 0)
expect(t, `out = len([1, 2, 3])`, 3)
expect(t, `out = len({})`, 0)
expect(t, `out = len({a:1, b:2})`, 2)
expect(t, `out = len(immutable([]))`, 0)
expect(t, `out = len(immutable([1, 2, 3]))`, 3)
expect(t, `out = len(immutable({}))`, 0)
expect(t, `out = len(immutable({a:1, b:2}))`, 2)
expectError(t, `len(1)`, "invalid type for argument")
expectError(t, `len("one", "two")`, "wrong number of arguments")
expect(t, `out = len("")`, nil, 0)
expect(t, `out = len("four")`, nil, 4)
expect(t, `out = len("hello world")`, nil, 11)
expect(t, `out = len([])`, nil, 0)
expect(t, `out = len([1, 2, 3])`, nil, 3)
expect(t, `out = len({})`, nil, 0)
expect(t, `out = len({a:1, b:2})`, nil, 2)
expect(t, `out = len(immutable([]))`, nil, 0)
expect(t, `out = len(immutable([1, 2, 3]))`, nil, 3)
expect(t, `out = len(immutable({}))`, nil, 0)
expect(t, `out = len(immutable({a:1, b:2}))`, nil, 2)
expectError(t, `len(1)`, nil, "invalid type for argument")
expectError(t, `len("one", "two")`, nil, "wrong number of arguments")
expect(t, `out = copy(1)`, 1)
expectError(t, `copy(1, 2)`, "wrong number of arguments")
expect(t, `out = copy(1)`, nil, 1)
expectError(t, `copy(1, 2)`, nil, "wrong number of arguments")
expect(t, `out = append([1, 2, 3], 4)`, ARR{1, 2, 3, 4})
expect(t, `out = append([1, 2, 3], 4, 5, 6)`, ARR{1, 2, 3, 4, 5, 6})
expect(t, `out = append([1, 2, 3], "foo", false)`, ARR{1, 2, 3, "foo", false})
expect(t, `out = append([1, 2, 3], 4)`, nil, ARR{1, 2, 3, 4})
expect(t, `out = append([1, 2, 3], 4, 5, 6)`, nil, ARR{1, 2, 3, 4, 5, 6})
expect(t, `out = append([1, 2, 3], "foo", false)`, nil, ARR{1, 2, 3, "foo", false})
expect(t, `out = int(1)`, 1)
expect(t, `out = int(1.8)`, 1)
expect(t, `out = int("-522")`, -522)
expect(t, `out = int(true)`, 1)
expect(t, `out = int(false)`, 0)
expect(t, `out = int('8')`, 56)
expect(t, `out = int([1])`, objects.UndefinedValue)
expect(t, `out = int({a: 1})`, objects.UndefinedValue)
expect(t, `out = int(undefined)`, objects.UndefinedValue)
expect(t, `out = int("-522", 1)`, -522)
expect(t, `out = int(undefined, 1)`, 1)
expect(t, `out = int(undefined, 1.8)`, 1.8)
expect(t, `out = int(undefined, string(1))`, "1")
expect(t, `out = int(undefined, undefined)`, objects.UndefinedValue)
expect(t, `out = int(1)`, nil, 1)
expect(t, `out = int(1.8)`, nil, 1)
expect(t, `out = int("-522")`, nil, -522)
expect(t, `out = int(true)`, nil, 1)
expect(t, `out = int(false)`, nil, 0)
expect(t, `out = int('8')`, nil, 56)
expect(t, `out = int([1])`, nil, objects.UndefinedValue)
expect(t, `out = int({a: 1})`, nil, objects.UndefinedValue)
expect(t, `out = int(undefined)`, nil, objects.UndefinedValue)
expect(t, `out = int("-522", 1)`, nil, -522)
expect(t, `out = int(undefined, 1)`, nil, 1)
expect(t, `out = int(undefined, 1.8)`, nil, 1.8)
expect(t, `out = int(undefined, string(1))`, nil, "1")
expect(t, `out = int(undefined, undefined)`, nil, objects.UndefinedValue)
expect(t, `out = string(1)`, "1")
expect(t, `out = string(1.8)`, "1.8")
expect(t, `out = string("-522")`, "-522")
expect(t, `out = string(true)`, "true")
expect(t, `out = string(false)`, "false")
expect(t, `out = string('8')`, "8")
expect(t, `out = string([1,8.1,true,3])`, "[1, 8.1, true, 3]")
expect(t, `out = string({b: "foo"})`, `{b: "foo"}`)
expect(t, `out = string(undefined)`, objects.UndefinedValue) // not "undefined"
expect(t, `out = string(1, "-522")`, "1")
expect(t, `out = string(undefined, "-522")`, "-522") // not "undefined"
expect(t, `out = string(1)`, nil, "1")
expect(t, `out = string(1.8)`, nil, "1.8")
expect(t, `out = string("-522")`, nil, "-522")
expect(t, `out = string(true)`, nil, "true")
expect(t, `out = string(false)`, nil, "false")
expect(t, `out = string('8')`, nil, "8")
expect(t, `out = string([1,8.1,true,3])`, nil, "[1, 8.1, true, 3]")
expect(t, `out = string({b: "foo"})`, nil, `{b: "foo"}`)
expect(t, `out = string(undefined)`, nil, objects.UndefinedValue) // not "undefined"
expect(t, `out = string(1, "-522")`, nil, "1")
expect(t, `out = string(undefined, "-522")`, nil, "-522") // not "undefined"
expect(t, `out = float(1)`, 1.0)
expect(t, `out = float(1.8)`, 1.8)
expect(t, `out = float("-52.2")`, -52.2)
expect(t, `out = float(true)`, objects.UndefinedValue)
expect(t, `out = float(false)`, objects.UndefinedValue)
expect(t, `out = float('8')`, objects.UndefinedValue)
expect(t, `out = float([1,8.1,true,3])`, objects.UndefinedValue)
expect(t, `out = float({a: 1, b: "foo"})`, objects.UndefinedValue)
expect(t, `out = float(undefined)`, objects.UndefinedValue)
expect(t, `out = float("-52.2", 1.8)`, -52.2)
expect(t, `out = float(undefined, 1)`, 1)
expect(t, `out = float(undefined, 1.8)`, 1.8)
expect(t, `out = float(undefined, "-52.2")`, "-52.2")
expect(t, `out = float(undefined, char(56))`, '8')
expect(t, `out = float(undefined, undefined)`, objects.UndefinedValue)
expect(t, `out = float(1)`, nil, 1.0)
expect(t, `out = float(1.8)`, nil, 1.8)
expect(t, `out = float("-52.2")`, nil, -52.2)
expect(t, `out = float(true)`, nil, objects.UndefinedValue)
expect(t, `out = float(false)`, nil, objects.UndefinedValue)
expect(t, `out = float('8')`, nil, objects.UndefinedValue)
expect(t, `out = float([1,8.1,true,3])`, nil, objects.UndefinedValue)
expect(t, `out = float({a: 1, b: "foo"})`, nil, objects.UndefinedValue)
expect(t, `out = float(undefined)`, nil, objects.UndefinedValue)
expect(t, `out = float("-52.2", 1.8)`, nil, -52.2)
expect(t, `out = float(undefined, 1)`, nil, 1)
expect(t, `out = float(undefined, 1.8)`, nil, 1.8)
expect(t, `out = float(undefined, "-52.2")`, nil, "-52.2")
expect(t, `out = float(undefined, char(56))`, nil, '8')
expect(t, `out = float(undefined, undefined)`, nil, objects.UndefinedValue)
expect(t, `out = char(56)`, '8')
expect(t, `out = char(1.8)`, objects.UndefinedValue)
expect(t, `out = char("-52.2")`, objects.UndefinedValue)
expect(t, `out = char(true)`, objects.UndefinedValue)
expect(t, `out = char(false)`, objects.UndefinedValue)
expect(t, `out = char('8')`, '8')
expect(t, `out = char([1,8.1,true,3])`, objects.UndefinedValue)
expect(t, `out = char({a: 1, b: "foo"})`, objects.UndefinedValue)
expect(t, `out = char(undefined)`, objects.UndefinedValue)
expect(t, `out = char(56, 'a')`, '8')
expect(t, `out = char(undefined, '8')`, '8')
expect(t, `out = char(undefined, 56)`, 56)
expect(t, `out = char(undefined, "-52.2")`, "-52.2")
expect(t, `out = char(undefined, undefined)`, objects.UndefinedValue)
expect(t, `out = char(56)`, nil, '8')
expect(t, `out = char(1.8)`, nil, objects.UndefinedValue)
expect(t, `out = char("-52.2")`, nil, objects.UndefinedValue)
expect(t, `out = char(true)`, nil, objects.UndefinedValue)
expect(t, `out = char(false)`, nil, objects.UndefinedValue)
expect(t, `out = char('8')`, nil, '8')
expect(t, `out = char([1,8.1,true,3])`, nil, objects.UndefinedValue)
expect(t, `out = char({a: 1, b: "foo"})`, nil, objects.UndefinedValue)
expect(t, `out = char(undefined)`, nil, objects.UndefinedValue)
expect(t, `out = char(56, 'a')`, nil, '8')
expect(t, `out = char(undefined, '8')`, nil, '8')
expect(t, `out = char(undefined, 56)`, nil, 56)
expect(t, `out = char(undefined, "-52.2")`, nil, "-52.2")
expect(t, `out = char(undefined, undefined)`, nil, objects.UndefinedValue)
expect(t, `out = bool(1)`, true) // non-zero integer: true
expect(t, `out = bool(0)`, false) // zero: true
expect(t, `out = bool(1.8)`, true) // all floats (except for NaN): true
expect(t, `out = bool(0.0)`, true) // all floats (except for NaN): true
expect(t, `out = bool("false")`, true) // non-empty string: true
expect(t, `out = bool("")`, false) // empty string: false
expect(t, `out = bool(true)`, true) // true: true
expect(t, `out = bool(false)`, false) // false: false
expect(t, `out = bool('8')`, true) // non-zero chars: true
expect(t, `out = bool(char(0))`, false) // zero char: false
expect(t, `out = bool([1])`, true) // non-empty arrays: true
expect(t, `out = bool([])`, false) // empty array: false
expect(t, `out = bool({a: 1})`, true) // non-empty maps: true
expect(t, `out = bool({})`, false) // empty maps: false
expect(t, `out = bool(undefined)`, false) // undefined: false
expect(t, `out = bool(1)`, nil, true) // non-zero integer: true
expect(t, `out = bool(0)`, nil, false) // zero: true
expect(t, `out = bool(1.8)`, nil, true) // all floats (except for NaN): true
expect(t, `out = bool(0.0)`, nil, true) // all floats (except for NaN): true
expect(t, `out = bool("false")`, nil, true) // non-empty string: true
expect(t, `out = bool("")`, nil, false) // empty string: false
expect(t, `out = bool(true)`, nil, true) // true: true
expect(t, `out = bool(false)`, nil, false) // false: false
expect(t, `out = bool('8')`, nil, true) // non-zero chars: true
expect(t, `out = bool(char(0))`, nil, false) // zero char: false
expect(t, `out = bool([1])`, nil, true) // non-empty arrays: true
expect(t, `out = bool([])`, nil, false) // empty array: false
expect(t, `out = bool({a: 1})`, nil, true) // non-empty maps: true
expect(t, `out = bool({})`, nil, false) // empty maps: false
expect(t, `out = bool(undefined)`, nil, false) // undefined: false
expect(t, `out = bytes(1)`, []byte{0})
expect(t, `out = bytes(1.8)`, objects.UndefinedValue)
expect(t, `out = bytes("-522")`, []byte{'-', '5', '2', '2'})
expect(t, `out = bytes(true)`, objects.UndefinedValue)
expect(t, `out = bytes(false)`, objects.UndefinedValue)
expect(t, `out = bytes('8')`, objects.UndefinedValue)
expect(t, `out = bytes([1])`, objects.UndefinedValue)
expect(t, `out = bytes({a: 1})`, objects.UndefinedValue)
expect(t, `out = bytes(undefined)`, objects.UndefinedValue)
expect(t, `out = bytes("-522", ['8'])`, []byte{'-', '5', '2', '2'})
expect(t, `out = bytes(undefined, "-522")`, "-522")
expect(t, `out = bytes(undefined, 1)`, 1)
expect(t, `out = bytes(undefined, 1.8)`, 1.8)
expect(t, `out = bytes(undefined, int("-522"))`, -522)
expect(t, `out = bytes(undefined, undefined)`, objects.UndefinedValue)
expect(t, `out = bytes(1)`, nil, []byte{0})
expect(t, `out = bytes(1.8)`, nil, objects.UndefinedValue)
expect(t, `out = bytes("-522")`, nil, []byte{'-', '5', '2', '2'})
expect(t, `out = bytes(true)`, nil, objects.UndefinedValue)
expect(t, `out = bytes(false)`, nil, objects.UndefinedValue)
expect(t, `out = bytes('8')`, nil, objects.UndefinedValue)
expect(t, `out = bytes([1])`, nil, objects.UndefinedValue)
expect(t, `out = bytes({a: 1})`, nil, objects.UndefinedValue)
expect(t, `out = bytes(undefined)`, nil, objects.UndefinedValue)
expect(t, `out = bytes("-522", ['8'])`, nil, []byte{'-', '5', '2', '2'})
expect(t, `out = bytes(undefined, "-522")`, nil, "-522")
expect(t, `out = bytes(undefined, 1)`, nil, 1)
expect(t, `out = bytes(undefined, 1.8)`, nil, 1.8)
expect(t, `out = bytes(undefined, int("-522"))`, nil, -522)
expect(t, `out = bytes(undefined, undefined)`, nil, objects.UndefinedValue)
expect(t, `out = is_error(error(1))`, true)
expect(t, `out = is_error(1)`, false)
expect(t, `out = is_error(error(1))`, nil, true)
expect(t, `out = is_error(1)`, nil, false)
expect(t, `out = is_undefined(undefined)`, true)
expect(t, `out = is_undefined(error(1))`, false)
// to_json
expect(t, `out = to_json(5)`, []byte("5"))
expect(t, `out = to_json("foobar")`, []byte(`"foobar"`))
expect(t, `out = to_json({foo: 5})`, []byte("{\"foo\":5}"))
expect(t, `out = to_json(immutable({foo: 5}))`, []byte("{\"foo\":5}"))
expect(t, `out = to_json([1,2,3])`, []byte("[1,2,3]"))
expect(t, `out = to_json(immutable([1,2,3]))`, []byte("[1,2,3]"))
expect(t, `out = to_json({foo: "bar"})`, []byte("{\"foo\":\"bar\"}"))
expect(t, `out = to_json({foo: 1.8})`, []byte("{\"foo\":1.8}"))
expect(t, `out = to_json({foo: true})`, []byte("{\"foo\":true}"))
expect(t, `out = to_json({foo: '8'})`, []byte("{\"foo\":56}"))
expect(t, `out = to_json({foo: bytes("foo")})`, []byte("{\"foo\":\"Zm9v\"}")) // json encoding returns []byte as base64 encoded string
expect(t, `out = to_json({foo: ["bar", 1, 1.8, '8', true]})`, []byte("{\"foo\":[\"bar\",1,1.8,56,true]}"))
expect(t, `out = to_json({foo: immutable(["bar", 1, 1.8, '8', true])})`, []byte("{\"foo\":[\"bar\",1,1.8,56,true]}"))
expect(t, `out = to_json({foo: [["bar", 1], ["bar", 1]]})`, []byte("{\"foo\":[[\"bar\",1],[\"bar\",1]]}"))
expect(t, `out = to_json({foo: {string: "bar", int: 1, float: 1.8, char: '8', bool: true}})`, []byte("{\"foo\":{\"bool\":true,\"char\":56,\"float\":1.8,\"int\":1,\"string\":\"bar\"}}"))
expect(t, `out = to_json({foo: immutable({string: "bar", int: 1, float: 1.8, char: '8', bool: true})})`, []byte("{\"foo\":{\"bool\":true,\"char\":56,\"float\":1.8,\"int\":1,\"string\":\"bar\"}}"))
expect(t, `out = to_json({foo: {map1: {string: "bar"}, map2: {int: "1"}}})`, []byte("{\"foo\":{\"map1\":{\"string\":\"bar\"},\"map2\":{\"int\":\"1\"}}}"))
expect(t, `out = to_json([["bar", 1], ["bar", 1]])`, []byte("[[\"bar\",1],[\"bar\",1]]"))
expect(t, `out = to_json(error("my error"))`, []byte(`"error: \"my error\""`))
// from_json
expect(t, `out = from_json("{\"foo\":5}").foo`, 5.0)
expect(t, `out = from_json("{\"foo\":\"bar\"}").foo`, "bar")
expect(t, `out = from_json("{\"foo\":1.8}").foo`, 1.8)
expect(t, `out = from_json("{\"foo\":true}").foo`, true)
expect(t, `out = from_json("{\"foo\":[\"bar\",1,1.8,56,true]}").foo`, ARR{"bar", 1.0, 1.8, 56.0, true})
expect(t, `out = from_json("{\"foo\":[[\"bar\",1],[\"bar\",1]]}").foo[0]`, ARR{"bar", 1.0})
expect(t, `out = from_json("{\"foo\":{\"bool\":true,\"char\":56,\"float\":1.8,\"int\":1,\"string\":\"bar\"}}").foo.bool`, true)
expect(t, `out = from_json("{\"foo\":{\"map1\":{\"string\":\"bar\"},\"map2\":{\"int\":\"1\"}}}").foo.map1.string`, "bar")
expect(t, `out = from_json("5")`, 5.0)
expect(t, `out = from_json("\"foobar\"")`, "foobar")
expect(t, `out = from_json("[\"bar\",1,1.8,56,true]")`, ARR{"bar", 1.0, 1.8, 56.0, true})
// sprintf
expect(t, `out = sprintf("")`, "")
expect(t, `out = sprintf("foo")`, "foo")
expect(t, `out = sprintf("foo %d %v %s", 1, 2, "bar")`, "foo 1 2 bar")
expect(t, `out = sprintf("foo %v", [1, "bar", true])`, "foo [1 bar true]")
expect(t, `out = sprintf("foo %v %d", [1, "bar", true], 19)`, "foo [1 bar true] 19")
expectError(t, `sprintf(1)`, "invalid type for argument") // format has to be String
expectError(t, `sprintf('c')`, "invalid type for argument") // format has to be String
expect(t, `out = is_undefined(undefined)`, nil, true)
expect(t, `out = is_undefined(error(1))`, nil, false)
// type_name
expect(t, `out = type_name(1)`, "int")
expect(t, `out = type_name(1.1)`, "float")
expect(t, `out = type_name("a")`, "string")
expect(t, `out = type_name([1,2,3])`, "array")
expect(t, `out = type_name({k:1})`, "map")
expect(t, `out = type_name('a')`, "char")
expect(t, `out = type_name(true)`, "bool")
expect(t, `out = type_name(false)`, "bool")
expect(t, `out = type_name(bytes( 1))`, "bytes")
expect(t, `out = type_name(undefined)`, "undefined")
expect(t, `out = type_name(error("err"))`, "error")
expect(t, `out = type_name(func() {})`, "compiled-function")
expect(t, `a := func(x) { return func() { return x } }; out = type_name(a(5))`, "closure") // closure
expect(t, `out = type_name(1)`, nil, "int")
expect(t, `out = type_name(1.1)`, nil, "float")
expect(t, `out = type_name("a")`, nil, "string")
expect(t, `out = type_name([1,2,3])`, nil, "array")
expect(t, `out = type_name({k:1})`, nil, "map")
expect(t, `out = type_name('a')`, nil, "char")
expect(t, `out = type_name(true)`, nil, "bool")
expect(t, `out = type_name(false)`, nil, "bool")
expect(t, `out = type_name(bytes( 1))`, nil, "bytes")
expect(t, `out = type_name(undefined)`, nil, "undefined")
expect(t, `out = type_name(error("err"))`, nil, "error")
expect(t, `out = type_name(func() {})`, nil, "compiled-function")
expect(t, `a := func(x) { return func() { return x } }; out = type_name(a(5))`, nil, "closure") // closure
// is_function
expect(t, `out = is_function(1)`, false)
expect(t, `out = is_function(func() {})`, true)
expect(t, `out = is_function(func(x) { return x })`, true)
expect(t, `out = is_function(len)`, false) // builtin function
expect(t, `a := func(x) { return func() { return x } }; out = is_function(a)`, true) // function
expect(t, `a := func(x) { return func() { return x } }; out = is_function(a(5))`, true) // closure
expectWithSymbols(t, `out = is_function(x)`, false, SYM{"x": &StringArray{Value: []string{"foo", "bar"}}}) // user object
expect(t, `out = is_function(1)`, nil, false)
expect(t, `out = is_function(func() {})`, nil, true)
expect(t, `out = is_function(func(x) { return x })`, nil, true)
expect(t, `out = is_function(len)`, nil, false) // builtin function
expect(t, `a := func(x) { return func() { return x } }; out = is_function(a)`, nil, true) // function
expect(t, `a := func(x) { return func() { return x } }; out = is_function(a(5))`, nil, true) // closure
expect(t, `out = is_function(x)`, Opts().Symbol("x", &StringArray{Value: []string{"foo", "bar"}}).Skip2ndPass(), false) // user object
// is_callable
expect(t, `out = is_callable(1)`, false)
expect(t, `out = is_callable(func() {})`, true)
expect(t, `out = is_callable(func(x) { return x })`, true)
expect(t, `out = is_callable(len)`, true) // builtin function
expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a)`, true) // function
expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a(5))`, true) // closure
expectWithSymbols(t, `out = is_callable(x)`, true, SYM{"x": &StringArray{Value: []string{"foo", "bar"}}}) // user object
expect(t, `out = is_callable(1)`, nil, false)
expect(t, `out = is_callable(func() {})`, nil, true)
expect(t, `out = is_callable(func(x) { return x })`, nil, true)
expect(t, `out = is_callable(len)`, nil, true) // builtin function
expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a)`, nil, true) // function
expect(t, `a := func(x) { return func() { return x } }; out = is_callable(a(5))`, nil, true) // closure
expect(t, `out = is_callable(x)`, Opts().Symbol("x", &StringArray{Value: []string{"foo", "bar"}}).Skip2ndPass(), true) // user object
}
func TestBytesN(t *testing.T) {
@ -207,11 +164,11 @@ func TestBytesN(t *testing.T) {
defer func() { tengo.MaxBytesLen = curMaxBytesLen }()
tengo.MaxBytesLen = 10
expect(t, `out = bytes(0)`, make([]byte, 0))
expect(t, `out = bytes(10)`, make([]byte, 10))
expectError(t, `bytes(11)`, "bytes size limit")
expect(t, `out = bytes(0)`, nil, make([]byte, 0))
expect(t, `out = bytes(10)`, nil, make([]byte, 10))
expectError(t, `bytes(11)`, nil, "bytes size limit")
tengo.MaxBytesLen = 1000
expect(t, `out = bytes(1000)`, make([]byte, 1000))
expectError(t, `bytes(1001)`, "bytes size limit")
expect(t, `out = bytes(1000)`, nil, make([]byte, 1000))
expectError(t, `bytes(1001)`, nil, "bytes size limit")
}

View file

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

View file

@ -3,9 +3,9 @@ package runtime_test
import "testing"
func TestCall(t *testing.T) {
expect(t, `a := { b: func(x) { return x + 2 } }; out = a.b(5)`, 7)
expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a.b.c(5)`, 7)
expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a["b"].c(5)`, 7)
expect(t, `a := { b: func(x) { return x + 2 } }; out = a.b(5)`, nil, 7)
expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a.b.c(5)`, nil, 7)
expect(t, `a := { b: { c: func(x) { return x + 2 } } }; out = a["b"].c(5)`, nil, 7)
expectError(t, `a := 1
b := func(a, c) {
c(a)
@ -15,5 +15,5 @@ c := func(a) {
a()
}
b(a, c)
`, "Runtime Error: not callable: int\n\tat test:7:4\n\tat test:3:4\n\tat test:9:1")
`, nil, "Runtime Error: not callable: int\n\tat test:7:4\n\tat test:3:4\n\tat test:9:1")
}

View file

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

View file

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

View file

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

View file

@ -5,16 +5,16 @@ import "testing"
func TestVMErrorInfo(t *testing.T) {
expectError(t, `a := 5
a + "boo"`,
"Runtime Error: invalid operation: int + string\n\tat test:2:1")
nil, "Runtime Error: invalid operation: int + string\n\tat test:2:1")
expectError(t, `a := 5
b := a(5)`,
"Runtime Error: not callable: int\n\tat test:2:6")
nil, "Runtime Error: not callable: int\n\tat test:2:6")
expectError(t, `a := 5
b := {}
b.x.y = 10`,
"Runtime Error: not index-assignable: undefined\n\tat test:3:1")
nil, "Runtime Error: not index-assignable: undefined\n\tat test:3:1")
expectError(t, `
a := func() {
@ -22,27 +22,28 @@ a := func() {
b += "foo"
}
a()`,
"Runtime Error: invalid operation: int + string\n\tat test:4:2")
nil, "Runtime Error: invalid operation: int + string\n\tat test:4:2")
expectErrorWithUserModules(t, `a := 5
a + import("mod1")`, map[string]string{
"mod1": `export "foo"`,
}, ": invalid operation: int + string\n\tat test:2:2")
expectError(t, `a := 5
a + import("mod1")`, Opts().Module(
"mod1", `export "foo"`,
), ": invalid operation: int + string\n\tat test:2:1")
expectErrorWithUserModules(t, `a := import("mod1")()`, map[string]string{
"mod1": `
expectError(t, `a := import("mod1")()`,
Opts().Module(
"mod1", `
export func() {
b := 5
return b + "foo"
}`,
}, "Runtime Error: invalid operation: int + string\n\tat mod1:4:9")
}`), "Runtime Error: invalid operation: int + string\n\tat mod1:4:9")
expectErrorWithUserModules(t, `a := import("mod1")()`, map[string]string{
"mod1": `export import("mod2")()`,
"mod2": `
expectError(t, `a := import("mod1")()`,
Opts().Module(
"mod1", `export import("mod2")()`).
Module(
"mod2", `
export func() {
b := 5
return b + "foo"
}`,
}, "Runtime Error: invalid operation: int + string\n\tat mod2:4:9")
}`), "Runtime Error: invalid operation: int + string\n\tat mod2:4:9")
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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) {
expect(t, `out = "Hello World!"`, "Hello World!")
expect(t, `out = "Hello" + " " + "World!"`, "Hello World!")
expect(t, `out = "Hello World!"`, nil, "Hello World!")
expect(t, `out = "Hello" + " " + "World!"`, nil, "Hello World!")
expect(t, `out = "Hello" == "Hello"`, true)
expect(t, `out = "Hello" == "World"`, false)
expect(t, `out = "Hello" != "Hello"`, false)
expect(t, `out = "Hello" != "World"`, true)
expect(t, `out = "Hello" == "Hello"`, nil, true)
expect(t, `out = "Hello" == "World"`, nil, false)
expect(t, `out = "Hello" != "Hello"`, nil, false)
expect(t, `out = "Hello" != "World"`, nil, true)
// index operator
str := "abcdef"
strStr := `"abcdef"`
strLen := 6
for idx := 0; idx < strLen; idx++ {
expect(t, fmt.Sprintf("out = %s[%d]", strStr, idx), str[idx])
expect(t, fmt.Sprintf("out = %s[0 + %d]", strStr, idx), str[idx])
expect(t, fmt.Sprintf("out = %s[1 + %d - 1]", strStr, idx), str[idx])
expect(t, fmt.Sprintf("idx := %d; out = %s[idx]", idx, strStr), str[idx])
expect(t, fmt.Sprintf("out = %s[%d]", strStr, idx), nil, str[idx])
expect(t, fmt.Sprintf("out = %s[0 + %d]", strStr, idx), nil, str[idx])
expect(t, fmt.Sprintf("out = %s[1 + %d - 1]", strStr, idx), nil, str[idx])
expect(t, fmt.Sprintf("idx := %d; out = %s[idx]", idx, strStr), nil, str[idx])
}
expect(t, fmt.Sprintf("%s[%d]", strStr, -1), objects.UndefinedValue)
expect(t, fmt.Sprintf("%s[%d]", strStr, strLen), objects.UndefinedValue)
expect(t, fmt.Sprintf("%s[%d]", strStr, -1), nil, objects.UndefinedValue)
expect(t, fmt.Sprintf("%s[%d]", strStr, strLen), nil, objects.UndefinedValue)
// slice operator
for low := 0; low <= strLen; low++ {
expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, low, low), "")
expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, low, low), nil, "")
for high := low; high <= strLen; high++ {
expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, low, high), str[low:high])
expect(t, fmt.Sprintf("out = %s[0 + %d : 0 + %d]", strStr, low, high), str[low:high])
expect(t, fmt.Sprintf("out = %s[1 + %d - 1 : 1 + %d - 1]", strStr, low, high), str[low:high])
expect(t, fmt.Sprintf("out = %s[:%d]", strStr, high), str[:high])
expect(t, fmt.Sprintf("out = %s[%d:]", strStr, low), str[low:])
expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, low, high), nil, str[low:high])
expect(t, fmt.Sprintf("out = %s[0 + %d : 0 + %d]", strStr, low, high), nil, str[low:high])
expect(t, fmt.Sprintf("out = %s[1 + %d - 1 : 1 + %d - 1]", strStr, low, high), nil, str[low:high])
expect(t, fmt.Sprintf("out = %s[:%d]", strStr, high), nil, str[:high])
expect(t, fmt.Sprintf("out = %s[%d:]", strStr, low), nil, str[low:])
}
}
expect(t, fmt.Sprintf("out = %s[:]", strStr), str[:])
expect(t, fmt.Sprintf("out = %s[:]", strStr), str)
expect(t, fmt.Sprintf("out = %s[%d:]", strStr, -1), str)
expect(t, fmt.Sprintf("out = %s[:%d]", strStr, strLen+1), str)
expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, 2, 2), "")
expect(t, fmt.Sprintf("out = %s[:]", strStr), nil, str[:])
expect(t, fmt.Sprintf("out = %s[:]", strStr), nil, str)
expect(t, fmt.Sprintf("out = %s[%d:]", strStr, -1), nil, str)
expect(t, fmt.Sprintf("out = %s[:%d]", strStr, strLen+1), nil, str)
expect(t, fmt.Sprintf("out = %s[%d:%d]", strStr, 2, 2), nil, "")
expectError(t, fmt.Sprintf("%s[:%d]", strStr, -1), "invalid slice index")
expectError(t, fmt.Sprintf("%s[%d:]", strStr, strLen+1), "invalid slice index")
expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 0, -1), "invalid slice index")
expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 2, 1), "invalid slice index")
expectError(t, fmt.Sprintf("%s[:%d]", strStr, -1), nil, "invalid slice index")
expectError(t, fmt.Sprintf("%s[%d:]", strStr, strLen+1), nil, "invalid slice index")
expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 0, -1), nil, "invalid slice index")
expectError(t, fmt.Sprintf("%s[%d:%d]", strStr, 2, 1), nil, "invalid slice index")
// string concatenation with other types
expect(t, `out = "foo" + 1`, "foo1")
expect(t, `out = "foo" + 1`, nil, "foo1")
// Float.String() returns the smallest number of digits
// necessary such that ParseFloat will return f exactly.
expect(t, `out = "foo" + 1.0`, "foo1") // <- note '1' instead of '1.0'
expect(t, `out = "foo" + 1.5`, "foo1.5")
expect(t, `out = "foo" + true`, "footrue")
expect(t, `out = "foo" + 'X'`, "fooX")
expect(t, `out = "foo" + error(5)`, "fooerror: 5")
expect(t, `out = "foo" + undefined`, "foo<undefined>")
expect(t, `out = "foo" + [1,2,3]`, "foo[1, 2, 3]")
//expect(t, `out = "foo" + {a: 1, b: 2}`, "foo{a: 1, b: 2}") // TODO: commented because order of key is not consistent
expect(t, `out = "foo" + 1.0`, nil, "foo1") // <- note '1' instead of '1.0'
expect(t, `out = "foo" + 1.5`, nil, "foo1.5")
expect(t, `out = "foo" + true`, nil, "footrue")
expect(t, `out = "foo" + 'X'`, nil, "fooX")
expect(t, `out = "foo" + error(5)`, nil, "fooerror: 5")
expect(t, `out = "foo" + undefined`, nil, "foo<undefined>")
expect(t, `out = "foo" + [1,2,3]`, nil, "foo[1, 2, 3]")
// also works with "+=" operator
expect(t, `out = "foo"; out += 1.5`, "foo1.5")
expect(t, `out = "foo"; out += 1.5`, nil, "foo1.5")
// string concats works only when string is LHS
expectError(t, `1 + "foo"`, "invalid operation")
expectError(t, `1 + "foo"`, nil, "invalid operation")
expectError(t, `"foo" - "bar"`, "invalid operation")
expectError(t, `"foo" - "bar"`, nil, "invalid operation")
}

View file

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

View file

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

View file

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

View file

@ -13,13 +13,11 @@ import (
// Compiled is a compiled instance of the user script.
// Use Script.Compile() to create Compiled object.
type Compiled struct {
globalIndexes map[string]int // global symbol name to index
bytecode *compiler.Bytecode
globals []objects.Object
builtinFunctions []objects.Object
builtinModules map[string]objects.Object
maxAllocs int64
lock sync.RWMutex
globalIndexes map[string]int // global symbol name to index
bytecode *compiler.Bytecode
globals []objects.Object
maxAllocs int64
lock sync.RWMutex
}
// Run executes the compiled script in the virtual machine.
@ -27,7 +25,7 @@ func (c *Compiled) Run() error {
c.lock.Lock()
defer c.lock.Unlock()
v := runtime.NewVM(c.bytecode, c.globals, c.builtinFunctions, c.builtinModules, c.maxAllocs)
v := runtime.NewVM(c.bytecode, c.globals, c.maxAllocs)
return v.Run()
}
@ -37,7 +35,7 @@ func (c *Compiled) RunContext(ctx context.Context) (err error) {
c.lock.Lock()
defer c.lock.Unlock()
v := runtime.NewVM(c.bytecode, c.globals, c.builtinFunctions, c.builtinModules, c.maxAllocs)
v := runtime.NewVM(c.bytecode, c.globals, c.maxAllocs)
ch := make(chan error, 1)
@ -63,12 +61,10 @@ func (c *Compiled) Clone() *Compiled {
defer c.lock.Unlock()
clone := &Compiled{
globalIndexes: c.globalIndexes,
bytecode: c.bytecode,
globals: make([]objects.Object, len(c.globals)),
builtinFunctions: c.builtinFunctions,
builtinModules: c.builtinModules,
maxAllocs: c.maxAllocs,
globalIndexes: c.globalIndexes,
bytecode: c.bytecode,
globals: make([]objects.Object, len(c.globals)),
maxAllocs: c.maxAllocs,
}
// copy global objects

View file

@ -14,12 +14,11 @@ import (
// Script can simplify compilation and execution of embedded scripts.
type Script struct {
variables map[string]*Variable
builtinFuncs []objects.Object
builtinModules map[string]objects.Object
userModuleLoader compiler.ModuleLoader
importModules map[string]objects.Importable
input []byte
maxAllocs int64
maxConstObjects int
enableFileImport bool
}
// New creates a Script instance with an input script.
@ -59,33 +58,9 @@ func (s *Script) Remove(name string) bool {
return true
}
// SetBuiltinFunctions allows to define builtin functions.
func (s *Script) SetBuiltinFunctions(funcs []*objects.BuiltinFunction) {
if funcs != nil {
s.builtinFuncs = make([]objects.Object, len(funcs))
for idx, fn := range funcs {
s.builtinFuncs[idx] = fn
}
} else {
s.builtinFuncs = []objects.Object{}
}
}
// SetBuiltinModules allows to define builtin modules.
func (s *Script) SetBuiltinModules(modules map[string]*objects.ImmutableMap) {
if modules != nil {
s.builtinModules = make(map[string]objects.Object, len(modules))
for k, mod := range modules {
s.builtinModules[k] = mod
}
} else {
s.builtinModules = map[string]objects.Object{}
}
}
// SetUserModuleLoader sets the user module loader for the compiler.
func (s *Script) SetUserModuleLoader(loader compiler.ModuleLoader) {
s.userModuleLoader = loader
// SetImports sets import modules.
func (s *Script) SetImports(modules map[string]objects.Importable) {
s.importModules = modules
}
// SetMaxAllocs sets the maximum number of objects allocations during the run time.
@ -99,9 +74,15 @@ func (s *Script) SetMaxConstObjects(n int) {
s.maxConstObjects = n
}
// EnableFileImport enables or disables module loading from local files.
// Local file modules are disabled by default.
func (s *Script) EnableFileImport(enable bool) {
s.enableFileImport = enable
}
// Compile compiles the script with all the defined variables, and, returns Compiled object.
func (s *Script) Compile() (*Compiled, error) {
symbolTable, builtinModules, globals, err := s.prepCompile()
symbolTable, globals, err := s.prepCompile()
if err != nil {
return nil, err
}
@ -115,12 +96,8 @@ func (s *Script) Compile() (*Compiled, error) {
return nil, err
}
c := compiler.NewCompiler(srcFile, symbolTable, nil, builtinModules, nil)
if s.userModuleLoader != nil {
c.SetModuleLoader(s.userModuleLoader)
}
c := compiler.NewCompiler(srcFile, symbolTable, nil, s.importModules, nil)
c.EnableFileImport(s.enableFileImport)
if err := c.Compile(file); err != nil {
return nil, err
}
@ -150,12 +127,10 @@ func (s *Script) Compile() (*Compiled, error) {
}
return &Compiled{
globalIndexes: globalIndexes,
bytecode: bytecode,
globals: globals,
builtinFunctions: s.builtinFuncs,
builtinModules: s.builtinModules,
maxAllocs: s.maxAllocs,
globalIndexes: globalIndexes,
bytecode: bytecode,
globals: globals,
maxAllocs: s.maxAllocs,
}, nil
}
@ -184,36 +159,15 @@ func (s *Script) RunContext(ctx context.Context) (compiled *Compiled, err error)
return
}
func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, builtinModules map[string]bool, globals []objects.Object, err error) {
func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, globals []objects.Object, err error) {
var names []string
for name := range s.variables {
names = append(names, name)
}
symbolTable = compiler.NewSymbolTable()
if s.builtinFuncs == nil {
s.builtinFuncs = make([]objects.Object, len(objects.Builtins))
for idx, fn := range objects.Builtins {
s.builtinFuncs[idx] = &objects.BuiltinFunction{
Name: fn.Name,
Value: fn.Value,
}
}
}
if s.builtinModules == nil {
s.builtinModules = make(map[string]objects.Object)
}
for idx, fn := range s.builtinFuncs {
f := fn.(*objects.BuiltinFunction)
symbolTable.DefineBuiltin(idx, f.Name)
}
builtinModules = make(map[string]bool)
for name := range s.builtinModules {
builtinModules[name] = true
for idx, fn := range objects.Builtins {
symbolTable.DefineBuiltin(idx, fn.Name)
}
globals = make([]objects.Object, runtime.GlobalsSize)

View file

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

View file

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

View file

@ -1,13 +1,12 @@
package script_test
import (
"errors"
"math"
"testing"
"github.com/d5/tengo/assert"
"github.com/d5/tengo/objects"
"github.com/d5/tengo/script"
"github.com/d5/tengo/stdlib"
)
func TestScript_Add(t *testing.T) {
@ -52,71 +51,42 @@ func TestScript_Run(t *testing.T) {
compiledGet(t, c, "a", int64(5))
}
func TestScript_SetBuiltinFunctions(t *testing.T) {
s := script.New([]byte(`a := len([1, 2, 3])`))
func TestScript_BuiltinModules(t *testing.T) {
s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`))
s.SetImports(map[string]objects.Importable{"math": stdlib.BuiltinModules["math"]})
c, err := s.Run()
assert.NoError(t, err)
assert.NotNil(t, c)
compiledGet(t, c, "a", int64(3))
compiledGet(t, c, "a", 19.84)
s = script.New([]byte(`a := len([1, 2, 3])`))
s.SetBuiltinFunctions([]*objects.BuiltinFunction{&objects.Builtins[4]})
c, err = s.Run()
assert.NoError(t, err)
assert.NotNil(t, c)
compiledGet(t, c, "a", int64(3))
compiledGet(t, c, "a", 19.84)
s.SetBuiltinFunctions([]*objects.BuiltinFunction{&objects.Builtins[0]})
s.SetImports(map[string]objects.Importable{"os": &objects.BuiltinModule{Attrs: map[string]objects.Object{}}})
_, err = s.Run()
assert.Error(t, err)
s.SetBuiltinFunctions(nil)
s.SetImports(nil)
_, err = s.Run()
assert.Error(t, err)
s = script.New([]byte(`a := import("b")`))
s.SetUserModuleLoader(func(name string) ([]byte, error) {
if name == "b" {
return []byte(`export import("c")`), nil
} else if name == "c" {
return []byte("export len([1, 2, 3])"), nil
}
return nil, errors.New("module not found")
})
s.SetBuiltinFunctions([]*objects.BuiltinFunction{&objects.Builtins[4]})
c, err = s.Run()
assert.NoError(t, err)
assert.NotNil(t, c)
compiledGet(t, c, "a", int64(3))
}
func TestScript_SetBuiltinModules(t *testing.T) {
s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`))
s.SetBuiltinModules(map[string]*objects.ImmutableMap{
"math": objectPtr(&objects.ImmutableMap{
Value: map[string]objects.Object{
"abs": &objects.UserFunction{Name: "abs", Value: func(args ...objects.Object) (ret objects.Object, err error) {
v, _ := objects.ToFloat64(args[0])
return &objects.Float{Value: math.Abs(v)}, nil
}},
},
}),
})
func TestScript_SourceModules(t *testing.T) {
s := script.New([]byte(`
enum := import("enum")
a := enum.all([1,2,3], func(_, v) {
return v > 0
})
`))
s.SetImports(stdlib.GetModules("enum"))
c, err := s.Run()
assert.NoError(t, err)
assert.NotNil(t, c)
compiledGet(t, c, "a", 19.84)
compiledGet(t, c, "a", true)
c, err = s.Run()
assert.NoError(t, err)
assert.NotNil(t, c)
compiledGet(t, c, "a", 19.84)
s.SetBuiltinModules(map[string]*objects.ImmutableMap{"os": objectPtr(&objects.ImmutableMap{Value: map[string]objects.Object{}})})
_, err = s.Run()
assert.Error(t, err)
s.SetBuiltinModules(nil)
s.SetImports(nil)
_, err = s.Run()
assert.Error(t, err)
}
@ -154,7 +124,3 @@ func TestScript_SetMaxConstObjects(t *testing.T) {
_, err = s.Compile()
assert.NoError(t, err)
}
func objectPtr(o objects.Object) *objects.ImmutableMap {
return o.(*objects.ImmutableMap)
}

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 (
"fmt"
"github.com/d5/tengo"
"github.com/d5/tengo/objects"
)
func getPrintArgs(args ...Object) ([]interface{}, error) {
var printArgs []interface{}
l := 0
for _, arg := range args {
s, _ := ToString(arg)
slen := len(s)
if l+slen > tengo.MaxStringLen { // make sure length does not exceed the limit
return nil, ErrStringLimit
}
l += slen
printArgs = append(printArgs, s)
}
return printArgs, nil
var fmtModule = map[string]objects.Object{
"print": &objects.UserFunction{Name: "print", Value: fmtPrint},
"printf": &objects.UserFunction{Name: "printf", Value: fmtPrintf},
"println": &objects.UserFunction{Name: "println", Value: fmtPrintln},
"sprintf": &objects.UserFunction{Name: "sprintf", Value: fmtSprintf},
}
// print(args...)
func builtinPrint(args ...Object) (Object, error) {
func fmtPrint(args ...objects.Object) (ret objects.Object, err error) {
printArgs, err := getPrintArgs(args...)
if err != nil {
return nil, err
@ -35,29 +25,15 @@ func builtinPrint(args ...Object) (Object, error) {
return nil, nil
}
// println(args...)
func builtinPrintln(args ...Object) (Object, error) {
printArgs, err := getPrintArgs(args...)
if err != nil {
return nil, err
}
printArgs = append(printArgs, "\n")
_, _ = fmt.Print(printArgs...)
return nil, nil
}
// printf("format", args...)
func builtinPrintf(args ...Object) (Object, error) {
func fmtPrintf(args ...objects.Object) (ret objects.Object, err error) {
numArgs := len(args)
if numArgs == 0 {
return nil, ErrWrongNumArguments
return nil, objects.ErrWrongNumArguments
}
format, ok := args[0].(*String)
format, ok := args[0].(*objects.String)
if !ok {
return nil, ErrInvalidArgumentType{
return nil, objects.ErrInvalidArgumentType{
Name: "format",
Expected: "string",
Found: args[0].TypeName(),
@ -70,7 +46,7 @@ func builtinPrintf(args ...Object) (Object, error) {
formatArgs := make([]interface{}, numArgs-1, numArgs-1)
for idx, arg := range args[1:] {
formatArgs[idx] = ToInterface(arg)
formatArgs[idx] = objects.ToInterface(arg)
}
fmt.Printf(format.Value, formatArgs...)
@ -78,16 +54,27 @@ func builtinPrintf(args ...Object) (Object, error) {
return nil, nil
}
// sprintf("format", args...)
func builtinSprintf(args ...Object) (Object, error) {
numArgs := len(args)
if numArgs == 0 {
return nil, ErrWrongNumArguments
func fmtPrintln(args ...objects.Object) (ret objects.Object, err error) {
printArgs, err := getPrintArgs(args...)
if err != nil {
return nil, err
}
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 {
return nil, ErrInvalidArgumentType{
return nil, objects.ErrInvalidArgumentType{
Name: "format",
Expected: "string",
Found: args[0].TypeName(),
@ -99,14 +86,31 @@ func builtinSprintf(args ...Object) (Object, error) {
formatArgs := make([]interface{}, numArgs-1, numArgs-1)
for idx, arg := range args[1:] {
formatArgs[idx] = ToInterface(arg)
formatArgs[idx] = objects.ToInterface(arg)
}
s := fmt.Sprintf(format.Value, formatArgs...)
if len(s) > tengo.MaxStringLen {
return nil, ErrStringLimit
return nil, objects.ErrStringLimit
}
return &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
import "github.com/d5/tengo/objects"
//go:generate go run gensrcmods.go
// Modules contain the standard modules.
var Modules = map[string]*objects.ImmutableMap{
"math": &objects.ImmutableMap{Value: mathModule},
"os": &objects.ImmutableMap{Value: osModule},
"text": &objects.ImmutableMap{Value: textModule},
"times": &objects.ImmutableMap{Value: timesModule},
"rand": &objects.ImmutableMap{Value: randModule},
}
import "github.com/d5/tengo/objects"
// AllModuleNames returns a list of all default module names.
func AllModuleNames() []string {
var names []string
for name := range Modules {
for name := range BuiltinModules {
names = append(names, name)
}
for name := range SourceModules {
names = append(names, name)
}
return names
@ -22,12 +18,16 @@ func AllModuleNames() []string {
// GetModules returns the modules for the given names.
// Duplicate names and invalid names are ignore.
func GetModules(names ...string) map[string]*objects.ImmutableMap {
modules := make(map[string]*objects.ImmutableMap)
func GetModules(names ...string) map[string]objects.Importable {
modules := make(map[string]objects.Importable)
for _, name := range names {
if mod := Modules[name]; mod != nil {
if mod := BuiltinModules[name]; mod != nil {
modules[name] = mod
}
if mod := SourceModules[name]; mod != nil {
modules[name] = mod
}
}
return modules
}

View file

@ -18,12 +18,9 @@ type IMAP map[string]interface{}
func TestAllModuleNames(t *testing.T) {
names := stdlib.AllModuleNames()
if !assert.Equal(t, len(stdlib.Modules), len(names)) {
if !assert.Equal(t, len(stdlib.BuiltinModules)+len(stdlib.SourceModules), len(names)) {
return
}
for _, name := range names {
assert.NotNil(t, stdlib.Modules[name], "name: %s", name)
}
}
func TestModulesRun(t *testing.T) {
@ -100,7 +97,7 @@ func TestGetModules(t *testing.T) {
type callres struct {
t *testing.T
o objects.Object
o interface{}
e error
}
@ -109,29 +106,44 @@ func (c callres) call(funcName string, args ...interface{}) callres {
return c
}
imap, ok := c.o.(*objects.ImmutableMap)
if !ok {
return c
}
m, ok := imap.Value[funcName]
if !ok {
return callres{t: c.t, e: fmt.Errorf("function not found: %s", funcName)}
}
f, ok := m.(*objects.UserFunction)
if !ok {
return callres{t: c.t, e: fmt.Errorf("non-callable: %s", funcName)}
}
var oargs []objects.Object
for _, v := range args {
oargs = append(oargs, object(v))
}
res, err := f.Value(oargs...)
switch o := c.o.(type) {
case *objects.BuiltinModule:
m, ok := o.Attrs[funcName]
if !ok {
return callres{t: c.t, e: fmt.Errorf("function not found: %s", funcName)}
}
return callres{t: c.t, o: res, e: err}
f, ok := m.(*objects.UserFunction)
if !ok {
return callres{t: c.t, e: fmt.Errorf("non-callable: %s", funcName)}
}
res, err := f.Value(oargs...)
return callres{t: c.t, o: res, e: err}
case *objects.UserFunction:
res, err := o.Value(oargs...)
return callres{t: c.t, o: res, e: err}
case *objects.ImmutableMap:
m, ok := o.Value[funcName]
if !ok {
return callres{t: c.t, e: fmt.Errorf("function not found: %s", funcName)}
}
f, ok := m.(*objects.UserFunction)
if !ok {
return callres{t: c.t, e: fmt.Errorf("non-callable: %s", funcName)}
}
res, err := f.Value(oargs...)
return callres{t: c.t, o: res, e: err}
default:
panic(fmt.Errorf("unexpected object: %v (%T)", o, o))
}
}
func (c callres) expect(expected interface{}, msgAndArgs ...interface{}) bool {
@ -144,7 +156,7 @@ func (c callres) expectError() bool {
}
func module(t *testing.T, moduleName string) callres {
mod, ok := stdlib.Modules[moduleName]
mod, ok := stdlib.BuiltinModules[moduleName]
if !ok {
return callres{t: t, e: fmt.Errorf("module not found: %s", moduleName)}
}
@ -219,7 +231,7 @@ func object(v interface{}) objects.Object {
func expect(t *testing.T, input string, expected interface{}) {
s := script.New([]byte(input))
s.SetBuiltinModules(stdlib.Modules)
s.SetImports(stdlib.GetModules(stdlib.AllModuleNames()...))
c, err := s.Run()
assert.NoError(t, err)
assert.NotNil(t, c)