diff options
| author | Mel <einebeere@gmail.com> | 2022-05-18 19:37:47 +0200 |
|---|---|---|
| committer | Mel <einebeere@gmail.com> | 2022-05-18 19:37:47 +0200 |
| commit | e4d68d39ce7f990895686139cd5cba20d2b2ef89 (patch) | |
| tree | 8862118abb2181f3bb244daaec5ced0497bc07fd /pkg | |
| parent | 089fa82b8f83695cb17b8f41d0376c03303e7663 (diff) | |
| download | jinx-e4d68d39ce7f990895686139cd5cba20d2b2ef89.tar.zst jinx-e4d68d39ce7f990895686139cd5cba20d2b2ef89.zip | |
Handle errors gracefully in VM
Diffstat (limited to 'pkg')
| -rw-r--r-- | pkg/bot/cmds.go | 10 | ||||
| -rw-r--r-- | pkg/lang/vm/errors.go | 58 | ||||
| -rw-r--r-- | pkg/lang/vm/exec.go | 124 | ||||
| -rw-r--r-- | pkg/lang/vm/stack.go | 52 | ||||
| -rw-r--r-- | pkg/lang/vm/value/type.go | 23 | ||||
| -rw-r--r-- | pkg/lang/vm/vm.go | 36 |
6 files changed, 255 insertions, 48 deletions
diff --git a/pkg/bot/cmds.go b/pkg/bot/cmds.go index 1dfb2cb..85d780e 100644 --- a/pkg/bot/cmds.go +++ b/pkg/bot/cmds.go @@ -31,8 +31,14 @@ func vmCmd(b *Bot, content string, msg events.Message) error { vm := vm.New(&bc) - vm.Run() - res := vm.GetResult() + if err := vm.Run(); err != nil { + return err + } + + res, err := vm.GetResult() + if err != nil { + return err + } b.logger.Debug().Msg("executed code") diff --git a/pkg/lang/vm/errors.go b/pkg/lang/vm/errors.go new file mode 100644 index 0000000..d19604a --- /dev/null +++ b/pkg/lang/vm/errors.go @@ -0,0 +1,58 @@ +package vm + +import ( + "errors" + "fmt" + "jinx/pkg/lang/vm/code" + "jinx/pkg/lang/vm/text" + "jinx/pkg/lang/vm/value" +) + +// Fatal errors + +var ( + ErrCallStackOverflow = errors.New("call stack overflow (max depth: 1000)") + ErrLocalStackOverflow = errors.New("local stack overflow (max depth: 1000)") + ErrNoPreviousCallFrame = errors.New("no previous call frame") + ErrCantPopRootFrame = errors.New("cannot pop root frame") + + ErrCallFrameEmpty = errors.New("current call frame is empty") +) + +type ErrLocalIndexOutOfBounds struct { + Index int + Len int +} + +func (e ErrLocalIndexOutOfBounds) Error() string { + return fmt.Sprintf("local index out of bounds: %d (len: %d)", e.Index, e.Len) +} + +type ErrInvalidOp struct { + Op uint8 +} + +func (e ErrInvalidOp) Error() string { + return fmt.Sprintf("invalid opcode: %d", e.Op) +} + +// Non-fatal errors, which will later be implemented as catchable exceptions + +type ErrInvalidOperandTypes struct { + Op code.Op + X value.Type + Y value.Type +} + +func (e ErrInvalidOperandTypes) Error() string { + return fmt.Sprintf("invalid operand types for op %s: %v, %v", text.OpToString(e.Op), e.X, e.Y) +} + +type ErrArrayIndexOutOfBounds struct { + Index int + Len int +} + +func (e ErrArrayIndexOutOfBounds) Error() string { + return fmt.Sprintf("array index out of bounds: %d (len: %d)", e.Index, e.Len) +} diff --git a/pkg/lang/vm/exec.go b/pkg/lang/vm/exec.go index f50d60b..c145acf 100644 --- a/pkg/lang/vm/exec.go +++ b/pkg/lang/vm/exec.go @@ -1,6 +1,9 @@ package vm -import "jinx/pkg/lang/vm/value" +import ( + "jinx/pkg/lang/vm/code" + "jinx/pkg/lang/vm/value" +) func (vm *VM) execPushInt(x int64) { vm.stack.Top().Push(value.NewInt(x)) @@ -26,20 +29,44 @@ func (vm *VM) execPushArray() { vm.stack.Top().Push(value.NewArray([]value.Value{})) } -func (vm *VM) execGetLocal(offset int) { +func (vm *VM) execGetLocal(offset int) error { top := vm.stack.Top() - top.Push(top.At(int(offset))) + + local, err := top.At(int(offset)) + if err != nil { + return err + } + + top.Push(local) + return nil } -func (vm *VM) execGetArg() { - vm.stack.Top().Push(vm.stack.Prev().Pop()) +func (vm *VM) execGetArg() error { + prev, err := vm.stack.Prev() + if err != nil { + return err + } + + arg, err := prev.Pop() + if err != nil { + return err + } + + vm.stack.Top().Push(arg) + return nil } -func (vm *VM) execAdd() { +func (vm *VM) execAdd() error { top := vm.stack.Top() - x := top.Pop() - y := top.Pop() + x, err := top.Pop() + if err != nil { + return err + } + y, err := top.Pop() + if err != nil { + return err + } var res value.Value @@ -54,7 +81,11 @@ func (vm *VM) execAdd() { yv := float64(y.Data().(value.FloatData)) res = value.NewFloat(float64(xv) + yv) default: - panic("invalid operand types") + return ErrInvalidOperandTypes{ + Op: code.OpAdd, + X: x.Type(), + Y: y.Type(), + } } case value.FloatType: xv := float64(x.Data().(value.FloatData)) @@ -66,27 +97,46 @@ func (vm *VM) execAdd() { yv := float64(y.Data().(value.FloatData)) res = value.NewFloat(xv + yv) default: - panic("invalid operand types") + return ErrInvalidOperandTypes{ + Op: code.OpAdd, + X: x.Type(), + Y: y.Type(), + } } case value.StringType: switch y.Type().Kind { case value.StringType: res = value.NewString(string(x.Data().(value.StringData)) + string(y.Data().(value.StringData))) default: - panic("invalid operand types") + return ErrInvalidOperandTypes{ + Op: code.OpAdd, + X: x.Type(), + Y: y.Type(), + } } default: - panic("invalid operand types") + return ErrInvalidOperandTypes{ + Op: code.OpAdd, + X: x.Type(), + Y: y.Type(), + } } top.Push(res) + return nil } -func (vm *VM) execSub() { +func (vm *VM) execSub() error { top := vm.stack.Top() - x := top.Pop() - y := top.Pop() + x, err := top.Pop() + if err != nil { + return err + } + y, err := top.Pop() + if err != nil { + return err + } var res value.Value @@ -101,7 +151,11 @@ func (vm *VM) execSub() { yv := float64(y.Data().(value.FloatData)) res = value.NewFloat(float64(xv) - yv) default: - panic("invalid operand types") + return ErrInvalidOperandTypes{ + Op: code.OpSub, + X: x.Type(), + Y: y.Type(), + } } case value.FloatType: xv := float64(x.Data().(value.FloatData)) @@ -113,31 +167,55 @@ func (vm *VM) execSub() { yv := float64(y.Data().(value.FloatData)) res = value.NewFloat(xv - yv) default: - panic("invalid operand types") + return ErrInvalidOperandTypes{ + Op: code.OpSub, + X: x.Type(), + Y: y.Type(), + } } default: - panic("invalid operand types") + return ErrInvalidOperandTypes{ + Op: code.OpSub, + X: x.Type(), + Y: y.Type(), + } } top.Push(res) + return nil } -func (vm *VM) execIndex() { +func (vm *VM) execIndex() error { top := vm.stack.Top() - v := top.Pop() - i := top.Pop() + v, err := top.Pop() + if err != nil { + return err + } + i, err := top.Pop() + if err != nil { + return err + } switch v.Type().Kind { case value.ArrayType: arr := v.Data().([]value.Value) idx := i.Data().(int64) if idx < 0 || idx >= int64(len(arr)) { - panic("index out of bounds") + return ErrArrayIndexOutOfBounds{ + Index: int(idx), + Len: len(arr), + } } top.Push(arr[idx]) default: - panic("invalid operand types") + return ErrInvalidOperandTypes{ + Op: code.OpIndex, + X: v.Type(), + Y: i.Type(), + } } + + return nil } diff --git a/pkg/lang/vm/stack.go b/pkg/lang/vm/stack.go index 1d028e5..9cf7db8 100644 --- a/pkg/lang/vm/stack.go +++ b/pkg/lang/vm/stack.go @@ -1,6 +1,8 @@ package vm -import "jinx/pkg/lang/vm/value" +import ( + "jinx/pkg/lang/vm/value" +) type CallStack []*LocalStack @@ -8,34 +10,64 @@ func NewCallStack() CallStack { return []*LocalStack{{}} } -func (cs *CallStack) Push() { +func (cs *CallStack) Push() error { + if len(*cs) > 1000 { + return ErrCallStackOverflow + } + *cs = append(*cs, &LocalStack{}) + return nil } -func (cs *CallStack) Pop() { +func (cs *CallStack) Pop() error { + if len(*cs) <= 1 { + return ErrCantPopRootFrame + } + *cs = (*cs)[:len(*cs)-1] + return nil } func (cs *CallStack) Top() *LocalStack { return (*cs)[len(*cs)-1] } -func (cs *CallStack) Prev() *LocalStack { - return (*cs)[len(*cs)-2] +func (cs *CallStack) Prev() (*LocalStack, error) { + if len(*cs) <= 1 { + return nil, ErrNoPreviousCallFrame + } + + return (*cs)[len(*cs)-2], nil } type LocalStack []value.Value -func (ls *LocalStack) Push(v value.Value) { +func (ls *LocalStack) Push(v value.Value) error { + if len(*ls) > 1000 { + return ErrLocalStackOverflow + } + *ls = append(*ls, v) + return nil } -func (ls *LocalStack) Pop() value.Value { +func (ls *LocalStack) Pop() (value.Value, error) { + if len(*ls) == 0 { + return value.Value{}, ErrCallFrameEmpty + } + v := (*ls)[len(*ls)-1] *ls = (*ls)[:len(*ls)-1] - return v + return v, nil } -func (ls *LocalStack) At(at int) value.Value { - return (*ls)[at] +func (ls *LocalStack) At(at int) (value.Value, error) { + if at >= len(*ls) { + return value.Value{}, ErrLocalIndexOutOfBounds{ + Index: at, + Len: len(*ls), + } + } + + return (*ls)[at], nil } diff --git a/pkg/lang/vm/value/type.go b/pkg/lang/vm/value/type.go index 1aec251..cd518d9 100644 --- a/pkg/lang/vm/value/type.go +++ b/pkg/lang/vm/value/type.go @@ -17,3 +17,26 @@ const ( type Type struct { Kind TypeKind } + +func (t Type) String() string { + switch t.Kind { + case IntType: + return "int" + case FloatType: + return "float" + case StringType: + return "string" + case BoolType: + return "bool" + case ArrayType: + return "array" + case NullType: + return "null" + case FunctionType: + return "function" + case ObjectType: + return "object" + } + + panic("invalid type kind") +} diff --git a/pkg/lang/vm/vm.go b/pkg/lang/vm/vm.go index 6746e64..c538841 100644 --- a/pkg/lang/vm/vm.go +++ b/pkg/lang/vm/vm.go @@ -19,19 +19,28 @@ func New(code *code.Code) *VM { } } -func (vm *VM) GetResult() string { - return fmt.Sprintf("%v", vm.stack.Top().Pop().Data()) +func (vm *VM) GetResult() (string, error) { + res, err := vm.stack.Top().Pop() + if err != nil { + return "", err + } + + return fmt.Sprintf("%v", res.Data()), nil } -func (vm *VM) Run() { +func (vm *VM) Run() error { for vm.pc < vm.code.Len() { op, advance := vm.code.GetOp(vm.pc) vm.pc += advance - if vm.step(op) == stepDecisionHalt { - return + if decision, err := vm.step(op); err != nil { + return err + } else if decision == stepDecisionHalt { + return nil } } + + return nil } type stepDecision int @@ -41,12 +50,13 @@ const ( stepDecisionHalt ) -func (vm *VM) step(op code.Op) stepDecision { +func (vm *VM) step(op code.Op) (stepDecision, error) { + var err error switch op { case code.OpNop: // do nothing case code.OpHalt: - return stepDecisionHalt + return stepDecisionHalt, nil case code.OpPushInt: x, advance := vm.code.GetInt(vm.pc) @@ -82,7 +92,7 @@ func (vm *VM) step(op code.Op) stepDecision { offset, advance := vm.code.GetInt(vm.pc) vm.pc += advance - vm.execGetLocal(int(offset)) + err = vm.execGetLocal(int(offset)) case code.OpGetMember: panic("not implemented") case code.OpGetArg: @@ -91,11 +101,11 @@ func (vm *VM) step(op code.Op) stepDecision { panic("not implemented") case code.OpAdd: - vm.execAdd() + err = vm.execAdd() case code.OpSub: - vm.execSub() + err = vm.execSub() case code.OpIndex: - vm.execIndex() + err = vm.execIndex() case code.OpCall: panic("not implemented") @@ -109,8 +119,8 @@ func (vm *VM) step(op code.Op) stepDecision { panic("not implemented") default: - panic(fmt.Errorf("unimplemented op: %v", op)) + err = ErrInvalidOp{Op: uint8(op)} } - return stepDecisionContinue + return stepDecisionContinue, err } |
