Added the WORD <name> = @<label | variable> syntax. Initializes a WORD variable with the address of (pointer to) that symbol.
This commit is contained in:
parent
8398a30c97
commit
b614bcb043
4 changed files with 218 additions and 1 deletions
10
commands.md
10
commands.md
|
|
@ -96,6 +96,12 @@ Calls a function with optional arguments.
|
|||
<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:**
|
||||
```
|
||||
initialize
|
||||
|
|
@ -734,6 +740,7 @@ Declares a 16-bit variable or constant.
|
|||
WORD <varname>
|
||||
WORD <varname> = <value>
|
||||
WORD <varname> = "<string>"
|
||||
WORD <varname> = @<label>
|
||||
WORD <varname> @ <address>
|
||||
WORD CONST <varname> = <value>
|
||||
```
|
||||
|
|
@ -743,10 +750,13 @@ WORD CONST <varname> = <value>
|
|||
WORD counter
|
||||
WORD address = $C000
|
||||
WORD message = "Hello"
|
||||
WORD spritePtr = @spriteData
|
||||
WORD irqVector @ $0314
|
||||
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
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
// WORD varname # word with init = 0
|
||||
// WORD varname = value # word with init value
|
||||
// WORD varname = "string" # word pointer to constant string
|
||||
// WORD varname = @label # word initialized to label address
|
||||
// WORD varname @ address # word at absolute address
|
||||
// WORD CONST varname = value # constant word
|
||||
type WordCommand struct {
|
||||
|
|
@ -111,7 +112,34 @@ func (c *WordCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext
|
|||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("WORD: invalid value %q: %w", valueStr, err)
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ WORD variables store 16-bit values (0-65535).
|
|||
WORD counter // Uninitialized
|
||||
WORD address = $C000 // Initialized to hex value
|
||||
WORD message = "Hello" // String literal (pointer to text)
|
||||
WORD dataPtr = @myData // Initialized to label address
|
||||
WORD screenPtr @ $FB // Zero-page pointer
|
||||
WORD CONST SCREEN_RAM = $0400 // Constant
|
||||
```
|
||||
|
|
@ -291,6 +292,8 @@ FEND
|
|||
initialize()
|
||||
setColor(1, 14)
|
||||
process(xpos, ypos, spriteData)
|
||||
printMessage("Hello world") // String literal (passed as pointer)
|
||||
drawSprite(x, y, @spriteData) // Label address (passed as pointer)
|
||||
```
|
||||
|
||||
### Parameter Passing Modes
|
||||
|
|
|
|||
Loading…
Reference in a new issue