diff --git a/internal/commands/peek.go b/internal/commands/peek.go
index d237f17..dbf91ac 100644
--- a/internal/commands/peek.go
+++ b/internal/commands/peek.go
@@ -10,7 +10,11 @@ import (
)
// PeekCommand handles PEEK statements
-// Syntax: PEEK
[offset] GIVING|->
+// Syntax:
+//
+// PEEK [offset] GIVING|-> # old syntax
+// = PEEK [offset] # new syntax
+//
// Where address is:
// - Byte variable (self-modifying code)
// - Word variable (self-modifying code or ZP pointer with offset)
@@ -38,7 +42,18 @@ func (c *PeekCommand) WillHandle(line preproc.Line) bool {
if err != nil || len(params) != 4 {
return false
}
- return strings.ToUpper(params[0]) == "PEEK"
+
+ // Old syntax: PEEK ... (4 params)
+ if strings.ToUpper(params[0]) == "PEEK" {
+ return true
+ }
+
+ // New syntax: = PEEK
+ if params[1] == "=" && strings.ToUpper(params[2]) == "PEEK" {
+ return true
+ }
+
+ return false
}
func (c *PeekCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
@@ -67,14 +82,37 @@ func (c *PeekCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext
scope := ctx.CurrentScope()
- // Validate separator (param 3)
- sep := strings.ToUpper(params[2])
- if sep != "GIVING" && sep != "->" {
- return fmt.Errorf("PEEK: parameter #3 must be 'GIVING' or '->', got %q", params[2])
+ var addrParam string
+ var destParam string
+
+ // Determine syntax and parse accordingly
+ if strings.ToUpper(params[0]) == "PEEK" {
+ // Old syntax: PEEK GIVING|->
+
+ // Validate separator (param 3)
+ sep := strings.ToUpper(params[2])
+ if sep != "GIVING" && sep != "->" {
+ return fmt.Errorf("PEEK: parameter #3 must be 'GIVING' or '->', got %q", params[2])
+ }
+
+ addrParam = params[1]
+ destParam = params[3]
+ } else {
+ // New syntax: = PEEK
+
+ if params[1] != "=" {
+ return fmt.Errorf("PEEK: expected '=' at position 2, got %q", params[1])
+ }
+
+ if strings.ToUpper(params[2]) != "PEEK" {
+ return fmt.Errorf("PEEK: expected 'PEEK' at position 3, got %q", params[2])
+ }
+
+ destParam = params[0]
+ addrParam = params[3]
}
- // Parse address (param 2) - may have [offset]
- addrParam := params[1]
+ // Parse address - may have [offset]
baseAddr, offsetParam := parseOffset(addrParam)
constLookup := func(name string) (int64, bool) {
@@ -145,8 +183,7 @@ func (c *PeekCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext
}
}
- // Parse destination variable (param 4)
- destParam := params[3]
+ // Parse destination variable
destSym := ctx.SymbolTable.Lookup(destParam, scope)
if destSym == nil {
return fmt.Errorf("PEEK: unknown destination variable %q", destParam)
diff --git a/internal/commands/peek_test.go b/internal/commands/peek_test.go
new file mode 100644
index 0000000..befeee2
--- /dev/null
+++ b/internal/commands/peek_test.go
@@ -0,0 +1,416 @@
+package commands
+
+import (
+ "strings"
+ "testing"
+
+ "c65gm/internal/compiler"
+ "c65gm/internal/preproc"
+)
+
+func TestPeekOldSyntax(t *testing.T) {
+ tests := []struct {
+ name string
+ line string
+ setupVars func(*compiler.SymbolTable)
+ wantAsm []string
+ }{
+ {
+ name: "peek constant address to byte",
+ line: "PEEK 1024 GIVING result",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddVar("result", "", compiler.KindByte, 0)
+ },
+ wantAsm: []string{
+ "\tlda 1024",
+ "\tsta result",
+ },
+ },
+ {
+ name: "peek ZP pointer to byte",
+ line: "PEEK ptr GIVING result",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddAbsolute("ptr", "", compiler.KindWord, 0x80)
+ st.AddVar("result", "", compiler.KindByte, 0)
+ },
+ wantAsm: []string{
+ "\tldy #0",
+ "\tlda (ptr),y",
+ "\tsta result",
+ },
+ },
+ {
+ name: "peek ZP pointer with offset to byte",
+ line: "PEEK ptr[5] GIVING result",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddAbsolute("ptr", "", compiler.KindWord, 0x80)
+ st.AddVar("result", "", compiler.KindByte, 0)
+ },
+ wantAsm: []string{
+ "\tldy #5",
+ "\tlda (ptr),y",
+ "\tsta result",
+ },
+ },
+ {
+ name: "peek with -> separator",
+ line: "PEEK 2048 -> result",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddVar("result", "", compiler.KindByte, 0)
+ },
+ wantAsm: []string{
+ "\tlda 2048",
+ "\tsta result",
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ pragma := preproc.NewPragma()
+ ctx := compiler.NewCompilerContext(pragma)
+ tt.setupVars(ctx.SymbolTable)
+
+ cmd := &PeekCommand{}
+ line := preproc.Line{
+ Text: tt.line,
+ 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", 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"))
+ }
+ })
+ }
+}
+
+func TestPeekNewSyntax(t *testing.T) {
+ tests := []struct {
+ name string
+ line string
+ setupVars func(*compiler.SymbolTable)
+ wantAsm []string
+ }{
+ {
+ name: "new syntax constant address to byte",
+ line: "result = PEEK 1024",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddVar("result", "", compiler.KindByte, 0)
+ },
+ wantAsm: []string{
+ "\tlda 1024",
+ "\tsta result",
+ },
+ },
+ {
+ name: "new syntax ZP pointer to byte",
+ line: "result = PEEK ptr",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddAbsolute("ptr", "", compiler.KindWord, 0x80)
+ st.AddVar("result", "", compiler.KindByte, 0)
+ },
+ wantAsm: []string{
+ "\tldy #0",
+ "\tlda (ptr),y",
+ "\tsta result",
+ },
+ },
+ {
+ name: "new syntax ZP pointer with offset",
+ line: "result = PEEK ptr[10]",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddAbsolute("ptr", "", compiler.KindWord, 0x80)
+ st.AddVar("result", "", compiler.KindByte, 0)
+ },
+ wantAsm: []string{
+ "\tldy #10",
+ "\tlda (ptr),y",
+ "\tsta result",
+ },
+ },
+ {
+ name: "new syntax ZP pointer with variable offset",
+ line: "result = PEEK ptr[idx]",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddAbsolute("ptr", "", compiler.KindWord, 0x80)
+ st.AddVar("idx", "", compiler.KindByte, 0)
+ st.AddVar("result", "", compiler.KindByte, 0)
+ },
+ wantAsm: []string{
+ "\tldy idx",
+ "\tlda (ptr),y",
+ "\tsta result",
+ },
+ },
+ {
+ name: "new syntax to word destination",
+ line: "result = PEEK 2048",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddVar("result", "", compiler.KindWord, 0)
+ },
+ wantAsm: []string{
+ "\tlda 2048",
+ "\tsta result",
+ "\tlda #0",
+ "\tsta result+1",
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ pragma := preproc.NewPragma()
+ ctx := compiler.NewCompilerContext(pragma)
+ tt.setupVars(ctx.SymbolTable)
+
+ cmd := &PeekCommand{}
+ line := preproc.Line{
+ Text: tt.line,
+ 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", 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"))
+ }
+ })
+ }
+}
+
+func TestPeekWNewSyntax(t *testing.T) {
+ tests := []struct {
+ name string
+ line string
+ setupVars func(*compiler.SymbolTable)
+ wantAsm []string
+ }{
+ {
+ name: "new syntax constant address",
+ line: "result = PEEKW 1024",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddVar("result", "", compiler.KindWord, 0)
+ },
+ wantAsm: []string{
+ "\tlda 1024",
+ "\tsta result",
+ "\tlda 1025",
+ "\tsta result+1",
+ },
+ },
+ {
+ name: "new syntax ZP pointer",
+ line: "result = PEEKW ptr",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddAbsolute("ptr", "", compiler.KindWord, 0x80)
+ st.AddVar("result", "", compiler.KindWord, 0)
+ },
+ wantAsm: []string{
+ "\tldy #0",
+ "\tlda (ptr),y",
+ "\tsta result",
+ "\tiny",
+ "\tlda (ptr),y",
+ "\tsta result+1",
+ },
+ },
+ {
+ name: "new syntax ZP pointer with offset",
+ line: "result = PEEKW ptr[8]",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddAbsolute("ptr", "", compiler.KindWord, 0x80)
+ st.AddVar("result", "", compiler.KindWord, 0)
+ },
+ wantAsm: []string{
+ "\tldy #8",
+ "\tlda (ptr),y",
+ "\tsta result",
+ "\tiny",
+ "\tlda (ptr),y",
+ "\tsta result+1",
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ pragma := preproc.NewPragma()
+ ctx := compiler.NewCompilerContext(pragma)
+ tt.setupVars(ctx.SymbolTable)
+
+ cmd := &PeekWCommand{}
+ line := preproc.Line{
+ Text: tt.line,
+ 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", 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"))
+ }
+ })
+ }
+}
+
+func TestPeekErrors(t *testing.T) {
+ tests := []struct {
+ name string
+ line string
+ setupVars func(*compiler.SymbolTable)
+ wantError string
+ }{
+ {
+ name: "unknown destination",
+ line: "PEEK 1024 GIVING unknown",
+ setupVars: func(st *compiler.SymbolTable) {
+ },
+ wantError: "unknown destination variable",
+ },
+ {
+ name: "constant destination",
+ line: "PEEK 1024 GIVING CONST",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddConst("CONST", "", compiler.KindByte, 100)
+ },
+ wantError: "cannot PEEK into constant",
+ },
+ {
+ name: "new syntax unknown destination",
+ line: "unknown = PEEK 1024",
+ setupVars: func(st *compiler.SymbolTable) {
+ },
+ wantError: "unknown destination variable",
+ },
+ {
+ name: "new syntax constant destination",
+ line: "CONST = PEEK 1024",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddConst("CONST", "", compiler.KindByte, 100)
+ },
+ wantError: "cannot PEEK into constant",
+ },
+ {
+ name: "offset without ZP pointer old syntax",
+ line: "PEEK addr[5] GIVING result",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddVar("addr", "", compiler.KindWord, 0)
+ st.AddVar("result", "", compiler.KindByte, 0)
+ },
+ wantError: "offset",
+ },
+ {
+ name: "offset without ZP pointer new syntax",
+ line: "result = PEEK addr[5]",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddVar("addr", "", compiler.KindWord, 0)
+ st.AddVar("result", "", compiler.KindByte, 0)
+ },
+ wantError: "offset",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ pragma := preproc.NewPragma()
+ ctx := compiler.NewCompilerContext(pragma)
+ tt.setupVars(ctx.SymbolTable)
+
+ cmd := &PeekCommand{}
+ line := preproc.Line{
+ Text: tt.line,
+ Kind: preproc.Source,
+ PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
+ }
+
+ err := cmd.Interpret(line, ctx)
+ if err == nil {
+ t.Fatal("Expected error but got none")
+ }
+ if !strings.Contains(err.Error(), tt.wantError) {
+ t.Errorf("Error message mismatch\ngot: %v\nwant substring: %s", err, tt.wantError)
+ }
+ })
+ }
+}
+
+func TestPeekWErrors(t *testing.T) {
+ tests := []struct {
+ name string
+ line string
+ setupVars func(*compiler.SymbolTable)
+ wantError string
+ }{
+ {
+ name: "byte destination",
+ line: "PEEKW 1024 GIVING result",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddVar("result", "", compiler.KindByte, 0)
+ },
+ wantError: "must be word type",
+ },
+ {
+ name: "new syntax byte destination",
+ line: "result = PEEKW 1024",
+ setupVars: func(st *compiler.SymbolTable) {
+ st.AddVar("result", "", compiler.KindByte, 0)
+ },
+ wantError: "must be word type",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ pragma := preproc.NewPragma()
+ ctx := compiler.NewCompilerContext(pragma)
+ tt.setupVars(ctx.SymbolTable)
+
+ cmd := &PeekWCommand{}
+ line := preproc.Line{
+ Text: tt.line,
+ Kind: preproc.Source,
+ PragmaSetIndex: pragma.GetCurrentPragmaSetIndex(),
+ }
+
+ err := cmd.Interpret(line, ctx)
+ if err == nil {
+ t.Fatal("Expected error but got none")
+ }
+ if !strings.Contains(err.Error(), tt.wantError) {
+ t.Errorf("Error message mismatch\ngot: %v\nwant substring: %s", err, tt.wantError)
+ }
+ })
+ }
+}
diff --git a/internal/commands/peekw.go b/internal/commands/peekw.go
index 74b0dab..228fdcc 100644
--- a/internal/commands/peekw.go
+++ b/internal/commands/peekw.go
@@ -10,7 +10,11 @@ import (
)
// PeekWCommand handles PEEKW statements (reads a word/2 bytes)
-// Syntax: PEEKW [offset] GIVING|->
+// Syntax:
+//
+// PEEKW [offset] GIVING|-> # old syntax
+// = PEEKW [offset] # new syntax
+//
// Where address is:
// - Byte variable (zero-page indexed addressing)
// - Word variable (self-modifying code or ZP pointer with offset)
@@ -39,7 +43,18 @@ func (c *PeekWCommand) WillHandle(line preproc.Line) bool {
if err != nil || len(params) != 4 {
return false
}
- return strings.ToUpper(params[0]) == "PEEKW"
+
+ // Old syntax: PEEKW ... (4 params)
+ if strings.ToUpper(params[0]) == "PEEKW" {
+ return true
+ }
+
+ // New syntax: = PEEKW
+ if params[1] == "=" && strings.ToUpper(params[2]) == "PEEKW" {
+ return true
+ }
+
+ return false
}
func (c *PeekWCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
@@ -68,14 +83,37 @@ func (c *PeekWCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContex
scope := ctx.CurrentScope()
- // Validate separator (param 3)
- sep := strings.ToUpper(params[2])
- if sep != "GIVING" && sep != "->" {
- return fmt.Errorf("PEEKW: parameter #3 must be 'GIVING' or '->', got %q", params[2])
+ var addrParam string
+ var destParam string
+
+ // Determine syntax and parse accordingly
+ if strings.ToUpper(params[0]) == "PEEKW" {
+ // Old syntax: PEEKW GIVING|->
+
+ // Validate separator (param 3)
+ sep := strings.ToUpper(params[2])
+ if sep != "GIVING" && sep != "->" {
+ return fmt.Errorf("PEEKW: parameter #3 must be 'GIVING' or '->', got %q", params[2])
+ }
+
+ addrParam = params[1]
+ destParam = params[3]
+ } else {
+ // New syntax: = PEEKW
+
+ if params[1] != "=" {
+ return fmt.Errorf("PEEKW: expected '=' at position 2, got %q", params[1])
+ }
+
+ if strings.ToUpper(params[2]) != "PEEKW" {
+ return fmt.Errorf("PEEKW: expected 'PEEKW' at position 3, got %q", params[2])
+ }
+
+ destParam = params[0]
+ addrParam = params[3]
}
- // Parse address (param 2) - may have [offset]
- addrParam := params[1]
+ // Parse address - may have [offset]
baseAddr, offsetParam := parseOffsetW(addrParam)
constLookup := func(name string) (int64, bool) {
@@ -146,8 +184,7 @@ func (c *PeekWCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContex
}
}
- // Parse destination variable (param 4)
- destParam := params[3]
+ // Parse destination variable
destSym := ctx.SymbolTable.Lookup(destParam, scope)
if destSym == nil {
return fmt.Errorf("PEEKW: unknown destination variable %q", destParam)