aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pkg/lang/vm/code/code.go59
-rw-r--r--pkg/lang/vm/code/op.go34
-rw-r--r--pkg/lang/vm/exec.go135
-rw-r--r--pkg/lang/vm/stack.go37
-rw-r--r--pkg/lang/vm/value/data.go17
-rw-r--r--pkg/lang/vm/value/type.go19
-rw-r--r--pkg/lang/vm/value/value.go52
-rw-r--r--pkg/lang/vm/vm.go108
8 files changed, 461 insertions, 0 deletions
diff --git a/pkg/lang/vm/code/code.go b/pkg/lang/vm/code/code.go
new file mode 100644
index 0000000..ab06d27
--- /dev/null
+++ b/pkg/lang/vm/code/code.go
@@ -0,0 +1,59 @@
+package code
+
+import (
+ "bytes"
+ "encoding/binary"
+ "math"
+ "strings"
+)
+
+type Code struct {
+ code []byte
+}
+
+func (c *Code) Len() int {
+ return len(c.code)
+}
+
+func (c *Code) GetOp(at int) (Op, int) {
+ return Op(c.code[at]), 1
+}
+
+func (c *Code) GetUint(at int) (uint64, int) {
+ advance := 8
+ x := binary.LittleEndian.Uint64(c.code[at : at+advance])
+ return x, advance
+}
+
+func (c *Code) GetInt(at int) (int64, int) {
+ x, advance := c.GetUint(at)
+ return int64(x), advance
+}
+
+func (c *Code) GetFloat(at int) (float64, int) {
+ x, advance := c.GetUint(at)
+ return math.Float64frombits(x), advance
+}
+
+func (c *Code) GetString(at int) (string, int) {
+ advance := 0
+ reader := bytes.NewReader(c.code[at:])
+ builder := strings.Builder{}
+
+ for {
+ r, size, err := reader.ReadRune()
+ advance += size
+
+ if err != nil {
+ break
+ }
+
+ if r == 0 {
+ break
+ }
+
+ builder.WriteRune(r)
+ }
+
+ return builder.String(), advance
+}
diff --git a/pkg/lang/vm/code/op.go b/pkg/lang/vm/code/op.go
new file mode 100644
index 0000000..ae37603
--- /dev/null
+++ b/pkg/lang/vm/code/op.go
@@ -0,0 +1,34 @@
+package code
+
+type Op uint8
+
+const (
+ OpNop Op = iota
+ OpHalt
+
+ OpPushInt
+ OpPushFloat
+ OpPushString
+ OpPushTrue
+ OpPushFalse
+ OpPushNull
+ OpPushArray
+ OpPushFunction
+ OpPushObject
+
+ OpGetGlobal
+ OpGetLocal
+ OpGetMember
+ OpGetArg
+ OpGetEnv
+
+ OpAdd
+ OpSub
+ OpIndex
+ OpCall
+
+ OpJmp
+ OpJez
+
+ OpRet
+)
diff --git a/pkg/lang/vm/exec.go b/pkg/lang/vm/exec.go
new file mode 100644
index 0000000..241e5f8
--- /dev/null
+++ b/pkg/lang/vm/exec.go
@@ -0,0 +1,135 @@
+package vm
+
+import "jinx/pkg/lang/vm/value"
+
+func (vm *VM) execPushInt(x int64) {
+ vm.stack.Top().Push(value.NewInt(x))
+}
+
+func (vm *VM) execPushFloat(x float64) {
+ vm.stack.Top().Push(value.NewFloat(x))
+}
+
+func (vm *VM) execPushString(str string) {
+ vm.stack.Top().Push(value.NewString(str))
+}
+
+func (vm *VM) execPushBool(b bool) {
+ vm.stack.Top().Push(value.NewBool(b))
+}
+
+func (vm *VM) execPushNull() {
+ vm.stack.Top().Push(value.NewNull())
+}
+
+func (vm *VM) execPushArray() {
+ vm.stack.Top().Push(value.NewArray([]value.Value{}))
+}
+
+func (vm *VM) execGetLocal(offset int) {
+ top := vm.stack.Top()
+ top.Push(top.At(int(offset)))
+}
+
+func (vm *VM) execGetArg() {
+ vm.stack.Top().Push(vm.stack.Prev().Pop())
+}
+
+func (vm *VM) execAdd() {
+ top := vm.stack.Top()
+
+ x := top.Pop()
+ y := top.Pop()
+
+ var res value.Value
+
+ switch x.Type().Kind {
+ case value.IntType:
+ xv := x.Data().(int64)
+ switch y.Type().Kind {
+ case value.IntType:
+ res = value.NewInt(xv + y.Data().(int64))
+ case value.FloatType:
+ res = value.NewFloat(float64(xv) + y.Data().(float64))
+ default:
+ panic("invalid operand types")
+ }
+ case value.FloatType:
+ xv := x.Data().(float64)
+ switch y.Type().Kind {
+ case value.IntType:
+ res = value.NewFloat(xv + float64(y.Data().(int64)))
+ case value.FloatType:
+ res = value.NewFloat(xv + y.Data().(float64))
+ default:
+ panic("invalid operand types")
+ }
+ case value.StringType:
+ switch y.Type().Kind {
+ case value.StringType:
+ res = value.NewString(x.Data().(string) + y.Data().(string))
+ default:
+ panic("invalid operand types")
+ }
+ default:
+ panic("invalid operand types")
+ }
+
+ top.Push(res)
+}
+
+func (vm *VM) execSub() {
+ top := vm.stack.Top()
+
+ x := top.Pop()
+ y := top.Pop()
+
+ var res value.Value
+
+ switch x.Type().Kind {
+ case value.IntType:
+ xv := x.Data().(int64)
+ switch y.Type().Kind {
+ case value.IntType:
+ res = value.NewInt(xv - y.Data().(int64))
+ case value.FloatType:
+ res = value.NewFloat(float64(xv) - y.Data().(float64))
+ default:
+ panic("invalid operand types")
+ }
+ case value.FloatType:
+ xv := x.Data().(float64)
+ switch y.Type().Kind {
+ case value.IntType:
+ res = value.NewFloat(xv - float64(y.Data().(int64)))
+ case value.FloatType:
+ res = value.NewFloat(xv - y.Data().(float64))
+ default:
+ panic("invalid operand types")
+ }
+ default:
+ panic("invalid operand types")
+ }
+
+ top.Push(res)
+}
+
+func (vm *VM) execIndex() {
+ top := vm.stack.Top()
+
+ v := top.Pop()
+ i := top.Pop()
+
+ 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")
+ }
+
+ top.Push(arr[idx])
+ default:
+ panic("invalid operand types")
+ }
+}
diff --git a/pkg/lang/vm/stack.go b/pkg/lang/vm/stack.go
new file mode 100644
index 0000000..433c0b7
--- /dev/null
+++ b/pkg/lang/vm/stack.go
@@ -0,0 +1,37 @@
+package vm
+
+import "jinx/pkg/lang/vm/value"
+
+type CallStack []*LocalStack
+
+func (cs *CallStack) Push() {
+ *cs = append(*cs, &LocalStack{})
+}
+
+func (cs *CallStack) Pop() {
+ *cs = (*cs)[:len(*cs)-1]
+}
+
+func (cs *CallStack) Top() *LocalStack {
+ return (*cs)[len(*cs)-1]
+}
+
+func (cs *CallStack) Prev() *LocalStack {
+ return (*cs)[len(*cs)-2]
+}
+
+type LocalStack []value.Value
+
+func (ls *LocalStack) Push(v value.Value) {
+ *ls = append(*ls, v)
+}
+
+func (ls *LocalStack) Pop() value.Value {
+ v := (*ls)[len(*ls)-1]
+ *ls = (*ls)[:len(*ls)-1]
+ return v
+}
+
+func (ls *LocalStack) At(at int) value.Value {
+ return (*ls)[at]
+}
diff --git a/pkg/lang/vm/value/data.go b/pkg/lang/vm/value/data.go
new file mode 100644
index 0000000..1d63d32
--- /dev/null
+++ b/pkg/lang/vm/value/data.go
@@ -0,0 +1,17 @@
+package value
+
+type IntData int64
+
+type FloatData float64
+
+type StringData string
+
+type BoolData bool
+
+type ArrayData []Value
+
+type NullData struct{}
+
+type FunctionData struct{} // TODO
+
+type ObjectData struct{} // TODO
diff --git a/pkg/lang/vm/value/type.go b/pkg/lang/vm/value/type.go
new file mode 100644
index 0000000..1aec251
--- /dev/null
+++ b/pkg/lang/vm/value/type.go
@@ -0,0 +1,19 @@
+package value
+
+type TypeKind int
+
+const (
+ IntType TypeKind = iota
+ FloatType
+ StringType
+ BoolType
+ ArrayType
+ NullType
+ FunctionType
+
+ ObjectType
+)
+
+type Type struct {
+ Kind TypeKind
+}
diff --git a/pkg/lang/vm/value/value.go b/pkg/lang/vm/value/value.go
new file mode 100644
index 0000000..e932ed3
--- /dev/null
+++ b/pkg/lang/vm/value/value.go
@@ -0,0 +1,52 @@
+package value
+
+type Value struct {
+ t Type
+ d any
+}
+
+func NewInt(x int64) Value {
+ t := Type{Kind: IntType}
+ return Value{t: t, d: IntData(x)}
+}
+
+func NewFloat(x float64) Value {
+ t := Type{Kind: FloatType}
+ return Value{t: t, d: FloatData(x)}
+}
+
+func NewString(str string) Value {
+ t := Type{Kind: StringType}
+ return Value{t: t, d: StringData(str)}
+}
+
+func NewBool(b bool) Value {
+ t := Type{Kind: BoolType}
+ return Value{t: t, d: BoolData(b)}
+}
+
+func NewArray(arr []Value) Value {
+ t := Type{Kind: ArrayType}
+ return Value{t: t, d: ArrayData(arr)}
+}
+
+func NewNull() Value {
+ t := Type{Kind: NullType}
+ return Value{t: t}
+}
+
+func NewFunction() Value {
+ panic("not implemented")
+}
+
+func NewObject() Value {
+ panic("not implemented")
+}
+
+func (v Value) Type() Type {
+ return v.t
+}
+
+func (v Value) Data() any {
+ return v.d
+}
diff --git a/pkg/lang/vm/vm.go b/pkg/lang/vm/vm.go
new file mode 100644
index 0000000..434eae3
--- /dev/null
+++ b/pkg/lang/vm/vm.go
@@ -0,0 +1,108 @@
+package vm
+
+import (
+ "fmt"
+ "jinx/pkg/lang/vm/code"
+)
+
+type VM struct {
+ code *code.Code
+ pc int
+ stack CallStack
+}
+
+func New(code *code.Code) *VM {
+ return &VM{
+ code: code,
+ pc: 0,
+ }
+}
+
+func (vm *VM) Run() {
+ for vm.pc < vm.code.Len() {
+ op, advance := vm.code.GetOp(vm.pc)
+ vm.pc += advance
+ vm.step(op)
+ }
+}
+
+type stepDecision int
+
+const (
+ stepDecisionContinue stepDecision = iota
+ stepDecisionHalt
+)
+
+func (vm *VM) step(op code.Op) stepDecision {
+ switch op {
+ case code.OpNop:
+ // do nothing
+ case code.OpHalt:
+ return stepDecisionHalt
+
+ case code.OpPushInt:
+ x, advance := vm.code.GetInt(vm.pc)
+ vm.pc += advance
+
+ vm.execPushInt(x)
+ case code.OpPushFloat:
+ x, advance := vm.code.GetFloat(vm.pc)
+ vm.pc += advance
+
+ vm.execPushFloat(x)
+ case code.OpPushString:
+ str, advance := vm.code.GetString(vm.pc)
+ vm.pc += advance
+
+ vm.execPushString(str)
+ case code.OpPushNull:
+ vm.execPushNull()
+ case code.OpPushTrue:
+ vm.execPushBool(true)
+ case code.OpPushFalse:
+ vm.execPushBool(false)
+ case code.OpPushArray:
+ vm.execPushArray()
+ case code.OpPushFunction:
+ panic("not implemented")
+ case code.OpPushObject:
+ panic("not implemented")
+
+ case code.OpGetGlobal:
+ panic("not implemented")
+ case code.OpGetLocal:
+ offset, advance := vm.code.GetInt(vm.pc)
+ vm.pc += advance
+
+ vm.execGetLocal(int(offset))
+ case code.OpGetMember:
+ panic("not implemented")
+ case code.OpGetArg:
+ vm.execGetArg()
+ case code.OpGetEnv:
+ panic("not implemented")
+
+ case code.OpAdd:
+ vm.execAdd()
+ case code.OpSub:
+ vm.execSub()
+ case code.OpIndex:
+ vm.execIndex()
+ case code.OpCall:
+ panic("not implemented")
+
+ case code.OpJmp:
+ pc, _ := vm.code.GetUint(vm.pc)
+ vm.pc = int(pc)
+ case code.OpJez:
+ panic("not implemented")
+
+ case code.OpRet:
+ panic("not implemented")
+
+ default:
+ panic(fmt.Errorf("unimplemented op: %v", op))
+ }
+
+ return stepDecisionContinue
+}