481 lines
12 KiB
Go
481 lines
12 KiB
Go
package vm
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"git.sharkk.net/Sharkk/Mako/types"
|
|
)
|
|
|
|
// 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 *types.Bytecode) {
|
|
vm.constants = bytecode.Constants
|
|
|
|
for ip := 0; ip < len(bytecode.Instructions); ip++ {
|
|
instruction := bytecode.Instructions[ip]
|
|
|
|
switch instruction.Opcode {
|
|
case types.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 bool:
|
|
vm.push(types.NewBoolean(v))
|
|
case nil:
|
|
vm.push(types.NewNull())
|
|
}
|
|
|
|
case types.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 types.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 types.OpSetGlobal:
|
|
constIndex := instruction.Operand
|
|
name := vm.constants[constIndex].(string)
|
|
value := vm.pop()
|
|
vm.globals[name] = value
|
|
|
|
case types.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 types.OpEnterScope:
|
|
// Push a new scope
|
|
vm.scopes = append(vm.scopes, Scope{
|
|
Variables: make(map[string]types.Value),
|
|
})
|
|
|
|
case types.OpExitScope:
|
|
// Pop the current scope
|
|
if len(vm.scopes) > 0 {
|
|
vm.scopes = vm.scopes[:len(vm.scopes)-1]
|
|
}
|
|
|
|
case types.OpNewTable:
|
|
vm.push(types.NewTableValue())
|
|
|
|
case types.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 types.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 types.OpDup:
|
|
if vm.sp > 0 {
|
|
vm.push(vm.stack[vm.sp-1])
|
|
}
|
|
|
|
case types.OpPop:
|
|
vm.pop()
|
|
|
|
case types.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)))
|
|
}
|
|
|
|
// Jump instructions
|
|
case types.OpJumpIfFalse:
|
|
condition := vm.pop()
|
|
// Consider falsy: false, null, 0
|
|
shouldJump := false
|
|
|
|
if condition.Type == types.TypeBoolean && !condition.Data.(bool) {
|
|
shouldJump = true
|
|
} else if condition.Type == types.TypeNull {
|
|
shouldJump = true
|
|
} else if condition.Type == types.TypeNumber && condition.Data.(float64) == 0 {
|
|
shouldJump = true
|
|
}
|
|
|
|
if shouldJump {
|
|
ip = instruction.Operand - 1 // -1 because loop will increment
|
|
}
|
|
|
|
case types.OpJump:
|
|
ip = instruction.Operand - 1 // -1 because loop will increment
|
|
|
|
// Arithmetic operations
|
|
case types.OpAdd:
|
|
right := vm.pop()
|
|
left := vm.pop()
|
|
|
|
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
|
result := left.Data.(float64) + right.Data.(float64)
|
|
vm.push(types.NewNumber(result))
|
|
} else if left.Type == types.TypeString && right.Type == types.TypeString {
|
|
// String concatenation
|
|
result := left.Data.(string) + right.Data.(string)
|
|
vm.push(types.NewString(result))
|
|
} else {
|
|
fmt.Println("Error: cannot add values of different types")
|
|
vm.push(types.NewNull())
|
|
}
|
|
|
|
case types.OpSubtract:
|
|
right := vm.pop()
|
|
left := vm.pop()
|
|
|
|
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
|
result := left.Data.(float64) - right.Data.(float64)
|
|
vm.push(types.NewNumber(result))
|
|
} else {
|
|
fmt.Println("Error: cannot subtract non-number values")
|
|
vm.push(types.NewNull())
|
|
}
|
|
|
|
case types.OpMultiply:
|
|
right := vm.pop()
|
|
left := vm.pop()
|
|
|
|
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
|
result := left.Data.(float64) * right.Data.(float64)
|
|
vm.push(types.NewNumber(result))
|
|
} else {
|
|
fmt.Println("Error: cannot multiply non-number values")
|
|
vm.push(types.NewNull())
|
|
}
|
|
|
|
case types.OpDivide:
|
|
right := vm.pop()
|
|
left := vm.pop()
|
|
|
|
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
|
// Check for division by zero
|
|
if right.Data.(float64) == 0 {
|
|
fmt.Println("Error: division by zero")
|
|
vm.push(types.NewNull())
|
|
} else {
|
|
result := left.Data.(float64) / right.Data.(float64)
|
|
vm.push(types.NewNumber(result))
|
|
}
|
|
} else {
|
|
fmt.Println("Error: cannot divide non-number values")
|
|
vm.push(types.NewNull())
|
|
}
|
|
|
|
case types.OpNegate:
|
|
operand := vm.pop()
|
|
|
|
if operand.Type == types.TypeNumber {
|
|
result := -operand.Data.(float64)
|
|
vm.push(types.NewNumber(result))
|
|
} else {
|
|
fmt.Println("Error: cannot negate non-number value")
|
|
vm.push(types.NewNull())
|
|
}
|
|
|
|
// Comparison operators with safer implementation
|
|
case types.OpEqual:
|
|
if vm.sp < 2 {
|
|
fmt.Println("Error: not enough operands for equality comparison")
|
|
vm.push(types.NewBoolean(false))
|
|
continue
|
|
}
|
|
|
|
right := vm.pop()
|
|
left := vm.pop()
|
|
|
|
if left.Type != right.Type {
|
|
vm.push(types.NewBoolean(false))
|
|
} else {
|
|
switch left.Type {
|
|
case types.TypeNumber:
|
|
vm.push(types.NewBoolean(left.Data.(float64) == right.Data.(float64)))
|
|
case types.TypeString:
|
|
vm.push(types.NewBoolean(left.Data.(string) == right.Data.(string)))
|
|
case types.TypeBoolean:
|
|
vm.push(types.NewBoolean(left.Data.(bool) == right.Data.(bool)))
|
|
case types.TypeNull:
|
|
vm.push(types.NewBoolean(true)) // null == null
|
|
default:
|
|
vm.push(types.NewBoolean(false))
|
|
}
|
|
}
|
|
|
|
case types.OpNotEqual:
|
|
if vm.sp < 2 {
|
|
fmt.Println("Error: not enough operands for inequality comparison")
|
|
vm.push(types.NewBoolean(true))
|
|
continue
|
|
}
|
|
|
|
right := vm.pop()
|
|
left := vm.pop()
|
|
|
|
if left.Type != right.Type {
|
|
vm.push(types.NewBoolean(true))
|
|
} else {
|
|
switch left.Type {
|
|
case types.TypeNumber:
|
|
vm.push(types.NewBoolean(left.Data.(float64) != right.Data.(float64)))
|
|
case types.TypeString:
|
|
vm.push(types.NewBoolean(left.Data.(string) != right.Data.(string)))
|
|
case types.TypeBoolean:
|
|
vm.push(types.NewBoolean(left.Data.(bool) != right.Data.(bool)))
|
|
case types.TypeNull:
|
|
vm.push(types.NewBoolean(false)) // null != null is false
|
|
default:
|
|
vm.push(types.NewBoolean(true))
|
|
}
|
|
}
|
|
|
|
case types.OpLessThan:
|
|
if vm.sp < 2 {
|
|
fmt.Println("Error: not enough operands for less-than comparison")
|
|
vm.push(types.NewBoolean(false))
|
|
continue
|
|
}
|
|
|
|
// Peek at values first before popping
|
|
right := vm.stack[vm.sp-1]
|
|
left := vm.stack[vm.sp-2]
|
|
|
|
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
|
// Now pop them
|
|
vm.pop()
|
|
vm.pop()
|
|
vm.push(types.NewBoolean(left.Data.(float64) < right.Data.(float64)))
|
|
} else {
|
|
// Pop the values to maintain stack balance
|
|
vm.pop()
|
|
vm.pop()
|
|
fmt.Println("Error: cannot compare non-number values with <")
|
|
vm.push(types.NewBoolean(false))
|
|
}
|
|
|
|
case types.OpGreaterThan:
|
|
if vm.sp < 2 {
|
|
fmt.Println("Error: not enough operands for greater-than comparison")
|
|
vm.push(types.NewBoolean(false))
|
|
continue
|
|
}
|
|
|
|
// Peek at values first before popping
|
|
right := vm.stack[vm.sp-1]
|
|
left := vm.stack[vm.sp-2]
|
|
|
|
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
|
// Now pop them
|
|
vm.pop()
|
|
vm.pop()
|
|
vm.push(types.NewBoolean(left.Data.(float64) > right.Data.(float64)))
|
|
} else {
|
|
// Pop the values to maintain stack balance
|
|
vm.pop()
|
|
vm.pop()
|
|
fmt.Println("Error: cannot compare non-number values with >")
|
|
vm.push(types.NewBoolean(false))
|
|
}
|
|
|
|
case types.OpLessEqual:
|
|
if vm.sp < 2 {
|
|
fmt.Println("Error: not enough operands for less-equal comparison")
|
|
vm.push(types.NewBoolean(false))
|
|
continue
|
|
}
|
|
|
|
// Peek at values first before popping
|
|
right := vm.stack[vm.sp-1]
|
|
left := vm.stack[vm.sp-2]
|
|
|
|
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
|
// Now pop them
|
|
vm.pop()
|
|
vm.pop()
|
|
vm.push(types.NewBoolean(left.Data.(float64) <= right.Data.(float64)))
|
|
} else {
|
|
// Pop the values to maintain stack balance
|
|
vm.pop()
|
|
vm.pop()
|
|
fmt.Println("Error: cannot compare non-number values with <=")
|
|
vm.push(types.NewBoolean(false))
|
|
}
|
|
|
|
case types.OpGreaterEqual:
|
|
if vm.sp < 2 {
|
|
fmt.Println("Error: not enough operands for greater-equal comparison")
|
|
vm.push(types.NewBoolean(false))
|
|
continue
|
|
}
|
|
|
|
// Peek at values first before popping
|
|
right := vm.stack[vm.sp-1]
|
|
left := vm.stack[vm.sp-2]
|
|
|
|
if left.Type == types.TypeNumber && right.Type == types.TypeNumber {
|
|
// Now pop them
|
|
vm.pop()
|
|
vm.pop()
|
|
vm.push(types.NewBoolean(left.Data.(float64) >= right.Data.(float64)))
|
|
} else {
|
|
// Pop the values to maintain stack balance
|
|
vm.pop()
|
|
vm.pop()
|
|
fmt.Println("Error: cannot compare non-number values with >=")
|
|
vm.push(types.NewBoolean(false))
|
|
}
|
|
|
|
case types.OpNot:
|
|
operand := vm.pop()
|
|
|
|
// Consider falsy: false, null, 0
|
|
isFalsy := false
|
|
|
|
if operand.Type == types.TypeBoolean && !operand.Data.(bool) {
|
|
isFalsy = true
|
|
} else if operand.Type == types.TypeNull {
|
|
isFalsy = true
|
|
} else if operand.Type == types.TypeNumber && operand.Data.(float64) == 0 {
|
|
isFalsy = true
|
|
}
|
|
|
|
vm.push(types.NewBoolean(!isFalsy))
|
|
}
|
|
}
|
|
}
|
|
|
|
func (vm *VM) push(value types.Value) {
|
|
if vm.sp >= len(vm.stack) {
|
|
// Grow stack if needed
|
|
newStack := make([]types.Value, len(vm.stack)*2)
|
|
copy(newStack, vm.stack)
|
|
vm.stack = newStack
|
|
}
|
|
vm.stack[vm.sp] = value
|
|
vm.sp++
|
|
}
|
|
|
|
func (vm *VM) pop() types.Value {
|
|
if vm.sp <= 0 {
|
|
// Return null instead of causing a panic when trying to pop from an empty stack
|
|
fmt.Println("Stack underflow error")
|
|
return types.NewNull()
|
|
}
|
|
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"
|
|
}
|
|
}
|