Fixed up POKE and POKEW ,-syntax
This commit is contained in:
parent
cf01db5e28
commit
3d94dde5ea
17 changed files with 1182 additions and 21 deletions
|
|
@ -1,5 +1,5 @@
|
|||
FROM ghcr.io/anomalyco/opencode:1.14.48
|
||||
#FROM ghcr.io/anomalyco/opencode:latest
|
||||
#FROM ghcr.io/anomalyco/opencode:1.14.48
|
||||
FROM ghcr.io/anomalyco/opencode:latest
|
||||
|
||||
RUN apk add --no-cache go gcc musl-dev
|
||||
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ FUNC sethires
|
|||
BYTE b
|
||||
b = PEEK $d011
|
||||
b = b | 32 //enable bitmap mode
|
||||
POKE $d011 , b
|
||||
POKE $d011, b
|
||||
|
||||
b = PEEK $d018
|
||||
b = b & %11110000
|
||||
b = b | 8 //enable bitmap mode
|
||||
POKE $d018 , b
|
||||
POKE $d018, b
|
||||
|
||||
|
||||
FEND
|
||||
|
|
@ -22,7 +22,7 @@ FEND
|
|||
FUNC fillmem({WORD start_addr @ $fa} {WORD end_addr @ $fc} {BYTE value})
|
||||
|
||||
WHILE start_addr <= end_addr
|
||||
POKE start_addr , value
|
||||
POKE start_addr, value
|
||||
start_addr++
|
||||
WEND
|
||||
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ LABEL start
|
|||
// Copy 40 bytes from scrdata to screen memory
|
||||
FOR i = 0 TO 39
|
||||
val = PEEK src
|
||||
POKE dst WITH val
|
||||
POKE dst, val
|
||||
src++
|
||||
dst++
|
||||
NEXT
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ FUNC wait_key
|
|||
WEND
|
||||
|
||||
// Reset key buffer
|
||||
POKE $c6 WITH 0
|
||||
POKE $c6, 0
|
||||
|
||||
FEND
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ FUNC wait_key
|
|||
ENDIF
|
||||
WEND
|
||||
|
||||
POKE $c6 WITH 0
|
||||
POKE $c6, 0
|
||||
FEND
|
||||
|
||||
//-----------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ FUNC wait_key
|
|||
ENDIF
|
||||
WEND
|
||||
|
||||
POKE $c6 WITH 0
|
||||
POKE $c6, 0
|
||||
FEND
|
||||
|
||||
//-----------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/bin/sh
|
||||
# Define filename as variable
|
||||
PROGNAME="multicolorbm"
|
||||
# Compile and assemble directly
|
||||
c65gm ${PROGNAME}.c65
|
||||
# Compile and assemble directly, keep intermediate .asm file, enable optimizations
|
||||
c65gm build -i ${PROGNAME}.c65 --keep-asm --opt
|
||||
|
|
@ -8,16 +8,16 @@ FUNC setmulti
|
|||
BYTE b
|
||||
b = PEEK $d011
|
||||
b = b | 32
|
||||
POKE $d011 , b
|
||||
POKE $d011, b
|
||||
|
||||
b = PEEK $d016
|
||||
b = b | 16
|
||||
POKE $d016 , b
|
||||
POKE $d016, b
|
||||
|
||||
b = PEEK $d018
|
||||
b = b & %11110000
|
||||
b = b | 8
|
||||
POKE $d018 , b
|
||||
POKE $d018, b
|
||||
|
||||
FEND
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ FEND
|
|||
FUNC fillmem({WORD start_addr @ $fa} {WORD end_addr @ $fc} {BYTE value})
|
||||
|
||||
WHILE start_addr <= end_addr
|
||||
POKE start_addr , value
|
||||
POKE start_addr, value
|
||||
start_addr++
|
||||
WEND
|
||||
|
||||
|
|
@ -41,7 +41,7 @@ FUNC main
|
|||
fillmem(screen, screen+999, $12)
|
||||
fillmem(colorram, colorram+999, $03)
|
||||
|
||||
POKE $d021 , 0
|
||||
POKE $d021, 0
|
||||
|
||||
WHILE 1
|
||||
fillmem($2000, $3fff, %00011011)
|
||||
|
|
|
|||
5
examples/multicolorbm_v2/cm.sh
Executable file
5
examples/multicolorbm_v2/cm.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
# Define filename as variable
|
||||
PROGNAME="multicolorbm_v2"
|
||||
# Compile and assemble directly, keep intermediate .asm file, enable optimizations
|
||||
c65gm build -i ${PROGNAME}.c65 --keep-asm --opt
|
||||
131
examples/multicolorbm_v2/multicolorbm_v2.c65
Normal file
131
examples/multicolorbm_v2/multicolorbm_v2.c65
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
//-----------------------------------------------------------
|
||||
// multicolorbm_v2 - Multi-color bitmap demo using SCRIPT MACRO
|
||||
//
|
||||
// Uses compile-time code generation via SCRIPT MACRO to produce
|
||||
// optimized fill routines instead of a generic runtime loop.
|
||||
// The fill_fast macro analyzes the memory range at compile time
|
||||
// and selects the most cycle-efficient 6502 fill strategy.
|
||||
//-----------------------------------------------------------
|
||||
|
||||
#INCLUDE <c64start.c65>
|
||||
#INCLUDE <c64defs.c65>
|
||||
|
||||
GOTO start
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// SCRIPT LIBRARY: optimized fill code generation
|
||||
//
|
||||
// At compile time, this analyzes the start/end/value and emits
|
||||
// specialized assembly. Small fills (<256 bytes) use a simple
|
||||
// X-indexed loop. Whole-page fills use page-based unrolling.
|
||||
// Large partial-page fills choose the cycle-cheapest strategy.
|
||||
//-----------------------------------------------------------
|
||||
SCRIPT LIBRARY
|
||||
def to_hex(v):
|
||||
digits = "0123456789abcdef"
|
||||
return digits[(v >> 12) & 15] + digits[(v >> 8) & 15] + digits[(v >> 4) & 15] + digits[v & 15]
|
||||
|
||||
def emit_fill_fast(start, end, value):
|
||||
total = end - start + 1
|
||||
|
||||
print("")
|
||||
print("; fill_fast $" + to_hex(start) + "..$" + to_hex(end) + " = " + str(total) + " bytes")
|
||||
print(" lda #" + str(value))
|
||||
|
||||
if total < 256:
|
||||
print(" ldx #" + str(total))
|
||||
print("-")
|
||||
print(" sta $" + to_hex(start) + "-1,x")
|
||||
print(" dex")
|
||||
print(" bne -")
|
||||
elif total == 256:
|
||||
print(" ldx #0")
|
||||
print("-")
|
||||
print(" sta $" + to_hex(start) + ",x")
|
||||
print(" inx")
|
||||
print(" bne -")
|
||||
else:
|
||||
full_pages = int(total / 256)
|
||||
remain = total - full_pages * 256
|
||||
|
||||
cycles_a = (5 * full_pages + 4) * 256 + 9 * remain
|
||||
cycles_b = (5 * (full_pages + 1) + 4) * 256 - 1
|
||||
|
||||
if cycles_a <= cycles_b:
|
||||
pages = [start + i * 256 for i in range(full_pages)]
|
||||
print(" ldx #0")
|
||||
print("-")
|
||||
for a in pages:
|
||||
print(" sta $" + to_hex(a) + ",x")
|
||||
print(" inx")
|
||||
print(" bne -")
|
||||
if remain > 0:
|
||||
print(" ldx #" + str(remain))
|
||||
print("--")
|
||||
print(" sta $" + to_hex(start + full_pages * 256) + "-1,x")
|
||||
print(" dex")
|
||||
print(" bne --")
|
||||
else:
|
||||
pages = [start + i * 256 for i in range(full_pages)]
|
||||
pages.append(end - 255)
|
||||
print(" ldx #0")
|
||||
print("-")
|
||||
for a in pages:
|
||||
print(" sta $" + to_hex(a) + ",x")
|
||||
print(" inx")
|
||||
print(" bne -")
|
||||
ENDSCRIPT
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// SCRIPT MACRO: inline fill_fast
|
||||
//
|
||||
// Replacements for runtime fillmem(). Generates optimized
|
||||
// assembly inline at each call site.
|
||||
//-----------------------------------------------------------
|
||||
SCRIPT MACRO fill_fast(start, end, value)
|
||||
emit_fill_fast(start, end, value)
|
||||
ENDSCRIPT
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// VIC-II multi-color bitmap setup (unchanged from v1)
|
||||
//-----------------------------------------------------------
|
||||
FUNC setmulti
|
||||
BYTE b
|
||||
b = PEEK $d011
|
||||
b = b | 32
|
||||
POKE $d011, b
|
||||
|
||||
b = PEEK $d016
|
||||
b = b | 16
|
||||
POKE $d016, b
|
||||
|
||||
b = PEEK $d018
|
||||
b = b & %11110000
|
||||
b = b | 8
|
||||
POKE $d018, b
|
||||
FEND
|
||||
|
||||
//-----------------------------------------------------------
|
||||
// Main program
|
||||
//
|
||||
// Uses @fill_fast macro instead of runtime fillmem() loops.
|
||||
// Each invocation generates optimal fill code for its range.
|
||||
//-----------------------------------------------------------
|
||||
FUNC main
|
||||
setmulti()
|
||||
|
||||
@fill_fast($0400, $0400+999, $12)
|
||||
@fill_fast(colorram, colorram+999, $03)
|
||||
|
||||
POKE $d021, 0
|
||||
|
||||
WHILE 1
|
||||
@fill_fast($2000, $3fff, %00011011)
|
||||
@fill_fast($2000, $3fff, %01101100)
|
||||
@fill_fast($2000, $3fff, %10110001)
|
||||
@fill_fast($2000, $3fff, %11000110)
|
||||
WEND
|
||||
FEND
|
||||
|
||||
LABEL start
|
||||
main()
|
||||
1
examples/multicolorbm_v2/start_in_vice.sh
Executable file
1
examples/multicolorbm_v2/start_in_vice.sh
Executable file
|
|
@ -0,0 +1 @@
|
|||
x64 -autostartprgmode 1 multicolorbm_v2.prg
|
||||
|
|
@ -43,7 +43,7 @@ FUNC wait_key
|
|||
WEND
|
||||
|
||||
// Reset key buffer
|
||||
POKE $c6 WITH 0
|
||||
POKE $c6, 0
|
||||
|
||||
FEND
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ type PokeCommand struct {
|
|||
}
|
||||
|
||||
func (c *PokeCommand) WillHandle(line preproc.Line) bool {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
params, err := utils.ParseParams(utils.NormalizeCommas(line.Text))
|
||||
if err != nil || len(params) != 4 {
|
||||
return false
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ func (c *PokeCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext
|
|||
// Store pragma set for Generate phase
|
||||
c.pragmaSet = ctx.Pragma.GetPragmaSetByIndex(line.PragmaSetIndex)
|
||||
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
params, err := utils.ParseParams(utils.NormalizeCommas(line.Text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
987
internal/commands/poke_test.go
Normal file
987
internal/commands/poke_test.go
Normal file
|
|
@ -0,0 +1,987 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
)
|
||||
|
||||
// setupPokeVars adds all needed symbols to a symbol table for POKE/POKEW tests.
|
||||
func setupPokeVars(st *compiler.SymbolTable) {
|
||||
st.AddConst("vic2", "", compiler.KindWord, 0xd000, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
st.AddVar("addrvar", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 2})
|
||||
st.AddVar("valvar", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 3})
|
||||
st.AddVar("wvalvar", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 4})
|
||||
st.AddVar("waddrvar", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 5})
|
||||
st.AddAbsolute("zpptr", "", compiler.KindWord, 0x80, preproc.Line{Filename: "test.c65", LineNo: 6})
|
||||
st.AddVar("offsvar", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 7})
|
||||
st.AddAbsolute("byaddr", "", compiler.KindByte, 0x40, preproc.Line{Filename: "test.c65", LineNo: 8})
|
||||
}
|
||||
|
||||
func newCtx() *compiler.CompilerContext {
|
||||
pragma := preproc.NewPragma()
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
setupPokeVars(ctx.SymbolTable)
|
||||
return ctx
|
||||
}
|
||||
|
||||
func newLine(text string, pragma *preproc.Pragma) preproc.Line {
|
||||
return preproc.Line{
|
||||
Text: text,
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// POKE comma-spacing tests (constant address + literal value — Case 4)
|
||||
// =============================================================================
|
||||
|
||||
func TestPokeCommaSpacing(t *testing.T) {
|
||||
expectedAsm := []string{
|
||||
"\tlda #5",
|
||||
"\tsta 53280",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
}{
|
||||
{"space before and after comma", "POKE $d020 , 5"},
|
||||
{"no space before or after comma", "POKE $d020,5"},
|
||||
{"no space before comma, space after", "POKE $d020, 5"},
|
||||
{"space before comma, no space after", "POKE $d020 ,5"},
|
||||
{"no space comma, expression address", "POKE $d020+0, 5"},
|
||||
{"WITH keyword", "POKE $d020 WITH 5"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := newCtx()
|
||||
cmd := &PokeCommand{}
|
||||
line := newLine(tt.line, ctx.Pragma)
|
||||
|
||||
if err := cmd.Interpret(line, ctx); err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
if !equalAsm(asm, expectedAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(expectedAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// POKEW comma-spacing tests (constant address + literal value — Case 4)
|
||||
// =============================================================================
|
||||
|
||||
func TestPokeWCommaSpacing(t *testing.T) {
|
||||
expectedAsm := []string{
|
||||
"\tlda #$65",
|
||||
"\tsta 53280",
|
||||
"\tlda #$00",
|
||||
"\tsta 53281",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
}{
|
||||
{"space before and after comma", "POKEW $d020 , 101"},
|
||||
{"no space before or after comma", "POKEW $d020,101"},
|
||||
{"no space before comma, space after", "POKEW $d020, 101"},
|
||||
{"space before comma, no space after", "POKEW $d020 ,101"},
|
||||
{"WITH keyword", "POKEW $d020 WITH 101"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := newCtx()
|
||||
cmd := &PokeWCommand{}
|
||||
line := newLine(tt.line, ctx.Pragma)
|
||||
|
||||
if err := cmd.Interpret(line, ctx); err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
if !equalAsm(asm, expectedAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(expectedAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// POKE Case 4 — Direct addressing (expression/constant address)
|
||||
// =============================================================================
|
||||
|
||||
func TestPokeDirectAddr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
wantAsm []string
|
||||
}{
|
||||
{
|
||||
name: "expression constant + expression value, no-space comma",
|
||||
line: "POKE vic2+15,5+2",
|
||||
wantAsm: []string{
|
||||
"\tlda #7",
|
||||
"\tsta 53263",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "variable value, no-space comma",
|
||||
line: "POKE $d020,valvar",
|
||||
wantAsm: []string{
|
||||
"\tlda valvar",
|
||||
"\tsta 53280",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "variable value, space before comma",
|
||||
line: "POKE $d020 ,valvar",
|
||||
wantAsm: []string{
|
||||
"\tlda valvar",
|
||||
"\tsta 53280",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "variable value, space after comma",
|
||||
line: "POKE $d020, valvar",
|
||||
wantAsm: []string{
|
||||
"\tlda valvar",
|
||||
"\tsta 53280",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "expression address + var value, no-space comma",
|
||||
line: "POKE vic2+15,valvar",
|
||||
wantAsm: []string{
|
||||
"\tlda valvar",
|
||||
"\tsta 53263",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := newCtx()
|
||||
cmd := &PokeCommand{}
|
||||
line := newLine(tt.line, ctx.Pragma)
|
||||
|
||||
if err := cmd.Interpret(line, ctx); err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
if !equalAsm(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// POKE Case 1 — ZP pointer (indexed indirect addressing)
|
||||
// =============================================================================
|
||||
|
||||
func TestPokeZPPointer(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
wantAsm []string
|
||||
}{
|
||||
{
|
||||
name: "no offset, literal value, no-space comma",
|
||||
line: "POKE zpptr,10",
|
||||
wantAsm: []string{
|
||||
"\tldy #0",
|
||||
"\tlda #10",
|
||||
"\tsta (zpptr),y",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no offset, var value, no-space comma",
|
||||
line: "POKE zpptr,valvar",
|
||||
wantAsm: []string{
|
||||
"\tldy #0",
|
||||
"\tlda valvar",
|
||||
"\tsta (zpptr),y",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal offset, literal value, no-space comma",
|
||||
line: "POKE zpptr[5],10",
|
||||
wantAsm: []string{
|
||||
"\tldy #5",
|
||||
"\tlda #10",
|
||||
"\tsta (zpptr),y",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal offset, var value, no-space comma",
|
||||
line: "POKE zpptr[5],valvar",
|
||||
wantAsm: []string{
|
||||
"\tldy #5",
|
||||
"\tlda valvar",
|
||||
"\tsta (zpptr),y",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "var offset, literal value, no-space comma",
|
||||
line: "POKE zpptr[offsvar],10",
|
||||
wantAsm: []string{
|
||||
"\tldy offsvar",
|
||||
"\tlda #10",
|
||||
"\tsta (zpptr),y",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "var offset, var value, no-space comma",
|
||||
line: "POKE zpptr[offsvar],valvar",
|
||||
wantAsm: []string{
|
||||
"\tldy offsvar",
|
||||
"\tlda valvar",
|
||||
"\tsta (zpptr),y",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal offset, var value, spaced comma",
|
||||
line: "POKE zpptr[5] , valvar",
|
||||
wantAsm: []string{
|
||||
"\tldy #5",
|
||||
"\tlda valvar",
|
||||
"\tsta (zpptr),y",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word variable value (uses low byte), no-space comma",
|
||||
line: "POKE zpptr[5],wvalvar",
|
||||
wantAsm: []string{
|
||||
"\tldy #5",
|
||||
"\tlda wvalvar",
|
||||
"\tsta (zpptr),y",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := newCtx()
|
||||
cmd := &PokeCommand{}
|
||||
line := newLine(tt.line, ctx.Pragma)
|
||||
|
||||
if err := cmd.Interpret(line, ctx); err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
if !equalAsm(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// POKE Case 2 — Byte variable address (self-modifying code)
|
||||
// =============================================================================
|
||||
|
||||
func TestPokeSMByteAddr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
wantAsm []string
|
||||
}{
|
||||
{
|
||||
name: "literal value, no-space comma",
|
||||
line: "POKE addrvar,10",
|
||||
wantAsm: []string{
|
||||
"\tlda addrvar",
|
||||
"\tsta _L1+1",
|
||||
"\tlda #10",
|
||||
"_L1",
|
||||
"\tsta $ff",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "var value, no-space comma",
|
||||
line: "POKE addrvar,valvar",
|
||||
wantAsm: []string{
|
||||
"\tlda addrvar",
|
||||
"\tsta _L1+1",
|
||||
"\tlda valvar",
|
||||
"_L1",
|
||||
"\tsta $ff",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word var value (uses low byte), no-space comma",
|
||||
line: "POKE addrvar,wvalvar",
|
||||
wantAsm: []string{
|
||||
"\tlda addrvar",
|
||||
"\tsta _L1+1",
|
||||
"\tlda wvalvar",
|
||||
"_L1",
|
||||
"\tsta $ff",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal value, spaced comma",
|
||||
line: "POKE addrvar , 10",
|
||||
wantAsm: []string{
|
||||
"\tlda addrvar",
|
||||
"\tsta _L1+1",
|
||||
"\tlda #10",
|
||||
"_L1",
|
||||
"\tsta $ff",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := newCtx()
|
||||
cmd := &PokeCommand{}
|
||||
line := newLine(tt.line, ctx.Pragma)
|
||||
|
||||
if err := cmd.Interpret(line, ctx); err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
if !equalAsm(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// POKE Case 3 — Word variable address (self-modifying code)
|
||||
// =============================================================================
|
||||
|
||||
func TestPokeSMWordAddr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
wantAsm []string
|
||||
}{
|
||||
{
|
||||
name: "literal value, no-space comma",
|
||||
line: "POKE waddrvar,10",
|
||||
wantAsm: []string{
|
||||
"\tlda waddrvar",
|
||||
"\tsta _L1+1",
|
||||
"\tlda waddrvar+1",
|
||||
"\tsta _L1+2",
|
||||
"\tlda #10",
|
||||
"_L1",
|
||||
"\tsta $ffff",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "var value, no-space comma",
|
||||
line: "POKE waddrvar,valvar",
|
||||
wantAsm: []string{
|
||||
"\tlda waddrvar",
|
||||
"\tsta _L1+1",
|
||||
"\tlda waddrvar+1",
|
||||
"\tsta _L1+2",
|
||||
"\tlda valvar",
|
||||
"_L1",
|
||||
"\tsta $ffff",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word var value (uses low byte), no-space comma",
|
||||
line: "POKE waddrvar,wvalvar",
|
||||
wantAsm: []string{
|
||||
"\tlda waddrvar",
|
||||
"\tsta _L1+1",
|
||||
"\tlda waddrvar+1",
|
||||
"\tsta _L1+2",
|
||||
"\tlda wvalvar",
|
||||
"_L1",
|
||||
"\tsta $ffff",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal value, spaced comma",
|
||||
line: "POKE waddrvar , 10",
|
||||
wantAsm: []string{
|
||||
"\tlda waddrvar",
|
||||
"\tsta _L1+1",
|
||||
"\tlda waddrvar+1",
|
||||
"\tsta _L1+2",
|
||||
"\tlda #10",
|
||||
"_L1",
|
||||
"\tsta $ffff",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := newCtx()
|
||||
cmd := &PokeCommand{}
|
||||
line := newLine(tt.line, ctx.Pragma)
|
||||
|
||||
if err := cmd.Interpret(line, ctx); err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
if !equalAsm(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// POKEW Case 4 — Direct addressing
|
||||
// =============================================================================
|
||||
|
||||
func TestPokeWDirectAddr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
wantAsm []string
|
||||
}{
|
||||
{
|
||||
name: "literal value, no-space comma",
|
||||
line: "POKEW $d020,101",
|
||||
wantAsm: []string{
|
||||
"\tlda #$65",
|
||||
"\tsta 53280",
|
||||
"\tlda #$00",
|
||||
"\tsta 53281",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal value >255, no-space comma",
|
||||
line: "POKEW $d020,$1234",
|
||||
wantAsm: []string{
|
||||
"\tlda #$34",
|
||||
"\tsta 53280",
|
||||
"\tlda #$12",
|
||||
"\tsta 53281",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word var value, no-space comma",
|
||||
line: "POKEW $d020,wvalvar",
|
||||
wantAsm: []string{
|
||||
"\tlda wvalvar",
|
||||
"\tsta 53280",
|
||||
"\tlda wvalvar+1",
|
||||
"\tsta 53281",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word var value, spaced comma",
|
||||
line: "POKEW $d020 , wvalvar",
|
||||
wantAsm: []string{
|
||||
"\tlda wvalvar",
|
||||
"\tsta 53280",
|
||||
"\tlda wvalvar+1",
|
||||
"\tsta 53281",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := newCtx()
|
||||
cmd := &PokeWCommand{}
|
||||
line := newLine(tt.line, ctx.Pragma)
|
||||
|
||||
if err := cmd.Interpret(line, ctx); err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
if !equalAsm(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// POKEW Case 1 — ZP pointer (indexed indirect, two stores)
|
||||
// =============================================================================
|
||||
|
||||
func TestPokeWZPPointer(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
wantAsm []string
|
||||
}{
|
||||
{
|
||||
name: "no offset, literal value, no-space comma",
|
||||
line: "POKEW zpptr,101",
|
||||
wantAsm: []string{
|
||||
"\tldy #0",
|
||||
"\tlda #$65",
|
||||
"\tsta (zpptr),y",
|
||||
"\tiny",
|
||||
"\tlda #$00",
|
||||
"\tsta (zpptr),y",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no offset, word var value, no-space comma",
|
||||
line: "POKEW zpptr,wvalvar",
|
||||
wantAsm: []string{
|
||||
"\tldy #0",
|
||||
"\tlda wvalvar",
|
||||
"\tsta (zpptr),y",
|
||||
"\tiny",
|
||||
"\tlda wvalvar+1",
|
||||
"\tsta (zpptr),y",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal offset, literal value, no-space comma",
|
||||
line: "POKEW zpptr[5],101",
|
||||
wantAsm: []string{
|
||||
"\tldy #5",
|
||||
"\tlda #$65",
|
||||
"\tsta (zpptr),y",
|
||||
"\tiny",
|
||||
"\tlda #$00",
|
||||
"\tsta (zpptr),y",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal offset, word var value, no-space comma",
|
||||
line: "POKEW zpptr[5],wvalvar",
|
||||
wantAsm: []string{
|
||||
"\tldy #5",
|
||||
"\tlda wvalvar",
|
||||
"\tsta (zpptr),y",
|
||||
"\tiny",
|
||||
"\tlda wvalvar+1",
|
||||
"\tsta (zpptr),y",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal offset, literal value, spaced comma",
|
||||
line: "POKEW zpptr[5] , 101",
|
||||
wantAsm: []string{
|
||||
"\tldy #5",
|
||||
"\tlda #$65",
|
||||
"\tsta (zpptr),y",
|
||||
"\tiny",
|
||||
"\tlda #$00",
|
||||
"\tsta (zpptr),y",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := newCtx()
|
||||
cmd := &PokeWCommand{}
|
||||
line := newLine(tt.line, ctx.Pragma)
|
||||
|
||||
if err := cmd.Interpret(line, ctx); err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
if !equalAsm(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// POKEW Case 2 — Byte variable address (zero-page indexed)
|
||||
// =============================================================================
|
||||
|
||||
func TestPokeWByteAddr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
wantAsm []string
|
||||
}{
|
||||
{
|
||||
name: "literal value, no-space comma",
|
||||
line: "POKEW byaddr,101",
|
||||
wantAsm: []string{
|
||||
"\tldx byaddr",
|
||||
"\tlda #$65",
|
||||
"\tsta $00,x",
|
||||
"\tinx",
|
||||
"\tlda #$00",
|
||||
"\tsta $00,x",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word var value, no-space comma",
|
||||
line: "POKEW byaddr,wvalvar",
|
||||
wantAsm: []string{
|
||||
"\tldx byaddr",
|
||||
"\tlda wvalvar",
|
||||
"\tsta $00,x",
|
||||
"\tinx",
|
||||
"\tlda wvalvar+1",
|
||||
"\tsta $00,x",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal value, spaced comma",
|
||||
line: "POKEW byaddr , 101",
|
||||
wantAsm: []string{
|
||||
"\tldx byaddr",
|
||||
"\tlda #$65",
|
||||
"\tsta $00,x",
|
||||
"\tinx",
|
||||
"\tlda #$00",
|
||||
"\tsta $00,x",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := newCtx()
|
||||
cmd := &PokeWCommand{}
|
||||
line := newLine(tt.line, ctx.Pragma)
|
||||
|
||||
if err := cmd.Interpret(line, ctx); err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
if !equalAsm(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// POKEW Case 3 — Word variable address (self-modifying code)
|
||||
// =============================================================================
|
||||
|
||||
func TestPokeWSMWordAddr(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
wantAsm []string
|
||||
}{
|
||||
{
|
||||
name: "literal value, no-space comma",
|
||||
line: "POKEW waddrvar,101",
|
||||
wantAsm: []string{
|
||||
"\tlda waddrvar",
|
||||
"\tsta _L1+1",
|
||||
"\tsta _L2+1",
|
||||
"\tlda waddrvar+1",
|
||||
"\tsta _L1+2",
|
||||
"\tsta _L2+2",
|
||||
"\tlda #$65",
|
||||
"_L1",
|
||||
"\tsta $ffff",
|
||||
"\tinc _L2+1",
|
||||
"\tbne _L2",
|
||||
"\tinc _L2+2",
|
||||
"_L2",
|
||||
"\tlda #$00",
|
||||
"\tsta $ffff",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word var value, no-space comma",
|
||||
line: "POKEW waddrvar,wvalvar",
|
||||
wantAsm: []string{
|
||||
"\tlda waddrvar",
|
||||
"\tsta _L1+1",
|
||||
"\tsta _L2+1",
|
||||
"\tlda waddrvar+1",
|
||||
"\tsta _L1+2",
|
||||
"\tsta _L2+2",
|
||||
"\tlda wvalvar",
|
||||
"_L1",
|
||||
"\tsta $ffff",
|
||||
"\tinc _L2+1",
|
||||
"\tbne _L2",
|
||||
"\tinc _L2+2",
|
||||
"_L2",
|
||||
"\tlda wvalvar+1",
|
||||
"\tsta $ffff",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal value, spaced comma",
|
||||
line: "POKEW waddrvar , 101",
|
||||
wantAsm: []string{
|
||||
"\tlda waddrvar",
|
||||
"\tsta _L1+1",
|
||||
"\tsta _L2+1",
|
||||
"\tlda waddrvar+1",
|
||||
"\tsta _L1+2",
|
||||
"\tsta _L2+2",
|
||||
"\tlda #$65",
|
||||
"_L1",
|
||||
"\tsta $ffff",
|
||||
"\tinc _L2+1",
|
||||
"\tbne _L2",
|
||||
"\tinc _L2+2",
|
||||
"_L2",
|
||||
"\tlda #$00",
|
||||
"\tsta $ffff",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := newCtx()
|
||||
cmd := &PokeWCommand{}
|
||||
line := newLine(tt.line, ctx.Pragma)
|
||||
|
||||
if err := cmd.Interpret(line, ctx); err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
if !equalAsm(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// =============================================================================
|
||||
// Error cases
|
||||
// =============================================================================
|
||||
|
||||
func TestPokeErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "offset on non-ZP word pointer",
|
||||
line: "POKE waddrvar[5],10",
|
||||
wantErr: "POKE: offset",
|
||||
},
|
||||
{
|
||||
name: "value out of byte range",
|
||||
line: "POKE $d020,256",
|
||||
wantErr: "out of byte range",
|
||||
},
|
||||
{
|
||||
name: "POKEW with byte variable as value",
|
||||
line: "POKEW $d020,valvar",
|
||||
wantErr: "cannot use byte variable",
|
||||
},
|
||||
{
|
||||
name: "POKEW self-referential (zp pointer == value)",
|
||||
line: "POKEW zpptr,zpptr",
|
||||
wantErr: "writing pointer",
|
||||
},
|
||||
{
|
||||
name: "invalid separator",
|
||||
line: "POKE $d020 WRONG 5",
|
||||
wantErr: "must be 'WITH' or ','",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := newCtx()
|
||||
|
||||
if strings.HasPrefix(tt.line, "POKEW") {
|
||||
cmd := &PokeWCommand{}
|
||||
line := newLine(tt.line, ctx.Pragma)
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if err == nil {
|
||||
t.Fatal("Interpret() expected error but got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), tt.wantErr) {
|
||||
t.Errorf("Interpret() error = %q, want containing %q", err.Error(), tt.wantErr)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
cmd := &PokeCommand{}
|
||||
line := newLine(tt.line, ctx.Pragma)
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if err == nil {
|
||||
t.Fatal("Interpret() expected error but got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), tt.wantErr) {
|
||||
t.Errorf("Interpret() error = %q, want containing %q", err.Error(), tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPokePragmaImmutable(t *testing.T) {
|
||||
t.Run("SM byte addr with USE_IMMUTABLE_CODE", func(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
pragma.AddPragma("_P_USE_IMMUTABLE_CODE", "1")
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("addrvar", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
|
||||
cmd := &PokeCommand{}
|
||||
line := preproc.Line{
|
||||
Text: "POKE addrvar,10",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
if err := cmd.Interpret(line, ctx); err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
_, err := cmd.Generate(ctx)
|
||||
if err == nil {
|
||||
t.Fatal("Generate() expected error with USE_IMMUTABLE_CODE")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "USE_IMMUTABLE_CODE") {
|
||||
t.Errorf("Generate() error = %q, want containing USE_IMMUTABLE_CODE", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("SM word addr with USE_IMMUTABLE_CODE", func(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
pragma.AddPragma("_P_USE_IMMUTABLE_CODE", "1")
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddVar("waddrvar", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
|
||||
cmd := &PokeCommand{}
|
||||
line := preproc.Line{
|
||||
Text: "POKE waddrvar,10",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
if err := cmd.Interpret(line, ctx); err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
_, err := cmd.Generate(ctx)
|
||||
if err == nil {
|
||||
t.Fatal("Generate() expected error with USE_IMMUTABLE_CODE")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "USE_IMMUTABLE_CODE") {
|
||||
t.Errorf("Generate() error = %q, want containing USE_IMMUTABLE_CODE", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("ZP pointer is allowed with USE_IMMUTABLE_CODE", func(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
pragma.AddPragma("_P_USE_IMMUTABLE_CODE", "1")
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
ctx.SymbolTable.AddAbsolute("zpptr", "", compiler.KindWord, 0x80, preproc.Line{Filename: "test.c65", LineNo: 1})
|
||||
|
||||
cmd := &PokeCommand{}
|
||||
line := preproc.Line{
|
||||
Text: "POKE zpptr,10",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
if err := cmd.Interpret(line, ctx); err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v (ZP pointers should be allowed)", err)
|
||||
}
|
||||
expected := []string{
|
||||
"\tldy #0",
|
||||
"\tlda #10",
|
||||
"\tsta (zpptr),y",
|
||||
}
|
||||
if !equalAsm(asm, expected) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(expected, "\n"))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("direct addr is allowed with USE_IMMUTABLE_CODE", func(t *testing.T) {
|
||||
pragma := preproc.NewPragma()
|
||||
pragma.AddPragma("_P_USE_IMMUTABLE_CODE", "1")
|
||||
ctx := compiler.NewCompilerContext(pragma)
|
||||
|
||||
cmd := &PokeCommand{}
|
||||
line := preproc.Line{
|
||||
Text: "POKE $d020,5",
|
||||
Kind: preproc.Source,
|
||||
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
|
||||
}
|
||||
|
||||
if err := cmd.Interpret(line, ctx); err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v (direct addr should be allowed)", err)
|
||||
}
|
||||
expected := []string{
|
||||
"\tlda #5",
|
||||
"\tsta 53280",
|
||||
}
|
||||
if !equalAsm(asm, expected) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(expected, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ type PokeWCommand struct {
|
|||
}
|
||||
|
||||
func (c *PokeWCommand) WillHandle(line preproc.Line) bool {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
params, err := utils.ParseParams(utils.NormalizeCommas(line.Text))
|
||||
if err != nil || len(params) != 4 {
|
||||
return false
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ func (c *PokeWCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContex
|
|||
// Store pragma set for Generate phase
|
||||
c.pragmaSet = ctx.Pragma.GetPragmaSetByIndex(line.PragmaSetIndex)
|
||||
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
params, err := utils.ParseParams(utils.NormalizeCommas(line.Text))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -102,6 +102,34 @@ func ToUpper(s string) string {
|
|||
return strings.ToUpper(s)
|
||||
}
|
||||
|
||||
// NormalizeCommas inserts spaces around commas outside quoted strings,
|
||||
// then collapses multiple spaces. This ensures commas are treated as
|
||||
// separate tokens when passed to ParseParams.
|
||||
func NormalizeCommas(s string) string {
|
||||
var result strings.Builder
|
||||
inString := false
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
ch := s[i]
|
||||
|
||||
if ch == '"' {
|
||||
inString = !inString
|
||||
result.WriteByte(ch)
|
||||
continue
|
||||
}
|
||||
|
||||
if !inString && ch == ',' {
|
||||
result.WriteByte(' ')
|
||||
result.WriteByte(',')
|
||||
result.WriteByte(' ')
|
||||
} else {
|
||||
result.WriteByte(ch)
|
||||
}
|
||||
}
|
||||
|
||||
return NormalizeSpaces(result.String())
|
||||
}
|
||||
|
||||
// ValidateIdentifier checks if s is a valid identifier (starts with letter/underscore, continues with alphanumeric/underscore)
|
||||
func ValidateIdentifier(s string) bool {
|
||||
if len(s) == 0 {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,15 @@
|
|||
"build": {
|
||||
"model": "deepseek/deepseek-v4-flash",
|
||||
"description": "Implementation and coding using DeepSeek V4 Flash"
|
||||
},
|
||||
"build-pro": {
|
||||
"model": "deepseek/deepseek-v4-pro",
|
||||
"mode": "primary",
|
||||
"options": {
|
||||
"thinking": { "type": "enabled" },
|
||||
"reasoningEffort": "high"
|
||||
},
|
||||
"description": "Implementation and coding using DeepSeek V4 Pro"
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue