summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/d5/tengo/compiler/compiler.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/d5/tengo/compiler/compiler.go')
-rw-r--r--vendor/github.com/d5/tengo/compiler/compiler.go846
1 files changed, 0 insertions, 846 deletions
diff --git a/vendor/github.com/d5/tengo/compiler/compiler.go b/vendor/github.com/d5/tengo/compiler/compiler.go
deleted file mode 100644
index 8bde5dc9..00000000
--- a/vendor/github.com/d5/tengo/compiler/compiler.go
+++ /dev/null
@@ -1,846 +0,0 @@
-package compiler
-
-import (
- "fmt"
- "io"
- "io/ioutil"
- "path/filepath"
- "reflect"
- "strings"
-
- "github.com/d5/tengo"
- "github.com/d5/tengo/compiler/ast"
- "github.com/d5/tengo/compiler/source"
- "github.com/d5/tengo/compiler/token"
- "github.com/d5/tengo/objects"
-)
-
-// Compiler compiles the AST into a bytecode.
-type Compiler struct {
- file *source.File
- parent *Compiler
- modulePath string
- constants []objects.Object
- symbolTable *SymbolTable
- scopes []CompilationScope
- scopeIndex int
- modules *objects.ModuleMap
- compiledModules map[string]*objects.CompiledFunction
- allowFileImport bool
- loops []*Loop
- loopIndex int
- trace io.Writer
- indent int
-}
-
-// NewCompiler creates a Compiler.
-func NewCompiler(file *source.File, symbolTable *SymbolTable, constants []objects.Object, modules *objects.ModuleMap, trace io.Writer) *Compiler {
- mainScope := CompilationScope{
- symbolInit: make(map[string]bool),
- sourceMap: make(map[int]source.Pos),
- }
-
- // symbol table
- if symbolTable == nil {
- symbolTable = NewSymbolTable()
- }
-
- // add builtin functions to the symbol table
- for idx, fn := range objects.Builtins {
- symbolTable.DefineBuiltin(idx, fn.Name)
- }
-
- // builtin modules
- if modules == nil {
- modules = objects.NewModuleMap()
- }
-
- return &Compiler{
- file: file,
- symbolTable: symbolTable,
- constants: constants,
- scopes: []CompilationScope{mainScope},
- scopeIndex: 0,
- loopIndex: -1,
- trace: trace,
- modules: modules,
- compiledModules: make(map[string]*objects.CompiledFunction),
- }
-}
-
-// Compile compiles the AST node.
-func (c *Compiler) Compile(node ast.Node) error {
- if c.trace != nil {
- if node != nil {
- defer un(trace(c, fmt.Sprintf("%s (%s)", node.String(), reflect.TypeOf(node).Elem().Name())))
- } else {
- defer un(trace(c, "<nil>"))
- }
- }
-
- switch node := node.(type) {
- case *ast.File:
- for _, stmt := range node.Stmts {
- if err := c.Compile(stmt); err != nil {
- return err
- }
- }
-
- case *ast.ExprStmt:
- if err := c.Compile(node.Expr); err != nil {
- return err
- }
- c.emit(node, OpPop)
-
- case *ast.IncDecStmt:
- op := token.AddAssign
- if node.Token == token.Dec {
- op = token.SubAssign
- }
-
- return c.compileAssign(node, []ast.Expr{node.Expr}, []ast.Expr{&ast.IntLit{Value: 1}}, op)
-
- case *ast.ParenExpr:
- if err := c.Compile(node.Expr); err != nil {
- return err
- }
-
- case *ast.BinaryExpr:
- if node.Token == token.LAnd || node.Token == token.LOr {
- return c.compileLogical(node)
- }
-
- if node.Token == token.Less {
- if err := c.Compile(node.RHS); err != nil {
- return err
- }
-
- if err := c.Compile(node.LHS); err != nil {
- return err
- }
-
- c.emit(node, OpBinaryOp, int(token.Greater))
-
- return nil
- } else if node.Token == token.LessEq {
- if err := c.Compile(node.RHS); err != nil {
- return err
- }
- if err := c.Compile(node.LHS); err != nil {
- return err
- }
-
- c.emit(node, OpBinaryOp, int(token.GreaterEq))
-
- return nil
- }
-
- if err := c.Compile(node.LHS); err != nil {
- return err
- }
- if err := c.Compile(node.RHS); err != nil {
- return err
- }
-
- switch node.Token {
- case token.Add:
- c.emit(node, OpBinaryOp, int(token.Add))
- case token.Sub:
- c.emit(node, OpBinaryOp, int(token.Sub))
- case token.Mul:
- c.emit(node, OpBinaryOp, int(token.Mul))
- case token.Quo:
- c.emit(node, OpBinaryOp, int(token.Quo))
- case token.Rem:
- c.emit(node, OpBinaryOp, int(token.Rem))
- case token.Greater:
- c.emit(node, OpBinaryOp, int(token.Greater))
- case token.GreaterEq:
- c.emit(node, OpBinaryOp, int(token.GreaterEq))
- case token.Equal:
- c.emit(node, OpEqual)
- case token.NotEqual:
- c.emit(node, OpNotEqual)
- case token.And:
- c.emit(node, OpBinaryOp, int(token.And))
- case token.Or:
- c.emit(node, OpBinaryOp, int(token.Or))
- case token.Xor:
- c.emit(node, OpBinaryOp, int(token.Xor))
- case token.AndNot:
- c.emit(node, OpBinaryOp, int(token.AndNot))
- case token.Shl:
- c.emit(node, OpBinaryOp, int(token.Shl))
- case token.Shr:
- c.emit(node, OpBinaryOp, int(token.Shr))
- default:
- return c.errorf(node, "invalid binary operator: %s", node.Token.String())
- }
-
- case *ast.IntLit:
- c.emit(node, OpConstant, c.addConstant(&objects.Int{Value: node.Value}))
-
- case *ast.FloatLit:
- c.emit(node, OpConstant, c.addConstant(&objects.Float{Value: node.Value}))
-
- case *ast.BoolLit:
- if node.Value {
- c.emit(node, OpTrue)
- } else {
- c.emit(node, OpFalse)
- }
-
- case *ast.StringLit:
- if len(node.Value) > tengo.MaxStringLen {
- return c.error(node, objects.ErrStringLimit)
- }
-
- c.emit(node, OpConstant, c.addConstant(&objects.String{Value: node.Value}))
-
- case *ast.CharLit:
- c.emit(node, OpConstant, c.addConstant(&objects.Char{Value: node.Value}))
-
- case *ast.UndefinedLit:
- c.emit(node, OpNull)
-
- case *ast.UnaryExpr:
- if err := c.Compile(node.Expr); err != nil {
- return err
- }
-
- switch node.Token {
- case token.Not:
- c.emit(node, OpLNot)
- case token.Sub:
- c.emit(node, OpMinus)
- case token.Xor:
- c.emit(node, OpBComplement)
- case token.Add:
- // do nothing?
- default:
- return c.errorf(node, "invalid unary operator: %s", node.Token.String())
- }
-
- case *ast.IfStmt:
- // open new symbol table for the statement
- c.symbolTable = c.symbolTable.Fork(true)
- defer func() {
- c.symbolTable = c.symbolTable.Parent(false)
- }()
-
- if node.Init != nil {
- if err := c.Compile(node.Init); err != nil {
- return err
- }
- }
-
- if err := c.Compile(node.Cond); err != nil {
- return err
- }
-
- // first jump placeholder
- jumpPos1 := c.emit(node, OpJumpFalsy, 0)
-
- if err := c.Compile(node.Body); err != nil {
- return err
- }
-
- if node.Else != nil {
- // second jump placeholder
- jumpPos2 := c.emit(node, OpJump, 0)
-
- // update first jump offset
- curPos := len(c.currentInstructions())
- c.changeOperand(jumpPos1, curPos)
-
- if err := c.Compile(node.Else); err != nil {
- return err
- }
-
- // update second jump offset
- curPos = len(c.currentInstructions())
- c.changeOperand(jumpPos2, curPos)
- } else {
- // update first jump offset
- curPos := len(c.currentInstructions())
- c.changeOperand(jumpPos1, curPos)
- }
-
- case *ast.ForStmt:
- return c.compileForStmt(node)
-
- case *ast.ForInStmt:
- return c.compileForInStmt(node)
-
- case *ast.BranchStmt:
- if node.Token == token.Break {
- curLoop := c.currentLoop()
- if curLoop == nil {
- return c.errorf(node, "break not allowed outside loop")
- }
- pos := c.emit(node, OpJump, 0)
- curLoop.Breaks = append(curLoop.Breaks, pos)
- } else if node.Token == token.Continue {
- curLoop := c.currentLoop()
- if curLoop == nil {
- return c.errorf(node, "continue not allowed outside loop")
- }
- pos := c.emit(node, OpJump, 0)
- curLoop.Continues = append(curLoop.Continues, pos)
- } else {
- panic(fmt.Errorf("invalid branch statement: %s", node.Token.String()))
- }
-
- case *ast.BlockStmt:
- if len(node.Stmts) == 0 {
- return nil
- }
-
- c.symbolTable = c.symbolTable.Fork(true)
- defer func() {
- c.symbolTable = c.symbolTable.Parent(false)
- }()
-
- for _, stmt := range node.Stmts {
- if err := c.Compile(stmt); err != nil {
- return err
- }
- }
-
- case *ast.AssignStmt:
- if err := c.compileAssign(node, node.LHS, node.RHS, node.Token); err != nil {
- return err
- }
-
- case *ast.Ident:
- symbol, _, ok := c.symbolTable.Resolve(node.Name)
- if !ok {
- return c.errorf(node, "unresolved reference '%s'", node.Name)
- }
-
- switch symbol.Scope {
- case ScopeGlobal:
- c.emit(node, OpGetGlobal, symbol.Index)
- case ScopeLocal:
- c.emit(node, OpGetLocal, symbol.Index)
- case ScopeBuiltin:
- c.emit(node, OpGetBuiltin, symbol.Index)
- case ScopeFree:
- c.emit(node, OpGetFree, symbol.Index)
- }
-
- case *ast.ArrayLit:
- for _, elem := range node.Elements {
- if err := c.Compile(elem); err != nil {
- return err
- }
- }
-
- c.emit(node, OpArray, len(node.Elements))
-
- case *ast.MapLit:
- for _, elt := range node.Elements {
- // key
- if len(elt.Key) > tengo.MaxStringLen {
- return c.error(node, objects.ErrStringLimit)
- }
- c.emit(node, OpConstant, c.addConstant(&objects.String{Value: elt.Key}))
-
- // value
- if err := c.Compile(elt.Value); err != nil {
- return err
- }
- }
-
- c.emit(node, OpMap, len(node.Elements)*2)
-
- case *ast.SelectorExpr: // selector on RHS side
- if err := c.Compile(node.Expr); err != nil {
- return err
- }
-
- if err := c.Compile(node.Sel); err != nil {
- return err
- }
-
- c.emit(node, OpIndex)
-
- case *ast.IndexExpr:
- if err := c.Compile(node.Expr); err != nil {
- return err
- }
-
- if err := c.Compile(node.Index); err != nil {
- return err
- }
-
- c.emit(node, OpIndex)
-
- case *ast.SliceExpr:
- if err := c.Compile(node.Expr); err != nil {
- return err
- }
-
- if node.Low != nil {
- if err := c.Compile(node.Low); err != nil {
- return err
- }
- } else {
- c.emit(node, OpNull)
- }
-
- if node.High != nil {
- if err := c.Compile(node.High); err != nil {
- return err
- }
- } else {
- c.emit(node, OpNull)
- }
-
- c.emit(node, OpSliceIndex)
-
- case *ast.FuncLit:
- c.enterScope()
-
- for _, p := range node.Type.Params.List {
- s := c.symbolTable.Define(p.Name)
-
- // function arguments is not assigned directly.
- s.LocalAssigned = true
- }
-
- if err := c.Compile(node.Body); err != nil {
- return err
- }
-
- // code optimization
- c.optimizeFunc(node)
-
- freeSymbols := c.symbolTable.FreeSymbols()
- numLocals := c.symbolTable.MaxSymbols()
- instructions, sourceMap := c.leaveScope()
-
- for _, s := range freeSymbols {
- switch s.Scope {
- case ScopeLocal:
- if !s.LocalAssigned {
- // Here, the closure is capturing a local variable that's not yet assigned its value.
- // One example is a local recursive function:
- //
- // func() {
- // foo := func(x) {
- // // ..
- // return foo(x-1)
- // }
- // }
- //
- // which translate into
- //
- // 0000 GETL 0
- // 0002 CLOSURE ? 1
- // 0006 DEFL 0
- //
- // . So the local variable (0) is being captured before it's assigned the value.
- //
- // Solution is to transform the code into something like this:
- //
- // func() {
- // foo := undefined
- // foo = func(x) {
- // // ..
- // return foo(x-1)
- // }
- // }
- //
- // that is equivalent to
- //
- // 0000 NULL
- // 0001 DEFL 0
- // 0003 GETL 0
- // 0005 CLOSURE ? 1
- // 0009 SETL 0
- //
-
- c.emit(node, OpNull)
- c.emit(node, OpDefineLocal, s.Index)
-
- s.LocalAssigned = true
- }
-
- c.emit(node, OpGetLocalPtr, s.Index)
- case ScopeFree:
- c.emit(node, OpGetFreePtr, s.Index)
- }
- }
-
- compiledFunction := &objects.CompiledFunction{
- Instructions: instructions,
- NumLocals: numLocals,
- NumParameters: len(node.Type.Params.List),
- VarArgs: node.Type.Params.VarArgs,
- SourceMap: sourceMap,
- }
-
- if len(freeSymbols) > 0 {
- c.emit(node, OpClosure, c.addConstant(compiledFunction), len(freeSymbols))
- } else {
- c.emit(node, OpConstant, c.addConstant(compiledFunction))
- }
-
- case *ast.ReturnStmt:
- if c.symbolTable.Parent(true) == nil {
- // outside the function
- return c.errorf(node, "return not allowed outside function")
- }
-
- if node.Result == nil {
- c.emit(node, OpReturn, 0)
- } else {
- if err := c.Compile(node.Result); err != nil {
- return err
- }
-
- c.emit(node, OpReturn, 1)
- }
-
- case *ast.CallExpr:
- if err := c.Compile(node.Func); err != nil {
- return err
- }
-
- for _, arg := range node.Args {
- if err := c.Compile(arg); err != nil {
- return err
- }
- }
-
- c.emit(node, OpCall, len(node.Args))
-
- case *ast.ImportExpr:
- if node.ModuleName == "" {
- return c.errorf(node, "empty module name")
- }
-
- if mod := c.modules.Get(node.ModuleName); mod != nil {
- v, err := mod.Import(node.ModuleName)
- if err != nil {
- return err
- }
-
- 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:
- // export statement must be in top-level scope
- if c.scopeIndex != 0 {
- return c.errorf(node, "export not allowed inside function")
- }
-
- // export statement is simply ignore when compiling non-module code
- if c.parent == nil {
- break
- }
-
- if err := c.Compile(node.Result); err != nil {
- return err
- }
-
- c.emit(node, OpImmutable)
- c.emit(node, OpReturn, 1)
-
- case *ast.ErrorExpr:
- if err := c.Compile(node.Expr); err != nil {
- return err
- }
-
- c.emit(node, OpError)
-
- case *ast.ImmutableExpr:
- if err := c.Compile(node.Expr); err != nil {
- return err
- }
-
- c.emit(node, OpImmutable)
-
- case *ast.CondExpr:
- if err := c.Compile(node.Cond); err != nil {
- return err
- }
-
- // first jump placeholder
- jumpPos1 := c.emit(node, OpJumpFalsy, 0)
-
- if err := c.Compile(node.True); err != nil {
- return err
- }
-
- // second jump placeholder
- jumpPos2 := c.emit(node, OpJump, 0)
-
- // update first jump offset
- curPos := len(c.currentInstructions())
- c.changeOperand(jumpPos1, curPos)
-
- if err := c.Compile(node.False); err != nil {
- return err
- }
-
- // update second jump offset
- curPos = len(c.currentInstructions())
- c.changeOperand(jumpPos2, curPos)
- }
-
- return nil
-}
-
-// Bytecode returns a compiled bytecode.
-func (c *Compiler) Bytecode() *Bytecode {
- return &Bytecode{
- FileSet: c.file.Set(),
- MainFunction: &objects.CompiledFunction{
- Instructions: c.currentInstructions(),
- SourceMap: c.currentSourceMap(),
- },
- Constants: c.constants,
- }
-}
-
-// 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, modulePath string, symbolTable *SymbolTable) *Compiler {
- child := NewCompiler(file, symbolTable, nil, c.modules, c.trace)
- child.modulePath = modulePath // module file path
- child.parent = c // parent to set to current compiler
-
- return child
-}
-
-func (c *Compiler) error(node ast.Node, err error) error {
- return &Error{
- fileSet: c.file.Set(),
- node: node,
- error: err,
- }
-}
-
-func (c *Compiler) errorf(node ast.Node, format string, args ...interface{}) error {
- return &Error{
- fileSet: c.file.Set(),
- node: node,
- error: fmt.Errorf(format, args...),
- }
-}
-
-func (c *Compiler) addConstant(o objects.Object) int {
- if c.parent != nil {
- // module compilers will use their parent's constants array
- return c.parent.addConstant(o)
- }
-
- c.constants = append(c.constants, o)
-
- if c.trace != nil {
- c.printTrace(fmt.Sprintf("CONST %04d %s", len(c.constants)-1, o))
- }
-
- return len(c.constants) - 1
-}
-
-func (c *Compiler) addInstruction(b []byte) int {
- posNewIns := len(c.currentInstructions())
-
- c.scopes[c.scopeIndex].instructions = append(c.currentInstructions(), b...)
-
- return posNewIns
-}
-
-func (c *Compiler) replaceInstruction(pos int, inst []byte) {
- copy(c.currentInstructions()[pos:], inst)
-
- if c.trace != nil {
- c.printTrace(fmt.Sprintf("REPLC %s",
- FormatInstructions(c.scopes[c.scopeIndex].instructions[pos:], pos)[0]))
- }
-}
-
-func (c *Compiler) changeOperand(opPos int, operand ...int) {
- op := Opcode(c.currentInstructions()[opPos])
- inst := MakeInstruction(op, operand...)
-
- c.replaceInstruction(opPos, inst)
-}
-
-// optimizeFunc performs some code-level optimization for the current function instructions
-// it removes unreachable (dead code) instructions and adds "returns" instruction if needed.
-func (c *Compiler) optimizeFunc(node ast.Node) {
- // any instructions between RETURN and the function end
- // or instructions between RETURN and jump target position
- // are considered as unreachable.
-
- // pass 1. identify all jump destinations
- dsts := make(map[int]bool)
- iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool {
- switch opcode {
- case OpJump, OpJumpFalsy, OpAndJump, OpOrJump:
- dsts[operands[0]] = true
- }
-
- return true
- })
-
- var newInsts []byte
-
- // pass 2. eliminate dead code
- posMap := make(map[int]int) // old position to new position
- var dstIdx int
- var deadCode bool
- iterateInstructions(c.scopes[c.scopeIndex].instructions, func(pos int, opcode Opcode, operands []int) bool {
- switch {
- case opcode == OpReturn:
- if deadCode {
- return true
- }
- deadCode = true
- case dsts[pos]:
- dstIdx++
- deadCode = false
- case deadCode:
- return true
- }
-
- posMap[pos] = len(newInsts)
- newInsts = append(newInsts, MakeInstruction(opcode, operands...)...)
- return true
- })
-
- // pass 3. update jump positions
- var lastOp Opcode
- var appendReturn bool
- endPos := len(c.scopes[c.scopeIndex].instructions)
- iterateInstructions(newInsts, func(pos int, opcode Opcode, operands []int) bool {
- switch opcode {
- case OpJump, OpJumpFalsy, OpAndJump, OpOrJump:
- newDst, ok := posMap[operands[0]]
- if ok {
- copy(newInsts[pos:], MakeInstruction(opcode, newDst))
- } else if endPos == operands[0] {
- // there's a jump instruction that jumps to the end of function
- // compiler should append "return".
- appendReturn = true
- } else {
- panic(fmt.Errorf("invalid jump position: %d", newDst))
- }
- }
- lastOp = opcode
- return true
- })
- if lastOp != OpReturn {
- appendReturn = true
- }
-
- // pass 4. update source map
- newSourceMap := make(map[int]source.Pos)
- for pos, srcPos := range c.scopes[c.scopeIndex].sourceMap {
- newPos, ok := posMap[pos]
- if ok {
- newSourceMap[newPos] = srcPos
- }
- }
-
- c.scopes[c.scopeIndex].instructions = newInsts
- c.scopes[c.scopeIndex].sourceMap = newSourceMap
-
- // append "return"
- if appendReturn {
- c.emit(node, OpReturn, 0)
- }
-}
-
-func (c *Compiler) emit(node ast.Node, opcode Opcode, operands ...int) int {
- filePos := source.NoPos
- if node != nil {
- filePos = node.Pos()
- }
-
- inst := MakeInstruction(opcode, operands...)
- pos := c.addInstruction(inst)
- c.scopes[c.scopeIndex].sourceMap[pos] = filePos
-
- if c.trace != nil {
- c.printTrace(fmt.Sprintf("EMIT %s",
- FormatInstructions(c.scopes[c.scopeIndex].instructions[pos:], pos)[0]))
- }
-
- return pos
-}
-
-func (c *Compiler) printTrace(a ...interface{}) {
- const (
- dots = ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . "
- n = len(dots)
- )
-
- i := 2 * c.indent
- for i > n {
- _, _ = fmt.Fprint(c.trace, dots)
- i -= n
- }
- _, _ = fmt.Fprint(c.trace, dots[0:i])
- _, _ = fmt.Fprintln(c.trace, a...)
-}
-
-func trace(c *Compiler, msg string) *Compiler {
- c.printTrace(msg, "{")
- c.indent++
-
- return c
-}
-
-func un(c *Compiler) {
- c.indent--
- c.printTrace("}")
-}