Compare commits

..

No commits in common. "2179a39e2c23d48923c9eea92989d571aaf87adb" and "53cdb95b6e3ad7f0a22e76f21a93f1e49ac604dd" have entirely different histories.

4 changed files with 250 additions and 1574 deletions

View File

@ -232,18 +232,3 @@ func GetOperandCount(op Opcode) int {
func InstructionSize(op Opcode) int { func InstructionSize(op Opcode) int {
return 1 + (GetOperandCount(op) * 2) // 1 byte opcode + 2 bytes per operand return 1 + (GetOperandCount(op) * 2) // 1 byte opcode + 2 bytes per operand
} }
var opcodeNames = map[Opcode]string{
OpLoadConst: "OP_LOAD_CONST",
OpLoadLocal: "OP_LOAD_LOCAL",
OpStoreLocal: "OP_STORE_LOCAL",
OpAdd: "OP_ADD",
OpSub: "OP_SUB",
OpMul: "OP_MUL",
OpDiv: "OP_DIV",
OpJump: "OP_JUMP",
OpJumpIfTrue: "OP_JUMP_TRUE",
OpJumpIfFalse: "OP_JUMP_FALSE",
OpReturn: "OP_RETURN",
OpEcho: "OP_ECHO",
}

File diff suppressed because it is too large Load Diff

View File

@ -1,297 +0,0 @@
package compiler
import "fmt"
// Constants for compiler limits
const (
MaxLocals = 256 // Maximum local variables per function
MaxUpvalues = 256 // Maximum upvalues per function
MaxConstants = 65536 // Maximum constants per chunk
)
// CompilerState holds state during compilation
type CompilerState struct {
Chunk *Chunk // Current chunk being compiled
Constants map[string]int // Constant pool index mapping
Functions []Function // Compiled functions
Structs []Struct // Compiled structs
Locals []Local // Local variable stack
Upvalues []UpvalueRef // Upvalue definitions
ScopeDepth int // Current scope nesting level
FunctionType FunctionType // Type of function being compiled
BreakJumps []int // Break jump addresses for loops
ContinueJumps []int // Continue jump addresses for loops
LoopStart int // Start of current loop for continue
LoopDepth int // Current loop nesting depth
parent *CompilerState // Parent compiler state for nested functions
CurrentLine int // Current source line being compiled
}
// Local represents a local variable during compilation
type Local struct {
Name string // Variable name
Depth int // Scope depth where declared
IsCaptured bool // Whether variable is captured by closure
Slot int // Stack slot index
}
// UpvalueRef represents an upvalue reference during compilation
type UpvalueRef struct {
Index uint8 // Index in enclosing function's locals or upvalues
IsLocal bool // True if captures local, false if captures upvalue
}
// FunctionType represents the type of function being compiled
type FunctionType uint8
const (
FunctionTypeScript FunctionType = iota // Top-level script
FunctionTypeFunction // Regular function
FunctionTypeMethod // Struct method
)
// CompileError represents a compilation error with location information
type CompileError struct {
Message string
Line int
Column int
}
func (ce CompileError) Error() string {
return fmt.Sprintf("Compile error at line %d, column %d: %s", ce.Line, ce.Column, ce.Message)
}
// NewCompilerState creates a new compiler state for compilation
func NewCompilerState(functionType FunctionType) *CompilerState {
return &CompilerState{
Chunk: NewChunk(),
Constants: make(map[string]int),
Functions: make([]Function, 0),
Structs: make([]Struct, 0),
Locals: make([]Local, 0, MaxLocals),
Upvalues: make([]UpvalueRef, 0, MaxUpvalues),
ScopeDepth: 0,
FunctionType: functionType,
BreakJumps: make([]int, 0),
ContinueJumps: make([]int, 0),
LoopStart: -1,
LoopDepth: 0,
parent: nil,
}
}
// NewChunk creates a new bytecode chunk
func NewChunk() *Chunk {
return &Chunk{
Code: make([]uint8, 0, 256),
Constants: make([]Value, 0, 64),
Lines: make([]int, 0, 256),
Functions: make([]Function, 0),
Structs: make([]Struct, 0),
}
}
// Scope management methods
func (cs *CompilerState) BeginScope() {
cs.ScopeDepth++
}
func (cs *CompilerState) EndScope() {
cs.ScopeDepth--
// Remove locals that go out of scope
for len(cs.Locals) > 0 && cs.Locals[len(cs.Locals)-1].Depth > cs.ScopeDepth {
local := cs.Locals[len(cs.Locals)-1]
if local.IsCaptured {
// Emit close upvalue instruction
cs.EmitByte(uint8(OpCloseUpvalue))
} else {
// Emit pop instruction
cs.EmitByte(uint8(OpPop))
}
cs.Locals = cs.Locals[:len(cs.Locals)-1]
}
}
// Local variable management
func (cs *CompilerState) AddLocal(name string) error {
if len(cs.Locals) >= MaxLocals {
return CompileError{
Message: "too many local variables in function",
}
}
local := Local{
Name: name,
Depth: -1, // Mark as uninitialized
IsCaptured: false,
Slot: len(cs.Locals),
}
cs.Locals = append(cs.Locals, local)
return nil
}
func (cs *CompilerState) MarkInitialized() {
if len(cs.Locals) > 0 {
cs.Locals[len(cs.Locals)-1].Depth = cs.ScopeDepth
}
}
func (cs *CompilerState) ResolveLocal(name string) int {
for i := len(cs.Locals) - 1; i >= 0; i-- {
local := &cs.Locals[i]
if local.Name == name {
if local.Depth == -1 {
// Variable used before initialization
return -2
}
return i
}
}
return -1
}
// Upvalue management
func (cs *CompilerState) AddUpvalue(index uint8, isLocal bool) int {
upvalueCount := len(cs.Upvalues)
// Check if upvalue already exists
for i := range upvalueCount {
upvalue := &cs.Upvalues[i]
if upvalue.Index == index && upvalue.IsLocal == isLocal {
return i
}
}
if upvalueCount >= MaxUpvalues {
return -1 // Too many upvalues
}
cs.Upvalues = append(cs.Upvalues, UpvalueRef{
Index: index,
IsLocal: isLocal,
})
return upvalueCount
}
// Constant pool management
func (cs *CompilerState) AddConstant(value Value) int {
// Check if constant already exists to avoid duplicates
key := cs.valueKey(value)
if index, exists := cs.Constants[key]; exists {
return index
}
if len(cs.Chunk.Constants) >= MaxConstants {
return -1 // Too many constants
}
index := len(cs.Chunk.Constants)
cs.Chunk.Constants = append(cs.Chunk.Constants, value)
cs.Constants[key] = index
return index
}
// Generate unique key for value in constant pool
func (cs *CompilerState) valueKey(value Value) string {
switch value.Type {
case ValueNil:
return "nil"
case ValueBool:
if value.Data.(bool) {
return "bool:true"
}
return "bool:false"
case ValueNumber:
return fmt.Sprintf("number:%g", value.Data.(float64))
case ValueString:
return fmt.Sprintf("string:%s", value.Data.(string))
default:
// For complex types, use memory address as fallback
return fmt.Sprintf("%T:%p", value.Data, value.Data)
}
}
// Bytecode emission methods
func (cs *CompilerState) EmitByte(byte uint8) {
cs.Chunk.Code = append(cs.Chunk.Code, byte)
cs.Chunk.Lines = append(cs.Chunk.Lines, cs.CurrentLine)
}
func (cs *CompilerState) EmitBytes(bytes ...uint8) {
for _, b := range bytes {
cs.EmitByte(b)
}
}
func (cs *CompilerState) EmitInstruction(op Opcode, operands ...uint16) {
bytes := EncodeInstruction(op, operands...)
cs.EmitBytes(bytes...)
}
func (cs *CompilerState) EmitJump(op Opcode) int {
cs.EmitByte(uint8(op))
cs.EmitByte(0xFF) // Placeholder
cs.EmitByte(0xFF) // Placeholder
return len(cs.Chunk.Code) - 2 // Return offset of jump address
}
func (cs *CompilerState) PatchJump(offset int) {
// Calculate jump distance
jump := len(cs.Chunk.Code) - offset - 2
if jump > 65535 {
// Jump too large - would need long jump instruction
return
}
cs.Chunk.Code[offset] = uint8(jump & 0xFF)
cs.Chunk.Code[offset+1] = uint8((jump >> 8) & 0xFF)
}
// Loop management
func (cs *CompilerState) EnterLoop() {
cs.LoopStart = len(cs.Chunk.Code)
cs.LoopDepth++
}
func (cs *CompilerState) ExitLoop() {
cs.LoopDepth--
if cs.LoopDepth == 0 {
cs.LoopStart = -1
}
// Patch break jumps
for _, jumpOffset := range cs.BreakJumps {
cs.PatchJump(jumpOffset)
}
cs.BreakJumps = cs.BreakJumps[:0]
// Patch continue jumps
for _, jumpOffset := range cs.ContinueJumps {
jump := cs.LoopStart - jumpOffset - 2
if jump < 65535 {
cs.Chunk.Code[jumpOffset] = uint8(jump & 0xFF)
cs.Chunk.Code[jumpOffset+1] = uint8((jump >> 8) & 0xFF)
}
}
cs.ContinueJumps = cs.ContinueJumps[:0]
}
func (cs *CompilerState) EmitBreak() {
jumpOffset := cs.EmitJump(OpJump)
cs.BreakJumps = append(cs.BreakJumps, jumpOffset)
}
func (cs *CompilerState) EmitContinue() {
if cs.LoopStart != -1 {
jumpOffset := cs.EmitJump(OpJump)
cs.ContinueJumps = append(cs.ContinueJumps, jumpOffset)
}
}
func (cs *CompilerState) SetLine(line int) {
cs.CurrentLine = line
}

View File

@ -1,364 +0,0 @@
package compiler_test
import (
"testing"
"git.sharkk.net/Sharkk/Mako/compiler"
"git.sharkk.net/Sharkk/Mako/parser"
)
// Helper function to compile source code and return chunk
func compileSource(t *testing.T, source string) *compiler.Chunk {
lexer := parser.NewLexer(source)
p := parser.NewParser(lexer)
program := p.ParseProgram()
if p.HasErrors() {
t.Fatalf("Parser errors: %v", p.ErrorStrings())
}
comp := compiler.NewCompiler()
chunk, errors := comp.Compile(program)
if len(errors) > 0 {
t.Fatalf("Compiler errors: %v", errors)
}
return chunk
}
// Helper to check instruction at position
func checkInstruction(t *testing.T, chunk *compiler.Chunk, pos int, expected compiler.Opcode, operands ...uint16) {
if pos >= len(chunk.Code) {
t.Fatalf("Position %d out of bounds (code length: %d)", pos, len(chunk.Code))
}
op, actualOperands, _ := compiler.DecodeInstruction(chunk.Code, pos)
if op != expected {
t.Errorf("Expected opcode %v at position %d, got %v", expected, pos, op)
}
if len(actualOperands) != len(operands) {
t.Errorf("Expected %d operands, got %d", len(operands), len(actualOperands))
return
}
for i, expected := range operands {
if actualOperands[i] != expected {
t.Errorf("Expected operand %d to be %d, got %d", i, expected, actualOperands[i])
}
}
}
// Test literal compilation
func TestNumberLiteral(t *testing.T) {
chunk := compileSource(t, "echo 42")
// Should have one constant (42) and load it
if len(chunk.Constants) != 1 {
t.Fatalf("Expected 1 constant, got %d", len(chunk.Constants))
}
if chunk.Constants[0].Type != compiler.ValueNumber {
t.Errorf("Expected number constant, got %v", chunk.Constants[0].Type)
}
if chunk.Constants[0].Data.(float64) != 42.0 {
t.Errorf("Expected constant value 42, got %v", chunk.Constants[0].Data)
}
// Check bytecode: OpLoadConst 0, OpEcho, OpReturnNil
checkInstruction(t, chunk, 0, compiler.OpLoadConst, 0)
checkInstruction(t, chunk, 3, compiler.OpEcho)
checkInstruction(t, chunk, 4, compiler.OpReturnNil)
}
func TestStringLiteral(t *testing.T) {
chunk := compileSource(t, `echo "hello"`)
if len(chunk.Constants) != 1 {
t.Fatalf("Expected 1 constant, got %d", len(chunk.Constants))
}
if chunk.Constants[0].Type != compiler.ValueString {
t.Errorf("Expected string constant, got %v", chunk.Constants[0].Type)
}
if chunk.Constants[0].Data.(string) != "hello" {
t.Errorf("Expected constant value 'hello', got %v", chunk.Constants[0].Data)
}
checkInstruction(t, chunk, 0, compiler.OpLoadConst, 0)
}
func TestBooleanLiterals(t *testing.T) {
chunk := compileSource(t, "echo true")
if chunk.Constants[0].Type != compiler.ValueBool {
t.Errorf("Expected bool constant, got %v", chunk.Constants[0].Type)
}
if chunk.Constants[0].Data.(bool) != true {
t.Errorf("Expected true, got %v", chunk.Constants[0].Data)
}
}
func TestNilLiteral(t *testing.T) {
chunk := compileSource(t, "echo nil")
if chunk.Constants[0].Type != compiler.ValueNil {
t.Errorf("Expected nil constant, got %v", chunk.Constants[0].Type)
}
}
// Test arithmetic operations
func TestArithmetic(t *testing.T) {
tests := []struct {
source string
expected compiler.Opcode
}{
{"echo 1 + 2", compiler.OpAdd},
{"echo 5 - 3", compiler.OpSub},
{"echo 4 * 6", compiler.OpMul},
{"echo 8 / 2", compiler.OpDiv},
}
for _, test := range tests {
chunk := compileSource(t, test.source)
// Should have: LoadConst 0, LoadConst 1, OpArithmetic, OpEcho, OpReturnNil
checkInstruction(t, chunk, 0, compiler.OpLoadConst, 0)
checkInstruction(t, chunk, 3, compiler.OpLoadConst, 1)
checkInstruction(t, chunk, 6, test.expected)
checkInstruction(t, chunk, 7, compiler.OpEcho)
}
}
// Test comparison operations
func TestComparison(t *testing.T) {
tests := []struct {
source string
expected compiler.Opcode
}{
{"echo 1 == 2", compiler.OpEq},
{"echo 1 != 2", compiler.OpNeq},
{"echo 1 < 2", compiler.OpLt},
{"echo 1 <= 2", compiler.OpLte},
{"echo 1 > 2", compiler.OpGt},
{"echo 1 >= 2", compiler.OpGte},
}
for _, test := range tests {
chunk := compileSource(t, test.source)
checkInstruction(t, chunk, 6, test.expected)
}
}
// Test prefix operations
func TestPrefixOperations(t *testing.T) {
tests := []struct {
source string
expected compiler.Opcode
}{
{"echo -42", compiler.OpNeg},
{"echo not true", compiler.OpNot},
}
for _, test := range tests {
chunk := compileSource(t, test.source)
checkInstruction(t, chunk, 3, test.expected)
}
}
// Test variable assignment
func TestLocalAssignment(t *testing.T) {
// Test local assignment within a function scope
chunk := compileSource(t, `
fn test()
x: number = 42
end
`)
// This tests function compilation which is not yet implemented
// For now, just check that it doesn't crash
if chunk == nil {
t.Skip("Function compilation not yet implemented")
}
}
func TestGlobalAssignment(t *testing.T) {
chunk := compileSource(t, "x = 42")
// Should have: LoadConst 0, StoreGlobal 1, OpReturnNil
// Constants: [42, "x"]
if len(chunk.Constants) != 2 {
t.Fatalf("Expected 2 constants, got %d", len(chunk.Constants))
}
// Check that we have the number and variable name
if chunk.Constants[0].Data.(float64) != 42.0 {
t.Errorf("Expected first constant to be 42, got %v", chunk.Constants[0].Data)
}
if chunk.Constants[1].Data.(string) != "x" {
t.Errorf("Expected second constant to be 'x', got %v", chunk.Constants[1].Data)
}
checkInstruction(t, chunk, 0, compiler.OpLoadConst, 0) // Load 42
checkInstruction(t, chunk, 3, compiler.OpStoreGlobal, 1) // Store to "x"
}
// Test echo statement
func TestEchoStatement(t *testing.T) {
chunk := compileSource(t, "echo 42")
// Should have: LoadConst 0, OpEcho, OpReturnNil
checkInstruction(t, chunk, 0, compiler.OpLoadConst, 0)
checkInstruction(t, chunk, 3, compiler.OpEcho)
checkInstruction(t, chunk, 4, compiler.OpReturnNil)
}
// Test if statement
func TestIfStatement(t *testing.T) {
chunk := compileSource(t, `
if true then
echo 1
end
`)
// Should start with: LoadConst, JumpIfFalse (with offset), Pop
checkInstruction(t, chunk, 0, compiler.OpLoadConst, 0) // Load true
// JumpIfFalse has 1 operand (the jump offset), but we don't need to check the exact value
op, operands, _ := compiler.DecodeInstruction(chunk.Code, 3)
if op != compiler.OpJumpIfFalse {
t.Errorf("Expected OpJumpIfFalse at position 3, got %v", op)
}
if len(operands) != 1 {
t.Errorf("Expected 1 operand for JumpIfFalse, got %d", len(operands))
}
checkInstruction(t, chunk, 6, compiler.OpPop) // Pop condition
}
// Test while loop
func TestWhileLoop(t *testing.T) {
chunk := compileSource(t, `
while true do
break
end
`)
// Should have condition evaluation and loop structure
checkInstruction(t, chunk, 0, compiler.OpLoadConst, 0) // Load true
// JumpIfFalse has 1 operand (the jump offset)
op, operands, _ := compiler.DecodeInstruction(chunk.Code, 3)
if op != compiler.OpJumpIfFalse {
t.Errorf("Expected OpJumpIfFalse at position 3, got %v", op)
}
if len(operands) != 1 {
t.Errorf("Expected 1 operand for JumpIfFalse, got %d", len(operands))
}
}
// Test table creation
func TestTableLiteral(t *testing.T) {
chunk := compileSource(t, "echo {1, 2, 3}")
// Should start with OpNewTable
checkInstruction(t, chunk, 0, compiler.OpNewTable)
}
// Test table with key-value pairs
func TestTableWithKeys(t *testing.T) {
chunk := compileSource(t, `echo {x = 1, y = 2}`)
checkInstruction(t, chunk, 0, compiler.OpNewTable)
// Should have subsequent operations to set fields
}
// Test function call
func TestFunctionCall(t *testing.T) {
chunk := compileSource(t, "print(42)")
// Should have: LoadGlobal "print", LoadConst 42, Call 1
// The exact positions depend on constant ordering
found := false
for i := 0; i < len(chunk.Code)-2; i++ {
op, operands, _ := compiler.DecodeInstruction(chunk.Code, i)
if op == compiler.OpCall && len(operands) > 0 && operands[0] == 1 {
found = true
break
}
}
if !found {
t.Error("Expected OpCall with 1 argument")
}
}
// Test constant deduplication
func TestConstantDeduplication(t *testing.T) {
chunk := compileSource(t, "echo 42\necho 42\necho 42")
// Should only have one constant despite multiple uses
if len(chunk.Constants) != 1 {
t.Errorf("Expected 1 constant (deduplicated), got %d", len(chunk.Constants))
}
}
// Test short-circuit evaluation
func TestShortCircuitAnd(t *testing.T) {
chunk := compileSource(t, "echo true and false")
// Should have conditional jumping for short-circuit
found := false
for i := 0; i < len(chunk.Code); i++ {
op, _, _ := compiler.DecodeInstruction(chunk.Code, i)
if op == compiler.OpJumpIfFalse {
found = true
break
}
}
if !found {
t.Error("Expected JumpIfFalse for short-circuit and")
}
}
func TestShortCircuitOr(t *testing.T) {
chunk := compileSource(t, "echo false or true")
// Should have conditional jumping for short-circuit
foundFalseJump := false
foundJump := false
for i := 0; i < len(chunk.Code); i++ {
op, _, _ := compiler.DecodeInstruction(chunk.Code, i)
if op == compiler.OpJumpIfFalse {
foundFalseJump = true
}
if op == compiler.OpJump {
foundJump = true
}
}
if !foundFalseJump || !foundJump {
t.Error("Expected JumpIfFalse and Jump for short-circuit or")
}
}
// Test complex expressions
func TestComplexExpression(t *testing.T) {
chunk := compileSource(t, "echo 1 + 2 * 3")
// Should follow correct precedence: Load 1, Load 2, Load 3, Mul, Add
if len(chunk.Constants) != 3 {
t.Fatalf("Expected 3 constants, got %d", len(chunk.Constants))
}
// Verify constants
expected := []float64{1, 2, 3}
for i, exp := range expected {
if chunk.Constants[i].Data.(float64) != exp {
t.Errorf("Expected constant %d to be %v, got %v", i, exp, chunk.Constants[i].Data)
}
}
}