Added the WORD <name> = @<label | variable> syntax. Initializes a WORD variable with the address of (pointer to) that symbol.

This commit is contained in:
Mattias Hansson 2026-02-24 23:04:58 +01:00
parent 8398a30c97
commit b614bcb043
4 changed files with 218 additions and 1 deletions

View file

@ -96,6 +96,12 @@ Calls a function with optional arguments.
<funcname>(<param1>[,<param2>,...]) <funcname>(<param1>[,<param2>,...])
``` ```
**Arguments can be:**
- Variables: `myvar`
- Constants or literals: `42`, `$FF`
- String literals: `"hello"` (passed as pointer to WORD parameter)
- Label addresses: `@spriteData` (passed as pointer to WORD parameter)
**Examples:** **Examples:**
``` ```
initialize initialize
@ -734,6 +740,7 @@ Declares a 16-bit variable or constant.
WORD <varname> WORD <varname>
WORD <varname> = <value> WORD <varname> = <value>
WORD <varname> = "<string>" WORD <varname> = "<string>"
WORD <varname> = @<label>
WORD <varname> @ <address> WORD <varname> @ <address>
WORD CONST <varname> = <value> WORD CONST <varname> = <value>
``` ```
@ -743,10 +750,13 @@ WORD CONST <varname> = <value>
WORD counter WORD counter
WORD address = $C000 WORD address = $C000
WORD message = "Hello" WORD message = "Hello"
WORD spritePtr = @spriteData
WORD irqVector @ $0314 WORD irqVector @ $0314
WORD CONST SCREEN = $0400 WORD CONST SCREEN = $0400
``` ```
The `@<label>` syntax initializes a WORD variable with the address of a label. This is useful for pointers to data blocks or code labels. The label can be a forward reference (declared later in the source) or an existing variable name. For local variables, the name is automatically expanded to the full scoped name.
--- ---
## XOR ## XOR

View file

