Added incr and decr and also local variable expansion in asm blocks.
This commit is contained in:
parent
7ac90260af
commit
bacd4851ef
5 changed files with 715 additions and 2 deletions
147
internal/commands/decr.go
Normal file
147
internal/commands/decr.go
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
"c65gm/internal/utils"
|
||||
)
|
||||
|
||||
// DecrCommand handles DECREMENT operations
|
||||
// Syntax:
|
||||
//
|
||||
// DEC <target> # old syntax
|
||||
// DECREMENT <target> # old syntax
|
||||
// <var>-- # new syntax (literal, no space - variables only)
|
||||
//
|
||||
// <target> can be a variable or absolute address (old syntax only)
|
||||
type DecrCommand struct {
|
||||
varName string
|
||||
varKind compiler.VarKind
|
||||
isAbsolute bool
|
||||
absAddr uint16
|
||||
}
|
||||
|
||||
func (c *DecrCommand) WillHandle(line preproc.Line) bool {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil || len(params) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Old syntax: DEC/DECREMENT
|
||||
keyword := strings.ToUpper(params[0])
|
||||
if (keyword == "DEC" || keyword == "DECREMENT") && len(params) == 2 {
|
||||
return true
|
||||
}
|
||||
|
||||
// New syntax: <var>-- (literal, no space)
|
||||
if len(params) == 1 && strings.HasSuffix(params[0], "--") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *DecrCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||
// Clear state
|
||||
c.varName = ""
|
||||
c.isAbsolute = false
|
||||
c.absAddr = 0
|
||||
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scope := ctx.CurrentScope()
|
||||
keyword := strings.ToUpper(params[0])
|
||||
|
||||
var targetParam string
|
||||
var isNewSyntax bool
|
||||
|
||||
if keyword == "DEC" || keyword == "DECREMENT" {
|
||||
// Old syntax: DEC/DECREMENT <target>
|
||||
if len(params) != 2 {
|
||||
return fmt.Errorf("DEC: wrong number of parameters")
|
||||
}
|
||||
targetParam = params[1]
|
||||
isNewSyntax = false
|
||||
} else if strings.HasSuffix(params[0], "--") {
|
||||
// New syntax: <var>-- (literal)
|
||||
if len(params) != 1 {
|
||||
return fmt.Errorf("DEC: wrong number of parameters")
|
||||
}
|
||||
targetParam = strings.TrimSuffix(params[0], "--")
|
||||
isNewSyntax = true
|
||||
} else {
|
||||
return fmt.Errorf("DEC: unrecognized syntax")
|
||||
}
|
||||
|
||||
// Try variable lookup
|
||||
sym := ctx.SymbolTable.Lookup(targetParam, scope)
|
||||
if sym != nil {
|
||||
if sym.IsConst() {
|
||||
return fmt.Errorf("DEC: cannot decrement constant %q", targetParam)
|
||||
}
|
||||
c.varName = sym.FullName()
|
||||
c.varKind = sym.GetVarKind()
|
||||
c.isAbsolute = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// For new syntax (--), must be a variable
|
||||
if isNewSyntax {
|
||||
return fmt.Errorf("DEC: unknown variable %q", targetParam)
|
||||
}
|
||||
|
||||
// Old syntax allows absolute addresses
|
||||
constLookup := func(name string) (int64, bool) {
|
||||
s := ctx.SymbolTable.Lookup(name, scope)
|
||||
if s != nil && s.IsConst() {
|
||||
return int64(s.Value), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
val, evalErr := utils.EvaluateExpression(targetParam, constLookup)
|
||||
if evalErr != nil {
|
||||
return fmt.Errorf("DEC: expected variable or absolute address, got %q: %w", targetParam, evalErr)
|
||||
}
|
||||
|
||||
if val < 0 || val > 65535 {
|
||||
return fmt.Errorf("DEC: address %d out of range (0-65535)", val)
|
||||
}
|
||||
|
||||
c.isAbsolute = true
|
||||
c.absAddr = uint16(val)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *DecrCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||
var asm []string
|
||||
|
||||
if c.isAbsolute {
|
||||
// Absolute address
|
||||
asm = append(asm, fmt.Sprintf("\tdec $%04x", c.absAddr))
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
// Variable
|
||||
if c.varKind == compiler.KindByte {
|
||||
asm = append(asm, fmt.Sprintf("\tdec %s", c.varName))
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
// Word variable - handle borrow from high byte
|
||||
label := ctx.GeneralStack.Push()
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s", c.varName))
|
||||
asm = append(asm, fmt.Sprintf("\tbne %s", label))
|
||||
asm = append(asm, fmt.Sprintf("\tdec %s+1", c.varName))
|
||||
asm = append(asm, label)
|
||||
asm = append(asm, fmt.Sprintf("\tdec %s", c.varName))
|
||||
|
||||
return asm, nil
|
||||
}
|
||||
146
internal/commands/incr.go
Normal file
146
internal/commands/incr.go
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
"c65gm/internal/utils"
|
||||
)
|
||||
|
||||
// IncrCommand handles INCREMENT operations
|
||||
// Syntax:
|
||||
//
|
||||
// INC <target> # old syntax
|
||||
// INCREMENT <target> # old syntax
|
||||
// <var>++ # new syntax (literal, no space - variables only)
|
||||
//
|
||||
// <target> can be a variable or absolute address (old syntax only)
|
||||
type IncrCommand struct {
|
||||
varName string
|
||||
varKind compiler.VarKind
|
||||
isAbsolute bool
|
||||
absAddr uint16
|
||||
}
|
||||
|
||||
func (c *IncrCommand) WillHandle(line preproc.Line) bool {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil || len(params) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Old syntax: INC/INCREMENT
|
||||
keyword := strings.ToUpper(params[0])
|
||||
if (keyword == "INC" || keyword == "INCREMENT") && len(params) == 2 {
|
||||
return true
|
||||
}
|
||||
|
||||
// New syntax: <var>++ (literal, no space)
|
||||
if len(params) == 1 && strings.HasSuffix(params[0], "++") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *IncrCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||
// Clear state
|
||||
c.varName = ""
|
||||
c.isAbsolute = false
|
||||
c.absAddr = 0
|
||||
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
scope := ctx.CurrentScope()
|
||||
keyword := strings.ToUpper(params[0])
|
||||
|
||||
var targetParam string
|
||||
var isNewSyntax bool
|
||||
|
||||
if keyword == "INC" || keyword == "INCREMENT" {
|
||||
// Old syntax: INC/INCREMENT <target>
|
||||
if len(params) != 2 {
|
||||
return fmt.Errorf("INC: wrong number of parameters")
|
||||
}
|
||||
targetParam = params[1]
|
||||
isNewSyntax = false
|
||||
} else if strings.HasSuffix(params[0], "++") {
|
||||
// New syntax: <var>++ (literal)
|
||||
if len(params) != 1 {
|
||||
return fmt.Errorf("INC: wrong number of parameters")
|
||||
}
|
||||
targetParam = strings.TrimSuffix(params[0], "++")
|
||||
isNewSyntax = true
|
||||
} else {
|
||||
return fmt.Errorf("INC: unrecognized syntax")
|
||||
}
|
||||
|
||||
// Try variable lookup
|
||||
sym := ctx.SymbolTable.Lookup(targetParam, scope)
|
||||
if sym != nil {
|
||||
if sym.IsConst() {
|
||||
return fmt.Errorf("INC: cannot increment constant %q", targetParam)
|
||||
}
|
||||
c.varName = sym.FullName()
|
||||
c.varKind = sym.GetVarKind()
|
||||
c.isAbsolute = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// For new syntax (++), must be a variable
|
||||
if isNewSyntax {
|
||||
return fmt.Errorf("INC: unknown variable %q", targetParam)
|
||||
}
|
||||
|
||||
// Old syntax allows absolute addresses
|
||||
constLookup := func(name string) (int64, bool) {
|
||||
s := ctx.SymbolTable.Lookup(name, scope)
|
||||
if s != nil && s.IsConst() {
|
||||
return int64(s.Value), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
val, evalErr := utils.EvaluateExpression(targetParam, constLookup)
|
||||
if evalErr != nil {
|
||||
return fmt.Errorf("INC: expected variable or absolute address, got %q: %w", targetParam, evalErr)
|
||||
}
|
||||
|
||||
if val < 0 || val > 65535 {
|
||||
return fmt.Errorf("INC: address %d out of range (0-65535)", val)
|
||||
}
|
||||
|
||||
c.isAbsolute = true
|
||||
c.absAddr = uint16(val)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *IncrCommand) Generate(ctx *compiler.CompilerContext) ([]string, error) {
|
||||
var asm []string
|
||||
|
||||
if c.isAbsolute {
|
||||
// Absolute address
|
||||
asm = append(asm, fmt.Sprintf("\tinc $%04x", c.absAddr))
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
// Variable
|
||||
if c.varKind == compiler.KindByte {
|
||||
asm = append(asm, fmt.Sprintf("\tinc %s", c.varName))
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
// Word variable - handle carry to high byte
|
||||
label := ctx.GeneralStack.Push()
|
||||
asm = append(asm, fmt.Sprintf("\tinc %s", c.varName))
|
||||
asm = append(asm, fmt.Sprintf("\tbne %s", label))
|
||||
asm = append(asm, fmt.Sprintf("\tinc %s+1", c.varName))
|
||||
asm = append(asm, label)
|
||||
|
||||
return asm, nil
|
||||
}
|
||||
402
internal/commands/incr_decr_test.go
Normal file
402
internal/commands/incr_decr_test.go
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
)
|
||||
|
||||
func TestIncrCommand_WillHandle(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
expected bool
|
||||
}{
|
||||
{"INC keyword", "INC myvar", true},
|
||||
{"INCREMENT keyword", "INCREMENT myvar", true},
|
||||
{"inc lowercase", "inc myvar", true},
|
||||
{"New syntax ++ literal", "myvar++", true},
|
||||
{"Invalid - space before ++", "myvar ++", false},
|
||||
{"Invalid - no params", "INC", false},
|
||||
{"Invalid - too many params old", "INC a b c", false},
|
||||
{"Invalid - wrong suffix", "myvar+-", false},
|
||||
{"Invalid - ADD command", "ADD x TO y", false},
|
||||
}
|
||||
|
||||
cmd := &IncrCommand{}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
line := preproc.Line{Text: tt.line, LineNo: 1, Filename: "test.c65"}
|
||||
result := cmd.WillHandle(line)
|
||||
if result != tt.expected {
|
||||
t.Errorf("WillHandle(%q) = %v, want %v", tt.line, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecrCommand_WillHandle(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
expected bool
|
||||
}{
|
||||
{"DEC keyword", "DEC myvar", true},
|
||||
{"DECREMENT keyword", "DECREMENT myvar", true},
|
||||
{"dec lowercase", "dec myvar", true},
|
||||
{"New syntax -- literal", "myvar--", true},
|
||||
{"Invalid - space before --", "myvar --", false},
|
||||
{"Invalid - no params", "DEC", false},
|
||||
{"Invalid - too many params old", "DEC a b c", false},
|
||||
{"Invalid - wrong suffix", "myvar-+", false},
|
||||
{"Invalid - SUB command", "SUB x FROM y", false},
|
||||
}
|
||||
|
||||
cmd := &DecrCommand{}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
line := preproc.Line{Text: tt.line, LineNo: 1, Filename: "test.c65"}
|
||||
result := cmd.WillHandle(line)
|
||||
if result != tt.expected {
|
||||
t.Errorf("WillHandle(%q) = %v, want %v", tt.line, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncrCommand_InterpretAndGenerate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(*compiler.CompilerContext)
|
||||
line string
|
||||
expectError bool
|
||||
checkAsm func(*testing.T, []string)
|
||||
}{
|
||||
{
|
||||
name: "INC byte variable old syntax",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0)
|
||||
},
|
||||
line: "INC counter",
|
||||
expectError: false,
|
||||
checkAsm: func(t *testing.T, asm []string) {
|
||||
if len(asm) != 1 {
|
||||
t.Errorf("Expected 1 asm line, got %d", len(asm))
|
||||
return
|
||||
}
|
||||
if !strings.Contains(asm[0], "inc counter") {
|
||||
t.Errorf("Expected 'inc counter', got %q", asm[0])
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "INC byte variable new syntax",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0)
|
||||
},
|
||||
line: "counter++",
|
||||
expectError: false,
|
||||
checkAsm: func(t *testing.T, asm []string) {
|
||||
if len(asm) != 1 {
|
||||
t.Errorf("Expected 1 asm line, got %d", len(asm))
|
||||
return
|
||||
}
|
||||
if !strings.Contains(asm[0], "inc counter") {
|
||||
t.Errorf("Expected 'inc counter', got %q", asm[0])
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "INC word variable old syntax",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0)
|
||||
},
|
||||
line: "INCREMENT pointer",
|
||||
expectError: false,
|
||||
checkAsm: func(t *testing.T, asm []string) {
|
||||
if len(asm) != 4 {
|
||||
t.Errorf("Expected 4 asm lines for word inc, got %d", len(asm))
|
||||
return
|
||||
}
|
||||
if !strings.Contains(asm[0], "inc pointer") {
|
||||
t.Errorf("Expected 'inc pointer' in line 0, got %q", asm[0])
|
||||
}
|
||||
if !strings.Contains(asm[1], "bne") {
|
||||
t.Errorf("Expected 'bne' in line 1, got %q", asm[1])
|
||||
}
|
||||
if !strings.Contains(asm[2], "inc pointer+1") {
|
||||
t.Errorf("Expected 'inc pointer+1' in line 2, got %q", asm[2])
|
||||
}
|
||||
// Line 3 should be the label
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "INC word variable new syntax",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0)
|
||||
},
|
||||
line: "pointer++",
|
||||
expectError: false,
|
||||
checkAsm: func(t *testing.T, asm []string) {
|
||||
if len(asm) != 4 {
|
||||
t.Errorf("Expected 4 asm lines for word inc, got %d", len(asm))
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "INC absolute address",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
// No variables needed
|
||||
},
|
||||
line: "INC $D020",
|
||||
expectError: false,
|
||||
checkAsm: func(t *testing.T, asm []string) {
|
||||
if len(asm) != 1 {
|
||||
t.Errorf("Expected 1 asm line, got %d", len(asm))
|
||||
return
|
||||
}
|
||||
if !strings.Contains(strings.ToLower(asm[0]), "inc $d020") {
|
||||
t.Errorf("Expected 'inc $d020', got %q", asm[0])
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "Error: INC unknown variable",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
// No setup
|
||||
},
|
||||
line: "INC unknown",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Error: INC constant variable",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100)
|
||||
},
|
||||
line: "INC MAX",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Error: new syntax on unknown variable",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
// No setup
|
||||
},
|
||||
line: "unknown++",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := compiler.NewCompilerContext(preproc.NewPragma())
|
||||
if tt.setup != nil {
|
||||
tt.setup(ctx)
|
||||
}
|
||||
|
||||
cmd := &IncrCommand{}
|
||||
line := preproc.Line{Text: tt.line, LineNo: 1, Filename: "test.c65"}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate error: %v", err)
|
||||
}
|
||||
|
||||
if tt.checkAsm != nil {
|
||||
tt.checkAsm(t, asm)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecrCommand_InterpretAndGenerate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setup func(*compiler.CompilerContext)
|
||||
line string
|
||||
expectError bool
|
||||
checkAsm func(*testing.T, []string)
|
||||
}{
|
||||
{
|
||||
name: "DEC byte variable old syntax",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0)
|
||||
},
|
||||
line: "DEC counter",
|
||||
expectError: false,
|
||||
checkAsm: func(t *testing.T, asm []string) {
|
||||
if len(asm) != 1 {
|
||||
t.Errorf("Expected 1 asm line, got %d", len(asm))
|
||||
return
|
||||
}
|
||||
if !strings.Contains(asm[0], "dec counter") {
|
||||
t.Errorf("Expected 'dec counter', got %q", asm[0])
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DEC byte variable new syntax",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
ctx.SymbolTable.AddVar("counter", "", compiler.KindByte, 0)
|
||||
},
|
||||
line: "counter--",
|
||||
expectError: false,
|
||||
checkAsm: func(t *testing.T, asm []string) {
|
||||
if len(asm) != 1 {
|
||||
t.Errorf("Expected 1 asm line, got %d", len(asm))
|
||||
return
|
||||
}
|
||||
if !strings.Contains(asm[0], "dec counter") {
|
||||
t.Errorf("Expected 'dec counter', got %q", asm[0])
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DEC word variable old syntax",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0)
|
||||
},
|
||||
line: "DECREMENT pointer",
|
||||
expectError: false,
|
||||
checkAsm: func(t *testing.T, asm []string) {
|
||||
if len(asm) != 5 {
|
||||
t.Errorf("Expected 5 asm lines for word dec, got %d", len(asm))
|
||||
return
|
||||
}
|
||||
if !strings.Contains(asm[0], "lda pointer") {
|
||||
t.Errorf("Expected 'lda pointer' in line 0, got %q", asm[0])
|
||||
}
|
||||
if !strings.Contains(asm[1], "bne") {
|
||||
t.Errorf("Expected 'bne' in line 1, got %q", asm[1])
|
||||
}
|
||||
if !strings.Contains(asm[2], "dec pointer+1") {
|
||||
t.Errorf("Expected 'dec pointer+1' in line 2, got %q", asm[2])
|
||||
}
|
||||
// Line 3 is the label
|
||||
if !strings.Contains(asm[4], "dec pointer") {
|
||||
t.Errorf("Expected 'dec pointer' in line 4, got %q", asm[4])
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DEC word variable new syntax",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
ctx.SymbolTable.AddVar("pointer", "", compiler.KindWord, 0)
|
||||
},
|
||||
line: "pointer--",
|
||||
expectError: false,
|
||||
checkAsm: func(t *testing.T, asm []string) {
|
||||
if len(asm) != 5 {
|
||||
t.Errorf("Expected 5 asm lines for word dec, got %d", len(asm))
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "DEC absolute address",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
// No variables needed
|
||||
},
|
||||
line: "DEC $D020",
|
||||
expectError: false,
|
||||
checkAsm: func(t *testing.T, asm []string) {
|
||||
if len(asm) != 1 {
|
||||
t.Errorf("Expected 1 asm line, got %d", len(asm))
|
||||
return
|
||||
}
|
||||
if !strings.Contains(strings.ToLower(asm[0]), "dec $d020") {
|
||||
t.Errorf("Expected 'dec $d020', got %q", asm[0])
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Error: DEC unknown variable",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
// No setup
|
||||
},
|
||||
line: "DEC unknown",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Error: DEC constant variable",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
ctx.SymbolTable.AddConst("MAX", "", compiler.KindByte, 100)
|
||||
},
|
||||
line: "DEC MAX",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "Error: new syntax on unknown variable",
|
||||
setup: func(ctx *compiler.CompilerContext) {
|
||||
// No setup
|
||||
},
|
||||
line: "unknown--",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := compiler.NewCompilerContext(preproc.NewPragma())
|
||||
if tt.setup != nil {
|
||||
tt.setup(ctx)
|
||||
}
|
||||
|
||||
cmd := &DecrCommand{}
|
||||
line := preproc.Line{Text: tt.line, LineNo: 1, Filename: "test.c65"}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if tt.expectError {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error: %v", err)
|
||||
}
|
||||
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate error: %v", err)
|
||||
}
|
||||
|
||||
if tt.checkAsm != nil {
|
||||
tt.checkAsm(t, asm)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIncrDecrCommand_FullNameResolution(t *testing.T) {
|
||||
// Test that variable name resolution works with full names
|
||||
ctx := compiler.NewCompilerContext(preproc.NewPragma())
|
||||
|
||||
// Add a variable with scoped name directly
|
||||
ctx.SymbolTable.AddVar("counter", "myfunc", compiler.KindWord, 0)
|
||||
|
||||
// Note: CurrentScope will return nil (global) since we're not in a function context
|
||||
// The symbol table lookup will try scoped search and fall back to global
|
||||
|
||||
// Test that using the base name in global scope won't find the scoped var
|
||||
incrCmd := &IncrCommand{}
|
||||
line := preproc.Line{Text: "INC counter", LineNo: 1, Filename: "test.c65"}
|
||||
err := incrCmd.Interpret(line, ctx)
|
||||
|
||||
// Should fail - counter exists only in myfunc scope, not global
|
||||
if err == nil {
|
||||
t.Errorf("Expected error when accessing scoped variable from global scope")
|
||||
}
|
||||
}
|
||||
|
|
@ -39,8 +39,24 @@ func (c *Compiler) Compile(lines []preproc.Line) ([]string, error) {
|
|||
// Skip non-source lines (assembler and script handled differently)
|
||||
if line.Kind != preproc.Source {
|
||||
if line.Kind == preproc.Assembler {
|
||||
// Pass through assembler lines verbatim
|
||||
codeOutput = append(codeOutput, line.Text)
|
||||
// Expand |varname| -> scoped_varname for local variables in ASM blocks
|
||||
text := line.Text
|
||||
for {
|
||||
start := strings.IndexByte(text, '|')
|
||||
if start == -1 {
|
||||
break
|
||||
}
|
||||
end := strings.IndexByte(text[start+1:], '|')
|
||||
if end == -1 {
|
||||
return nil, fmt.Errorf("%s:%d: unclosed | in assembler line", line.Filename, line.LineNo)
|
||||
}
|
||||
end += start + 1
|
||||
|
||||
varName := text[start+1 : end]
|
||||
expandedName := c.ctx.SymbolTable.ExpandName(varName, c.ctx.CurrentScope())
|
||||
text = text[:start] + expandedName + text[end+1:]
|
||||
}
|
||||
codeOutput = append(codeOutput, text)
|
||||
}
|
||||
// Script lines ignored for now
|
||||
continue
|
||||
|
|
|
|||
2
main.go
2
main.go
|
|
@ -93,6 +93,8 @@ func registerCommands(comp *compiler.Compiler) {
|
|||
comp.Registry().Register(&commands.FuncCommand{})
|
||||
comp.Registry().Register(commands.NewCallCommand(comp.Context().FunctionHandler))
|
||||
comp.Registry().Register(&commands.FendCommand{})
|
||||
comp.Registry().Register(&commands.IncrCommand{})
|
||||
comp.Registry().Register(&commands.DecrCommand{})
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue