Mako/types/types.go
2025-05-06 18:13:24 -05:00

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()
}