@ -15,6 +15,7 @@ import (
// WORD varname # word with init = 0 // WORD varname # word with init = 0
// WORD varname = value # word with init value // WORD varname = value # word with init value
// WORD varname = "string" # word pointer to constant string // WORD varname = "string" # word pointer to constant string
// WORD varname = @label # word initialized to label address
// WORD varname @ address # word at absolute address // WORD varname @ address # word at absolute address
// WORD CONST varname = value # constant word // WORD CONST varname = value # constant word
type WordCommand struct { type WordCommand struct {
@ -111,7 +112,34 @@ func (c *WordCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext
return nil return nil
} }
// Not a string, evaluate as expression // Check for label reference: @label
if strings.HasPrefix(valueStr, "@") {
if operator != "=" {
return fmt.Errorf("WORD: expected '=' when assigning label reference, got %q", operator)
}
labelName := valueStr[1:] // strip @
if !utils.ValidateIdentifier(labelName) {
return fmt.Errorf("WORD: invalid label identifier %q", labelName)
}
// Look up to expand local variable names, otherwise use as-is (forward reference)
labelRef := labelName
if sym := ctx.SymbolTable.Lookup(labelName, ctx.CurrentScope()); sym != nil {
labelRef = sym.FullName()
}
err = ctx.SymbolTable.AddLabel(varName, scope, labelRef)
if err != nil {
return fmt.Errorf("WORD: %w", err)
}
c.varName = varName
c.value = 0 // label refs don't have numeric values
return nil
}
// Not a string or label, evaluate as expression
value, err = utils.EvaluateExpression(valueStr, constLookup) value, err = utils.EvaluateExpression(valueStr, constLookup)
if err != nil { if err != nil {
return fmt.Errorf("WORD: invalid value %q: %w", valueStr, err) return fmt.Errorf("WORD: invalid value %q: %w", valueStr, err)

View file

@ -423,6 +423,182 @@ func TestWordCommand_WithConstantExpression(t *testing.T) {
} }
} }
func TestWordCommand_LabelReference(t *testing.T) {
tests := []struct {
name string
text string
wantErr bool
checkVar func(*testing.T, *compiler.CompilerContext)
}{
{
name: "word with label reference",
text: "WORD ptr = @myLabel",
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.Has(compiler.FlagLabelRef) {
t.Error("Expected label reference flag")
}
if sym.LabelRef != "myLabel" {
t.Errorf("Expected LabelRef 'myLabel', got %q", sym.LabelRef)
}
},
},
{
name: "word referencing another variable",
text: "WORD ptr = @existingVar",
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.Has(compiler.FlagLabelRef) {
t.Error("Expected label reference flag")
}
// Should expand to the full name of existingVar
if sym.LabelRef != "existingVar" {
t.Errorf("Expected LabelRef 'existingVar', got %q", sym.LabelRef)
}
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pragma := preproc.NewPragma()
ctx := compiler.NewCompilerContext(pragma)
// Pre-declare existingVar for the reference test
if tt.name == "word referencing another variable" {
ctx.SymbolTable.AddVar("existingVar", "", compiler.KindWord, 0)
}
cmd := &WordCommand{}
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")
}
} else {
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
if tt.checkVar != nil {
tt.checkVar(t, ctx)
}
}
})
}
}
func TestWordCommand_LabelReferenceLocalExpansion(t *testing.T) {
pragma := preproc.NewPragma()
ctx := compiler.NewCompilerContext(pragma)
// Enter a function scope
_, err := ctx.FunctionHandler.HandleFuncDecl(preproc.Line{
Text: "FUNC myFunc ( {BYTE dummy} )",
Filename: "test.c65",
LineNo: 1,
Kind: preproc.Source,
})
if err != nil {
t.Fatalf("HandleFuncDecl failed: %v", err)
}
// Verify we're in function scope
if ctx.FunctionHandler.CurrentFunction() != "myFunc" {
t.Fatalf("Expected current function 'myFunc', got %q", ctx.FunctionHandler.CurrentFunction())
}
// Declare a local variable in function scope
ctx.SymbolTable.AddVar("localBuffer", "myFunc", compiler.KindWord, 0)
// Now declare another variable referencing the local
cmd := &WordCommand{}
line := preproc.Line{
Text: "WORD ptr = @localBuffer",
Filename: "test.c65",
LineNo: 2,
Kind: preproc.Source,
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
}
err = cmd.Interpret(line, ctx)
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
// Check that LabelRef was expanded to the full name
sym := ctx.SymbolTable.Lookup("ptr", []string{"myFunc"})
if sym == nil {
t.Fatal("Expected variable ptr to be declared")
}
if !sym.Has(compiler.FlagLabelRef) {
t.Error("Expected label reference flag")
}
// Should be expanded to "myFunc_localBuffer"
if sym.LabelRef != "myFunc_localBuffer" {
t.Errorf("Expected LabelRef 'myFunc_localBuffer', got %q", sym.LabelRef)
}
}
func TestWordCommand_LabelReferenceErrors(t *testing.T) {
tests := []struct {
name string
text string
errContains string
}{
{
name: "label ref with wrong operator",
text: "WORD x @ @myLabel",
errContains: "expected '=' when assigning label reference",
},
{
name: "invalid label identifier",
text: "WORD x = @123invalid",
errContains: "invalid label identifier",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
pragma := preproc.NewPragma()
ctx := compiler.NewCompilerContext(pragma)
cmd := &WordCommand{}
line := preproc.Line{
Text: tt.text,
Filename: "test.c65",
LineNo: 1,
Kind: preproc.Source,
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
}
err := cmd.Interpret(line, ctx)
if err == nil {
t.Fatal("Expected error, got nil")
}
if !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("Error %q does not contain %q", err.Error(), tt.errContains)
}
})
}
}
func TestWordCommand_MultipleStrings(t *testing.T) { func TestWordCommand_MultipleStrings(t *testing.T) {
pragma := preproc.NewPragma() pragma := preproc.NewPragma()
ctx := compiler.NewCompilerContext(pragma) ctx := compiler.NewCompilerContext(pragma)

View file

@ -102,6 +102,7 @@ WORD variables store 16-bit values (0-65535).
WORD counter // Uninitialized WORD counter // Uninitialized
WORD address = $C000 // Initialized to hex value WORD address = $C000 // Initialized to hex value
WORD message = "Hello" // String literal (pointer to text) WORD message = "Hello" // String literal (pointer to text)
WORD dataPtr = @myData // Initialized to label address
WORD screenPtr @ $FB // Zero-page pointer WORD screenPtr @ $FB // Zero-page pointer
WORD CONST SCREEN_RAM = $0400 // Constant WORD CONST SCREEN_RAM = $0400 // Constant
``` ```
@ -291,6 +292,8 @@ FEND
initialize() initialize()
setColor(1, 14) setColor(1, 14)
process(xpos, ypos, spriteData) process(xpos, ypos, spriteData)
printMessage("Hello world") // String literal (passed as pointer)
drawSprite(x, y, @spriteData) // Label address (passed as pointer)
``` ```
### Parameter Passing Modes ### Parameter Passing Modes