package vm import ( "bytes" "fmt" "io" "reflect" "regexp" "strings" "text/tabwriter" "github.com/expr-lang/expr/ast" "github.com/expr-lang/expr/builtin" "github.com/expr-lang/expr/file" "github.com/expr-lang/expr/vm/runtime" ) // Program represents a compiled expression. type Program struct { Bytecode []Opcode Arguments []int Constants []any source file.Source node ast.Node locations []file.Location variables int functions []Function debugInfo map[string]string span *Span } // NewProgram returns a new Program. It's used by the compiler. func NewProgram( source file.Source, node ast.Node, locations []file.Location, variables int, constants []any, bytecode []Opcode, arguments []int, functions []Function, debugInfo map[string]string, span *Span, ) *Program { return &Program{ source: source, node: node, locations: locations, variables: variables, Constants: constants, Bytecode: bytecode, Arguments: arguments, functions: functions, debugInfo: debugInfo, span: span, } } // Source returns origin file.Source. func (program *Program) Source() file.Source { return program.source } // Node returns origin ast.Node. func (program *Program) Node() ast.Node { return program.node } // Locations returns a slice of bytecode's locations. func (program *Program) Locations() []file.Location { return program.locations } // Disassemble returns opcodes as a string. func (program *Program) Disassemble() string { var buf bytes.Buffer w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0) program.DisassembleWriter(w) _ = w.Flush() return buf.String() } // DisassembleWriter takes a writer and writes opcodes to it. func (program *Program) DisassembleWriter(w io.Writer) { ip := 0 for ip < len(program.Bytecode) { pp := ip op := program.Bytecode[ip] arg := program.Arguments[ip] ip += 1 code := func(label string) { _, _ = fmt.Fprintf(w, "%v\t%v\n", pp, label) } jump := func(label string) { _, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t(%v)\n", pp, label, arg, ip+arg) } jumpBack := func(label string) { _, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t(%v)\n", pp, label, arg, ip-arg) } argument := func(label string) { _, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\n", pp, label, arg) } argumentWithInfo := func(label string, prefix string) { _, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, label, arg, program.debugInfo[fmt.Sprintf("%s_%d", prefix, arg)]) } constant := func(label string) { var c any if arg < len(program.Constants) { c = program.Constants[arg] } else { c = "out of range" } if r, ok := c.(*regexp.Regexp); ok { c = r.String() } if field, ok := c.(*runtime.Field); ok { c = fmt.Sprintf("{%v %v}", strings.Join(field.Path, "."), field.Index) } if method, ok := c.(*runtime.Method); ok { c = fmt.Sprintf("{%v %v}", method.Name, method.Index) } _, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, label, arg, c) } builtinArg := func(label string) { _, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, label, arg, builtin.Builtins[arg].Name) } switch op { case OpInvalid: code("OpInvalid") case OpPush: constant("OpPush") case OpInt: argument("OpInt") case OpPop: code("OpPop") case OpStore: argumentWithInfo("OpStore", "var") case OpLoadVar: argumentWithInfo("OpLoadVar", "var") case OpLoadConst: constant("OpLoadConst") case OpLoadField: constant("OpLoadField") case OpLoadFast: constant("OpLoadFast") case OpLoadMethod: constant("OpLoadMethod") case OpLoadFunc: argumentWithInfo("OpLoadFunc", "func") case OpLoadEnv: code("OpLoadEnv") case OpFetch: code("OpFetch") case OpFetchField: constant("OpFetchField") case OpMethod: constant("OpMethod") case OpTrue: code("OpTrue") case OpFalse: code("OpFalse") case OpNil: code("OpNil") case OpNegate: code("OpNegate") case OpNot: code("OpNot") case OpEqual: code("OpEqual") case OpEqualInt: code("OpEqualInt") case OpEqualString: code("OpEqualString") case OpJump: jump("OpJump") case OpJumpIfTrue: jump("OpJumpIfTrue") case OpJumpIfFalse: jump("OpJumpIfFalse") case OpJumpIfNil: jump("OpJumpIfNil") case OpJumpIfNotNil: jump("OpJumpIfNotNil") case OpJumpIfEnd: jump("OpJumpIfEnd") case OpJumpBackward: jumpBack("OpJumpBackward") case OpIn: code("OpIn") case OpLess: code("OpLess") case OpMore: code("OpMore") case OpLessOrEqual: code("OpLessOrEqual") case OpMoreOrEqual: code("OpMoreOrEqual") case OpAdd: code("OpAdd") case OpSubtract: code("OpSubtract") case OpMultiply: code("OpMultiply") case OpDivide: code("OpDivide") case OpModulo: code("OpModulo") case OpExponent: code("OpExponent") case OpRange: code("OpRange") case OpMatches: code("OpMatches") case OpMatchesConst: constant("OpMatchesConst") case OpContains: code("OpContains") case OpStartsWith: code("OpStartsWith") case OpEndsWith: code("OpEndsWith") case OpSlice: code("OpSlice") case OpCall: argument("OpCall") case OpCall0: argumentWithInfo("OpCall0", "func") case OpCall1: argumentWithInfo("OpCall1", "func") case OpCall2: argumentWithInfo("OpCall2", "func") case OpCall3: argumentWithInfo("OpCall3", "func") case OpCallN: argument("OpCallN") case OpCallFast: argument("OpCallFast") case OpCallSafe: argument("OpCallSafe") case OpCallTyped: signature := reflect.TypeOf(FuncTypes[arg]).Elem().String() _, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, "OpCallTyped", arg, signature) case OpCallBuiltin1: builtinArg("OpCallBuiltin1") case OpArray: code("OpArray") case OpMap: code("OpMap") case OpLen: code("OpLen") case OpCast: argument("OpCast") case OpDeref: code("OpDeref") case OpIncrementIndex: code("OpIncrementIndex") case OpDecrementIndex: code("OpDecrementIndex") case OpIncrementCount: code("OpIncrementCount") case OpGetIndex: code("OpGetIndex") case OpGetCount: code("OpGetCount") case OpGetLen: code("OpGetLen") case OpGetAcc: code("OpGetAcc") case OpSetAcc: code("OpSetAcc") case OpSetIndex: code("OpSetIndex") case OpPointer: code("OpPointer") case OpThrow: code("OpThrow") case OpCreate: argument("OpCreate") case OpGroupBy: code("OpGroupBy") case OpSortBy: code("OpSortBy") case OpSort: code("OpSort") case OpProfileStart: code("OpProfileStart") case OpProfileEnd: code("OpProfileEnd") case OpBegin: code("OpBegin") case OpEnd: code("OpEnd") default: _, _ = fmt.Fprintf(w, "%v\t%#x (unknown)\n", ip, op) } } }