293 lines
6.0 KiB
Go
293 lines
6.0 KiB
Go
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()
|
|
}
|