236 lines
4.8 KiB
Go
236 lines
4.8 KiB
Go
package vm
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"git.sharkk.net/Sharkk/Mako/types"
|
|
)
|
|
|
|
type Opcode byte
|
|
|
|
const (
|
|
OpConstant Opcode = iota
|
|
OpSetLocal // Set local variable
|
|
OpGetLocal // Get local variable
|
|
OpSetGlobal // Set global variable
|
|
OpGetGlobal // Get global variable
|
|
OpEcho
|
|
OpNewTable
|
|
OpSetIndex
|
|
OpGetIndex
|
|
OpDup
|
|
OpPop
|
|
OpEnterScope // New opcode for entering a block
|
|
OpExitScope // New opcode for exiting a block
|
|
)
|
|
|
|
type Instruction struct {
|
|
Opcode Opcode
|
|
Operand int
|
|
}
|
|
|
|
type Bytecode struct {
|
|
Constants []any
|
|
Instructions []Instruction
|
|
}
|
|
|
|
// Scope represents a lexical scope
|
|
type Scope struct {
|
|
Variables map[string]types.Value
|
|
}
|
|
|
|
type VM struct {
|
|
constants []any
|
|
globals map[string]types.Value
|
|
scopes []Scope // Stack of local scopes
|
|
stack []types.Value
|
|
sp int // Stack pointer
|
|
}
|
|
|
|
func New() *VM {
|
|
return &VM{
|
|
globals: make(map[string]types.Value),
|
|
scopes: []Scope{}, // Initially no scopes
|
|
stack: make([]types.Value, 1024),
|
|
sp: 0,
|
|
}
|
|
}
|
|
|
|
func (vm *VM) Run(bytecode *Bytecode) {
|
|
vm.constants = bytecode.Constants
|
|
|
|
for ip := 0; ip < len(bytecode.Instructions); ip++ {
|
|
instruction := bytecode.Instructions[ip]
|
|
|
|
switch instruction.Opcode {
|
|
case OpConstant:
|
|
constIndex := instruction.Operand
|
|
constant := vm.constants[constIndex]
|
|
|
|
switch v := constant.(type) {
|
|
case string:
|
|
vm.push(types.NewString(v))
|
|
case float64:
|
|
vm.push(types.NewNumber(v))
|
|
}
|
|
|
|
case OpSetLocal:
|
|
constIndex := instruction.Operand
|
|
name := vm.constants[constIndex].(string)
|
|
value := vm.pop()
|
|
|
|
// Set in current scope if it exists
|
|
if len(vm.scopes) > 0 {
|
|
vm.scopes[len(vm.scopes)-1].Variables[name] = value
|
|
} else {
|
|
// No scope, set as global
|
|
vm.globals[name] = value
|
|
}
|
|
|
|
case OpGetLocal:
|
|
constIndex := instruction.Operand
|
|
name := vm.constants[constIndex].(string)
|
|
|
|
// Check local scopes from innermost to outermost
|
|
found := false
|
|
for i := len(vm.scopes) - 1; i >= 0; i-- {
|
|
if val, ok := vm.scopes[i].Variables[name]; ok {
|
|
vm.push(val)
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
// If not found in locals, check globals
|
|
if !found {
|
|
if val, ok := vm.globals[name]; ok {
|
|
vm.push(val)
|
|
} else {
|
|
vm.push(types.NewNull())
|
|
}
|
|
}
|
|
|
|
case OpSetGlobal:
|
|
constIndex := instruction.Operand
|
|
name := vm.constants[constIndex].(string)
|
|
value := vm.pop()
|
|
vm.globals[name] = value
|
|
|
|
case OpGetGlobal:
|
|
constIndex := instruction.Operand
|
|
name := vm.constants[constIndex].(string)
|
|
if val, ok := vm.globals[name]; ok {
|
|
vm.push(val)
|
|
} else {
|
|
vm.push(types.NewNull())
|
|
}
|
|
|
|
case OpEnterScope:
|
|
// Push a new scope
|
|
vm.scopes = append(vm.scopes, Scope{
|
|
Variables: make(map[string]types.Value),
|
|
})
|
|
|
|
case OpExitScope:
|
|
// Pop the current scope
|
|
if len(vm.scopes) > 0 {
|
|
vm.scopes = vm.scopes[:len(vm.scopes)-1]
|
|
}
|
|
|
|
case OpNewTable:
|
|
vm.push(types.NewTableValue())
|
|
|
|
case OpSetIndex:
|
|
value := vm.pop()
|
|
key := vm.pop()
|
|
tableVal := vm.pop()
|
|
|
|
if tableVal.Type != types.TypeTable {
|
|
fmt.Println("Error: attempt to index non-table value")
|
|
vm.push(types.NewNull())
|
|
continue
|
|
}
|
|
|
|
table := tableVal.Data.(*types.Table)
|
|
table.Set(key, value)
|
|
vm.push(tableVal)
|
|
|
|
case OpGetIndex:
|
|
key := vm.pop()
|
|
tableVal := vm.pop()
|
|
|
|
if tableVal.Type != types.TypeTable {
|
|
fmt.Println("Error: attempt to index non-table value")
|
|
vm.push(types.NewNull())
|
|
continue
|
|
}
|
|
|
|
table := tableVal.Data.(*types.Table)
|
|
value := table.Get(key)
|
|
vm.push(value)
|
|
|
|
case OpDup:
|
|
if vm.sp > 0 {
|
|
vm.push(vm.stack[vm.sp-1])
|
|
}
|
|
|
|
case OpPop:
|
|
vm.pop()
|
|
|
|
case OpEcho:
|
|
value := vm.pop()
|
|
switch value.Type {
|
|
case types.TypeString:
|
|
fmt.Println(value.Data.(string))
|
|
case types.TypeNumber:
|
|
fmt.Println(value.Data.(float64))
|
|
case types.TypeBoolean:
|
|
fmt.Println(value.Data.(bool))
|
|
case types.TypeNull:
|
|
fmt.Println("null")
|
|
case types.TypeTable:
|
|
fmt.Println(vm.formatTable(value.Data.(*types.Table)))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (vm *VM) push(value types.Value) {
|
|
vm.stack[vm.sp] = value
|
|
vm.sp++
|
|
}
|
|
|
|
func (vm *VM) pop() types.Value {
|
|
vm.sp--
|
|
return vm.stack[vm.sp]
|
|
}
|
|
|
|
func (vm *VM) formatTable(table *types.Table) string {
|
|
result := "{"
|
|
for i, entry := range table.Entries {
|
|
result += vm.formatValue(entry.Key) + " = " + vm.formatValue(entry.Value)
|
|
if i < len(table.Entries)-1 {
|
|
result += ", "
|
|
}
|
|
}
|
|
result += "}"
|
|
return result
|
|
}
|
|
|
|
func (vm *VM) formatValue(value types.Value) string {
|
|
switch value.Type {
|
|
case types.TypeString:
|
|
return "\"" + value.Data.(string) + "\""
|
|
case types.TypeNumber:
|
|
return fmt.Sprintf("%v", value.Data.(float64))
|
|
case types.TypeBoolean:
|
|
return fmt.Sprintf("%v", value.Data.(bool))
|
|
case types.TypeNull:
|
|
return "null"
|
|
case types.TypeTable:
|
|
return vm.formatTable(value.Data.(*types.Table))
|
|
default:
|
|
return "unknown"
|
|
}
|
|
}
|