From 4cc597d2e22a748f9214058c60f40dcb4a34c6c3 Mon Sep 17 00:00:00 2001 From: Anton Medvedev Date: Sat, 13 Jan 2024 12:18:35 +0100 Subject: [PATCH] Add build tag: expr_debug --- .github/workflows/test.yml | 11 ++++ debug/debugger.go | 10 ++-- vm/debug.go | 5 ++ vm/debug_off.go | 5 ++ vm/debug_test.go | 40 +++++++++++++++ vm/utils.go | 11 ++++ vm/vm.go | 100 ++++++++++++++++--------------------- vm/vm_test.go | 27 ---------- 8 files changed, 123 insertions(+), 86 deletions(-) create mode 100644 vm/debug.go create mode 100644 vm/debug_off.go create mode 100644 vm/debug_test.go create mode 100644 vm/utils.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8a8b87873..2fa55aae8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/debug/debugger.go b/debug/debugger.go index a870973ca..6f341e30e 100644 --- a/debug/debugger.go +++ b/debug/debugger.go @@ -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 diff --git a/vm/debug.go b/vm/debug.go new file mode 100644 index 000000000..ab95bf9a0 --- /dev/null +++ b/vm/debug.go @@ -0,0 +1,5 @@ +//go:build expr_debug + +package vm + +const debug = true diff --git a/vm/debug_off.go b/vm/debug_off.go new file mode 100644 index 000000000..e0f2955a1 --- /dev/null +++ b/vm/debug_off.go @@ -0,0 +1,5 @@ +//go:build !expr_debug + +package vm + +const debug = false diff --git a/vm/debug_test.go b/vm/debug_test.go new file mode 100644 index 000000000..576960f06 --- /dev/null +++ b/vm/debug_test.go @@ -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) +} diff --git a/vm/utils.go b/vm/utils.go new file mode 100644 index 000000000..f9ec81a8c --- /dev/null +++ b/vm/utils.go @@ -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() diff --git a/vm/vm.go b/vm/vm.go index 4c2ff04fa..5ae4d0687 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -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() { diff --git a/vm/vm_test.go b/vm/vm_test.go index 0cbbb8998..17768c148 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -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)