Fixed disabling some pragmas and test to go with them

This commit is contained in:
Mattias Hansson 2026-04-13 16:02:16 +02:00
parent 2d2d665ebd
commit 51b9476a85
9 changed files with 127 additions and 7 deletions

View file

@ -173,7 +173,7 @@ func (c *GosubCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
// Check for immutable code pragma when using variable target
if c.isVar {
if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" {
if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" && c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "0" {
return nil, fmt.Errorf("GOSUB: USE_IMMUTABLE_CODE pragma set but variable target requires self-modifying code")
}
}

View file

@ -196,7 +196,7 @@ func (c *PeekCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
// Check for immutable code pragma when using self-modifying code
if c.isAddrVar && !c.isZPPointer {
if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" {
if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" && c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "0" {
return nil, fmt.Errorf("PEEK: USE_IMMUTABLE_CODE pragma set but construct requires self-modifying code (consider using zero page word for address)")
}
}

View file

@ -414,3 +414,97 @@ func TestPeekWErrors(t *testing.T) {
})
}
}
func TestPeekImmutableCodePragma(t *testing.T) {
// Test that _P_USE_IMMUTABLE_CODE pragma with value "1" causes error for variable address
t.Run("Pragma enabled with 1", func(t *testing.T) {
pragma := preproc.NewPragma()
pragma.AddPragma("_P_USE_IMMUTABLE_CODE", "1")
ctx := compiler.NewCompilerContext(pragma)
// Add a non-ZP word variable for address
ctx.SymbolTable.AddVar("addr", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
cmd := &PeekCommand{}
line := preproc.Line{
Text: "PEEK addr GIVING result",
Kind: preproc.Source,
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
}
// First interpret the command
err := cmd.Interpret(line, ctx)
if err != nil {
t.Fatalf("Interpret() error = %v", err)
}
// Then generate code - this is where the pragma check happens
_, err = cmd.Generate(ctx)
if err == nil {
t.Error("Expected error when _P_USE_IMMUTABLE_CODE=1 and using variable address, got nil")
} else if !strings.Contains(err.Error(), "USE_IMMUTABLE_CODE pragma set") {
t.Errorf("Expected error about USE_IMMUTABLE_CODE pragma, got: %v", err)
}
})
// Test that _P_USE_IMMUTABLE_CODE pragma with value "0" does NOT cause error
t.Run("Pragma disabled with 0", func(t *testing.T) {
pragma := preproc.NewPragma()
pragma.AddPragma("_P_USE_IMMUTABLE_CODE", "0")
ctx := compiler.NewCompilerContext(pragma)
// Add a non-ZP word variable for address
ctx.SymbolTable.AddVar("addr", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
cmd := &PeekCommand{}
line := preproc.Line{
Text: "PEEK addr GIVING result",
Kind: preproc.Source,
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
}
// First interpret the command
err := cmd.Interpret(line, ctx)
if err != nil {
t.Fatalf("Interpret() error = %v", err)
}
// Then generate code - this is where the pragma check happens
_, err = cmd.Generate(ctx)
if err != nil {
t.Errorf("Expected no error when _P_USE_IMMUTABLE_CODE=0, got: %v", err)
}
})
// Test that _P_USE_IMMUTABLE_CODE pragma not set does NOT cause error
t.Run("Pragma not set", func(t *testing.T) {
pragma := preproc.NewPragma()
// Don't add the pragma at all
ctx := compiler.NewCompilerContext(pragma)
// Add a non-ZP word variable for address
ctx.SymbolTable.AddVar("addr", "", compiler.KindWord, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
ctx.SymbolTable.AddVar("result", "", compiler.KindByte, 0, preproc.Line{Filename: "test.c65", LineNo: 1})
cmd := &PeekCommand{}
line := preproc.Line{
Text: "PEEK addr GIVING result",
Kind: preproc.Source,
PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
}
// First interpret the command
err := cmd.Interpret(line, ctx)
if err != nil {
t.Fatalf("Interpret() error = %v", err)
}
// Then generate code - this is where the pragma check happens
_, err = cmd.Generate(ctx)
if err != nil {
t.Errorf("Expected no error when _P_USE_IMMUTABLE_CODE not set, got: %v", err)
}
})
}

View file

@ -240,7 +240,7 @@ func (c *PeekWCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
// Case 3: Word variable (self-modifying code)
if c.isAddrVar && c.addrVarKind == compiler.KindWord {
// Check for immutable code pragma
if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" {
if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" && c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "0" {
return nil, fmt.Errorf("PEEKW: USE_IMMUTABLE_CODE pragma set but construct requires self-modifying code (consider using zero page word for address)")
}

View file

@ -178,7 +178,7 @@ func (c *PokeCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
// Check for immutable code pragma when using self-modifying code
if c.isAddrVar && !c.isZPPointer {
if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" {
if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" && c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "0" {
return nil, fmt.Errorf("POKE: USE_IMMUTABLE_CODE pragma set but construct requires self-modifying code (consider using zero page word for address)")
}
}

View file

@ -180,7 +180,7 @@ func (c *PokeWCommand) Generate(ctx *compiler.CompilerContext) ([]string, error)
// Check for immutable code pragma when using self-modifying code
if c.isAddrVar && c.addrVarKind == compiler.KindWord && !c.isZPPointer {
if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" {
if c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "" && c.pragmaSet.GetPragma("_P_USE_IMMUTABLE_CODE") != "0" {
return nil, fmt.Errorf("POKEW: USE_IMMUTABLE_CODE pragma set but construct requires self-modifying code (consider using zero page word for address)")
}
}

View file

@ -29,7 +29,7 @@ func NewConstantStringHandler() *ConstantStringHandler {
}
func (h *ConstantStringHandler) AddConstStr(label, value string, compress bool, ps preproc.PragmaSet) string {
useCBM := ps.GetPragma("_P_USE_CBM_STRINGS") != ""
useCBM := ps.GetPragma("_P_USE_CBM_STRINGS") != "" && ps.GetPragma("_P_USE_CBM_STRINGS") != "0"
if compress {
if existingLabel, exists := h.compressMap[value]; exists {

View file

@ -109,6 +109,31 @@ func TestGenerateConstStrDecls_CBM(t *testing.T) {
}
}
func TestGenerateConstStrDecls_CBM_DisabledWithZero(t *testing.T) {
h := NewConstantStringHandler()
ps := mockPragmaSet(map[string]string{"_P_USE_CBM_STRINGS": "0"})
h.AddConstStr("str1", "\"hello\"", false, ps)
result := h.GenerateConstStrDecls()
expected := []string{
"str1",
" !raw \"hello\"",
" !8 0",
}
if len(result) != len(expected) {
t.Fatalf("Expected %d lines, got %d", len(expected), len(result))
}
for i, exp := range expected {
if result[i] != exp {
t.Errorf("Line %d: expected %q, got %q", i, exp, result[i])
}
}
}
func TestGenerateConstStrDecls_MixedPragmas(t *testing.T) {
h := NewConstantStringHandler()
psNormal := preproc.PragmaSet{}

View file

@ -241,11 +241,12 @@ Sets compiler pragmas (options).
- Prevents self-modifying code generation
- Required for ROM-based code
- Errors on PEEK/POKE/GOSUB with variable addresses
- Value: any non-zero value enables
**_P_USE_CBM_STRINGS**
- Encodes strings in CBM PETSCII format
- Default: ASCII encoding
- Value: any non-empty value enables
- Value: any non-zero value enables
**_P_IGNORE_UNUSED**
- Suppresses "variable declared but never used" warnings