package types import "fmt" // ValueType represents the type of a value type ValueType byte const ( TypeNull ValueType = iota TypeNumber TypeString TypeBoolean TypeTable TypeFunction ) type Opcode byte const ( OpConstant Opcode = iota OpSetLocal OpGetLocal OpSetGlobal OpGetGlobal OpEcho OpNewTable OpSetIndex OpGetIndex OpDup OpPop OpEnterScope OpExitScope OpAdd OpSubtract OpMultiply OpDivide OpNegate OpJumpIfFalse OpJump OpEqual OpNotEqual OpLessThan OpGreaterThan OpLessEqual OpGreaterEqual OpNot OpFunction OpCall OpReturn OpClosure ) type Instruction struct { Opcode Opcode Operand int } type Bytecode struct { Constants []any Instructions []Instruction } type Value struct { Type ValueType Data any } // Equal checks if two values are equal func (v Value) Equal(other Value) bool { if v.Type != other.Type { return false } switch v.Type { case TypeNull: return true // null == null case TypeNumber: return v.Data.(float64) == other.Data.(float64) case TypeString: return v.Data.(string) == other.Data.(string) case TypeBoolean: return v.Data.(bool) == other.Data.(bool) case TypeTable: return v.Data.(*Table).Equal(other.Data.(*Table)) case TypeFunction: // Two functions are equal if they point to the same function object return v.Data.(*Function) == other.Data.(*Function) default: return false } } func NewNull() Value { return Value{Type: TypeNull, Data: nil} } func NewString(s string) Value { return Value{Type: TypeString, Data: s} } func NewNumber(n float64) Value { return Value{Type: TypeNumber, Data: n} } func NewBoolean(b bool) Value { return Value{Type: TypeBoolean, Data: b} } // Function represents a callable function type Function struct { Instructions []Instruction NumParams int NumLocals int NumConstants int Constants []any // Function's own constant pool UpvalueIndexes []int // Indexes of upvalues (for closures) } func NewFunction(instructions []Instruction, numParams int, constants []any, upvalues []int) *Function { return &Function{ Instructions: instructions, NumParams: numParams, NumConstants: len(constants), Constants: constants, UpvalueIndexes: upvalues, } } func NewFunctionValue(function *Function) Value { return Value{Type: TypeFunction, Data: function} } // Upvalue represents a reference to a value that has been closed over type Upvalue struct { Value *Value // Pointer to the closed-over value } // TableEntry maintains insertion order type TableEntry struct { Key Value Value Value } // Table with ordered entries type Table struct { Entries []TableEntry // Preserves insertion order HashMap map[string]int // Fast lookups for string keys NumMap map[float64]int // Fast lookups for number keys BoolMap map[bool]int // Fast lookups for boolean keys // We can't have a map for table keys since they're not comparable in Go // We'll handle table keys by linear search in Entries } // Equal compares two tables for equality func (t *Table) Equal(other *Table) bool { if len(t.Entries) != len(other.Entries) { return false } // Check if every key-value pair in t exists in other for _, entry := range t.Entries { found := false for _, otherEntry := range other.Entries { if entry.Key.Equal(otherEntry.Key) && entry.Value.Equal(otherEntry.Value) { found = true break } } if !found { return false } } return true } func NewTable() *Table { return &Table{ Entries: []TableEntry{}, HashMap: make(map[string]int), NumMap: make(map[float64]int), BoolMap: make(map[bool]int), } } func NewTableValue() Value { return Value{Type: TypeTable, Data: NewTable()} } // SetKey is a helper to generate a string representation of a key for maps func (t *Table) SetKey(key Value) string { switch key.Type { case TypeString: return "s:" + key.Data.(string) case TypeNumber: return "n:" + fmt.Sprintf("%v", key.Data.(float64)) case TypeBoolean: if key.Data.(bool) { return "b:true" } return "b:false" case TypeNull: return "null" case TypeTable: // For tables, we can use a simple hash or just indicate it's a table return "t:table" // This won't distinguish between different tables default: return "unknown" } } // TableSet preserves insertion order func (t *Table) Set(key, value Value) { idx := -1 // Check if the key is a table if key.Type == TypeTable { // For table keys, we need to do a linear search for i, entry := range t.Entries { if entry.Key.Type == TypeTable && entry.Key.Data.(*Table).Equal(key.Data.(*Table)) { idx = i break } } } else { // Use the existing maps for other types switch key.Type { case TypeString: if i, ok := t.HashMap[key.Data.(string)]; ok { idx = i } case TypeNumber: if i, ok := t.NumMap[key.Data.(float64)]; ok { idx = i } case TypeBoolean: if i, ok := t.BoolMap[key.Data.(bool)]; ok { idx = i } } } if idx >= 0 { // Update existing entry t.Entries[idx].Value = value } else { // Add new entry t.Entries = append(t.Entries, TableEntry{Key: key, Value: value}) idx = len(t.Entries) - 1 // Update lookup maps switch key.Type { case TypeString: t.HashMap[key.Data.(string)] = idx case TypeNumber: t.NumMap[key.Data.(float64)] = idx case TypeBoolean: t.BoolMap[key.Data.(bool)] = idx } } } func (t *Table) Get(key Value) Value { // Check if the key is a table if key.Type == TypeTable { // For table keys, we need to do a linear search for _, entry := range t.Entries { if entry.Key.Type == TypeTable && entry.Key.Data.(*Table).Equal(key.Data.(*Table)) { return entry.Value } } return NewNull() } // Use the existing maps for other types switch key.Type { case TypeString: if i, ok := t.HashMap[key.Data.(string)]; ok { return t.Entries[i].Value } case TypeNumber: if i, ok := t.NumMap[key.Data.(float64)]; ok { return t.Entries[i].Value } case TypeBoolean: if i, ok := t.BoolMap[key.Data.(bool)]; ok { return t.Entries[i].Value } } return NewNull() }