package vm import ( "fmt" "git.sharkk.net/Sharkk/Mako/types" ) // Scope represents a lexical scope type Scope struct { Variables map[string]types.Value } // Frame represents a call frame on the call stack type Frame struct { Function *types.Function // The function being executed IP int // Instruction pointer BasePointer int // Base stack pointer for this frame ReturnAddr int // Return address in the caller Upvalues []*types.Upvalue // Closed-over variables } type VM struct { constants []any globals map[string]types.Value scopes []Scope // Stack of local scopes stack []types.Value sp int // Stack pointer frames []Frame // Call frames fp int // Frame pointer (index of current frame) upvalues []*types.Upvalue // Upvalues for closures } func New() *VM { return &VM{ globals: make(map[string]types.Value), scopes: []Scope{}, // Initially no scopes stack: make([]types.Value, 1024), sp: 0, frames: make([]Frame, 64), // Support up to 64 nested calls fp: -1, // No active frame yet upvalues: []*types.Upvalue{}, } } // Reset resets the VM to its initial state // Can be called as vm.Reset() to reuse an existing VM instance func (vm *VM) Reset() *VM { vm.constants = nil vm.globals = make(map[string]types.Value) vm.scopes = []Scope{} vm.stack = make([]types.Value, 1024) vm.sp = 0 vm.frames = make([]Frame, 64) vm.fp = -1 vm.upvalues = []*types.Upvalue{} return vm } // GetGlobal retrieves a global variable by name func (vm *VM) GetGlobal(name string) (types.Value, bool) { val, ok := vm.globals[name] return val, ok } // Global returns all global variables for testing purposes func (vm *VM) Globals() map[string]types.Value { return vm.globals } // CurrentStack returns the current stack values for testing func (vm *VM) CurrentStack() []types.Value { return vm.stack[:vm.sp] } func (vm *VM) Run(bytecode *types.Bytecode) { vm.constants = bytecode.Constants vm.runCode(bytecode.Instructions, 0) } func (vm *VM) runCode(instructions []types.Instruction, basePointer int) types.Value { for ip := 0; ip < len(instructions); ip++ { instruction := 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.Function: vm.push(types.NewFunctionValue(v)) } 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("nil") case types.TypeTable: fmt.Println(vm.formatTable(value.Data.(*types.Table))) case types.TypeFunction: fmt.Println("") } // 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 // Function instructions case types.OpFunction: constIndex := instruction.Operand function := vm.constants[constIndex].(*types.Function) // Use the helper function to create a proper function value vm.push(types.NewFunctionValue(function)) case types.OpCall: numArgs := instruction.Operand // The function is at position sp-numArgs-1 if vm.sp <= numArgs { fmt.Println("Error: stack underflow during function call") vm.push(types.NewNull()) continue } fnVal := vm.stack[vm.sp-numArgs-1] if fnVal.Type != types.TypeFunction { fmt.Printf("Error: attempt to call non-function value\n") vm.push(types.NewNull()) continue } function, ok := fnVal.Data.(*types.Function) if !ok { fmt.Printf("Error: function data is invalid\n") vm.push(types.NewNull()) continue } // Check if we have the correct number of arguments if numArgs != function.NumParams { fmt.Printf("Error: function expects %d arguments, got %d\n", function.NumParams, numArgs) vm.push(types.NewNull()) continue } // Create a new call frame frame := Frame{ Function: function, IP: 0, BasePointer: vm.sp - numArgs - 1, // Below the function and args ReturnAddr: ip, Upvalues: make([]*types.Upvalue, len(function.UpvalueIndexes)), } // Save the current frame vm.fp++ if vm.fp >= len(vm.frames) { // Grow the frame stack if needed newFrames := make([]Frame, len(vm.frames)*2) copy(newFrames, vm.frames) vm.frames = newFrames } vm.frames[vm.fp] = frame // Save the current constants oldConstants := vm.constants // Switch to function's constants vm.constants = function.Constants // Run the function code returnValue := vm.runCode(function.Instructions, frame.BasePointer) // Restore the old constants vm.constants = oldConstants // Restore state vm.fp-- // Replace the function with the return value vm.stack[frame.BasePointer] = returnValue // Adjust the stack pointer to remove the arguments vm.sp = frame.BasePointer + 1 case types.OpReturn: returnValue := vm.pop() // If we're in a function call, return to the caller if vm.fp >= 0 { frame := vm.frames[vm.fp] // Restore the stack to just below the function vm.sp = frame.BasePointer // Push the return value vm.push(returnValue) // Return to the caller return returnValue } // Top-level return vm.push(returnValue) return returnValue // 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()) } 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() vm.push(types.NewBoolean(left.Equal(right))) 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() vm.push(types.NewBoolean(!left.Equal(right))) 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)) } } // Return null for the top-level code when no explicit return is found return types.NewNull() } 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 "nil" case types.TypeTable: return vm.formatTable(value.Data.(*types.Table)) case types.TypeFunction: return "" default: return "unknown" } }