Add build tag: expr_debug (#521)

This commit is contained in:
Anton Medvedev
2024-01-13 13:14:38 +01:00
committed by GitHub
parent 9352898016
commit 74e30b9ecd
8 changed files with 123 additions and 86 deletions
+11
View File
@@ -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
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
//go:build expr_debug
package vm
const debug = true
+5
View File
@@ -0,0 +1,5 @@
//go:build !expr_debug
package vm
const debug = false
+40
View File
@@ -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
View File
@@ -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()
+44 -56
View File
@@ -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() {
-27
View File
@@ -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)