1414 lines
34 KiB
Go
1414 lines
34 KiB
Go
package compiler
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"c65gm/internal/preproc"
|
|
)
|
|
|
|
// Helper to create a test Line
|
|
func makeLine(text string) preproc.Line {
|
|
return preproc.Line{
|
|
RawText: text,
|
|
Text: text,
|
|
Filename: "test.c65",
|
|
LineNo: 1,
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: 0,
|
|
}
|
|
}
|
|
|
|
func TestFixIntuitiveFuncs(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected string
|
|
}{
|
|
{"func(a,b)", "func ( a b )"},
|
|
{"func( a, b )", "func ( a b )"},
|
|
{"func(a,b,c)", "func ( a b c )"},
|
|
{"CALL func()", "CALL func ( )"},
|
|
{"func()", "func ( )"},
|
|
{`func("hello",x)`, `func ( "hello" x )`},
|
|
{`func("a,b",c)`, `func ( "a,b" c )`},
|
|
{"func ( a , b )", "func ( a b )"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result := fixIntuitiveFuncs(tt.input)
|
|
if result != tt.expected {
|
|
t.Errorf("fixIntuitiveFuncs(%q) = %q, want %q", tt.input, result, tt.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBuildComplexParams(t *testing.T) {
|
|
tests := []struct {
|
|
input []string
|
|
expected []string
|
|
wantErr bool
|
|
}{
|
|
{
|
|
input: []string{"a", "b", "c"},
|
|
expected: []string{"a", "b", "c"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
input: []string{"{BYTE", "x}"},
|
|
expected: []string{"{BYTE x}"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
input: []string{"{WORD", "ptr}"},
|
|
expected: []string{"{WORD ptr}"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
input: []string{"{BYTE", "a}", "{WORD", "b}"},
|
|
expected: []string{"{BYTE a}", "{WORD b}"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
input: []string{"x", "{BYTE", "a}", "y"},
|
|
expected: []string{"x", "{BYTE a}", "y"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
input: []string{"{BYTE", "x"},
|
|
expected: nil,
|
|
wantErr: true, // unclosed
|
|
},
|
|
{
|
|
input: []string{"x}"},
|
|
expected: nil,
|
|
wantErr: true, // unmatched close
|
|
},
|
|
{
|
|
input: []string{"{BYTE", "{WORD", "x}"},
|
|
expected: nil,
|
|
wantErr: true, // nested open
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result, err := buildComplexParams(tt.input)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Errorf("buildComplexParams(%v) expected error, got nil", tt.input)
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Errorf("buildComplexParams(%v) unexpected error: %v", tt.input, err)
|
|
continue
|
|
}
|
|
if len(result) != len(tt.expected) {
|
|
t.Errorf("buildComplexParams(%v) = %v, want %v", tt.input, result, tt.expected)
|
|
continue
|
|
}
|
|
for i := range result {
|
|
if result[i] != tt.expected[i] {
|
|
t.Errorf("buildComplexParams(%v)[%d] = %q, want %q", tt.input, i, result[i], tt.expected[i])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseParams(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
expected []string
|
|
wantErr bool
|
|
}{
|
|
{"FUNC test", []string{"FUNC", "test"}, false},
|
|
{"FUNC test ( a b )", []string{"FUNC", "test", "(", "a", "b", ")"}, false},
|
|
{`CALL print ( "hello world" )`, []string{"CALL", "print", "(", `"hello world"`, ")"}, false},
|
|
{" FUNC test ", []string{"FUNC", "test"}, false},
|
|
{`func("unterminated`, nil, true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
result, err := parseParams(tt.input)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Errorf("parseParams(%q) expected error, got nil", tt.input)
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Errorf("parseParams(%q) unexpected error: %v", tt.input, err)
|
|
continue
|
|
}
|
|
if len(result) != len(tt.expected) {
|
|
t.Errorf("parseParams(%q) = %v, want %v", tt.input, result, tt.expected)
|
|
continue
|
|
}
|
|
for i := range result {
|
|
if result[i] != tt.expected[i] {
|
|
t.Errorf("parseParams(%q)[%d] = %q, want %q", tt.input, i, result[i], tt.expected[i])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseParamSpec(t *testing.T) {
|
|
tests := []struct {
|
|
input string
|
|
wantDir ParamDirection
|
|
wantName string
|
|
wantImplicit bool
|
|
wantImplDecl string
|
|
wantErr bool
|
|
}{
|
|
{"varname", DirIn, "varname", false, "", false},
|
|
{"in:varname", DirIn, "varname", false, "", false},
|
|
{"out:varname", DirOut, "varname", false, "", false},
|
|
{"io:varname", DirIn | DirOut, "varname", false, "", false},
|
|
{"{BYTE temp}", DirIn, "temp", true, "BYTE temp", false},
|
|
{"{WORD result}", DirIn, "result", true, "WORD result", false},
|
|
{"out:{BYTE x}", DirOut, "x", true, "BYTE x", false},
|
|
{"io:{WORD ptr}", DirIn | DirOut, "ptr", true, "WORD ptr", false},
|
|
{"invalid:dir:x", 0, "", false, "", true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
dir, name, isImpl, implDecl, err := parseParamSpec(tt.input)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Errorf("parseParamSpec(%q) expected error, got nil", tt.input)
|
|
}
|
|
continue
|
|
}
|
|
if err != nil {
|
|
t.Errorf("parseParamSpec(%q) unexpected error: %v", tt.input, err)
|
|
continue
|
|
}
|
|
if dir != tt.wantDir {
|
|
t.Errorf("parseParamSpec(%q) direction = %v, want %v", tt.input, dir, tt.wantDir)
|
|
}
|
|
if name != tt.wantName {
|
|
t.Errorf("parseParamSpec(%q) name = %q, want %q", tt.input, name, tt.wantName)
|
|
}
|
|
if isImpl != tt.wantImplicit {
|
|
t.Errorf("parseParamSpec(%q) implicit = %v, want %v", tt.input, isImpl, tt.wantImplicit)
|
|
}
|
|
if implDecl != tt.wantImplDecl {
|
|
t.Errorf("parseParamSpec(%q) implDecl = %q, want %q", tt.input, implDecl, tt.wantImplDecl)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncDecl_VoidFunction(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
asm, err := fh.HandleFuncDecl(makeLine("FUNC test_void"))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncDecl failed: %v", err)
|
|
}
|
|
|
|
if len(asm) != 1 {
|
|
t.Fatalf("expected 1 asm line, got %d", len(asm))
|
|
}
|
|
if asm[0] != "test_void" {
|
|
t.Errorf("expected label 'test_void', got %q", asm[0])
|
|
}
|
|
|
|
if !fh.FuncExists("test_void") {
|
|
t.Error("function should exist")
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncDecl_WithExistingParams(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
// Pre-declare parameters
|
|
st.AddVar("x", "test_func", KindByte, 0)
|
|
st.AddVar("y", "test_func", KindWord, 0)
|
|
|
|
asm, err := fh.HandleFuncDecl(makeLine("FUNC test_func ( x y )"))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncDecl failed: %v", err)
|
|
}
|
|
|
|
if len(asm) != 1 {
|
|
t.Fatalf("expected 1 asm line, got %d", len(asm))
|
|
}
|
|
|
|
funcDecl := fh.findFunc("test_func")
|
|
if funcDecl == nil {
|
|
t.Fatal("function not found")
|
|
}
|
|
if len(funcDecl.Params) != 2 {
|
|
t.Fatalf("expected 2 params, got %d", len(funcDecl.Params))
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncDecl_ImplicitDeclarations(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
asm, err := fh.HandleFuncDecl(makeLine("FUNC test_impl ( {BYTE a} {WORD b} )"))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncDecl failed: %v", err)
|
|
}
|
|
|
|
if len(asm) != 1 {
|
|
t.Fatalf("expected 1 asm line, got %d", len(asm))
|
|
}
|
|
|
|
// Check that variables were declared
|
|
symA := st.Lookup("a", []string{"test_impl"})
|
|
if symA == nil {
|
|
t.Fatal("parameter 'a' not declared")
|
|
}
|
|
if !symA.IsByte() {
|
|
t.Error("parameter 'a' should be byte")
|
|
}
|
|
|
|
symB := st.Lookup("b", []string{"test_impl"})
|
|
if symB == nil {
|
|
t.Fatal("parameter 'b' not declared")
|
|
}
|
|
if !symB.IsWord() {
|
|
t.Error("parameter 'b' should be word")
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncDecl_WithDirections(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
_, err := fh.HandleFuncDecl(makeLine("FUNC test_dir ( in:{BYTE a} out:{BYTE b} io:{WORD c} )"))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncDecl failed: %v", err)
|
|
}
|
|
|
|
funcDecl := fh.findFunc("test_dir")
|
|
if funcDecl == nil {
|
|
t.Fatal("function not found")
|
|
}
|
|
|
|
if len(funcDecl.Params) != 3 {
|
|
t.Fatalf("expected 3 params, got %d", len(funcDecl.Params))
|
|
}
|
|
|
|
if funcDecl.Params[0].Direction != DirIn {
|
|
t.Error("param 0 should be DirIn")
|
|
}
|
|
if funcDecl.Params[1].Direction != DirOut {
|
|
t.Error("param 1 should be DirOut")
|
|
}
|
|
if funcDecl.Params[2].Direction != (DirIn | DirOut) {
|
|
t.Error("param 2 should be DirIn|DirOut")
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncDecl_Errors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
line string
|
|
preDecl func(*SymbolTable)
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "redeclaration",
|
|
line: "FUNC duplicate ( {BYTE x} )",
|
|
preDecl: func(st *SymbolTable) {},
|
|
wantErr: "already declared",
|
|
},
|
|
{
|
|
name: "missing param",
|
|
line: "FUNC test ( missing )",
|
|
wantErr: "not declared",
|
|
},
|
|
{
|
|
name: "const param",
|
|
line: "FUNC test ( constval )",
|
|
preDecl: func(st *SymbolTable) {
|
|
st.AddConst("constval", "test", KindByte, 42)
|
|
},
|
|
wantErr: "cannot be a constant",
|
|
},
|
|
{
|
|
name: "invalid implicit",
|
|
line: "FUNC test ( {INVALID x} )",
|
|
wantErr: "must be BYTE or WORD",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
if tt.preDecl != nil {
|
|
tt.preDecl(st)
|
|
}
|
|
|
|
// Special case for redeclaration test
|
|
if tt.name == "redeclaration" {
|
|
fh.HandleFuncDecl(makeLine("FUNC duplicate ( {BYTE x} )"))
|
|
}
|
|
|
|
_, err := fh.HandleFuncDecl(makeLine(tt.line))
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), tt.wantErr) {
|
|
t.Errorf("error %q does not contain %q", err.Error(), tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncCall_VarArgs(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
// Declare function with params
|
|
st.AddVar("param_a", "test_func", KindByte, 0)
|
|
st.AddVar("param_b", "test_func", KindWord, 0)
|
|
fh.HandleFuncDecl(makeLine("FUNC test_func ( param_a param_b )"))
|
|
|
|
// Declare caller variables
|
|
st.AddVar("var_a", "", KindByte, 0)
|
|
st.AddVar("var_b", "", KindWord, 0)
|
|
|
|
asm, err := fh.HandleFuncCall(makeLine("CALL test_func ( var_a var_b )"))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncCall failed: %v", err)
|
|
}
|
|
|
|
// Check generated assembly
|
|
expectedLines := []string{
|
|
" lda var_a",
|
|
" sta test_func_param_a",
|
|
" lda var_b",
|
|
" sta test_func_param_b",
|
|
" lda var_b+1",
|
|
" sta test_func_param_b+1",
|
|
" jsr test_func",
|
|
}
|
|
|
|
if len(asm) != len(expectedLines) {
|
|
t.Fatalf("expected %d asm lines, got %d", len(expectedLines), len(asm))
|
|
}
|
|
|
|
for i, expected := range expectedLines {
|
|
if asm[i] != expected {
|
|
t.Errorf("asm[%d] = %q, want %q", i, asm[i], expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncCall_OutParams(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
// Declare function with out param
|
|
st.AddVar("result", "get_result", KindByte, 0)
|
|
fh.HandleFuncDecl(makeLine("FUNC get_result ( out:result )"))
|
|
|
|
// Declare caller variable
|
|
st.AddVar("output", "", KindByte, 0)
|
|
|
|
asm, err := fh.HandleFuncCall(makeLine("CALL get_result ( output )"))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncCall failed: %v", err)
|
|
}
|
|
|
|
// Should have JSR and OUT assignment
|
|
found_jsr := false
|
|
found_out := false
|
|
for _, line := range asm {
|
|
if strings.Contains(line, "jsr get_result") {
|
|
found_jsr = true
|
|
}
|
|
if strings.Contains(line, "lda get_result_result") {
|
|
found_out = true
|
|
}
|
|
}
|
|
|
|
if !found_jsr {
|
|
t.Error("missing jsr instruction")
|
|
}
|
|
if !found_out {
|
|
t.Error("missing out assignment")
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncCall_ConstArgs(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
// Declare function
|
|
st.AddVar("x", "test_const", KindByte, 0)
|
|
st.AddVar("y", "test_const", KindWord, 0)
|
|
fh.HandleFuncDecl(makeLine("FUNC test_const ( x y )"))
|
|
|
|
asm, err := fh.HandleFuncCall(makeLine("CALL test_const ( 42 $1234 )"))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncCall failed: %v", err)
|
|
}
|
|
|
|
// Check for immediate loads
|
|
foundByte := false
|
|
foundWord := false
|
|
for _, line := range asm {
|
|
if strings.Contains(line, "lda #42") {
|
|
foundByte = true
|
|
}
|
|
if strings.Contains(line, "lda #18") { // 0x12
|
|
foundWord = true
|
|
}
|
|
}
|
|
|
|
if !foundByte {
|
|
t.Error("missing byte constant load")
|
|
}
|
|
if !foundWord {
|
|
t.Error("missing word constant load")
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncCall_LabelArg(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
// Declare function
|
|
st.AddVar("ptr", "test_label", KindWord, 0)
|
|
fh.HandleFuncDecl(makeLine("FUNC test_label ( ptr )"))
|
|
|
|
asm, err := fh.HandleFuncCall(makeLine("CALL test_label ( @my_label )"))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncCall failed: %v", err)
|
|
}
|
|
|
|
// Check for label reference
|
|
foundLow := false
|
|
foundHigh := false
|
|
for _, line := range asm {
|
|
if strings.Contains(line, "#<my_label") {
|
|
foundLow = true
|
|
}
|
|
if strings.Contains(line, "#>my_label") {
|
|
foundHigh = true
|
|
}
|
|
}
|
|
|
|
if !foundLow || !foundHigh {
|
|
t.Error("missing label reference code")
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncCall_StringArg(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
// Declare function
|
|
st.AddVar("str_ptr", "print", KindWord, 0)
|
|
fh.HandleFuncDecl(makeLine("FUNC print ( str_ptr )"))
|
|
|
|
asm, err := fh.HandleFuncCall(makeLine(`CALL print ( "hello" )`))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncCall failed: %v", err)
|
|
}
|
|
|
|
// Check that label was generated
|
|
if ls.Size() != 1 {
|
|
t.Errorf("expected 1 label generated, got %d", ls.Size())
|
|
}
|
|
|
|
// Check for label reference in asm
|
|
foundLabel := false
|
|
for _, line := range asm {
|
|
if strings.Contains(line, "#<L1") || strings.Contains(line, "#>L1") {
|
|
foundLabel = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !foundLabel {
|
|
t.Error("missing string label reference")
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncCall_Errors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setup func(*FunctionHandler, *SymbolTable)
|
|
line string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "function not declared",
|
|
setup: func(fh *FunctionHandler, st *SymbolTable) {},
|
|
line: "CALL undefined ( )",
|
|
wantErr: "not declared",
|
|
},
|
|
{
|
|
name: "wrong arg count",
|
|
setup: func(fh *FunctionHandler, st *SymbolTable) {
|
|
st.AddVar("x", "test", KindByte, 0)
|
|
fh.HandleFuncDecl(makeLine("FUNC test ( x )"))
|
|
},
|
|
line: "CALL test ( 1 2 )",
|
|
wantErr: "expected 1 arguments, got 2",
|
|
},
|
|
//
|
|
/* This is no error anymore, just a warning.
|
|
{
|
|
name: "type mismatch",
|
|
setup: func(fh *FunctionHandler, st *SymbolTable) {
|
|
st.AddVar("param", "test", KindByte, 0)
|
|
fh.HandleFuncDecl(makeLine("FUNC test ( param )"))
|
|
st.AddVar("wvar", "", KindWord, 0)
|
|
},
|
|
line: "CALL test ( wvar )",
|
|
wantErr: "type mismatch",
|
|
}, */
|
|
{
|
|
name: "const to out param",
|
|
setup: func(fh *FunctionHandler, st *SymbolTable) {
|
|
st.AddVar("result", "test", KindByte, 0)
|
|
fh.HandleFuncDecl(makeLine("FUNC test ( out:result )"))
|
|
},
|
|
line: "CALL test ( 42 )",
|
|
wantErr: "out/io parameter",
|
|
},
|
|
{
|
|
name: "label to byte param",
|
|
setup: func(fh *FunctionHandler, st *SymbolTable) {
|
|
st.AddVar("x", "test", KindByte, 0)
|
|
fh.HandleFuncDecl(makeLine("FUNC test ( x )"))
|
|
},
|
|
line: "CALL test ( @label )",
|
|
wantErr: "byte parameter",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
tt.setup(fh, st)
|
|
|
|
_, err := fh.HandleFuncCall(makeLine(tt.line))
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), tt.wantErr) {
|
|
t.Errorf("error %q does not contain %q", err.Error(), tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncCall_EmptyParens(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
// Declare void function
|
|
fh.HandleFuncDecl(makeLine("FUNC init"))
|
|
|
|
tests := []struct {
|
|
name string
|
|
line string
|
|
}{
|
|
{"naked call", "init"},
|
|
{"empty parens", "init()"},
|
|
{"CALL keyword", "CALL init"},
|
|
{"CALL with parens", "CALL init()"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
asm, err := fh.HandleFuncCall(makeLine(tt.line))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncCall failed: %v", err)
|
|
}
|
|
|
|
// Should have JSR instruction
|
|
foundJSR := false
|
|
for _, line := range asm {
|
|
if strings.Contains(line, "jsr init") {
|
|
foundJSR = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !foundJSR {
|
|
t.Error("missing jsr instruction")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEndFunction(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
// Declare function (pushes to stack)
|
|
fh.HandleFuncDecl(makeLine("FUNC test ( {BYTE x} )"))
|
|
|
|
if fh.CurrentFunction() != "test" {
|
|
t.Errorf("current function = %q, want 'test'", fh.CurrentFunction())
|
|
}
|
|
|
|
// End function
|
|
fh.EndFunction()
|
|
|
|
if fh.CurrentFunction() != "" {
|
|
t.Errorf("current function = %q, want ''", fh.CurrentFunction())
|
|
}
|
|
}
|
|
|
|
func TestCurrentFunction(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
if fh.CurrentFunction() != "" {
|
|
t.Error("expected empty current function initially")
|
|
}
|
|
|
|
fh.HandleFuncDecl(makeLine("FUNC func1 ( {BYTE x} )"))
|
|
if fh.CurrentFunction() != "func1" {
|
|
t.Errorf("expected 'func1', got %q", fh.CurrentFunction())
|
|
}
|
|
|
|
fh.HandleFuncDecl(makeLine("FUNC func2 ( {BYTE y} )"))
|
|
if fh.CurrentFunction() != "func2" {
|
|
t.Errorf("expected 'func2', got %q", fh.CurrentFunction())
|
|
}
|
|
|
|
fh.EndFunction()
|
|
if fh.CurrentFunction() != "" {
|
|
t.Errorf("expected '', got %q", fh.CurrentFunction())
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncDecl_AbsoluteVariables(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
_, err := fh.HandleFuncDecl(makeLine("FUNC test_abs ( {BYTE param1 @ $fa} {WORD param2 @ $fb} )"))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncDecl failed: %v", err)
|
|
}
|
|
|
|
// Check that variables were declared as absolute
|
|
symByte := st.Lookup("param1", []string{"test_abs"})
|
|
if symByte == nil {
|
|
t.Fatal("parameter 'param1' not declared")
|
|
}
|
|
if !symByte.IsByte() {
|
|
t.Error("parameter 'param1' should be byte")
|
|
}
|
|
if !symByte.IsAbsolute() {
|
|
t.Error("parameter 'param1' should be absolute")
|
|
}
|
|
if symByte.AbsAddr != 0xfa {
|
|
t.Errorf("parameter 'param1' address = $%02x, want $fa", symByte.AbsAddr)
|
|
}
|
|
if !symByte.IsZeroPage() {
|
|
t.Error("parameter 'param1' should be in zero page")
|
|
}
|
|
|
|
symWord := st.Lookup("param2", []string{"test_abs"})
|
|
if symWord == nil {
|
|
t.Fatal("parameter 'param2' not declared")
|
|
}
|
|
if !symWord.IsWord() {
|
|
t.Error("parameter 'param2' should be word")
|
|
}
|
|
if !symWord.IsAbsolute() {
|
|
t.Error("parameter 'param2' should be absolute")
|
|
}
|
|
if symWord.AbsAddr != 0xfb {
|
|
t.Errorf("parameter 'param2' address = $%02x, want $fb", symWord.AbsAddr)
|
|
}
|
|
if !symWord.IsZeroPage() {
|
|
t.Error("parameter 'param2' should be in zero page")
|
|
}
|
|
|
|
// Check function declaration
|
|
funcDecl := fh.findFunc("test_abs")
|
|
if funcDecl == nil {
|
|
t.Fatal("function not found")
|
|
}
|
|
if len(funcDecl.Params) != 2 {
|
|
t.Fatalf("expected 2 params, got %d", len(funcDecl.Params))
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncDecl_AbsoluteWithDirections(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
_, err := fh.HandleFuncDecl(makeLine("FUNC test_abs_dir ( in:{BYTE input @ $fa} out:{BYTE output @ $fb} io:{WORD data @ $fc} )"))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncDecl failed: %v", err)
|
|
}
|
|
|
|
funcDecl := fh.findFunc("test_abs_dir")
|
|
if funcDecl == nil {
|
|
t.Fatal("function not found")
|
|
}
|
|
|
|
if len(funcDecl.Params) != 3 {
|
|
t.Fatalf("expected 3 params, got %d", len(funcDecl.Params))
|
|
}
|
|
|
|
if funcDecl.Params[0].Direction != DirIn {
|
|
t.Error("param 0 should be DirIn")
|
|
}
|
|
if funcDecl.Params[1].Direction != DirOut {
|
|
t.Error("param 1 should be DirOut")
|
|
}
|
|
if funcDecl.Params[2].Direction != (DirIn | DirOut) {
|
|
t.Error("param 2 should be DirIn|DirOut")
|
|
}
|
|
|
|
// Verify absolute addresses
|
|
if funcDecl.Params[0].Symbol.AbsAddr != 0xfa {
|
|
t.Errorf("param 0 address = $%02x, want $fa", funcDecl.Params[0].Symbol.AbsAddr)
|
|
}
|
|
if funcDecl.Params[1].Symbol.AbsAddr != 0xfb {
|
|
t.Errorf("param 1 address = $%02x, want $fb", funcDecl.Params[1].Symbol.AbsAddr)
|
|
}
|
|
if funcDecl.Params[2].Symbol.AbsAddr != 0xfc {
|
|
t.Errorf("param 2 address = $%02x, want $fc", funcDecl.Params[2].Symbol.AbsAddr)
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncDecl_AbsoluteMixedParams(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
// Mix absolute and regular params
|
|
_, err := fh.HandleFuncDecl(makeLine("FUNC test_mixed ( {BYTE abs_param @ $fa} {WORD reg_param} )"))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncDecl failed: %v", err)
|
|
}
|
|
|
|
symAbs := st.Lookup("abs_param", []string{"test_mixed"})
|
|
if symAbs == nil {
|
|
t.Fatal("parameter 'abs_param' not declared")
|
|
}
|
|
if !symAbs.IsAbsolute() {
|
|
t.Error("parameter 'abs_param' should be absolute")
|
|
}
|
|
|
|
symReg := st.Lookup("reg_param", []string{"test_mixed"})
|
|
if symReg == nil {
|
|
t.Fatal("parameter 'reg_param' not declared")
|
|
}
|
|
if symReg.IsAbsolute() {
|
|
t.Error("parameter 'reg_param' should not be absolute")
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncDecl_AbsoluteErrors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
line string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "invalid operator",
|
|
line: "FUNC test ( {BYTE x = $fa} )",
|
|
wantErr: "expected '@' operator",
|
|
},
|
|
{
|
|
name: "invalid address",
|
|
line: "FUNC test ( {BYTE x @ invalid} )",
|
|
wantErr: "invalid address",
|
|
},
|
|
{
|
|
name: "address out of range",
|
|
line: "FUNC test ( {BYTE x @ $10000} )",
|
|
wantErr: "out of range",
|
|
},
|
|
{
|
|
name: "negative address",
|
|
line: "FUNC test ( {BYTE x @ -1} )",
|
|
wantErr: "invalid address",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
_, err := fh.HandleFuncDecl(makeLine(tt.line))
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
if !strings.Contains(err.Error(), tt.wantErr) {
|
|
t.Errorf("error %q does not contain %q", err.Error(), tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncCall_AbsoluteParams(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
// Declare function with absolute params
|
|
fh.HandleFuncDecl(makeLine("FUNC test_abs ( {BYTE param_a @ $fa} {WORD param_b @ $fb} )"))
|
|
|
|
// Declare caller variables
|
|
st.AddVar("var_a", "", KindByte, 0)
|
|
st.AddVar("var_b", "", KindWord, 0)
|
|
|
|
asm, err := fh.HandleFuncCall(makeLine("CALL test_abs ( var_a var_b )"))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncCall failed: %v", err)
|
|
}
|
|
|
|
// Check generated assembly uses correct names
|
|
expectedLines := []string{
|
|
" lda var_a",
|
|
" sta test_abs_param_a",
|
|
" lda var_b",
|
|
" sta test_abs_param_b",
|
|
" lda var_b+1",
|
|
" sta test_abs_param_b+1",
|
|
" jsr test_abs",
|
|
}
|
|
|
|
if len(asm) != len(expectedLines) {
|
|
t.Fatalf("expected %d asm lines, got %d", len(expectedLines), len(asm))
|
|
}
|
|
|
|
for i, expected := range expectedLines {
|
|
if asm[i] != expected {
|
|
t.Errorf("asm[%d] = %q, want %q", i, asm[i], expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHandleFuncCall_AbsoluteOutParams(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
// Declare function with absolute out param
|
|
fh.HandleFuncDecl(makeLine("FUNC get_result ( out:{BYTE result @ $fa} )"))
|
|
|
|
// Declare caller variable
|
|
st.AddVar("output", "", KindByte, 0)
|
|
|
|
asm, err := fh.HandleFuncCall(makeLine("CALL get_result ( output )"))
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncCall failed: %v", err)
|
|
}
|
|
|
|
// Should have JSR and OUT assignment
|
|
found_jsr := false
|
|
found_out := false
|
|
for _, line := range asm {
|
|
if strings.Contains(line, "jsr get_result") {
|
|
found_jsr = true
|
|
}
|
|
if strings.Contains(line, "lda get_result_result") {
|
|
found_out = true
|
|
}
|
|
}
|
|
|
|
if !found_jsr {
|
|
t.Error("missing jsr instruction")
|
|
}
|
|
if !found_out {
|
|
t.Error("missing out assignment")
|
|
}
|
|
}
|
|
|
|
func TestParseImplicitDecl_Absolute(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
tests := []struct {
|
|
name string
|
|
decl string
|
|
funcName string
|
|
wantErr bool
|
|
checkFn func(*testing.T, *SymbolTable)
|
|
}{
|
|
{
|
|
name: "byte at hex address",
|
|
decl: "BYTE x @ $fa",
|
|
funcName: "test",
|
|
wantErr: false,
|
|
checkFn: func(t *testing.T, st *SymbolTable) {
|
|
sym := st.Lookup("x", []string{"test"})
|
|
if sym == nil {
|
|
t.Fatal("symbol not found")
|
|
}
|
|
if !sym.IsAbsolute() || sym.AbsAddr != 0xfa {
|
|
t.Errorf("expected absolute at $fa, got absolute=%v addr=$%02x", sym.IsAbsolute(), sym.AbsAddr)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "word at decimal address",
|
|
decl: "WORD ptr @ 251",
|
|
funcName: "test",
|
|
wantErr: false,
|
|
checkFn: func(t *testing.T, st *SymbolTable) {
|
|
sym := st.Lookup("ptr", []string{"test"})
|
|
if sym == nil {
|
|
t.Fatal("symbol not found")
|
|
}
|
|
if !sym.IsAbsolute() || sym.AbsAddr != 251 {
|
|
t.Errorf("expected absolute at 251, got absolute=%v addr=%d", sym.IsAbsolute(), sym.AbsAddr)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "invalid operator",
|
|
decl: "BYTE x = $fa",
|
|
funcName: "test",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "too few parts",
|
|
decl: "BYTE x @",
|
|
funcName: "test",
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "too many parts",
|
|
decl: "BYTE x @ $fa extra",
|
|
funcName: "test",
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Clear symbol table for each test
|
|
st = NewSymbolTable()
|
|
fh.symTable = st
|
|
|
|
err := fh.parseImplicitDecl(tt.decl, tt.funcName)
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Fatal("expected error, got nil")
|
|
}
|
|
return
|
|
}
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if tt.checkFn != nil {
|
|
tt.checkFn(t, st)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAbsoluteOverlapDetection(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
code string
|
|
wantOverlapAddr uint16
|
|
wantFunc1 string
|
|
wantFunc2 string
|
|
}{
|
|
{
|
|
name: "direct call with overlap",
|
|
code: `
|
|
BYTE dummy
|
|
FUNC funcB ( {BYTE data @ $fa} )
|
|
FEND
|
|
|
|
FUNC funcA ( {BYTE temp @ $fa} )
|
|
CALL funcB(dummy)
|
|
FEND
|
|
`,
|
|
wantOverlapAddr: 0xFA,
|
|
wantFunc1: "funcA",
|
|
wantFunc2: "funcB",
|
|
},
|
|
{
|
|
name: "transitive call with overlap",
|
|
code: `
|
|
BYTE dummy
|
|
FUNC funcC ( {BYTE data @ $fa} )
|
|
FEND
|
|
|
|
FUNC funcB
|
|
CALL funcC(dummy)
|
|
FEND
|
|
|
|
FUNC funcA ( {BYTE temp @ $fa} )
|
|
CALL funcB()
|
|
FEND
|
|
`,
|
|
wantOverlapAddr: 0xFA,
|
|
wantFunc1: "funcA",
|
|
wantFunc2: "funcC",
|
|
},
|
|
{
|
|
name: "word overlap with byte",
|
|
code: `
|
|
BYTE dummy
|
|
FUNC funcB ( {BYTE data @ $fb} )
|
|
FEND
|
|
|
|
FUNC funcA ( {WORD value @ $fa} )
|
|
CALL funcB(dummy)
|
|
FEND
|
|
`,
|
|
wantOverlapAddr: 0xFB,
|
|
wantFunc1: "funcA",
|
|
wantFunc2: "funcB",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
// Parse code into lines
|
|
lines := strings.Split(strings.TrimSpace(tt.code), "\n")
|
|
|
|
// Process each line
|
|
for i, lineText := range lines {
|
|
lineText = strings.TrimSpace(lineText)
|
|
if lineText == "" {
|
|
continue
|
|
}
|
|
|
|
pline := preproc.Line{
|
|
Text: lineText,
|
|
LineNo: i + 1,
|
|
Filename: "test.c65",
|
|
}
|
|
|
|
if strings.HasPrefix(lineText, "BYTE ") || strings.HasPrefix(lineText, "WORD ") {
|
|
// Variable declaration - add to symbol table
|
|
parts := strings.Fields(lineText)
|
|
if len(parts) >= 2 {
|
|
varKind := KindByte
|
|
if strings.ToUpper(parts[0]) == "WORD" {
|
|
varKind = KindWord
|
|
}
|
|
st.AddVar(parts[1], "", varKind, 0)
|
|
}
|
|
} else if strings.HasPrefix(lineText, "FUNC ") {
|
|
_, err := fh.HandleFuncDecl(pline)
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncDecl failed: %v", err)
|
|
}
|
|
} else if strings.HasPrefix(lineText, "CALL ") {
|
|
_, err := fh.HandleFuncCall(pline)
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncCall failed: %v", err)
|
|
}
|
|
} else if lineText == "FEND" {
|
|
fh.EndFunction()
|
|
}
|
|
}
|
|
|
|
// Analyze overlaps
|
|
overlaps := fh.AnalyzeAbsoluteOverlaps()
|
|
|
|
if len(overlaps) == 0 {
|
|
t.Fatalf("Expected overlap at address $%04X, but none detected", tt.wantOverlapAddr)
|
|
}
|
|
|
|
// Find the expected overlap
|
|
found := false
|
|
for _, overlap := range overlaps {
|
|
if overlap.Address == tt.wantOverlapAddr &&
|
|
overlap.Func1 == tt.wantFunc1 &&
|
|
overlap.Func2 == tt.wantFunc2 {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
t.Errorf("Expected overlap at $%04X between %s and %s, got: %+v",
|
|
tt.wantOverlapAddr, tt.wantFunc1, tt.wantFunc2, overlaps)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAbsoluteLocalVariables(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
code string
|
|
wantOverlapAddr uint16
|
|
wantFunc1 string
|
|
wantFunc2 string
|
|
}{
|
|
{
|
|
name: "local absolute variables overlap",
|
|
code: `
|
|
FUNC funcB
|
|
BYTE localB @ $fa
|
|
FEND
|
|
|
|
FUNC funcA
|
|
BYTE localA @ $fa
|
|
CALL funcB()
|
|
FEND
|
|
`,
|
|
wantOverlapAddr: 0xFA,
|
|
wantFunc1: "funcA",
|
|
wantFunc2: "funcB",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
// Connect symbol table to function handler
|
|
st.SetFunctionHandler(fh)
|
|
|
|
// Parse code into lines
|
|
lines := strings.Split(strings.TrimSpace(tt.code), "\n")
|
|
currentFunc := ""
|
|
|
|
// Process each line
|
|
for i, lineText := range lines {
|
|
lineText = strings.TrimSpace(lineText)
|
|
if lineText == "" {
|
|
continue
|
|
}
|
|
|
|
pline := preproc.Line{
|
|
Text: lineText,
|
|
LineNo: i + 1,
|
|
Filename: "test.c65",
|
|
}
|
|
|
|
if strings.HasPrefix(lineText, "BYTE ") || strings.HasPrefix(lineText, "WORD ") {
|
|
// Parse variable declaration
|
|
parts := strings.Fields(lineText)
|
|
if len(parts) >= 4 && parts[2] == "@" {
|
|
// Absolute variable: BYTE name @ $addr
|
|
varKind := KindByte
|
|
if strings.ToUpper(parts[0]) == "WORD" {
|
|
varKind = KindWord
|
|
}
|
|
addrStr := strings.TrimPrefix(parts[3], "$")
|
|
var addr uint16
|
|
fmt.Sscanf(addrStr, "%x", &addr)
|
|
st.AddAbsolute(parts[1], currentFunc, varKind, addr)
|
|
}
|
|
} else if strings.HasPrefix(lineText, "FUNC ") {
|
|
_, err := fh.HandleFuncDecl(pline)
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncDecl failed: %v", err)
|
|
}
|
|
// Extract function name
|
|
funcParts := strings.Fields(lineText)
|
|
if len(funcParts) >= 2 {
|
|
currentFunc = funcParts[1]
|
|
}
|
|
} else if strings.HasPrefix(lineText, "CALL ") {
|
|
_, err := fh.HandleFuncCall(pline)
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncCall failed: %v", err)
|
|
}
|
|
} else if lineText == "FEND" {
|
|
fh.EndFunction()
|
|
currentFunc = ""
|
|
}
|
|
}
|
|
|
|
// Analyze overlaps
|
|
overlaps := fh.AnalyzeAbsoluteOverlaps()
|
|
|
|
if len(overlaps) == 0 {
|
|
t.Fatalf("Expected overlap at address $%04X, but none detected", tt.wantOverlapAddr)
|
|
}
|
|
|
|
// Find the expected overlap
|
|
found := false
|
|
for _, overlap := range overlaps {
|
|
if overlap.Address == tt.wantOverlapAddr &&
|
|
overlap.Func1 == tt.wantFunc1 &&
|
|
overlap.Func2 == tt.wantFunc2 {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
t.Errorf("Expected overlap at $%04X between %s and %s, got: %+v",
|
|
tt.wantOverlapAddr, tt.wantFunc1, tt.wantFunc2, overlaps)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAbsoluteNoOverlap(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
code string
|
|
}{
|
|
{
|
|
name: "different addresses",
|
|
code: `
|
|
BYTE dummy
|
|
FUNC funcB ( {BYTE data @ $fb} )
|
|
FEND
|
|
|
|
FUNC funcA ( {BYTE temp @ $fa} )
|
|
CALL funcB(dummy)
|
|
FEND
|
|
`,
|
|
},
|
|
{
|
|
name: "no call relationship",
|
|
code: `
|
|
FUNC funcA ( {BYTE temp @ $fa} )
|
|
FEND
|
|
|
|
FUNC funcB ( {BYTE data @ $fa} )
|
|
FEND
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
st := NewSymbolTable()
|
|
ls := NewLabelStack("L")
|
|
csh := NewConstantStringHandler()
|
|
pragma := preproc.NewPragma()
|
|
fh := NewFunctionHandler(st, ls, csh, pragma)
|
|
|
|
// Parse code into lines
|
|
lines := strings.Split(strings.TrimSpace(tt.code), "\n")
|
|
|
|
// Process each line
|
|
for i, lineText := range lines {
|
|
lineText = strings.TrimSpace(lineText)
|
|
if lineText == "" {
|
|
continue
|
|
}
|
|
|
|
pline := preproc.Line{
|
|
Text: lineText,
|
|
LineNo: i + 1,
|
|
Filename: "test.c65",
|
|
}
|
|
|
|
if strings.HasPrefix(lineText, "BYTE ") || strings.HasPrefix(lineText, "WORD ") {
|
|
// Variable declaration - add to symbol table
|
|
parts := strings.Fields(lineText)
|
|
if len(parts) >= 2 {
|
|
varKind := KindByte
|
|
if strings.ToUpper(parts[0]) == "WORD" {
|
|
varKind = KindWord
|
|
}
|
|
st.AddVar(parts[1], "", varKind, 0)
|
|
}
|
|
} else if strings.HasPrefix(lineText, "FUNC ") {
|
|
_, err := fh.HandleFuncDecl(pline)
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncDecl failed: %v", err)
|
|
}
|
|
} else if strings.HasPrefix(lineText, "CALL ") {
|
|
_, err := fh.HandleFuncCall(pline)
|
|
if err != nil {
|
|
t.Fatalf("HandleFuncCall failed: %v", err)
|
|
}
|
|
} else if lineText == "FEND" {
|
|
fh.EndFunction()
|
|
}
|
|
}
|
|
|
|
// Analyze overlaps
|
|
overlaps := fh.AnalyzeAbsoluteOverlaps()
|
|
|
|
if len(overlaps) > 0 {
|
|
t.Errorf("Expected no overlaps, but got: %+v", overlaps)
|
|
}
|
|
})
|
|
}
|
|
}
|