about summary refs log tree commit diff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-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
+}