1188 lines
28 KiB
Go
1188 lines
28 KiB
Go
package compiler
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
|
|
"git.sharkk.net/Sharkk/Mako/parser"
|
|
)
|
|
|
|
// Compiler holds the compilation state and compiles AST to bytecode
|
|
type Compiler struct {
|
|
current *CompilerState // Current compilation state
|
|
enclosing *CompilerState // Enclosing function state for closures
|
|
errors []CompileError // Compilation errors
|
|
}
|
|
|
|
// NewCompiler creates a new compiler instance
|
|
func NewCompiler() *Compiler {
|
|
return &Compiler{
|
|
current: NewCompilerState(FunctionTypeScript),
|
|
errors: make([]CompileError, 0),
|
|
}
|
|
}
|
|
|
|
// Compile compiles a program AST to bytecode with optimizations
|
|
func (c *Compiler) Compile(program *parser.Program) (*Chunk, []CompileError) {
|
|
for _, stmt := range program.Statements {
|
|
c.compileStatement(stmt)
|
|
}
|
|
|
|
c.current.EmitInstruction(OpReturnNil)
|
|
|
|
if len(c.errors) > 0 {
|
|
return nil, c.errors
|
|
}
|
|
|
|
// Apply optimizations
|
|
c.optimizeChunk(c.current.Chunk)
|
|
|
|
return c.current.Chunk, nil
|
|
}
|
|
|
|
// Statement compilation
|
|
func (c *Compiler) compileStatement(stmt parser.Statement) {
|
|
if lineNode := c.getLineFromNode(stmt); lineNode != 0 {
|
|
c.current.SetLine(lineNode)
|
|
}
|
|
|
|
switch s := stmt.(type) {
|
|
case *parser.StructStatement:
|
|
c.compileStructStatement(s)
|
|
case *parser.MethodDefinition:
|
|
c.compileMethodDefinition(s)
|
|
case *parser.Assignment:
|
|
c.compileAssignment(s)
|
|
case *parser.ExpressionStatement:
|
|
c.compileExpression(s.Expression)
|
|
c.current.EmitInstruction(OpPop) // Discard result
|
|
case *parser.EchoStatement:
|
|
c.compileExpression(s.Value)
|
|
c.current.EmitInstruction(OpEcho)
|
|
case *parser.IfStatement:
|
|
c.compileIfStatement(s)
|
|
case *parser.WhileStatement:
|
|
c.compileWhileStatement(s)
|
|
case *parser.ForStatement:
|
|
c.compileForStatement(s)
|
|
case *parser.ForInStatement:
|
|
c.compileForInStatement(s)
|
|
case *parser.ReturnStatement:
|
|
c.compileReturnStatement(s)
|
|
case *parser.ExitStatement:
|
|
c.compileExitStatement(s)
|
|
case *parser.BreakStatement:
|
|
c.current.EmitBreak()
|
|
default:
|
|
c.addError(fmt.Sprintf("unknown statement type: %T", stmt))
|
|
}
|
|
}
|
|
|
|
// Enhanced constant folding engine with complete coverage
|
|
func (c *Compiler) tryConstantFold(expr parser.Expression) *Value {
|
|
switch e := expr.(type) {
|
|
case *parser.NumberLiteral:
|
|
return &Value{Type: ValueNumber, Data: e.Value}
|
|
case *parser.StringLiteral:
|
|
return &Value{Type: ValueString, Data: e.Value}
|
|
case *parser.BooleanLiteral:
|
|
return &Value{Type: ValueBool, Data: e.Value}
|
|
case *parser.NilLiteral:
|
|
return &Value{Type: ValueNil, Data: nil}
|
|
case *parser.PrefixExpression:
|
|
return c.foldPrefixExpression(e)
|
|
case *parser.InfixExpression:
|
|
return c.foldInfixExpression(e)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Compiler) foldPrefixExpression(expr *parser.PrefixExpression) *Value {
|
|
rightValue := c.tryConstantFold(expr.Right)
|
|
if rightValue == nil {
|
|
return nil
|
|
}
|
|
|
|
switch expr.Operator {
|
|
case "-":
|
|
if rightValue.Type == ValueNumber {
|
|
num := rightValue.Data.(float64)
|
|
return &Value{Type: ValueNumber, Data: -num}
|
|
}
|
|
case "not":
|
|
return &Value{Type: ValueBool, Data: !c.isTruthy(*rightValue)}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Compiler) foldInfixExpression(expr *parser.InfixExpression) *Value {
|
|
leftValue := c.tryConstantFold(expr.Left)
|
|
rightValue := c.tryConstantFold(expr.Right)
|
|
|
|
if leftValue == nil || rightValue == nil {
|
|
return nil
|
|
}
|
|
|
|
// Arithmetic operations
|
|
if leftValue.Type == ValueNumber && rightValue.Type == ValueNumber {
|
|
l := leftValue.Data.(float64)
|
|
r := rightValue.Data.(float64)
|
|
|
|
switch expr.Operator {
|
|
case "+":
|
|
return &Value{Type: ValueNumber, Data: l + r}
|
|
case "-":
|
|
return &Value{Type: ValueNumber, Data: l - r}
|
|
case "*":
|
|
return &Value{Type: ValueNumber, Data: l * r}
|
|
case "/":
|
|
if r != 0 {
|
|
return &Value{Type: ValueNumber, Data: l / r}
|
|
}
|
|
return nil
|
|
case "%":
|
|
if r != 0 {
|
|
return &Value{Type: ValueNumber, Data: math.Mod(l, r)}
|
|
}
|
|
return nil
|
|
case "<":
|
|
return &Value{Type: ValueBool, Data: l < r}
|
|
case "<=":
|
|
return &Value{Type: ValueBool, Data: l <= r}
|
|
case ">":
|
|
return &Value{Type: ValueBool, Data: l > r}
|
|
case ">=":
|
|
return &Value{Type: ValueBool, Data: l >= r}
|
|
}
|
|
}
|
|
|
|
// String operations
|
|
if leftValue.Type == ValueString && rightValue.Type == ValueString {
|
|
l := leftValue.Data.(string)
|
|
r := rightValue.Data.(string)
|
|
|
|
switch expr.Operator {
|
|
case "+":
|
|
return &Value{Type: ValueString, Data: l + r}
|
|
}
|
|
}
|
|
|
|
// Comparison operations
|
|
switch expr.Operator {
|
|
case "==":
|
|
return &Value{Type: ValueBool, Data: c.valuesEqual(*leftValue, *rightValue)}
|
|
case "!=":
|
|
return &Value{Type: ValueBool, Data: !c.valuesEqual(*leftValue, *rightValue)}
|
|
case "and":
|
|
if !c.isTruthy(*leftValue) {
|
|
return leftValue
|
|
}
|
|
return rightValue
|
|
case "or":
|
|
if c.isTruthy(*leftValue) {
|
|
return leftValue
|
|
}
|
|
return rightValue
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *Compiler) isTruthy(value Value) bool {
|
|
switch value.Type {
|
|
case ValueNil:
|
|
return false
|
|
case ValueBool:
|
|
return value.Data.(bool)
|
|
case ValueNumber:
|
|
return value.Data.(float64) != 0
|
|
case ValueString:
|
|
return value.Data.(string) != ""
|
|
default:
|
|
return true
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) valuesEqual(a, b Value) bool {
|
|
if a.Type != b.Type {
|
|
return false
|
|
}
|
|
switch a.Type {
|
|
case ValueNil:
|
|
return true
|
|
case ValueBool:
|
|
return a.Data.(bool) == b.Data.(bool)
|
|
case ValueNumber:
|
|
aNum := a.Data.(float64)
|
|
bNum := b.Data.(float64)
|
|
if math.IsNaN(aNum) && math.IsNaN(bNum) {
|
|
return true
|
|
}
|
|
return aNum == bNum
|
|
case ValueString:
|
|
return a.Data.(string) == b.Data.(string)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Expression compilation with enhanced constant folding
|
|
func (c *Compiler) compileExpression(expr parser.Expression) {
|
|
if lineNode := c.getLineFromNode(expr); lineNode != 0 {
|
|
c.current.SetLine(lineNode)
|
|
}
|
|
|
|
// Try constant folding first
|
|
if constValue := c.tryConstantFold(expr); constValue != nil {
|
|
c.emitConstant(*constValue)
|
|
return
|
|
}
|
|
|
|
switch e := expr.(type) {
|
|
case *parser.Identifier:
|
|
c.compileIdentifier(e)
|
|
case *parser.NumberLiteral:
|
|
c.compileNumberLiteral(e)
|
|
case *parser.StringLiteral:
|
|
c.compileStringLiteral(e)
|
|
case *parser.BooleanLiteral:
|
|
c.compileBooleanLiteral(e)
|
|
case *parser.NilLiteral:
|
|
c.compileNilLiteral(e)
|
|
case *parser.TableLiteral:
|
|
c.compileTableLiteral(e)
|
|
case *parser.StructConstructor:
|
|
c.compileStructConstructor(e)
|
|
case *parser.FunctionLiteral:
|
|
c.compileFunctionLiteral(e)
|
|
case *parser.CallExpression:
|
|
c.compileCallExpression(e)
|
|
case *parser.PrefixExpression:
|
|
c.compilePrefixExpression(e)
|
|
case *parser.InfixExpression:
|
|
c.compileInfixExpression(e)
|
|
case *parser.IndexExpression:
|
|
c.compileIndexExpression(e)
|
|
case *parser.DotExpression:
|
|
c.compileDotExpression(e)
|
|
case *parser.Assignment:
|
|
c.compileAssignmentExpression(e)
|
|
default:
|
|
c.addError(fmt.Sprintf("unknown expression type: %T", expr))
|
|
}
|
|
}
|
|
|
|
// Optimized constant emission
|
|
func (c *Compiler) emitConstant(value Value) {
|
|
switch value.Type {
|
|
case ValueNil:
|
|
c.current.EmitInstruction(OpLoadNil)
|
|
case ValueBool:
|
|
if value.Data.(bool) {
|
|
c.current.EmitInstruction(OpLoadTrue)
|
|
} else {
|
|
c.current.EmitInstruction(OpLoadFalse)
|
|
}
|
|
case ValueNumber:
|
|
num := value.Data.(float64)
|
|
if num == 0 {
|
|
c.current.EmitInstruction(OpLoadZero)
|
|
} else if num == 1 {
|
|
c.current.EmitInstruction(OpLoadOne)
|
|
} else if num == -1 {
|
|
c.current.EmitInstruction(OpLoadOne)
|
|
c.current.EmitInstruction(OpNeg)
|
|
} else {
|
|
index := c.current.AddConstant(value)
|
|
if index == -1 {
|
|
c.addError("too many constants")
|
|
return
|
|
}
|
|
c.current.EmitInstruction(OpLoadConst, uint16(index))
|
|
}
|
|
default:
|
|
index := c.current.AddConstant(value)
|
|
if index == -1 {
|
|
c.addError("too many constants")
|
|
return
|
|
}
|
|
c.current.EmitInstruction(OpLoadConst, uint16(index))
|
|
}
|
|
}
|
|
|
|
// Literal compilation
|
|
func (c *Compiler) compileNumberLiteral(node *parser.NumberLiteral) {
|
|
value := Value{Type: ValueNumber, Data: node.Value}
|
|
c.emitConstant(value)
|
|
}
|
|
|
|
func (c *Compiler) compileStringLiteral(node *parser.StringLiteral) {
|
|
value := Value{Type: ValueString, Data: node.Value}
|
|
c.emitConstant(value)
|
|
}
|
|
|
|
func (c *Compiler) compileBooleanLiteral(node *parser.BooleanLiteral) {
|
|
value := Value{Type: ValueBool, Data: node.Value}
|
|
c.emitConstant(value)
|
|
}
|
|
|
|
func (c *Compiler) compileNilLiteral(node *parser.NilLiteral) {
|
|
c.current.EmitInstruction(OpLoadNil)
|
|
}
|
|
|
|
// Identifier compilation
|
|
func (c *Compiler) compileIdentifier(node *parser.Identifier) {
|
|
slot := c.current.ResolveLocal(node.Value)
|
|
if slot != -1 {
|
|
if slot == -2 {
|
|
c.addError("can't read local variable in its own initializer")
|
|
return
|
|
}
|
|
c.emitLoadLocal(slot)
|
|
return
|
|
}
|
|
|
|
upvalue := c.resolveUpvalue(node.Value)
|
|
if upvalue != -1 {
|
|
c.current.EmitInstruction(OpGetUpvalue, uint16(upvalue))
|
|
return
|
|
}
|
|
|
|
// Global variable
|
|
value := Value{Type: ValueString, Data: node.Value}
|
|
index := c.current.AddConstant(value)
|
|
if index == -1 {
|
|
c.addError("too many constants")
|
|
return
|
|
}
|
|
c.current.EmitInstruction(OpLoadGlobal, uint16(index))
|
|
}
|
|
|
|
// Local variable access helpers
|
|
func (c *Compiler) emitLoadLocal(slot int) {
|
|
switch slot {
|
|
case 0:
|
|
c.current.EmitInstruction(OpLoadLocal0)
|
|
case 1:
|
|
c.current.EmitInstruction(OpLoadLocal1)
|
|
case 2:
|
|
c.current.EmitInstruction(OpLoadLocal2)
|
|
default:
|
|
c.current.EmitInstruction(OpLoadLocal, uint16(slot))
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) emitStoreLocal(slot int) {
|
|
switch slot {
|
|
case 0:
|
|
c.current.EmitInstruction(OpStoreLocal0)
|
|
case 1:
|
|
c.current.EmitInstruction(OpStoreLocal1)
|
|
case 2:
|
|
c.current.EmitInstruction(OpStoreLocal2)
|
|
default:
|
|
c.current.EmitInstruction(OpStoreLocal, uint16(slot))
|
|
}
|
|
}
|
|
|
|
// Assignment compilation
|
|
func (c *Compiler) compileAssignment(node *parser.Assignment) {
|
|
c.compileExpression(node.Value)
|
|
|
|
switch target := node.Target.(type) {
|
|
case *parser.Identifier:
|
|
if node.IsDeclaration {
|
|
if c.current.FunctionType == FunctionTypeScript && c.current.ScopeDepth == 0 {
|
|
// Global variable
|
|
value := Value{Type: ValueString, Data: target.Value}
|
|
index := c.current.AddConstant(value)
|
|
if index == -1 {
|
|
c.addError("too many constants")
|
|
return
|
|
}
|
|
c.current.EmitInstruction(OpStoreGlobal, uint16(index))
|
|
} else {
|
|
// Local variable
|
|
if err := c.current.AddLocal(target.Value); err != nil {
|
|
c.addError(err.Error())
|
|
return
|
|
}
|
|
c.current.MarkInitialized()
|
|
}
|
|
} else {
|
|
// Assignment to existing variable
|
|
slot := c.current.ResolveLocal(target.Value)
|
|
if slot != -1 {
|
|
c.emitStoreLocal(slot)
|
|
} else {
|
|
upvalue := c.resolveUpvalue(target.Value)
|
|
if upvalue != -1 {
|
|
c.current.EmitInstruction(OpSetUpvalue, uint16(upvalue))
|
|
} else {
|
|
// Global assignment
|
|
value := Value{Type: ValueString, Data: target.Value}
|
|
index := c.current.AddConstant(value)
|
|
if index == -1 {
|
|
c.addError("too many constants")
|
|
return
|
|
}
|
|
c.current.EmitInstruction(OpStoreGlobal, uint16(index))
|
|
}
|
|
}
|
|
}
|
|
case *parser.DotExpression:
|
|
c.compileDotAssignment(target)
|
|
case *parser.IndexExpression:
|
|
c.compileExpression(target.Left)
|
|
c.compileExpression(target.Index)
|
|
c.current.EmitInstruction(OpSetIndex)
|
|
default:
|
|
c.addError("invalid assignment target")
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) compileDotAssignment(dot *parser.DotExpression) {
|
|
if ident, ok := dot.Left.(*parser.Identifier); ok {
|
|
slot := c.current.ResolveLocal(ident.Value)
|
|
if slot != -1 && slot <= 2 {
|
|
value := Value{Type: ValueString, Data: dot.Key}
|
|
index := c.current.AddConstant(value)
|
|
if index == -1 {
|
|
c.addError("too many constants")
|
|
return
|
|
}
|
|
c.current.EmitInstruction(OpSetLocalField, uint16(slot), uint16(index))
|
|
return
|
|
}
|
|
}
|
|
|
|
c.compileExpression(dot.Left)
|
|
value := Value{Type: ValueString, Data: dot.Key}
|
|
index := c.current.AddConstant(value)
|
|
if index == -1 {
|
|
c.addError("too many constants")
|
|
return
|
|
}
|
|
c.current.EmitInstruction(OpSetField, uint16(index))
|
|
}
|
|
|
|
func (c *Compiler) compileAssignmentExpression(node *parser.Assignment) {
|
|
c.compileAssignment(node)
|
|
}
|
|
|
|
// Operator compilation
|
|
func (c *Compiler) compilePrefixExpression(node *parser.PrefixExpression) {
|
|
c.compileExpression(node.Right)
|
|
|
|
switch node.Operator {
|
|
case "-":
|
|
c.current.EmitInstruction(OpNeg)
|
|
case "not":
|
|
c.current.EmitInstruction(OpNot)
|
|
default:
|
|
c.addError(fmt.Sprintf("unknown prefix operator: %s", node.Operator))
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) compileInfixExpression(node *parser.InfixExpression) {
|
|
if c.tryOptimizeIncDec(node) {
|
|
return
|
|
}
|
|
|
|
if c.tryOptimizeArithmeticWithConstant(node) {
|
|
return
|
|
}
|
|
|
|
// Short-circuit operators
|
|
if node.Operator == "and" {
|
|
c.compileExpression(node.Left)
|
|
jump := c.current.EmitJump(OpJumpIfFalse)
|
|
c.current.EmitInstruction(OpPop)
|
|
c.compileExpression(node.Right)
|
|
c.current.PatchJump(jump)
|
|
return
|
|
}
|
|
|
|
if node.Operator == "or" {
|
|
c.compileExpression(node.Left)
|
|
elseJump := c.current.EmitJump(OpJumpIfFalse)
|
|
endJump := c.current.EmitJump(OpJump)
|
|
c.current.PatchJump(elseJump)
|
|
c.current.EmitInstruction(OpPop)
|
|
c.compileExpression(node.Right)
|
|
c.current.PatchJump(endJump)
|
|
return
|
|
}
|
|
|
|
// Regular binary operators
|
|
c.compileExpression(node.Left)
|
|
c.compileExpression(node.Right)
|
|
|
|
switch node.Operator {
|
|
case "+":
|
|
c.current.EmitInstruction(OpAdd)
|
|
case "-":
|
|
c.current.EmitInstruction(OpSub)
|
|
case "*":
|
|
c.current.EmitInstruction(OpMul)
|
|
case "/":
|
|
c.current.EmitInstruction(OpDiv)
|
|
case "%":
|
|
c.current.EmitInstruction(OpMod)
|
|
case "==":
|
|
c.current.EmitInstruction(OpEq)
|
|
case "!=":
|
|
c.current.EmitInstruction(OpNeq)
|
|
case "<":
|
|
c.current.EmitInstruction(OpLt)
|
|
case "<=":
|
|
c.current.EmitInstruction(OpLte)
|
|
case ">":
|
|
c.current.EmitInstruction(OpGt)
|
|
case ">=":
|
|
c.current.EmitInstruction(OpGte)
|
|
default:
|
|
c.addError(fmt.Sprintf("unknown infix operator: %s", node.Operator))
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) tryOptimizeIncDec(node *parser.InfixExpression) bool {
|
|
if node.Operator != "+" && node.Operator != "-" {
|
|
return false
|
|
}
|
|
|
|
leftIdent, ok := node.Left.(*parser.Identifier)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
rightLit, ok := node.Right.(*parser.NumberLiteral)
|
|
if !ok || rightLit.Value != 1 {
|
|
return false
|
|
}
|
|
|
|
slot := c.current.ResolveLocal(leftIdent.Value)
|
|
if slot == -1 {
|
|
return false
|
|
}
|
|
|
|
if node.Operator == "+" {
|
|
c.current.EmitInstruction(OpInc, uint16(slot))
|
|
} else {
|
|
c.current.EmitInstruction(OpDec, uint16(slot))
|
|
}
|
|
|
|
c.emitLoadLocal(slot)
|
|
return true
|
|
}
|
|
|
|
func (c *Compiler) tryOptimizeArithmeticWithConstant(node *parser.InfixExpression) bool {
|
|
if node.Operator != "+" && node.Operator != "-" {
|
|
return false
|
|
}
|
|
|
|
leftIdent, ok := node.Left.(*parser.Identifier)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
rightLit, ok := node.Right.(*parser.NumberLiteral)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
slot := c.current.ResolveLocal(leftIdent.Value)
|
|
if slot == -1 {
|
|
return false
|
|
}
|
|
|
|
if rightLit.Value >= 0 && rightLit.Value <= 255 && rightLit.Value == math.Floor(rightLit.Value) {
|
|
c.emitLoadLocal(slot)
|
|
|
|
constValue := Value{Type: ValueNumber, Data: rightLit.Value}
|
|
constIndex := c.current.AddConstant(constValue)
|
|
if constIndex == -1 {
|
|
return false
|
|
}
|
|
|
|
if node.Operator == "+" {
|
|
c.current.EmitInstruction(OpAddConst, uint16(constIndex))
|
|
} else {
|
|
c.current.EmitInstruction(OpSubConst, uint16(constIndex))
|
|
}
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (c *Compiler) compileDotExpression(node *parser.DotExpression) {
|
|
if ident, ok := node.Left.(*parser.Identifier); ok {
|
|
slot := c.current.ResolveLocal(ident.Value)
|
|
if slot != -1 && slot <= 2 {
|
|
value := Value{Type: ValueString, Data: node.Key}
|
|
index := c.current.AddConstant(value)
|
|
if index == -1 {
|
|
c.addError("too many constants")
|
|
return
|
|
}
|
|
c.current.EmitInstruction(OpGetLocalField, uint16(slot), uint16(index))
|
|
return
|
|
}
|
|
}
|
|
|
|
c.compileExpression(node.Left)
|
|
value := Value{Type: ValueString, Data: node.Key}
|
|
index := c.current.AddConstant(value)
|
|
if index == -1 {
|
|
c.addError("too many constants")
|
|
return
|
|
}
|
|
c.current.EmitInstruction(OpGetField, uint16(index))
|
|
}
|
|
|
|
func (c *Compiler) compileCallExpression(node *parser.CallExpression) {
|
|
if ident, ok := node.Function.(*parser.Identifier); ok {
|
|
slot := c.current.ResolveLocal(ident.Value)
|
|
if slot == 0 || slot == 1 {
|
|
for _, arg := range node.Arguments {
|
|
c.compileExpression(arg)
|
|
}
|
|
|
|
if slot == 0 {
|
|
c.current.EmitInstruction(OpCallLocal0, uint16(len(node.Arguments)))
|
|
} else {
|
|
c.current.EmitInstruction(OpCallLocal1, uint16(len(node.Arguments)))
|
|
}
|
|
return
|
|
}
|
|
}
|
|
|
|
c.compileExpression(node.Function)
|
|
for _, arg := range node.Arguments {
|
|
c.compileExpression(arg)
|
|
}
|
|
c.current.EmitInstruction(OpCall, uint16(len(node.Arguments)))
|
|
}
|
|
|
|
func (c *Compiler) compileIndexExpression(node *parser.IndexExpression) {
|
|
c.compileExpression(node.Left)
|
|
c.compileExpression(node.Index)
|
|
c.current.EmitInstruction(OpGetIndex)
|
|
}
|
|
|
|
func (c *Compiler) compileTableLiteral(node *parser.TableLiteral) {
|
|
c.current.EmitInstruction(OpNewTable)
|
|
|
|
for _, pair := range node.Pairs {
|
|
if pair.Key == nil {
|
|
c.compileExpression(pair.Value)
|
|
c.current.EmitInstruction(OpTableInsert)
|
|
} else {
|
|
c.current.EmitInstruction(OpDup)
|
|
c.compileExpression(pair.Key)
|
|
c.compileExpression(pair.Value)
|
|
c.current.EmitInstruction(OpSetIndex)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) compileStructStatement(node *parser.StructStatement) {
|
|
fields := make([]StructField, len(node.Fields))
|
|
for i, field := range node.Fields {
|
|
fields[i] = StructField{
|
|
Name: field.Name,
|
|
Type: c.typeInfoToValueType(field.TypeHint),
|
|
Offset: uint16(i),
|
|
}
|
|
}
|
|
|
|
structDef := Struct{
|
|
Name: node.Name,
|
|
Fields: fields,
|
|
Methods: make(map[string]uint16),
|
|
ID: node.ID,
|
|
}
|
|
|
|
c.current.Chunk.Structs = append(c.current.Chunk.Structs, structDef)
|
|
}
|
|
|
|
func (c *Compiler) compileMethodDefinition(node *parser.MethodDefinition) {
|
|
enclosing := c.current
|
|
c.current = NewCompilerState(FunctionTypeMethod)
|
|
c.current.parent = enclosing
|
|
c.enclosing = enclosing
|
|
|
|
c.current.BeginScope()
|
|
|
|
if err := c.current.AddLocal("self"); err != nil {
|
|
c.addError(err.Error())
|
|
return
|
|
}
|
|
c.current.MarkInitialized()
|
|
|
|
for _, param := range node.Function.Parameters {
|
|
if err := c.current.AddLocal(param.Name); err != nil {
|
|
c.addError(err.Error())
|
|
return
|
|
}
|
|
c.current.MarkInitialized()
|
|
}
|
|
|
|
for _, stmt := range node.Function.Body {
|
|
c.compileStatement(stmt)
|
|
}
|
|
|
|
c.current.EmitInstruction(OpReturnNil)
|
|
|
|
function := Function{
|
|
Name: node.MethodName,
|
|
Arity: len(node.Function.Parameters) + 1,
|
|
Variadic: node.Function.Variadic,
|
|
LocalCount: len(c.current.Locals),
|
|
UpvalCount: len(c.current.Upvalues),
|
|
Chunk: *c.current.Chunk,
|
|
Defaults: []Value{},
|
|
}
|
|
|
|
functionIndex := len(enclosing.Chunk.Functions)
|
|
enclosing.Chunk.Functions = append(enclosing.Chunk.Functions, function)
|
|
|
|
for i := range enclosing.Chunk.Structs {
|
|
if enclosing.Chunk.Structs[i].ID == node.StructID {
|
|
enclosing.Chunk.Structs[i].Methods[node.MethodName] = uint16(functionIndex)
|
|
break
|
|
}
|
|
}
|
|
|
|
c.current = enclosing
|
|
c.enclosing = nil
|
|
}
|
|
|
|
func (c *Compiler) compileStructConstructor(node *parser.StructConstructor) {
|
|
c.current.EmitInstruction(OpNewStruct, node.StructID)
|
|
|
|
for _, field := range node.Fields {
|
|
if field.Key != nil {
|
|
c.current.EmitInstruction(OpDup)
|
|
|
|
var fieldName string
|
|
if ident, ok := field.Key.(*parser.Identifier); ok {
|
|
fieldName = ident.Value
|
|
} else if str, ok := field.Key.(*parser.StringLiteral); ok {
|
|
fieldName = str.Value
|
|
} else {
|
|
c.addError("struct field names must be identifiers or strings")
|
|
continue
|
|
}
|
|
|
|
fieldIndex := c.findStructFieldIndex(node.StructID, fieldName)
|
|
if fieldIndex == -1 {
|
|
c.addError(fmt.Sprintf("struct has no field '%s'", fieldName))
|
|
continue
|
|
}
|
|
|
|
c.compileExpression(field.Value)
|
|
c.current.EmitInstruction(OpSetProperty, uint16(fieldIndex))
|
|
} else {
|
|
c.addError("struct constructors require named field assignments")
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) compileFunctionLiteral(node *parser.FunctionLiteral) {
|
|
enclosing := c.current
|
|
c.current = NewCompilerState(FunctionTypeFunction)
|
|
c.current.parent = enclosing
|
|
c.enclosing = enclosing
|
|
|
|
c.current.BeginScope()
|
|
|
|
for _, param := range node.Parameters {
|
|
if err := c.current.AddLocal(param.Name); err != nil {
|
|
c.addError(err.Error())
|
|
return
|
|
}
|
|
c.current.MarkInitialized()
|
|
}
|
|
|
|
for _, stmt := range node.Body {
|
|
c.compileStatement(stmt)
|
|
}
|
|
|
|
c.current.EmitInstruction(OpReturnNil)
|
|
|
|
function := Function{
|
|
Name: "",
|
|
Arity: len(node.Parameters),
|
|
Variadic: node.Variadic,
|
|
LocalCount: len(c.current.Locals),
|
|
UpvalCount: len(c.current.Upvalues),
|
|
Chunk: *c.current.Chunk,
|
|
Defaults: []Value{},
|
|
}
|
|
|
|
functionIndex := len(enclosing.Chunk.Functions)
|
|
enclosing.Chunk.Functions = append(enclosing.Chunk.Functions, function)
|
|
|
|
c.current = enclosing
|
|
c.enclosing = nil
|
|
|
|
c.current.EmitInstruction(OpClosure, uint16(functionIndex), uint16(function.UpvalCount))
|
|
}
|
|
|
|
// Control flow compilation
|
|
func (c *Compiler) compileIfStatement(node *parser.IfStatement) {
|
|
c.compileExpression(node.Condition)
|
|
|
|
thenJump := c.current.EmitJump(OpJumpIfFalse)
|
|
c.current.EmitInstruction(OpPop)
|
|
|
|
c.current.BeginScope()
|
|
for _, stmt := range node.Body {
|
|
c.compileStatement(stmt)
|
|
}
|
|
c.current.EndScope()
|
|
|
|
elseJump := c.current.EmitJump(OpJump)
|
|
c.current.PatchJump(thenJump)
|
|
c.current.EmitInstruction(OpPop)
|
|
|
|
var elseifJumps []int
|
|
for _, elseif := range node.ElseIfs {
|
|
c.compileExpression(elseif.Condition)
|
|
nextJump := c.current.EmitJump(OpJumpIfFalse)
|
|
c.current.EmitInstruction(OpPop)
|
|
|
|
c.current.BeginScope()
|
|
for _, stmt := range elseif.Body {
|
|
c.compileStatement(stmt)
|
|
}
|
|
c.current.EndScope()
|
|
|
|
elseifJumps = append(elseifJumps, c.current.EmitJump(OpJump))
|
|
c.current.PatchJump(nextJump)
|
|
c.current.EmitInstruction(OpPop)
|
|
}
|
|
|
|
if len(node.Else) > 0 {
|
|
c.current.BeginScope()
|
|
for _, stmt := range node.Else {
|
|
c.compileStatement(stmt)
|
|
}
|
|
c.current.EndScope()
|
|
}
|
|
|
|
c.current.PatchJump(elseJump)
|
|
for _, jump := range elseifJumps {
|
|
c.current.PatchJump(jump)
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) compileWhileStatement(node *parser.WhileStatement) {
|
|
c.current.EnterLoop()
|
|
|
|
c.compileExpression(node.Condition)
|
|
exitJump := c.current.EmitJump(OpJumpIfFalse)
|
|
c.current.EmitInstruction(OpPop)
|
|
|
|
c.current.BeginScope()
|
|
for _, stmt := range node.Body {
|
|
c.compileStatement(stmt)
|
|
}
|
|
c.current.EndScope()
|
|
|
|
jump := len(c.current.Chunk.Code) - c.current.LoopStart + 2
|
|
c.current.EmitInstruction(OpLoopBack, uint16(jump))
|
|
|
|
c.current.PatchJump(exitJump)
|
|
c.current.EmitInstruction(OpPop)
|
|
|
|
c.current.ExitLoop()
|
|
}
|
|
|
|
func (c *Compiler) compileForStatement(node *parser.ForStatement) {
|
|
c.current.BeginScope()
|
|
c.current.EnterLoop()
|
|
|
|
c.compileExpression(node.Start)
|
|
if err := c.current.AddLocal(node.Variable.Value); err != nil {
|
|
c.addError(err.Error())
|
|
return
|
|
}
|
|
c.current.MarkInitialized()
|
|
loopVar := len(c.current.Locals) - 1
|
|
|
|
c.compileExpression(node.End)
|
|
endSlot := len(c.current.Locals)
|
|
if err := c.current.AddLocal("__end"); err != nil {
|
|
c.addError(err.Error())
|
|
return
|
|
}
|
|
c.current.MarkInitialized()
|
|
|
|
if node.Step != nil {
|
|
c.compileExpression(node.Step)
|
|
} else {
|
|
c.current.EmitInstruction(OpLoadOne)
|
|
}
|
|
stepSlot := len(c.current.Locals)
|
|
if err := c.current.AddLocal("__step"); err != nil {
|
|
c.addError(err.Error())
|
|
return
|
|
}
|
|
c.current.MarkInitialized()
|
|
|
|
conditionStart := len(c.current.Chunk.Code)
|
|
c.emitLoadLocal(loopVar)
|
|
c.emitLoadLocal(endSlot)
|
|
c.current.EmitInstruction(OpLte)
|
|
exitJump := c.current.EmitJump(OpJumpIfFalse)
|
|
c.current.EmitInstruction(OpPop)
|
|
|
|
for _, stmt := range node.Body {
|
|
c.compileStatement(stmt)
|
|
}
|
|
|
|
c.emitLoadLocal(loopVar)
|
|
c.emitLoadLocal(stepSlot)
|
|
c.current.EmitInstruction(OpAdd)
|
|
c.emitStoreLocal(loopVar)
|
|
|
|
jumpBack := len(c.current.Chunk.Code) - conditionStart + 2
|
|
c.current.EmitInstruction(OpLoopBack, uint16(jumpBack))
|
|
|
|
c.current.PatchJump(exitJump)
|
|
c.current.EmitInstruction(OpPop)
|
|
|
|
c.current.ExitLoop()
|
|
c.current.EndScope()
|
|
}
|
|
|
|
func (c *Compiler) compileForInStatement(node *parser.ForInStatement) {
|
|
c.current.BeginScope()
|
|
c.current.EnterLoop()
|
|
|
|
c.compileExpression(node.Iterable)
|
|
|
|
if node.Key != nil {
|
|
if err := c.current.AddLocal(node.Key.Value); err != nil {
|
|
c.addError(err.Error())
|
|
return
|
|
}
|
|
c.current.MarkInitialized()
|
|
}
|
|
|
|
if err := c.current.AddLocal(node.Value.Value); err != nil {
|
|
c.addError(err.Error())
|
|
return
|
|
}
|
|
c.current.MarkInitialized()
|
|
|
|
conditionStart := len(c.current.Chunk.Code)
|
|
|
|
c.current.EmitInstruction(OpLoadNil)
|
|
c.current.EmitInstruction(OpNot)
|
|
|
|
exitJump := c.current.EmitJump(OpJumpIfFalse)
|
|
c.current.EmitInstruction(OpPop)
|
|
|
|
for _, stmt := range node.Body {
|
|
c.compileStatement(stmt)
|
|
}
|
|
|
|
jumpBack := len(c.current.Chunk.Code) - conditionStart + 2
|
|
c.current.EmitInstruction(OpLoopBack, uint16(jumpBack))
|
|
|
|
c.current.PatchJump(exitJump)
|
|
c.current.EmitInstruction(OpPop)
|
|
|
|
c.current.ExitLoop()
|
|
c.current.EndScope()
|
|
}
|
|
|
|
func (c *Compiler) compileReturnStatement(node *parser.ReturnStatement) {
|
|
if node.Value != nil {
|
|
c.compileExpression(node.Value)
|
|
c.current.EmitInstruction(OpReturn)
|
|
} else {
|
|
c.current.EmitInstruction(OpReturnNil)
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) compileExitStatement(node *parser.ExitStatement) {
|
|
if node.Value != nil {
|
|
c.compileExpression(node.Value)
|
|
} else {
|
|
c.current.EmitInstruction(OpLoadZero)
|
|
}
|
|
c.current.EmitInstruction(OpExit)
|
|
}
|
|
|
|
// Optimization
|
|
func (c *Compiler) optimizeChunk(chunk *Chunk) {
|
|
c.peepholeOptimize(chunk)
|
|
c.eliminateDeadCode(chunk)
|
|
}
|
|
|
|
func (c *Compiler) peepholeOptimize(chunk *Chunk) {
|
|
code := chunk.Code
|
|
changed := true
|
|
|
|
for changed {
|
|
changed = false
|
|
i := 0
|
|
|
|
for i < len(code)-6 {
|
|
op1, _, next1 := DecodeInstruction(code, i)
|
|
|
|
if next1 < len(code) {
|
|
op2, _, next2 := DecodeInstruction(code, next1)
|
|
|
|
if op1 == OpLoadConst && op2 == OpPop {
|
|
c.removeInstructions(chunk, i, next2)
|
|
changed = true
|
|
continue
|
|
}
|
|
|
|
if op1 == OpLoadZero && op2 == OpAdd {
|
|
code[i] = uint8(OpNoop)
|
|
code[next1] = uint8(OpNoop)
|
|
changed = true
|
|
}
|
|
|
|
if op1 == OpNeg && op2 == OpNeg {
|
|
code[i] = uint8(OpNoop)
|
|
code[next1] = uint8(OpNoop)
|
|
changed = true
|
|
}
|
|
|
|
if (op1 == OpLoadTrue || op1 == OpLoadFalse) && op2 == OpNot {
|
|
if op1 == OpLoadTrue {
|
|
code[i] = uint8(OpLoadFalse)
|
|
} else {
|
|
code[i] = uint8(OpLoadTrue)
|
|
}
|
|
code[next1] = uint8(OpNoop)
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
i = next1
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) removeInstructions(chunk *Chunk, start, end int) {
|
|
for i := start; i < end && i < len(chunk.Code); i++ {
|
|
chunk.Code[i] = uint8(OpNoop)
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) eliminateDeadCode(chunk *Chunk) {
|
|
code := chunk.Code
|
|
i := 0
|
|
|
|
for i < len(code) {
|
|
op, _, next := DecodeInstruction(code, i)
|
|
|
|
if op == OpReturn || op == OpReturnNil || op == OpExit {
|
|
for j := next; j < len(code); j++ {
|
|
nextOp, _, nextNext := DecodeInstruction(code, j)
|
|
if c.isJumpTarget(chunk, j) {
|
|
break
|
|
}
|
|
if nextOp == OpNoop {
|
|
j = nextNext - 1
|
|
continue
|
|
}
|
|
code[j] = uint8(OpNoop)
|
|
j = nextNext - 1
|
|
}
|
|
}
|
|
|
|
i = next
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) isJumpTarget(chunk *Chunk, offset int) bool {
|
|
return false
|
|
}
|
|
|
|
// Helper methods
|
|
func (c *Compiler) resolveUpvalue(name string) int {
|
|
if c.enclosing == nil {
|
|
return -1
|
|
}
|
|
|
|
local := c.enclosing.ResolveLocal(name)
|
|
if local != -1 {
|
|
c.enclosing.Locals[local].IsCaptured = true
|
|
return c.current.AddUpvalue(uint8(local), true)
|
|
}
|
|
|
|
upvalue := c.resolveUpvalueInEnclosing(name)
|
|
if upvalue != -1 {
|
|
return c.current.AddUpvalue(uint8(upvalue), false)
|
|
}
|
|
|
|
return -1
|
|
}
|
|
|
|
func (c *Compiler) resolveUpvalueInEnclosing(name string) int {
|
|
if c.enclosing == nil {
|
|
return -1
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (c *Compiler) typeInfoToValueType(typeInfo parser.TypeInfo) ValueType {
|
|
switch typeInfo.Type {
|
|
case parser.TypeNumber:
|
|
return ValueNumber
|
|
case parser.TypeString:
|
|
return ValueString
|
|
case parser.TypeBool:
|
|
return ValueBool
|
|
case parser.TypeNil:
|
|
return ValueNil
|
|
case parser.TypeTable:
|
|
return ValueTable
|
|
case parser.TypeFunction:
|
|
return ValueFunction
|
|
case parser.TypeStruct:
|
|
return ValueStruct
|
|
default:
|
|
return ValueNil
|
|
}
|
|
}
|
|
|
|
func (c *Compiler) findStructFieldIndex(structID uint16, fieldName string) int {
|
|
for _, structDef := range c.current.Chunk.Structs {
|
|
if structDef.ID == structID {
|
|
for i, field := range structDef.Fields {
|
|
if field.Name == fieldName {
|
|
return i
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func (c *Compiler) addError(message string) {
|
|
c.errors = append(c.errors, CompileError{
|
|
Message: message,
|
|
Line: c.current.CurrentLine,
|
|
Column: 0,
|
|
})
|
|
}
|
|
|
|
func (c *Compiler) getLineFromNode(node any) int {
|
|
return 0 // Placeholder - would extract from AST node
|
|
}
|
|
|
|
func (c *Compiler) Errors() []CompileError { return c.errors }
|
|
func (c *Compiler) HasErrors() bool { return len(c.errors) > 0 }
|