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:
parent
052ae5906b
commit
61890b15cb
74 changed files with 1625 additions and 1725 deletions
5
Makefile
5
Makefile
|
@ -1,10 +1,13 @@
|
|||
vet:
|
||||
go vet ./...
|
||||
|
||||
generate:
|
||||
go generate ./...
|
||||
|
||||
lint:
|
||||
golint -set_exit_status ./...
|
||||
|
||||
test: vet lint
|
||||
test: generate vet lint
|
||||
go test -race -cover ./...
|
||||
|
||||
fmt:
|
||||
|
|
88
cli/cli.go
88
cli/cli.go
|
@ -23,33 +23,28 @@ const (
|
|||
replPrompt = ">> "
|
||||
)
|
||||
|
||||
//Options represent REPL options
|
||||
// Options represent CLI options
|
||||
type Options struct {
|
||||
//Compile output file
|
||||
// Compile output file
|
||||
CompileOutput string
|
||||
|
||||
//Show help flag
|
||||
// Show help flag
|
||||
ShowHelp bool
|
||||
|
||||
//Show version flag
|
||||
// Show version flag
|
||||
ShowVersion bool
|
||||
|
||||
//Input file
|
||||
// Input file
|
||||
InputFile string
|
||||
|
||||
//Version
|
||||
// Version
|
||||
Version string
|
||||
|
||||
//Builtin modules
|
||||
BuiltinModules map[string]objects.Object
|
||||
// Import modules
|
||||
Modules map[string]objects.Importable
|
||||
}
|
||||
|
||||
var (
|
||||
bm map[string]bool
|
||||
builtinModules map[string]objects.Object
|
||||
)
|
||||
|
||||
//Run REPL
|
||||
// Run CLI
|
||||
func Run(options *Options) {
|
||||
if options.ShowHelp {
|
||||
doHelp()
|
||||
|
@ -59,15 +54,9 @@ func Run(options *Options) {
|
|||
return
|
||||
}
|
||||
|
||||
builtinModules = options.BuiltinModules
|
||||
bm = make(map[string]bool, len(builtinModules))
|
||||
for k := range builtinModules {
|
||||
bm[k] = true
|
||||
}
|
||||
|
||||
if options.InputFile == "" {
|
||||
// REPL
|
||||
runREPL(os.Stdin, os.Stdout)
|
||||
runREPL(options.Modules, os.Stdin, os.Stdout)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -78,12 +67,12 @@ func Run(options *Options) {
|
|||
}
|
||||
|
||||
if options.CompileOutput != "" {
|
||||
if err := compileOnly(inputData, options.InputFile, options.CompileOutput); err != nil {
|
||||
if err := compileOnly(options.Modules, inputData, options.InputFile, options.CompileOutput); err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
} else if filepath.Ext(options.InputFile) == sourceFileExt {
|
||||
if err := compileAndRun(inputData, options.InputFile); err != nil {
|
||||
if err := compileAndRun(options.Modules, inputData, options.InputFile); err != nil {
|
||||
_, _ = fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
@ -127,8 +116,8 @@ func doHelp() {
|
|||
fmt.Println()
|
||||
}
|
||||
|
||||
func compileOnly(data []byte, inputFile, outputFile string) (err error) {
|
||||
bytecode, err := compileSrc(data, filepath.Base(inputFile))
|
||||
func compileOnly(modules map[string]objects.Importable, data []byte, inputFile, outputFile string) (err error) {
|
||||
bytecode, err := compileSrc(modules, data, filepath.Base(inputFile))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -159,13 +148,13 @@ func compileOnly(data []byte, inputFile, outputFile string) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func compileAndRun(data []byte, inputFile string) (err error) {
|
||||
bytecode, err := compileSrc(data, filepath.Base(inputFile))
|
||||
func compileAndRun(modules map[string]objects.Importable, data []byte, inputFile string) (err error) {
|
||||
bytecode, err := compileSrc(modules, data, filepath.Base(inputFile))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
machine := runtime.NewVM(bytecode, nil, nil, builtinModules, -1)
|
||||
machine := runtime.NewVM(bytecode, nil, -1)
|
||||
|
||||
err = machine.Run()
|
||||
if err != nil {
|
||||
|
@ -182,7 +171,7 @@ func runCompiled(data []byte) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
machine := runtime.NewVM(bytecode, nil, nil, builtinModules, -1)
|
||||
machine := runtime.NewVM(bytecode, nil, -1)
|
||||
|
||||
err = machine.Run()
|
||||
if err != nil {
|
||||
|
@ -192,7 +181,7 @@ func runCompiled(data []byte) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func runREPL(in io.Reader, out io.Writer) {
|
||||
func runREPL(modules map[string]objects.Importable, in io.Reader, out io.Writer) {
|
||||
stdin := bufio.NewScanner(in)
|
||||
|
||||
fileSet := source.NewFileSet()
|
||||
|
@ -203,6 +192,28 @@ func runREPL(in io.Reader, out io.Writer) {
|
|||
symbolTable.DefineBuiltin(idx, fn.Name)
|
||||
}
|
||||
|
||||
// embed println function
|
||||
symbol := symbolTable.Define("__repl_println__")
|
||||
globals[symbol.Index] = &objects.UserFunction{
|
||||
Name: "println",
|
||||
Value: func(args ...objects.Object) (ret objects.Object, err error) {
|
||||
var printArgs []interface{}
|
||||
for _, arg := range args {
|
||||
if _, isUndefined := arg.(*objects.Undefined); isUndefined {
|
||||
printArgs = append(printArgs, "<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,
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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"] = {
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -119,68 +119,40 @@ Users can add and use a custom user type in Tengo code by implementing [Object](
|
|||
|
||||
To securely compile and execute _potentially_ unsafe script code, you can use the following Script functions.
|
||||
|
||||
#### Script.SetBuiltinFunctions(funcs []*objects.BuiltinFunction)
|
||||
#### Script.SetImports(modules map[string]objects.Importable)
|
||||
|
||||
SetBuiltinFunctions resets all builtin functions in the compiler to the ones provided in the input parameter. Compiler will report a compile-time error if the a function not set is referenced. Passing `nil` will disable all builtin functions. All builtin functions **are included by default** unless `SetBuiltinFunctions` is called.
|
||||
|
||||
```golang
|
||||
s := script.New([]byte(`print([1, 2, 3])`))
|
||||
|
||||
_, err := s.Run() // prints [1, 2, 3]
|
||||
|
||||
s.SetBuiltinFunctions(nil)
|
||||
|
||||
_, err := s.Run() // compile error
|
||||
|
||||
s.SetBuiltinFunctions([]*objects.BuiltinFunction{&objects.Builtins[0]})
|
||||
|
||||
_, err := s.Run() // prints [1, 2, 3]
|
||||
```
|
||||
|
||||
#### Script.SetBuiltinModules(modules map[string]*objects.ImmutableMap)
|
||||
|
||||
SetBuiltinModules adds builtin modules provided in the input parameter. This can be used to add [standard library](https://github.com/d5/tengo/blob/master/docs/stdlib.md) modules into the compiler and VM. Compiler will report a compile-time error if the code tries to import a module that hasn't been included. Passing `nil` will disable all builtin modules. No standard library modules are included by default unless `SetBuiltinModules` is called.
|
||||
SetImports sets the import modules with corresponding names. Script **does not** include any modules by default. You can use this function to include the [Standard Library](https://github.com/d5/tengo/blob/master/docs/stdlib.md).
|
||||
|
||||
```golang
|
||||
s := script.New([]byte(`math := import("math"); a := math.abs(-19.84)`))
|
||||
|
||||
_, err := s.Run() // compile error
|
||||
|
||||
s.SetBuiltinModules(stdlib.Modules)
|
||||
|
||||
_, err := s.Run() // a = 19.84
|
||||
|
||||
s.SetBuiltinModules(nil)
|
||||
|
||||
_, err := s.Run() // compile error
|
||||
|
||||
s.SetBuiltinModules(map[string]*objects.ImmutableMap{"math": stdlib.Modules["math"]})
|
||||
|
||||
_, err := s.Run() // a = 19.84
|
||||
s.SetImports(map[string]objects.Importable{
|
||||
"math": stdlib.BuiltinModules["math"],
|
||||
})
|
||||
// or
|
||||
s.SetImports(stdlib.GetModules("math"))
|
||||
// or, to include all stdlib at once
|
||||
s.SetImports(stdlib.GetModules(stdlib.AllModuleNames()...))
|
||||
```
|
||||
|
||||
#### Script.SetUserModuleLoader(loader compiler.ModuleLoader)
|
||||
|
||||
SetUserModuleLoader replaces the default user-module loader of the compiler, which tries to read the source from a local file.
|
||||
You can also include Tengo's written module using `objects.SourceModule` (which implements `objects.Importable`).
|
||||
|
||||
```golang
|
||||
s := script.New([]byte(`math := import("mod1"); a := math.foo()`))
|
||||
|
||||
s.SetUserModuleLoader(func(moduleName string) ([]byte, error) {
|
||||
if moduleName == "mod1" {
|
||||
return []byte(`foo := func() { return 5 }`), nil
|
||||
}
|
||||
s := script.New([]byte(`double := import("double"); a := double(20)`))
|
||||
|
||||
return nil, errors.New("module not found")
|
||||
s.SetImports(map[string]objects.Importable{
|
||||
"double": &objects.SourceModule{Src: []byte(`export func(x) { return x * 2 }`)},
|
||||
})
|
||||
```
|
||||
|
||||
Note that when a script is being added to another script as a module (via `Script.AddModule`), it does not inherit the module loader from the main script.
|
||||
|
||||
#### Script.SetMaxAllocs(n int64)
|
||||
|
||||
SetMaxAllocs sets the maximum number of object allocations. Note this is a cumulative metric that tracks only the object creations. Set this to a negative number (e.g. `-1`) if you don't need to limit the number of allocations.
|
||||
|
||||
#### Script.EnableFileImport(enable bool)
|
||||
|
||||
EnableFileImport enables or disables module loading from the local files. It's disabled by default.
|
||||
|
||||
#### tengo.MaxStringLen
|
||||
|
||||
|
|
12
docs/stdlib-fmt.md
Normal file
12
docs/stdlib-fmt.md
Normal 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
10
docs/stdlib-json.md
Normal 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.
|
|
@ -4,4 +4,6 @@
|
|||
- [text](https://github.com/d5/tengo/blob/master/docs/stdlib-text.md): regular expressions, string conversion, and manipulation
|
||||
- [math](https://github.com/d5/tengo/blob/master/docs/stdlib-math.md): mathematical constants and functions
|
||||
- [times](https://github.com/d5/tengo/blob/master/docs/stdlib-times.md): time-related functions
|
||||
- [rand](https://github.com/d5/tengo/blob/master/docs/stdlib-rand.md): random functions
|
||||
- [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
|
|
@ -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:
|
||||
|
|
|
@ -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
16
objects/builtin_module.go
Normal 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
|
||||
}
|
|
@ -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()...)
|
||||
}
|
||||
|
|
|
@ -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
7
objects/importable.go
Normal 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
11
objects/source_module.go
Normal 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
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
13
runtime/vm_source_modules_test.go
Normal file
13
runtime/vm_source_modules_test.go
Normal 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)
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
14
stdlib/builtin_modules.go
Normal 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
7
stdlib/enum_test.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package stdlib_test
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEnum(t *testing.T) {
|
||||
|
||||
}
|
|
@ -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
11
stdlib/fmt_test.go
Normal 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
54
stdlib/gensrcmods.go
Normal 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
69
stdlib/json.go
Normal 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
33
stdlib/json_test.go
Normal 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
50
stdlib/source_modules.go
Normal 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
40
stdlib/srcmod_enum.tengo
Normal 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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue