948 lines
21 KiB
Go
948 lines
21 KiB
Go
package preproc
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestPreProcess_BasicDefine(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"#DEFINE FOO = 42",
|
|
"LDA #FOO",
|
|
"STA $D020",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if len(lines) != 2 {
|
|
t.Fatalf("expected 2 lines, got %d", len(lines))
|
|
}
|
|
|
|
if lines[0].Text != "LDA #42" {
|
|
t.Errorf("expected 'LDA #42', got %q", lines[0].Text)
|
|
}
|
|
|
|
if lines[0].Kind != Source {
|
|
t.Errorf("expected Kind=Source, got %v", lines[0].Kind)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_DefineExpansion(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"#DEFINE BASE = $D000",
|
|
"#DEFINE OFFSET = 32",
|
|
"#DEFINE ADDR = BASE+OFFSET",
|
|
"STA ADDR",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if len(lines) != 1 {
|
|
t.Fatalf("expected 1 line, got %d", len(lines))
|
|
}
|
|
|
|
if lines[0].Text != "STA $D000+32" {
|
|
t.Errorf("expected 'STA $D000+32', got %q", lines[0].Text)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_IncludeGuard(t *testing.T) {
|
|
files := map[string][]string{
|
|
"lib.c65": {
|
|
"#IFNDEF __LIB",
|
|
"#DEFINE __LIB = 1",
|
|
"LABEL lib_init",
|
|
"#IFEND",
|
|
},
|
|
"main.c65": {
|
|
"#INCLUDE lib.c65",
|
|
"#INCLUDE lib.c65",
|
|
"GOTO main",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("main.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if len(lines) != 2 {
|
|
t.Fatalf("expected 2 lines, got %d", len(lines))
|
|
}
|
|
|
|
if lines[0].Text != "LABEL lib_init" {
|
|
t.Errorf("expected 'LABEL lib_init', got %q", lines[0].Text)
|
|
}
|
|
if lines[1].Text != "GOTO main" {
|
|
t.Errorf("expected 'GOTO main', got %q", lines[1].Text)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_ASMBlock(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"#DEFINE FOO = 42",
|
|
"ASM",
|
|
" lda #FOO",
|
|
" sta $d020",
|
|
"ENDASM",
|
|
"LDA #FOO",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
// ASM and ENDASM markers are stripped, so only 2 asm lines + 1 source line
|
|
if len(lines) != 3 {
|
|
t.Fatalf("expected 3 lines, got %d", len(lines))
|
|
}
|
|
|
|
// ASM content should NOT be processed
|
|
if lines[0].Text != " lda #FOO" {
|
|
t.Errorf("expected ' lda #FOO', got %q", lines[0].Text)
|
|
}
|
|
if lines[0].Kind != Assembler {
|
|
t.Errorf("expected Kind=Assembler, got %v", lines[0].Kind)
|
|
}
|
|
|
|
if lines[1].Text != " sta $d020" {
|
|
t.Errorf("expected ' sta $d020', got %q", lines[1].Text)
|
|
}
|
|
if lines[1].Kind != Assembler {
|
|
t.Errorf("expected Kind=Assembler, got %v", lines[1].Kind)
|
|
}
|
|
|
|
// After ENDASM, defines work again
|
|
if lines[2].Text != "LDA #42" {
|
|
t.Errorf("expected 'LDA #42', got %q", lines[2].Text)
|
|
}
|
|
if lines[2].Kind != Source {
|
|
t.Errorf("expected Kind=Source, got %v", lines[2].Kind)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_ScriptBlock(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"#DEFINE VAR = 100",
|
|
"SCRIPT",
|
|
" x = VAR + 1",
|
|
" print(x)",
|
|
"ENDSCRIPT",
|
|
"LDA #VAR",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
// SCRIPT and ENDSCRIPT markers are stripped
|
|
if len(lines) != 3 {
|
|
t.Fatalf("expected 3 lines, got %d", len(lines))
|
|
}
|
|
|
|
// Script content should NOT be processed
|
|
if lines[0].Text != " x = VAR + 1" {
|
|
t.Errorf("expected ' x = VAR + 1', got %q", lines[0].Text)
|
|
}
|
|
if lines[0].Kind != Script {
|
|
t.Errorf("expected Kind=Script, got %v", lines[0].Kind)
|
|
}
|
|
|
|
if lines[1].Text != " print(x)" {
|
|
t.Errorf("expected ' print(x)', got %q", lines[1].Text)
|
|
}
|
|
if lines[1].Kind != Script {
|
|
t.Errorf("expected Kind=Script, got %v", lines[1].Kind)
|
|
}
|
|
|
|
// After ENDSCRIPT, defines work again
|
|
if lines[2].Text != "LDA #100" {
|
|
t.Errorf("expected 'LDA #100', got %q", lines[2].Text)
|
|
}
|
|
if lines[2].Kind != Source {
|
|
t.Errorf("expected Kind=Source, got %v", lines[2].Kind)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_CommentStripping(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"LDA #42 // load accumulator",
|
|
"STA $D020 // border color",
|
|
"NOP // comment only",
|
|
"JMP $0810",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if len(lines) != 4 {
|
|
t.Fatalf("expected 4 lines, got %d", len(lines))
|
|
}
|
|
|
|
if lines[0].Text != "LDA #42" {
|
|
t.Errorf("expected 'LDA #42', got %q", lines[0].Text)
|
|
}
|
|
if lines[0].RawText != "LDA #42 // load accumulator" {
|
|
t.Errorf("expected RawText to preserve comment, got %q", lines[0].RawText)
|
|
}
|
|
|
|
if lines[1].Text != "STA $D020" {
|
|
t.Errorf("expected 'STA $D020', got %q", lines[1].Text)
|
|
}
|
|
|
|
if lines[2].Text != "NOP" {
|
|
t.Errorf("expected 'NOP', got %q", lines[2].Text)
|
|
}
|
|
|
|
if lines[3].Text != "JMP $0810" {
|
|
t.Errorf("expected 'JMP $0810', got %q", lines[3].Text)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_CommentInASMBlock(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"ASM",
|
|
" lda #42 // this comment stays",
|
|
" sta $d020 // this too",
|
|
"ENDASM",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if len(lines) != 2 {
|
|
t.Fatalf("expected 2 lines, got %d", len(lines))
|
|
}
|
|
|
|
// Comments should be preserved in ASM blocks
|
|
if lines[0].Text != " lda #42 // this comment stays" {
|
|
t.Errorf("expected comment preserved, got %q", lines[0].Text)
|
|
}
|
|
if lines[1].Text != " sta $d020 // this too" {
|
|
t.Errorf("expected comment preserved, got %q", lines[1].Text)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_CommentInScriptBlock(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"SCRIPT",
|
|
" x = 1 // script comment",
|
|
" y = 2 // another one",
|
|
"ENDSCRIPT",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if len(lines) != 2 {
|
|
t.Fatalf("expected 2 lines, got %d", len(lines))
|
|
}
|
|
|
|
// Comments should be preserved in Script blocks
|
|
if lines[0].Text != " x = 1 // script comment" {
|
|
t.Errorf("expected comment preserved, got %q", lines[0].Text)
|
|
}
|
|
if lines[1].Text != " y = 2 // another one" {
|
|
t.Errorf("expected comment preserved, got %q", lines[1].Text)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_RawTextPreservation(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"#DEFINE FOO = 42",
|
|
"LDA #FOO // comment here",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if len(lines) != 1 {
|
|
t.Fatalf("expected 1 line, got %d", len(lines))
|
|
}
|
|
|
|
// RawText should be original
|
|
if lines[0].RawText != "LDA #FOO // comment here" {
|
|
t.Errorf("expected RawText 'LDA #FOO // comment here', got %q", lines[0].RawText)
|
|
}
|
|
|
|
// Text should be processed
|
|
if lines[0].Text != "LDA #42" {
|
|
t.Errorf("expected Text 'LDA #42', got %q", lines[0].Text)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_MismatchedBlockTerminators(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
lines []string
|
|
}{
|
|
{
|
|
name: "ASM ended with ENDSCRIPT",
|
|
lines: []string{
|
|
"ASM",
|
|
" lda #42",
|
|
"ENDSCRIPT",
|
|
"NOP",
|
|
},
|
|
},
|
|
{
|
|
name: "SCRIPT ended with ENDASM",
|
|
lines: []string{
|
|
"SCRIPT",
|
|
" x = 1",
|
|
"ENDASM",
|
|
"NOP",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": tt.lines,
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
// Wrong terminator won't close the block
|
|
// All lines including the wrong terminator should be in the block
|
|
if len(lines) < 2 {
|
|
t.Errorf("expected at least 2 lines, got %d", len(lines))
|
|
}
|
|
|
|
// The mismatched terminator should be treated as block content
|
|
// and NOP should still be in the block too
|
|
for _, line := range lines {
|
|
if line.Kind == Source {
|
|
t.Errorf("found Source line when all should be in block: %q", line.Text)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_NestedConditionals(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"#DEFINE OUTER",
|
|
"#IFDEF OUTER",
|
|
" LINE1",
|
|
" #IFDEF INNER",
|
|
" LINE2",
|
|
" #IFEND",
|
|
" LINE3",
|
|
"#IFEND",
|
|
"ALWAYS",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if len(lines) != 3 {
|
|
t.Fatalf("expected 3 lines, got %d", len(lines))
|
|
}
|
|
|
|
expected := []string{"LINE1", "LINE3", "ALWAYS"}
|
|
for i, exp := range expected {
|
|
if !strings.Contains(lines[i].Text, exp) {
|
|
t.Errorf("line %d: expected %q, got %q", i, exp, lines[i].Text)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_ConditionalBlocking(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"#IFNDEF UNDEFINED",
|
|
" VISIBLE",
|
|
" #DEFINE FOO = 1",
|
|
"#IFEND",
|
|
"#IFDEF UNDEFINED",
|
|
" INVISIBLE",
|
|
" #DEFINE BAR = 2",
|
|
"#IFEND",
|
|
"LDA #FOO",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if len(lines) != 2 {
|
|
t.Fatalf("expected 2 lines, got %d", len(lines))
|
|
}
|
|
|
|
// FOO should be defined, BAR should not
|
|
if lines[1].Text != "LDA #1" {
|
|
t.Errorf("expected 'LDA #1', got %q", lines[1].Text)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_NestedIncludes(t *testing.T) {
|
|
files := map[string][]string{
|
|
"a.c65": {
|
|
"#INCLUDE b.c65",
|
|
"LINE_A",
|
|
},
|
|
"b.c65": {
|
|
"#INCLUDE c.c65",
|
|
"LINE_B",
|
|
},
|
|
"c.c65": {
|
|
"LINE_C",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("a.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if len(lines) != 3 {
|
|
t.Fatalf("expected 3 lines, got %d", len(lines))
|
|
}
|
|
|
|
expected := []string{"LINE_C", "LINE_B", "LINE_A"}
|
|
for i, exp := range expected {
|
|
if lines[i].Text != exp {
|
|
t.Errorf("line %d: expected %q, got %q", i, exp, lines[i].Text)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_Filename(t *testing.T) {
|
|
files := map[string][]string{
|
|
"main.c65": {
|
|
"#INCLUDE lib.c65",
|
|
"MAIN_LINE",
|
|
},
|
|
"lib.c65": {
|
|
"LIB_LINE",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("main.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if lines[0].Filename != "lib.c65" {
|
|
t.Errorf("expected filename 'lib.c65', got %q", lines[0].Filename)
|
|
}
|
|
if lines[1].Filename != "main.c65" {
|
|
t.Errorf("expected filename 'main.c65', got %q", lines[1].Filename)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_LineNumbers(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"LINE1",
|
|
"LINE2",
|
|
"#DEFINE FOO = 1",
|
|
"LINE4",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
expectedLines := []int{1, 2, 4}
|
|
for i, exp := range expectedLines {
|
|
if lines[i].LineNo != exp {
|
|
t.Errorf("line %d: expected line number %d, got %d", i, exp, lines[i].LineNo)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_Undef(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"#DEFINE FOO = 1",
|
|
"LDA #FOO",
|
|
"#UNDEF FOO",
|
|
"LDA #FOO",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if lines[0].Text != "LDA #1" {
|
|
t.Errorf("expected 'LDA #1', got %q", lines[0].Text)
|
|
}
|
|
if lines[1].Text != "LDA #FOO" {
|
|
t.Errorf("expected 'LDA #FOO' (unexpanded), got %q", lines[1].Text)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_PragmaWithDefines(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"#DEFINE SETTING = 1",
|
|
"#PRAGMA USE_FEATURE SETTING",
|
|
"CODE",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if len(lines) != 1 {
|
|
t.Fatalf("expected 1 line, got %d", len(lines))
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_EmptyFile(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if len(lines) != 0 {
|
|
t.Errorf("expected 0 lines, got %d", len(lines))
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_MissingInclude(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"#INCLUDE notfound.c65",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
_, err := PreProcess("test.c65", reader)
|
|
if err == nil {
|
|
t.Error("expected error for missing include")
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_HaltDirective(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"LINE1",
|
|
"#HALT",
|
|
"LINE2",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
_, err := PreProcess("test.c65", reader)
|
|
if err == nil {
|
|
t.Error("expected HaltError")
|
|
}
|
|
if _, ok := err.(HaltError); !ok {
|
|
t.Errorf("expected HaltError, got %T", err)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_ConditionalHalt(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"LINE1",
|
|
"#IFDEF UNDEFINED",
|
|
"#HALT",
|
|
"#IFEND",
|
|
"LINE2",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
// HALT is inside false conditional, should not trigger
|
|
if len(lines) != 2 {
|
|
t.Errorf("expected 2 lines, got %d", len(lines))
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_ComplexIncludeGuard(t *testing.T) {
|
|
files := map[string][]string{
|
|
"c64scr.c65": {
|
|
"#IFNDEF __C64_SCR",
|
|
"#DEFINE __C64_SCR = 1",
|
|
"GOTO lib_c64scr_skip",
|
|
"LABEL lib_c64scr_blank",
|
|
"ASM",
|
|
" lda $d011",
|
|
"ENDASM",
|
|
"SUBEND",
|
|
"LABEL lib_c64scr_skip",
|
|
"#IFEND",
|
|
},
|
|
"main.c65": {
|
|
"#INCLUDE c64scr.c65",
|
|
"#INCLUDE c64scr.c65",
|
|
"MAIN",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("main.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
// First include should emit the library, second should be blocked
|
|
count := 0
|
|
for _, line := range lines {
|
|
if strings.Contains(line.Text, "lda $d011") {
|
|
count++
|
|
}
|
|
}
|
|
|
|
if count != 1 {
|
|
t.Errorf("expected 1 occurrence from multiple includes of same file, got %d", count)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_UnbalancedConditionals(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"#DEFINE FOO",
|
|
"#IFDEF FOO",
|
|
" LINE1",
|
|
" LINE2",
|
|
// Missing #IFEND
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
_, err := PreProcess("test.c65", reader)
|
|
if err == nil {
|
|
t.Error("expected error for unbalanced conditionals")
|
|
}
|
|
if !strings.Contains(err.Error(), "unbalanced") {
|
|
t.Errorf("expected 'unbalanced' error, got: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_FilenameAndLineNumberTracking(t *testing.T) {
|
|
files := map[string][]string{
|
|
"main.c65": {
|
|
"MAIN_LINE1", // line 1
|
|
"#INCLUDE lib.c65", // line 2 (directive, not emitted)
|
|
"MAIN_LINE2", // line 3
|
|
"MAIN_LINE3", // line 4
|
|
},
|
|
"lib.c65": {
|
|
"LIB_LINE1", // line 1
|
|
"LIB_LINE2", // line 2
|
|
"LIB_LINE3", // line 3
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("main.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
expected := []struct {
|
|
text string
|
|
filename string
|
|
lineNo int
|
|
}{
|
|
{"MAIN_LINE1", "main.c65", 1},
|
|
{"LIB_LINE1", "lib.c65", 1},
|
|
{"LIB_LINE2", "lib.c65", 2},
|
|
{"LIB_LINE3", "lib.c65", 3},
|
|
{"MAIN_LINE2", "main.c65", 3},
|
|
{"MAIN_LINE3", "main.c65", 4},
|
|
}
|
|
|
|
if len(lines) != len(expected) {
|
|
t.Fatalf("expected %d lines, got %d", len(expected), len(lines))
|
|
}
|
|
|
|
for i, exp := range expected {
|
|
if lines[i].Text != exp.text {
|
|
t.Errorf("line %d: expected text %q, got %q", i, exp.text, lines[i].Text)
|
|
}
|
|
if lines[i].Filename != exp.filename {
|
|
t.Errorf("line %d: expected filename %q, got %q", i, exp.filename, lines[i].Filename)
|
|
}
|
|
if lines[i].LineNo != exp.lineNo {
|
|
t.Errorf("line %d: expected line number %d, got %d", i, exp.lineNo, lines[i].LineNo)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_PragmaTracking(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"LINE1", // no pragmas yet (index 0)
|
|
"LINE2", // no pragmas yet (index 0)
|
|
"#PRAGMA FEATURE1 enabled", // directive, not emitted
|
|
"LINE3", // FEATURE1=enabled (index 1)
|
|
"LINE4", // FEATURE1=enabled (index 1)
|
|
"#PRAGMA FEATURE2 123", // directive, not emitted
|
|
"LINE5", // FEATURE1=enabled, FEATURE2=123 (index 2)
|
|
"#PRAGMA FEATURE1 disabled", // directive, not emitted
|
|
"LINE6", // FEATURE1=disabled, FEATURE2=123 (index 3)
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if len(lines) != 6 {
|
|
t.Fatalf("expected 6 lines, got %d", len(lines))
|
|
}
|
|
|
|
// Create pragma instance to verify the indices
|
|
pragma := NewPragma()
|
|
pragma.AddPragma("FEATURE1", "enabled")
|
|
pragma.AddPragma("FEATURE2", "123")
|
|
pragma.AddPragma("FEATURE1", "disabled")
|
|
|
|
tests := []struct {
|
|
lineIdx int
|
|
text string
|
|
pragmaSetIdx int
|
|
feature1 string
|
|
feature2 string
|
|
}{
|
|
{0, "LINE1", 0, "", ""},
|
|
{1, "LINE2", 0, "", ""},
|
|
{2, "LINE3", 1, "enabled", ""},
|
|
{3, "LINE4", 1, "enabled", ""},
|
|
{4, "LINE5", 2, "enabled", "123"},
|
|
{5, "LINE6", 3, "disabled", "123"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
line := lines[tt.lineIdx]
|
|
|
|
if line.Text != tt.text {
|
|
t.Errorf("line %d: expected text %q, got %q", tt.lineIdx, tt.text, line.Text)
|
|
}
|
|
|
|
if line.PragmaSetIndex != tt.pragmaSetIdx {
|
|
t.Errorf("line %d: expected pragma set index %d, got %d",
|
|
tt.lineIdx, tt.pragmaSetIdx, line.PragmaSetIndex)
|
|
}
|
|
|
|
// Verify we can retrieve pragma values using the index
|
|
pragmaSet := pragma.GetPragmaSetByIndex(line.PragmaSetIndex)
|
|
|
|
if got := pragmaSet.GetPragma("FEATURE1"); got != tt.feature1 {
|
|
t.Errorf("line %d: expected FEATURE1=%q, got %q", tt.lineIdx, tt.feature1, got)
|
|
}
|
|
|
|
if got := pragmaSet.GetPragma("FEATURE2"); got != tt.feature2 {
|
|
t.Errorf("line %d: expected FEATURE2=%q, got %q", tt.lineIdx, tt.feature2, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_Halt(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"LINE1",
|
|
"LINE2",
|
|
"#HALT",
|
|
"LINE3",
|
|
"LINE4",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
|
|
if err == nil {
|
|
t.Fatal("expected HaltError, got nil")
|
|
}
|
|
|
|
if _, ok := err.(HaltError); !ok {
|
|
t.Fatalf("expected HaltError, got %T: %v", err, err)
|
|
}
|
|
|
|
if len(lines) != 2 {
|
|
t.Errorf("expected 2 lines before halt, got %d", len(lines))
|
|
}
|
|
|
|
if len(lines) >= 1 && lines[0].Text != "LINE1" {
|
|
t.Errorf("expected LINE1, got %q", lines[0].Text)
|
|
}
|
|
|
|
if len(lines) >= 2 && lines[1].Text != "LINE2" {
|
|
t.Errorf("expected LINE2, got %q", lines[1].Text)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_HaltInInclude(t *testing.T) {
|
|
files := map[string][]string{
|
|
"main.c65": {
|
|
"MAIN_LINE1",
|
|
"#INCLUDE lib.c65",
|
|
"MAIN_LINE2",
|
|
},
|
|
"lib.c65": {
|
|
"LIB_LINE1",
|
|
"#HALT",
|
|
"LIB_LINE2",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("main.c65", reader)
|
|
|
|
if err == nil {
|
|
t.Fatal("expected HaltError, got nil")
|
|
}
|
|
|
|
if _, ok := err.(HaltError); !ok {
|
|
t.Fatalf("expected HaltError, got %T: %v", err, err)
|
|
}
|
|
|
|
if len(lines) != 2 {
|
|
t.Fatalf("expected 2 lines before halt, got %d", len(lines))
|
|
}
|
|
|
|
if lines[0].Text != "MAIN_LINE1" {
|
|
t.Errorf("expected MAIN_LINE1, got %q", lines[0].Text)
|
|
}
|
|
|
|
if lines[1].Text != "LIB_LINE1" {
|
|
t.Errorf("expected LIB_LINE1, got %q", lines[1].Text)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_MixedBlocksAndComments(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"#DEFINE X = 10",
|
|
"LDA #X // source comment",
|
|
"ASM",
|
|
" lda #X // asm comment",
|
|
"ENDASM",
|
|
"SCRIPT",
|
|
" y = X // script comment",
|
|
"ENDSCRIPT",
|
|
"STA $D020 // another source comment",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
expected := []struct {
|
|
text string
|
|
kind LineKind
|
|
}{
|
|
{"LDA #10", Source},
|
|
{" lda #X // asm comment", Assembler},
|
|
{" y = X // script comment", Script},
|
|
{"STA $D020", Source},
|
|
}
|
|
|
|
if len(lines) != len(expected) {
|
|
t.Fatalf("expected %d lines, got %d", len(expected), len(lines))
|
|
}
|
|
|
|
for i, exp := range expected {
|
|
if lines[i].Text != exp.text {
|
|
t.Errorf("line %d: expected text %q, got %q", i, exp.text, lines[i].Text)
|
|
}
|
|
if lines[i].Kind != exp.kind {
|
|
t.Errorf("line %d: expected Kind=%v, got %v", i, exp.kind, lines[i].Kind)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_EmptyASMBlock(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"ASM",
|
|
"ENDASM",
|
|
"NOP",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
// Empty block produces no lines, just NOP
|
|
if len(lines) != 1 {
|
|
t.Fatalf("expected 1 line, got %d", len(lines))
|
|
}
|
|
|
|
if lines[0].Text != "NOP" {
|
|
t.Errorf("expected 'NOP', got %q", lines[0].Text)
|
|
}
|
|
}
|
|
|
|
func TestPreProcess_EmptyScriptBlock(t *testing.T) {
|
|
files := map[string][]string{
|
|
"test.c65": {
|
|
"SCRIPT",
|
|
"ENDSCRIPT",
|
|
"NOP",
|
|
},
|
|
}
|
|
reader := NewMockFileReader(files)
|
|
lines, err := PreProcess("test.c65", reader)
|
|
if err != nil {
|
|
t.Fatalf("PreProcess failed: %v", err)
|
|
}
|
|
|
|
if len(lines) != 1 {
|
|
t.Fatalf("expected 1 line, got %d", len(lines))
|
|
}
|
|
|
|
if lines[0].Text != "NOP" {
|
|
t.Errorf("expected 'NOP', got %q", lines[0].Text)
|
|
}
|
|
}
|