440 lines
10 KiB
Go
440 lines
10 KiB
Go
package commands
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"c65gm/internal/compiler"
|
|
"c65gm/internal/preproc"
|
|
)
|
|
|
|
func TestByteCommand_WillHandle(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
text string
|
|
want bool
|
|
}{
|
|
{
|
|
name: "handles BYTE",
|
|
text: "BYTE x",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "handles byte lowercase",
|
|
text: "byte x",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "handles BYTE with init",
|
|
text: "BYTE x = 10",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "handles BYTE at absolute",
|
|
text: "BYTE x @ $C000",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "handles BYTE CONST",
|
|
text: "BYTE CONST x = 10",
|
|
want: true,
|
|
},
|
|
{
|
|
name: "does not handle WORD",
|
|
text: "WORD x",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "does not handle empty",
|
|
text: "",
|
|
want: false,
|
|
},
|
|
{
|
|
name: "does not handle other command",
|
|
text: "LET x 5",
|
|
want: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
cmd := &ByteCommand{}
|
|
line := preproc.Line{Text: tt.text}
|
|
got := cmd.WillHandle(line)
|
|
if got != tt.want {
|
|
t.Errorf("WillHandle() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestByteCommand_Interpret(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
text string
|
|
wantErr bool
|
|
errContains string
|
|
checkVar func(*testing.T, *compiler.CompilerContext)
|
|
}{
|
|
{
|
|
name: "simple byte",
|
|
text: "BYTE x",
|
|
wantErr: false,
|
|
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
|
|
sym := ctx.SymbolTable.Lookup("x", nil)
|
|
if sym == nil {
|
|
t.Fatal("Expected variable x to be declared")
|
|
}
|
|
if !sym.IsByte() {
|
|
t.Error("Expected byte variable")
|
|
}
|
|
if sym.IsConst() {
|
|
t.Error("Expected regular variable, not const")
|
|
}
|
|
if sym.Value != 0 {
|
|
t.Errorf("Expected init value 0, got %d", sym.Value)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "byte with decimal init",
|
|
text: "BYTE counter = 42",
|
|
wantErr: false,
|
|
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
|
|
sym := ctx.SymbolTable.Lookup("counter", nil)
|
|
if sym == nil {
|
|
t.Fatal("Expected variable counter to be declared")
|
|
}
|
|
if sym.Value != 42 {
|
|
t.Errorf("Expected init value 42, got %d", sym.Value)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "byte with hex init",
|
|
text: "BYTE status = $FF",
|
|
wantErr: false,
|
|
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
|
|
sym := ctx.SymbolTable.Lookup("status", nil)
|
|
if sym == nil {
|
|
t.Fatal("Expected variable status to be declared")
|
|
}
|
|
if sym.Value != 255 {
|
|
t.Errorf("Expected init value 255, got %d", sym.Value)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "byte at absolute address",
|
|
text: "BYTE ptr @ $C000",
|
|
wantErr: false,
|
|
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
|
|
sym := ctx.SymbolTable.Lookup("ptr", nil)
|
|
if sym == nil {
|
|
t.Fatal("Expected variable ptr to be declared")
|
|
}
|
|
if !sym.IsAbsolute() {
|
|
t.Error("Expected absolute variable")
|
|
}
|
|
if sym.AbsAddr != 0xC000 {
|
|
t.Errorf("Expected address $C000, got $%04X", sym.AbsAddr)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "byte at zero page",
|
|
text: "BYTE zpvar @ $80",
|
|
wantErr: false,
|
|
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
|
|
sym := ctx.SymbolTable.Lookup("zpvar", nil)
|
|
if sym == nil {
|
|
t.Fatal("Expected variable zpvar to be declared")
|
|
}
|
|
if !sym.IsZeroPage() {
|
|
t.Error("Expected zero page variable")
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "const byte",
|
|
text: "BYTE CONST maxval = 255",
|
|
wantErr: false,
|
|
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
|
|
sym := ctx.SymbolTable.Lookup("maxval", nil)
|
|
if sym == nil {
|
|
t.Fatal("Expected constant maxval to be declared")
|
|
}
|
|
if !sym.IsConst() {
|
|
t.Error("Expected constant")
|
|
}
|
|
if sym.Value != 255 {
|
|
t.Errorf("Expected value 255, got %d", sym.Value)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "const byte with hex",
|
|
text: "BYTE CONST flag = $FF",
|
|
wantErr: false,
|
|
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
|
|
sym := ctx.SymbolTable.Lookup("flag", nil)
|
|
if sym == nil {
|
|
t.Fatal("Expected constant flag to be declared")
|
|
}
|
|
if !sym.IsConst() {
|
|
t.Error("Expected constant")
|
|
}
|
|
if sym.Value != 255 {
|
|
t.Errorf("Expected value 255, got %d", sym.Value)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "byte with expression",
|
|
text: "BYTE x = 10+20",
|
|
wantErr: false,
|
|
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
|
|
sym := ctx.SymbolTable.Lookup("x", nil)
|
|
if sym == nil {
|
|
t.Fatal("Expected variable x to be declared")
|
|
}
|
|
if sym.Value != 30 {
|
|
t.Errorf("Expected value 30, got %d", sym.Value)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "byte with binary",
|
|
text: "BYTE x = %11111111",
|
|
wantErr: false,
|
|
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
|
|
sym := ctx.SymbolTable.Lookup("x", nil)
|
|
if sym == nil {
|
|
t.Fatal("Expected variable x to be declared")
|
|
}
|
|
if sym.Value != 255 {
|
|
t.Errorf("Expected value 255, got %d", sym.Value)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "byte with bitwise OR",
|
|
text: "BYTE x = $F0|$0F",
|
|
wantErr: false,
|
|
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
|
|
sym := ctx.SymbolTable.Lookup("x", nil)
|
|
if sym == nil {
|
|
t.Fatal("Expected variable x to be declared")
|
|
}
|
|
if sym.Value != 255 {
|
|
t.Errorf("Expected value 255, got %d", sym.Value)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "byte with bitwise AND",
|
|
text: "BYTE x = $FF&$0F",
|
|
wantErr: false,
|
|
checkVar: func(t *testing.T, ctx *compiler.CompilerContext) {
|
|
sym := ctx.SymbolTable.Lookup("x", nil)
|
|
if sym == nil {
|
|
t.Fatal("Expected variable x to be declared")
|
|
}
|
|
if sym.Value != 15 {
|
|
t.Errorf("Expected value 15, got %d", sym.Value)
|
|
}
|
|
},
|
|
},
|
|
{
|
|
name: "byte with out of range value",
|
|
text: "BYTE x = 256",
|
|
wantErr: true,
|
|
errContains: "out of range",
|
|
},
|
|
{
|
|
name: "const byte out of range",
|
|
text: "BYTE CONST x = 256",
|
|
wantErr: true,
|
|
errContains: "out of range",
|
|
},
|
|
{
|
|
name: "byte without name",
|
|
text: "BYTE",
|
|
wantErr: true,
|
|
errContains: "wrong number of parameters",
|
|
},
|
|
{
|
|
name: "byte with invalid identifier",
|
|
text: "BYTE 123invalid",
|
|
wantErr: true,
|
|
errContains: "invalid identifier",
|
|
},
|
|
{
|
|
name: "byte with wrong operator",
|
|
text: "BYTE x + 10",
|
|
wantErr: true,
|
|
errContains: "expected '=' or '@'",
|
|
},
|
|
{
|
|
name: "const without equals",
|
|
text: "BYTE CONST x 10",
|
|
wantErr: true,
|
|
errContains: "expected '='",
|
|
},
|
|
{
|
|
name: "wrong keyword instead of CONST",
|
|
text: "BYTE VAR x = 10",
|
|
wantErr: true,
|
|
errContains: "expected CONST keyword",
|
|
},
|
|
{
|
|
name: "duplicate declaration",
|
|
text: "BYTE x",
|
|
wantErr: true,
|
|
errContains: "already declared",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
|
|
// For duplicate test, pre-declare the variable
|
|
if tt.name == "duplicate declaration" {
|
|
ctx.SymbolTable.AddVar("x", "", compiler.KindByte, 0)
|
|
}
|
|
|
|
cmd := &ByteCommand{}
|
|
line := preproc.Line{
|
|
Text: tt.text,
|
|
Filename: "test.c65",
|
|
LineNo: 1,
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Fatal("Expected error, got nil")
|
|
}
|
|
if tt.errContains != "" && !strings.Contains(err.Error(), tt.errContains) {
|
|
t.Errorf("Error %q does not contain %q", err.Error(), tt.errContains)
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %v", err)
|
|
}
|
|
if tt.checkVar != nil {
|
|
tt.checkVar(t, ctx)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestByteCommand_Generate(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
|
|
cmd := &ByteCommand{}
|
|
line := preproc.Line{
|
|
Text: "BYTE x = 10",
|
|
Filename: "test.c65",
|
|
LineNo: 1,
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: 0,
|
|
}
|
|
|
|
// Interpret first
|
|
if err := cmd.Interpret(line, ctx); err != nil {
|
|
t.Fatalf("Interpret failed: %v", err)
|
|
}
|
|
|
|
// Generate should return nil (variables handled by assembleOutput)
|
|
output, err := cmd.Generate(ctx)
|
|
if err != nil {
|
|
t.Errorf("Generate returned error: %v", err)
|
|
}
|
|
if output != nil {
|
|
t.Errorf("Generate should return nil, got %v", output)
|
|
}
|
|
}
|
|
|
|
func TestByteCommand_InFunctionScope(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
|
|
// Simulate being inside a function
|
|
// We'd need to push function context, but for now we can test the scoping manually
|
|
scope := "myFunc"
|
|
ctx.SymbolTable.AddVar("localVar", scope, compiler.KindByte, 5)
|
|
|
|
// Check it was added with correct scope
|
|
sym := ctx.SymbolTable.Lookup("localVar", []string{scope})
|
|
if sym == nil {
|
|
t.Fatal("Expected local variable to be declared")
|
|
}
|
|
if sym.Scope != scope {
|
|
t.Errorf("Expected scope %q, got %q", scope, sym.Scope)
|
|
}
|
|
if sym.FullName() != "myFunc_localVar" {
|
|
t.Errorf("Expected full name myFunc_localVar, got %q", sym.FullName())
|
|
}
|
|
}
|
|
|
|
func TestByteCommand_WithConstantExpression(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
|
|
// First, declare a constant
|
|
ctx.SymbolTable.AddConst("MAXVAL", "", compiler.KindByte, 200)
|
|
ctx.SymbolTable.AddConst("OFFSET", "", compiler.KindByte, 10)
|
|
|
|
// Now declare a byte using the constant in an expression
|
|
cmd := &ByteCommand{}
|
|
line := preproc.Line{
|
|
Text: "BYTE x = MAXVAL+OFFSET",
|
|
Filename: "test.c65",
|
|
LineNo: 1,
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: 0,
|
|
}
|
|
|
|
if err := cmd.Interpret(line, ctx); err != nil {
|
|
t.Fatalf("Interpret failed: %v", err)
|
|
}
|
|
|
|
// Check value
|
|
sym := ctx.SymbolTable.Lookup("x", nil)
|
|
if sym == nil {
|
|
t.Fatal("Expected variable x to be declared")
|
|
}
|
|
if sym.Value != 210 {
|
|
t.Errorf("Expected value 210 (200+10), got %d", sym.Value)
|
|
}
|
|
}
|
|
|
|
func TestByteCommand_ConstantNotFound(t *testing.T) {
|
|
pragma := preproc.NewPragma()
|
|
ctx := compiler.NewCompilerContext(pragma)
|
|
|
|
cmd := &ByteCommand{}
|
|
line := preproc.Line{
|
|
Text: "BYTE x = UNKNOWN",
|
|
Filename: "test.c65",
|
|
LineNo: 1,
|
|
Kind: preproc.Source,
|
|
PragmaSetIndex: 0,
|
|
}
|
|
|
|
err := cmd.Interpret(line, ctx)
|
|
if err == nil {
|
|
t.Fatal("Expected error for unknown constant")
|
|
}
|
|
if !strings.Contains(err.Error(), "not found") {
|
|
t.Errorf("Error should mention constant not found, got: %v", err)
|
|
}
|
|
}
|