mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
Add build tag: expr_debug (#521)
This commit is contained in:
@@ -20,3 +20,14 @@ jobs:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Test
|
||||
run: go test ./...
|
||||
|
||||
debug:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Go 1.18
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.18
|
||||
- name: Test
|
||||
run: go test -tags=expr_debug -run=TestDebugger -v ./vm
|
||||
|
||||
+7
-3
@@ -7,9 +7,10 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
. "github.com/expr-lang/expr/vm"
|
||||
"github.com/gdamore/tcell/v2"
|
||||
"github.com/rivo/tview"
|
||||
|
||||
. "github.com/expr-lang/expr/vm"
|
||||
)
|
||||
|
||||
func StartDebugger(program *Program, env any) {
|
||||
@@ -112,14 +113,17 @@ func StartDebugger(program *Program, env any) {
|
||||
}
|
||||
|
||||
stack.Clear()
|
||||
for i, value := range vm.Stack() {
|
||||
for i, value := range vm.Stack {
|
||||
stack.SetCellSimple(i, 0, fmt.Sprintf("% *d: ", 2, i))
|
||||
stack.SetCellSimple(i, 1, fmt.Sprintf("%#v", value))
|
||||
}
|
||||
stack.ScrollToEnd()
|
||||
|
||||
scope.Clear()
|
||||
s := vm.Scope()
|
||||
var s *Scope
|
||||
if len(vm.Scopes) > 0 {
|
||||
s = vm.Scopes[len(vm.Scopes)-1]
|
||||
}
|
||||
if s != nil {
|
||||
type pair struct {
|
||||
key string
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
//go:build expr_debug
|
||||
|
||||
package vm
|
||||
|
||||
const debug = true
|
||||
@@ -0,0 +1,5 @@
|
||||
//go:build !expr_debug
|
||||
|
||||
package vm
|
||||
|
||||
const debug = false
|
||||
@@ -0,0 +1,40 @@
|
||||
//go:build expr_debug
|
||||
|
||||
package vm_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/expr-lang/expr/compiler"
|
||||
"github.com/expr-lang/expr/parser"
|
||||
"github.com/expr-lang/expr/vm"
|
||||
)
|
||||
|
||||
func TestDebugger(t *testing.T) {
|
||||
input := `[1, 2]`
|
||||
|
||||
node, err := parser.Parse(input)
|
||||
require.NoError(t, err)
|
||||
|
||||
program, err := compiler.Compile(node, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
debug := vm.Debug()
|
||||
go func() {
|
||||
debug.Step()
|
||||
debug.Step()
|
||||
debug.Step()
|
||||
debug.Step()
|
||||
}()
|
||||
go func() {
|
||||
for range debug.Position() {
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = debug.Run(program, nil)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, debug.Stack, 0)
|
||||
require.Nil(t, debug.Scopes)
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type Function = func(params ...any) (any, error)
|
||||
|
||||
var MemoryBudget uint = 1e6
|
||||
|
||||
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
@@ -13,11 +13,6 @@ import (
|
||||
"github.com/expr-lang/expr/vm/runtime"
|
||||
)
|
||||
|
||||
var MemoryBudget uint = 1e6
|
||||
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
type Function = func(params ...any) (any, error)
|
||||
|
||||
func Run(program *Program, env any) (any, error) {
|
||||
if program == nil {
|
||||
return nil, fmt.Errorf("program is nil")
|
||||
@@ -27,15 +22,24 @@ func Run(program *Program, env any) (any, error) {
|
||||
return vm.Run(program, env)
|
||||
}
|
||||
|
||||
func Debug() *VM {
|
||||
vm := &VM{
|
||||
debug: true,
|
||||
step: make(chan struct{}, 0),
|
||||
curr: make(chan int, 0),
|
||||
}
|
||||
return vm
|
||||
}
|
||||
|
||||
type VM struct {
|
||||
stack []any
|
||||
Stack []any
|
||||
Scopes []*Scope
|
||||
ip int
|
||||
scopes []*Scope
|
||||
memory uint
|
||||
memoryBudget uint
|
||||
debug bool
|
||||
step chan struct{}
|
||||
curr chan int
|
||||
memory uint
|
||||
memoryBudget uint
|
||||
}
|
||||
|
||||
type Scope struct {
|
||||
@@ -47,15 +51,6 @@ type Scope struct {
|
||||
Acc any
|
||||
}
|
||||
|
||||
func Debug() *VM {
|
||||
vm := &VM{
|
||||
debug: true,
|
||||
step: make(chan struct{}, 0),
|
||||
curr: make(chan int, 0),
|
||||
}
|
||||
return vm
|
||||
}
|
||||
|
||||
func (vm *VM) Run(program *Program, env any) (_ any, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@@ -74,14 +69,14 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
if vm.stack == nil {
|
||||
vm.stack = make([]any, 0, 2)
|
||||
if vm.Stack == nil {
|
||||
vm.Stack = make([]any, 0, 2)
|
||||
} else {
|
||||
vm.stack = vm.stack[0:0]
|
||||
vm.Stack = vm.Stack[0:0]
|
||||
}
|
||||
|
||||
if vm.scopes != nil {
|
||||
vm.scopes = vm.scopes[0:0]
|
||||
if vm.Scopes != nil {
|
||||
vm.Scopes = vm.Scopes[0:0]
|
||||
}
|
||||
|
||||
vm.memoryBudget = MemoryBudget
|
||||
@@ -89,7 +84,7 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
|
||||
vm.ip = 0
|
||||
|
||||
for vm.ip < len(program.Bytecode) {
|
||||
if vm.debug {
|
||||
if debug && vm.debug {
|
||||
<-vm.step
|
||||
}
|
||||
|
||||
@@ -204,7 +199,7 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
|
||||
}
|
||||
|
||||
case OpJumpIfEnd:
|
||||
scope := vm.Scope()
|
||||
scope := vm.scope()
|
||||
if scope.Index >= scope.Len {
|
||||
vm.ip += arg
|
||||
}
|
||||
@@ -399,7 +394,7 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
|
||||
|
||||
case OpValidateArgs:
|
||||
fn := vm.pop().(Function)
|
||||
mem, err := fn(vm.stack[len(vm.stack)-arg:]...)
|
||||
mem, err := fn(vm.Stack[len(vm.Stack)-arg:]...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -443,49 +438,49 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
|
||||
vm.push(runtime.Deref(a))
|
||||
|
||||
case OpIncrementIndex:
|
||||
vm.Scope().Index++
|
||||
vm.scope().Index++
|
||||
|
||||
case OpDecrementIndex:
|
||||
scope := vm.Scope()
|
||||
scope := vm.scope()
|
||||
scope.Index--
|
||||
|
||||
case OpIncrementCount:
|
||||
scope := vm.Scope()
|
||||
scope := vm.scope()
|
||||
scope.Count++
|
||||
|
||||
case OpGetIndex:
|
||||
vm.push(vm.Scope().Index)
|
||||
vm.push(vm.scope().Index)
|
||||
|
||||
case OpSetIndex:
|
||||
scope := vm.Scope()
|
||||
scope := vm.scope()
|
||||
scope.Index = vm.pop().(int)
|
||||
|
||||
case OpGetCount:
|
||||
scope := vm.Scope()
|
||||
scope := vm.scope()
|
||||
vm.push(scope.Count)
|
||||
|
||||
case OpGetLen:
|
||||
scope := vm.Scope()
|
||||
scope := vm.scope()
|
||||
vm.push(scope.Len)
|
||||
|
||||
case OpGetGroupBy:
|
||||
vm.push(vm.Scope().GroupBy)
|
||||
vm.push(vm.scope().GroupBy)
|
||||
|
||||
case OpGetAcc:
|
||||
vm.push(vm.Scope().Acc)
|
||||
vm.push(vm.scope().Acc)
|
||||
|
||||
case OpSetAcc:
|
||||
vm.Scope().Acc = vm.pop()
|
||||
vm.scope().Acc = vm.pop()
|
||||
|
||||
case OpPointer:
|
||||
scope := vm.Scope()
|
||||
scope := vm.scope()
|
||||
vm.push(scope.Array.Index(scope.Index).Interface())
|
||||
|
||||
case OpThrow:
|
||||
panic(vm.pop().(error))
|
||||
|
||||
case OpGroupBy:
|
||||
scope := vm.Scope()
|
||||
scope := vm.scope()
|
||||
if scope.GroupBy == nil {
|
||||
scope.GroupBy = make(map[any][]any)
|
||||
}
|
||||
@@ -496,29 +491,29 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
|
||||
case OpBegin:
|
||||
a := vm.pop()
|
||||
array := reflect.ValueOf(a)
|
||||
vm.scopes = append(vm.scopes, &Scope{
|
||||
vm.Scopes = append(vm.Scopes, &Scope{
|
||||
Array: array,
|
||||
Len: array.Len(),
|
||||
})
|
||||
|
||||
case OpEnd:
|
||||
vm.scopes = vm.scopes[:len(vm.scopes)-1]
|
||||
vm.Scopes = vm.Scopes[:len(vm.Scopes)-1]
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown bytecode %#x", op))
|
||||
}
|
||||
|
||||
if vm.debug {
|
||||
if debug && vm.debug {
|
||||
vm.curr <- vm.ip
|
||||
}
|
||||
}
|
||||
|
||||
if vm.debug {
|
||||
if debug && vm.debug {
|
||||
close(vm.curr)
|
||||
close(vm.step)
|
||||
}
|
||||
|
||||
if len(vm.stack) > 0 {
|
||||
if len(vm.Stack) > 0 {
|
||||
return vm.pop(), nil
|
||||
}
|
||||
|
||||
@@ -526,16 +521,16 @@ func (vm *VM) Run(program *Program, env any) (_ any, err error) {
|
||||
}
|
||||
|
||||
func (vm *VM) push(value any) {
|
||||
vm.stack = append(vm.stack, value)
|
||||
vm.Stack = append(vm.Stack, value)
|
||||
}
|
||||
|
||||
func (vm *VM) current() any {
|
||||
return vm.stack[len(vm.stack)-1]
|
||||
return vm.Stack[len(vm.Stack)-1]
|
||||
}
|
||||
|
||||
func (vm *VM) pop() any {
|
||||
value := vm.stack[len(vm.stack)-1]
|
||||
vm.stack = vm.stack[:len(vm.stack)-1]
|
||||
value := vm.Stack[len(vm.Stack)-1]
|
||||
vm.Stack = vm.Stack[:len(vm.Stack)-1]
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -546,15 +541,8 @@ func (vm *VM) memGrow(size uint) {
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) Stack() []any {
|
||||
return vm.stack
|
||||
}
|
||||
|
||||
func (vm *VM) Scope() *Scope {
|
||||
if len(vm.scopes) > 0 {
|
||||
return vm.scopes[len(vm.scopes)-1]
|
||||
}
|
||||
return nil
|
||||
func (vm *VM) scope() *Scope {
|
||||
return vm.Scopes[len(vm.Scopes)-1]
|
||||
}
|
||||
|
||||
func (vm *VM) Step() {
|
||||
|
||||
@@ -20,33 +20,6 @@ func TestRun_NilProgram(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestRun_Debugger(t *testing.T) {
|
||||
input := `[1, 2]`
|
||||
|
||||
node, err := parser.Parse(input)
|
||||
require.NoError(t, err)
|
||||
|
||||
program, err := compiler.Compile(node, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
debug := vm.Debug()
|
||||
go func() {
|
||||
debug.Step()
|
||||
debug.Step()
|
||||
debug.Step()
|
||||
debug.Step()
|
||||
}()
|
||||
go func() {
|
||||
for range debug.Position() {
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = debug.Run(program, nil)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, debug.Stack(), 0)
|
||||
require.Nil(t, debug.Scope())
|
||||
}
|
||||
|
||||
func TestRun_ReuseVM(t *testing.T) {
|
||||
node, err := parser.Parse(`map(1..2, {#})`)
|
||||
require.NoError(t, err)
|
||||
|
||||
Reference in New Issue
Block a user