Added and, or, xor and subtract commands
This commit is contained in:
parent
ac40f67ec0
commit
88f90fe5be
13 changed files with 2836 additions and 52 deletions
|
|
@ -103,15 +103,20 @@ func (c *AddCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext)
|
|||
return fmt.Errorf("ADD: cannot assign to constant %q", destName)
|
||||
}
|
||||
c.destVarName = destSym.FullName()
|
||||
c.destVarKind = getVarKind(destSym)
|
||||
c.destVarKind = destSym.GetVarKind()
|
||||
|
||||
// Parse param1
|
||||
if err := c.parseParam(params[1], &c.param1VarName, &c.param1VarKind, &c.param1Value, &c.param1IsVar, ctx, scope, constLookup); err != nil {
|
||||
var err error
|
||||
c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
|
||||
params[1], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ADD: param1: %w", err)
|
||||
}
|
||||
|
||||
// Parse param2
|
||||
if err := c.parseParam(params[3], &c.param2VarName, &c.param2VarKind, &c.param2Value, &c.param2IsVar, ctx, scope, constLookup); err != nil {
|
||||
c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
|
||||
params[3], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ADD: param2: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -139,15 +144,20 @@ func (c *AddCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext)
|
|||
return fmt.Errorf("ADD: cannot assign to constant %q", destName)
|
||||
}
|
||||
c.destVarName = destSym.FullName()
|
||||
c.destVarKind = getVarKind(destSym)
|
||||
c.destVarKind = destSym.GetVarKind()
|
||||
|
||||
// Parse param1
|
||||
if err := c.parseParam(params[2], &c.param1VarName, &c.param1VarKind, &c.param1Value, &c.param1IsVar, ctx, scope, constLookup); err != nil {
|
||||
var err error
|
||||
c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
|
||||
params[2], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ADD: param1: %w", err)
|
||||
}
|
||||
|
||||
// Parse param2
|
||||
if err := c.parseParam(params[4], &c.param2VarName, &c.param2VarKind, &c.param2Value, &c.param2IsVar, ctx, scope, constLookup); err != nil {
|
||||
c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
|
||||
params[4], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ADD: param2: %w", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -155,42 +165,6 @@ func (c *AddCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext)
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *AddCommand) parseParam(
|
||||
param string,
|
||||
varName *string,
|
||||
varKind *compiler.VarKind,
|
||||
value *uint16,
|
||||
isVar *bool,
|
||||
ctx *compiler.CompilerContext,
|
||||
scope []string,
|
||||
constLookup utils.ConstantLookup,
|
||||
) error {
|
||||
// Try variable lookup first
|
||||
sym := ctx.SymbolTable.Lookup(param, scope)
|
||||
if sym != nil {
|
||||
// It's a variable or constant
|
||||
*varName = sym.FullName()
|
||||
*varKind = getVarKind(sym)
|
||||
*value = sym.Value
|
||||
*isVar = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Not a variable, must be an expression
|
||||
val, err := utils.EvaluateExpression(param, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("not a valid variable or expression: %w", err)
|
||||
}
|
||||
|
||||
if val < 0 || val > 65535 {
|
||||
return fmt.Errorf("value %d out of range (0-65535)", val)
|
||||
}
|
||||
|
||||
*value = uint16(val)
|
||||
*isVar = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AddCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
||||
var asm []string
|
||||
|
||||
|
|
@ -263,11 +237,3 @@ func (c *AddCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
|||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
// getVarKind extracts VarKind from Symbol
|
||||
func getVarKind(sym *compiler.Symbol) compiler.VarKind {
|
||||
if sym.IsByte() {
|
||||
return compiler.KindByte
|
||||
}
|
||||
return compiler.KindWord
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,8 +181,11 @@ func TestAddCommand_Interpret_OldSyntax(t *testing.T) {
|
|||
text: "ADD MAX TO a GIVING c",
|
||||
wantErr: false,
|
||||
check: func(t *testing.T, cmd *AddCommand) {
|
||||
if !cmd.param1IsVar || cmd.param1VarName != "MAX" {
|
||||
t.Errorf("param1 should be constant MAX")
|
||||
if cmd.param1IsVar {
|
||||
t.Errorf("param1 should be literal (constant inlined), got isVar=true")
|
||||
}
|
||||
if cmd.param1Value != 100 {
|
||||
t.Errorf("param1 value = %d, want 100", cmd.param1Value)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
237
internal/commands/and.go
Normal file
237
internal/commands/and.go
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
"c65gm/internal/utils"
|
||||
)
|
||||
|
||||
// AndCommand handles bitwise AND operations
|
||||
// Syntax:
|
||||
//
|
||||
// AND <param1> WITH <param2> GIVING <dest> # old syntax with WITH/GIVING
|
||||
// AND <param1> WITH <param2> -> <dest> # old syntax with WITH/->
|
||||
// <dest> = <param1> & <param2> # new syntax
|
||||
type AndCommand struct {
|
||||
param1VarName string
|
||||
param1VarKind compiler.VarKind
|
||||
param1Value uint16
|
||||
param1IsVar bool
|
||||
|
||||
param2VarName string
|
||||
param2VarKind compiler.VarKind
|
||||
param2Value uint16
|
||||
param2IsVar bool
|
||||
|
||||
destVarName string
|
||||
destVarKind compiler.VarKind
|
||||
}
|
||||
|
||||
func (c *AndCommand) WillHandle(line preproc.Line) bool {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil || len(params) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Old syntax: AND ... (must have exactly 6 params)
|
||||
if strings.ToUpper(params[0]) == "AND" && len(params) == 6 {
|
||||
return true
|
||||
}
|
||||
|
||||
// New syntax: <dest> = <param1> & <param2>
|
||||
if len(params) == 5 && params[1] == "=" && params[3] == "&" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *AndCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||
// Clear state
|
||||
c.param1VarName = ""
|
||||
c.param1IsVar = false
|
||||
c.param1Value = 0
|
||||
c.param2VarName = ""
|
||||
c.param2IsVar = false
|
||||
c.param2Value = 0
|
||||
c.destVarName = ""
|
||||
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
paramCount := len(params)
|
||||
scope := ctx.CurrentScope()
|
||||
|
||||
// Create constant lookup function
|
||||
constLookup := func(name string) (int64, bool) {
|
||||
sym := ctx.SymbolTable.Lookup(name, scope)
|
||||
if sym != nil && sym.IsConst() {
|
||||
return int64(sym.Value), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Determine syntax and parse accordingly
|
||||
if strings.ToUpper(params[0]) == "AND" {
|
||||
// Old syntax: AND <param1> WITH <param2> GIVING/-> <dest>
|
||||
if paramCount != 6 {
|
||||
return fmt.Errorf("AND: wrong number of parameters (%d), expected 6", paramCount)
|
||||
}
|
||||
|
||||
separator1 := strings.ToUpper(params[2])
|
||||
if separator1 != "WITH" {
|
||||
return fmt.Errorf("AND: parameter #3 must be 'WITH', got %q", params[2])
|
||||
}
|
||||
|
||||
separator2 := strings.ToUpper(params[4])
|
||||
if separator2 != "GIVING" && separator2 != "->" {
|
||||
return fmt.Errorf("AND: parameter #5 must be 'GIVING' or '->', got %q", params[4])
|
||||
}
|
||||
|
||||
// Parse destination
|
||||
destName := params[5]
|
||||
destSym := ctx.SymbolTable.Lookup(destName, scope)
|
||||
if destSym == nil {
|
||||
return fmt.Errorf("AND: unknown variable %q", destName)
|
||||
}
|
||||
if destSym.IsConst() {
|
||||
return fmt.Errorf("AND: cannot assign to constant %q", destName)
|
||||
}
|
||||
c.destVarName = destSym.FullName()
|
||||
c.destVarKind = destSym.GetVarKind()
|
||||
|
||||
// Parse param1
|
||||
var err error
|
||||
c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
|
||||
params[1], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("AND: param1: %w", err)
|
||||
}
|
||||
|
||||
// Parse param2
|
||||
c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
|
||||
params[3], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("AND: param2: %w", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
// New syntax: <dest> = <param1> & <param2>
|
||||
if paramCount != 5 {
|
||||
return fmt.Errorf("AND: wrong number of parameters (%d), expected 5", paramCount)
|
||||
}
|
||||
|
||||
if params[1] != "=" {
|
||||
return fmt.Errorf("AND: expected '=' at position 2, got %q", params[1])
|
||||
}
|
||||
|
||||
if params[3] != "&" {
|
||||
return fmt.Errorf("AND: expected '&' at position 4, got %q", params[3])
|
||||
}
|
||||
|
||||
// Parse destination
|
||||
destName := params[0]
|
||||
destSym := ctx.SymbolTable.Lookup(destName, scope)
|
||||
if destSym == nil {
|
||||
return fmt.Errorf("AND: unknown variable %q", destName)
|
||||
}
|
||||
if destSym.IsConst() {
|
||||
return fmt.Errorf("AND: cannot assign to constant %q", destName)
|
||||
}
|
||||
c.destVarName = destSym.FullName()
|
||||
c.destVarKind = destSym.GetVarKind()
|
||||
|
||||
// Parse param1
|
||||
var err error
|
||||
c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
|
||||
params[2], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("AND: param1: %w", err)
|
||||
}
|
||||
|
||||
// Parse param2
|
||||
c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
|
||||
params[4], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("AND: param2: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AndCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
||||
var asm []string
|
||||
|
||||
// If both params are literals, fold at compile time
|
||||
if !c.param1IsVar && !c.param2IsVar {
|
||||
result := c.param1Value & c.param2Value
|
||||
lo := uint8(result & 0xFF)
|
||||
hi := uint8((result >> 8) & 0xFF)
|
||||
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||
|
||||
if c.destVarKind == compiler.KindWord {
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||
}
|
||||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
// At least one param is a variable - generate AND code
|
||||
// Load param1
|
||||
if c.param1IsVar {
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName))
|
||||
} else {
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF)))
|
||||
}
|
||||
|
||||
// AND with param2
|
||||
if c.param2IsVar {
|
||||
asm = append(asm, fmt.Sprintf("\tand %s", c.param2VarName))
|
||||
} else {
|
||||
asm = append(asm, fmt.Sprintf("\tand #$%02x", uint8(c.param2Value&0xFF)))
|
||||
}
|
||||
|
||||
// Store low byte
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||
|
||||
// If destination is word, handle high byte
|
||||
if c.destVarKind == compiler.KindWord {
|
||||
// Load high byte of param1
|
||||
if c.param1IsVar {
|
||||
if c.param1VarKind == compiler.KindWord {
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName))
|
||||
} else {
|
||||
asm = append(asm, "\tlda #0")
|
||||
}
|
||||
} else {
|
||||
hi := uint8((c.param1Value >> 8) & 0xFF)
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
|
||||
}
|
||||
|
||||
// AND with high byte of param2
|
||||
if c.param2IsVar {
|
||||
if c.param2VarKind == compiler.KindWord {
|
||||
asm = append(asm, fmt.Sprintf("\tand %s+1", c.param2VarName))
|
||||
} else {
|
||||
asm = append(asm, "\tand #0")
|
||||
}
|
||||
} else {
|
||||
hi := uint8((c.param2Value >> 8) & 0xFF)
|
||||
asm = append(asm, fmt.Sprintf("\tand #$%02x", hi))
|
||||
}
|
||||
|
||||
// Store high byte
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||
}
|
||||
|
||||
return asm, nil
|
||||
}
|
||||
426
internal/commands/and_test.go
Normal file
426
internal/commands/and_test.go
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
)
|
||||
|
||||
func TestAndCommand_WillHandle(t *testing.T) {
|
||||
cmd := &AndCommand{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
want bool
|
||||
}{
|
||||
{"old syntax WITH/GIVING", "AND a WITH b GIVING c", true},
|
||||
{"old syntax WITH/arrow", "AND a WITH b -> c", true},
|
||||
{"new syntax", "result = x & y", true},
|
||||
{"not AND", "ADD a TO b GIVING c", false},
|
||||
{"wrong param count", "AND a b c", false},
|
||||
{"empty", "", false},
|
||||
{"new syntax wrong op", "result = x + y", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
||||
got := cmd.WillHandle(line)
|
||||
if got != tt.want {
|
||||
t.Errorf("WillHandle() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAndCommand_OldSyntax(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
setupVars func(*compiler.SymbolTable)
|
||||
wantAsm []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "byte AND byte -> byte (variables)",
|
||||
line: "AND a WITH b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("b", "", compiler.KindByte, 0x0F)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tand b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "byte AND byte -> word",
|
||||
line: "AND a WITH b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("b", "", compiler.KindByte, 0x0F)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tand b",
|
||||
"\tsta result",
|
||||
"\tlda #0",
|
||||
"\tand #0",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word AND word -> word",
|
||||
line: "AND x WITH y GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("x", "", compiler.KindWord, 0x1234)
|
||||
st.AddVar("y", "", compiler.KindWord, 0x0F0F)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda x",
|
||||
"\tand y",
|
||||
"\tsta result",
|
||||
"\tlda x+1",
|
||||
"\tand y+1",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "byte AND literal -> byte",
|
||||
line: "AND a WITH $F0 GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tand #$f0",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal AND byte -> byte",
|
||||
line: "AND 255 WITH b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("b", "", compiler.KindByte, 0x0F)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$ff",
|
||||
"\tand b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "constant folding: 255 AND 15 -> byte",
|
||||
line: "AND 255 WITH 15 GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$0f",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "constant folding: $FFFF AND $0F0F -> word",
|
||||
line: "AND $FFFF WITH $0F0F GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$0f",
|
||||
"\tsta result",
|
||||
"\tlda #$0f",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "arrow syntax",
|
||||
line: "AND a WITH b -> result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tand b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word AND byte -> byte",
|
||||
line: "AND wval WITH bval GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
||||
st.AddVar("bval", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda wval",
|
||||
"\tand bval",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word AND byte -> word",
|
||||
line: "AND wval WITH bval GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
||||
st.AddVar("bval", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda wval",
|
||||
"\tand bval",
|
||||
"\tsta result",
|
||||
"\tlda wval+1",
|
||||
"\tand #0",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error: unknown destination variable",
|
||||
line: "AND a WITH b GIVING unknown",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error: wrong separator",
|
||||
line: "AND a TO b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error: cannot assign to constant",
|
||||
line: "AND a WITH b GIVING MAXVAL",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
st.AddConst("MAXVAL", "", compiler.KindByte, 255)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := compiler.NewCompilerContext(&preproc.Pragma{})
|
||||
tt.setupVars(ctx.SymbolTable)
|
||||
|
||||
cmd := &AndCommand{}
|
||||
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if 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 TestAndCommand_NewSyntax(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
setupVars func(*compiler.SymbolTable)
|
||||
wantAsm []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "byte & byte -> byte",
|
||||
line: "result = a & b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("b", "", compiler.KindByte, 0x0F)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tand b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "byte & byte -> word",
|
||||
line: "result = a & b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("b", "", compiler.KindByte, 0x0F)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tand b",
|
||||
"\tsta result",
|
||||
"\tlda #0",
|
||||
"\tand #0",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word & word -> word",
|
||||
line: "result = x & y",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("x", "", compiler.KindWord, 0x1234)
|
||||
st.AddVar("y", "", compiler.KindWord, 0x0F0F)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda x",
|
||||
"\tand y",
|
||||
"\tsta result",
|
||||
"\tlda x+1",
|
||||
"\tand y+1",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "variable & literal",
|
||||
line: "result = a & $F0",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tand #$f0",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "constant folding",
|
||||
line: "result = 255 & 15",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$0f",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "constant folding word",
|
||||
line: "result = $FFFF & $1234",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$34",
|
||||
"\tsta result",
|
||||
"\tlda #$12",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "using constant in expression",
|
||||
line: "result = a & MASK",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
||||
st.AddConst("MASK", "", compiler.KindByte, 0xF0)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tand #$f0",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error: unknown destination",
|
||||
line: "unknown = a & b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error: wrong operator",
|
||||
line: "result = a + b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := compiler.NewCompilerContext(&preproc.Pragma{})
|
||||
tt.setupVars(ctx.SymbolTable)
|
||||
|
||||
cmd := &AndCommand{}
|
||||
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if 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"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// equalAsm compares two assembly slices for equality
|
||||
func equalAsm(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
237
internal/commands/or.go
Normal file
237
internal/commands/or.go
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
"c65gm/internal/utils"
|
||||
)
|
||||
|
||||
// OrCommand handles bitwise OR operations
|
||||
// Syntax:
|
||||
//
|
||||
// OR <param1> WITH <param2> GIVING <dest> # old syntax with WITH/GIVING
|
||||
// OR <param1> WITH <param2> -> <dest> # old syntax with WITH/->
|
||||
// <dest> = <param1> | <param2> # new syntax
|
||||
type OrCommand struct {
|
||||
param1VarName string
|
||||
param1VarKind compiler.VarKind
|
||||
param1Value uint16
|
||||
param1IsVar bool
|
||||
|
||||
param2VarName string
|
||||
param2VarKind compiler.VarKind
|
||||
param2Value uint16
|
||||
param2IsVar bool
|
||||
|
||||
destVarName string
|
||||
destVarKind compiler.VarKind
|
||||
}
|
||||
|
||||
func (c *OrCommand) WillHandle(line preproc.Line) bool {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil || len(params) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Old syntax: OR ... (must have exactly 6 params)
|
||||
if strings.ToUpper(params[0]) == "OR" && len(params) == 6 {
|
||||
return true
|
||||
}
|
||||
|
||||
// New syntax: <dest> = <param1> | <param2>
|
||||
if len(params) == 5 && params[1] == "=" && params[3] == "|" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *OrCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||
// Clear state
|
||||
c.param1VarName = ""
|
||||
c.param1IsVar = false
|
||||
c.param1Value = 0
|
||||
c.param2VarName = ""
|
||||
c.param2IsVar = false
|
||||
c.param2Value = 0
|
||||
c.destVarName = ""
|
||||
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
paramCount := len(params)
|
||||
scope := ctx.CurrentScope()
|
||||
|
||||
// Create constant lookup function
|
||||
constLookup := func(name string) (int64, bool) {
|
||||
sym := ctx.SymbolTable.Lookup(name, scope)
|
||||
if sym != nil && sym.IsConst() {
|
||||
return int64(sym.Value), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Determine syntax and parse accordingly
|
||||
if strings.ToUpper(params[0]) == "OR" {
|
||||
// Old syntax: OR <param1> WITH <param2> GIVING/-> <dest>
|
||||
if paramCount != 6 {
|
||||
return fmt.Errorf("OR: wrong number of parameters (%d), expected 6", paramCount)
|
||||
}
|
||||
|
||||
separator1 := strings.ToUpper(params[2])
|
||||
if separator1 != "WITH" {
|
||||
return fmt.Errorf("OR: parameter #3 must be 'WITH', got %q", params[2])
|
||||
}
|
||||
|
||||
separator2 := strings.ToUpper(params[4])
|
||||
if separator2 != "GIVING" && separator2 != "->" {
|
||||
return fmt.Errorf("OR: parameter #5 must be 'GIVING' or '->', got %q", params[4])
|
||||
}
|
||||
|
||||
// Parse destination
|
||||
destName := params[5]
|
||||
destSym := ctx.SymbolTable.Lookup(destName, scope)
|
||||
if destSym == nil {
|
||||
return fmt.Errorf("OR: unknown variable %q", destName)
|
||||
}
|
||||
if destSym.IsConst() {
|
||||
return fmt.Errorf("OR: cannot assign to constant %q", destName)
|
||||
}
|
||||
c.destVarName = destSym.FullName()
|
||||
c.destVarKind = destSym.GetVarKind()
|
||||
|
||||
// Parse param1
|
||||
var err error
|
||||
c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
|
||||
params[1], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OR: param1: %w", err)
|
||||
}
|
||||
|
||||
// Parse param2
|
||||
c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
|
||||
params[3], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OR: param2: %w", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
// New syntax: <dest> = <param1> | <param2>
|
||||
if paramCount != 5 {
|
||||
return fmt.Errorf("OR: wrong number of parameters (%d), expected 5", paramCount)
|
||||
}
|
||||
|
||||
if params[1] != "=" {
|
||||
return fmt.Errorf("OR: expected '=' at position 2, got %q", params[1])
|
||||
}
|
||||
|
||||
if params[3] != "|" {
|
||||
return fmt.Errorf("OR: expected '|' at position 4, got %q", params[3])
|
||||
}
|
||||
|
||||
// Parse destination
|
||||
destName := params[0]
|
||||
destSym := ctx.SymbolTable.Lookup(destName, scope)
|
||||
if destSym == nil {
|
||||
return fmt.Errorf("OR: unknown variable %q", destName)
|
||||
}
|
||||
if destSym.IsConst() {
|
||||
return fmt.Errorf("OR: cannot assign to constant %q", destName)
|
||||
}
|
||||
c.destVarName = destSym.FullName()
|
||||
c.destVarKind = destSym.GetVarKind()
|
||||
|
||||
// Parse param1
|
||||
var err error
|
||||
c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
|
||||
params[2], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OR: param1: %w", err)
|
||||
}
|
||||
|
||||
// Parse param2
|
||||
c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
|
||||
params[4], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OR: param2: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *OrCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
||||
var asm []string
|
||||
|
||||
// If both params are literals, fold at compile time
|
||||
if !c.param1IsVar && !c.param2IsVar {
|
||||
result := c.param1Value | c.param2Value
|
||||
lo := uint8(result & 0xFF)
|
||||
hi := uint8((result >> 8) & 0xFF)
|
||||
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||
|
||||
if c.destVarKind == compiler.KindWord {
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||
}
|
||||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
// At least one param is a variable - generate OR code
|
||||
// Load param1
|
||||
if c.param1IsVar {
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName))
|
||||
} else {
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF)))
|
||||
}
|
||||
|
||||
// OR with param2
|
||||
if c.param2IsVar {
|
||||
asm = append(asm, fmt.Sprintf("\tora %s", c.param2VarName))
|
||||
} else {
|
||||
asm = append(asm, fmt.Sprintf("\tora #$%02x", uint8(c.param2Value&0xFF)))
|
||||
}
|
||||
|
||||
// Store low byte
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||
|
||||
// If destination is word, handle high byte
|
||||
if c.destVarKind == compiler.KindWord {
|
||||
// Load high byte of param1
|
||||
if c.param1IsVar {
|
||||
if c.param1VarKind == compiler.KindWord {
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName))
|
||||
} else {
|
||||
asm = append(asm, "\tlda #0")
|
||||
}
|
||||
} else {
|
||||
hi := uint8((c.param1Value >> 8) & 0xFF)
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
|
||||
}
|
||||
|
||||
// OR with high byte of param2
|
||||
if c.param2IsVar {
|
||||
if c.param2VarKind == compiler.KindWord {
|
||||
asm = append(asm, fmt.Sprintf("\tora %s+1", c.param2VarName))
|
||||
} else {
|
||||
asm = append(asm, "\tora #0")
|
||||
}
|
||||
} else {
|
||||
hi := uint8((c.param2Value >> 8) & 0xFF)
|
||||
asm = append(asm, fmt.Sprintf("\tora #$%02x", hi))
|
||||
}
|
||||
|
||||
// Store high byte
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||
}
|
||||
|
||||
return asm, nil
|
||||
}
|
||||
426
internal/commands/or_test.go
Normal file
426
internal/commands/or_test.go
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
)
|
||||
|
||||
func TestOrCommand_WillHandle(t *testing.T) {
|
||||
cmd := &OrCommand{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
want bool
|
||||
}{
|
||||
{"old syntax WITH/GIVING", "OR a WITH b GIVING c", true},
|
||||
{"old syntax WITH/arrow", "OR a WITH b -> c", true},
|
||||
{"new syntax", "result = x | y", true},
|
||||
{"not OR", "ADD a TO b GIVING c", false},
|
||||
{"wrong param count", "OR a b c", false},
|
||||
{"empty", "", false},
|
||||
{"new syntax wrong op", "result = x + y", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
||||
got := cmd.WillHandle(line)
|
||||
if got != tt.want {
|
||||
t.Errorf("WillHandle() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrCommand_OldSyntax(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
setupVars func(*compiler.SymbolTable)
|
||||
wantAsm []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "byte OR byte -> byte (variables)",
|
||||
line: "OR a WITH b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xF0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0x0F)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tora b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "byte OR byte -> word",
|
||||
line: "OR a WITH b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xF0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0x0F)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tora b",
|
||||
"\tsta result",
|
||||
"\tlda #0",
|
||||
"\tora #0",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word OR word -> word",
|
||||
line: "OR x WITH y GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("x", "", compiler.KindWord, 0x1234)
|
||||
st.AddVar("y", "", compiler.KindWord, 0x0F0F)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda x",
|
||||
"\tora y",
|
||||
"\tsta result",
|
||||
"\tlda x+1",
|
||||
"\tora y+1",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "byte OR literal -> byte",
|
||||
line: "OR a WITH $0F GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xF0)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tora #$0f",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal OR byte -> byte",
|
||||
line: "OR 15 WITH b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("b", "", compiler.KindByte, 0xF0)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$0f",
|
||||
"\tora b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "constant folding: 15 OR 240 -> byte",
|
||||
line: "OR 15 WITH 240 GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$ff",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "constant folding: $00F0 OR $0F00 -> word",
|
||||
line: "OR $00F0 WITH $0F00 GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$f0",
|
||||
"\tsta result",
|
||||
"\tlda #$0f",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "arrow syntax",
|
||||
line: "OR a WITH b -> result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tora b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word OR byte -> byte",
|
||||
line: "OR wval WITH bval GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
||||
st.AddVar("bval", "", compiler.KindByte, 0x0F)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda wval",
|
||||
"\tora bval",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word OR byte -> word",
|
||||
line: "OR wval WITH bval GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
||||
st.AddVar("bval", "", compiler.KindByte, 0x0F)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda wval",
|
||||
"\tora bval",
|
||||
"\tsta result",
|
||||
"\tlda wval+1",
|
||||
"\tora #0",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error: unknown destination variable",
|
||||
line: "OR a WITH b GIVING unknown",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error: wrong separator",
|
||||
line: "OR a TO b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error: cannot assign to constant",
|
||||
line: "OR a WITH b GIVING MAXVAL",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
st.AddConst("MAXVAL", "", compiler.KindByte, 255)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := compiler.NewCompilerContext(&preproc.Pragma{})
|
||||
tt.setupVars(ctx.SymbolTable)
|
||||
|
||||
cmd := &OrCommand{}
|
||||
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if !equalAsmOr(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrCommand_NewSyntax(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
setupVars func(*compiler.SymbolTable)
|
||||
wantAsm []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "byte | byte -> byte",
|
||||
line: "result = a | b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xF0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0x0F)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tora b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "byte | byte -> word",
|
||||
line: "result = a | b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xF0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0x0F)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tora b",
|
||||
"\tsta result",
|
||||
"\tlda #0",
|
||||
"\tora #0",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word | word -> word",
|
||||
line: "result = x | y",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("x", "", compiler.KindWord, 0x1234)
|
||||
st.AddVar("y", "", compiler.KindWord, 0x0F0F)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda x",
|
||||
"\tora y",
|
||||
"\tsta result",
|
||||
"\tlda x+1",
|
||||
"\tora y+1",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "variable | literal",
|
||||
line: "result = a | $0F",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xF0)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tora #$0f",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "constant folding",
|
||||
line: "result = 15 | 240",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$ff",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "constant folding word",
|
||||
line: "result = $00F0 | $0F00",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$f0",
|
||||
"\tsta result",
|
||||
"\tlda #$0f",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "using constant in expression",
|
||||
line: "result = a | BITS",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xF0)
|
||||
st.AddConst("BITS", "", compiler.KindByte, 0x0F)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\tora #$0f",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error: unknown destination",
|
||||
line: "unknown = a | b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error: wrong operator",
|
||||
line: "result = a + b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := compiler.NewCompilerContext(&preproc.Pragma{})
|
||||
tt.setupVars(ctx.SymbolTable)
|
||||
|
||||
cmd := &OrCommand{}
|
||||
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if !equalAsmOr(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// equalAsmOr compares two assembly slices for equality
|
||||
func equalAsmOr(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
259
internal/commands/subtr.go
Normal file
259
internal/commands/subtr.go
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
"c65gm/internal/utils"
|
||||
)
|
||||
|
||||
// SubtractCommand handles SUBTRACT operations
|
||||
// Syntax:
|
||||
//
|
||||
// SUBTRACT <param2> FROM <param1> GIVING <dest> # old syntax: param1 - param2
|
||||
// SUBT <param1> - <param2> -> <dest> # old syntax: param1 - param2
|
||||
// <dest> = <param1> - <param2> # new syntax: param1 - param2
|
||||
//
|
||||
// Note: FROM syntax swaps parameter order (SUBTRACT a FROM b means b-a)
|
||||
type SubtractCommand struct {
|
||||
param1VarName string
|
||||
param1VarKind compiler.VarKind
|
||||
param1Value uint16
|
||||
param1IsVar bool
|
||||
|
||||
param2VarName string
|
||||
param2VarKind compiler.VarKind
|
||||
param2Value uint16
|
||||
param2IsVar bool
|
||||
|
||||
destVarName string
|
||||
destVarKind compiler.VarKind
|
||||
}
|
||||
|
||||
func (c *SubtractCommand) WillHandle(line preproc.Line) bool {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil || len(params) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Old syntax: SUBTRACT/SUBT ... (must have exactly 6 params)
|
||||
kw := strings.ToUpper(params[0])
|
||||
if (kw == "SUBTRACT" || kw == "SUBT") && len(params) == 6 {
|
||||
return true
|
||||
}
|
||||
|
||||
// New syntax: <dest> = <param1> - <param2>
|
||||
if len(params) == 5 && params[1] == "=" && params[3] == "-" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *SubtractCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||
// Clear state
|
||||
c.param1VarName = ""
|
||||
c.param1IsVar = false
|
||||
c.param1Value = 0
|
||||
c.param2VarName = ""
|
||||
c.param2IsVar = false
|
||||
c.param2Value = 0
|
||||
c.destVarName = ""
|
||||
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
paramCount := len(params)
|
||||
scope := ctx.CurrentScope()
|
||||
|
||||
// Create constant lookup function
|
||||
constLookup := func(name string) (int64, bool) {
|
||||
sym := ctx.SymbolTable.Lookup(name, scope)
|
||||
if sym != nil && sym.IsConst() {
|
||||
return int64(sym.Value), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Determine syntax and parse accordingly
|
||||
kw := strings.ToUpper(params[0])
|
||||
if kw == "SUBTRACT" || kw == "SUBT" {
|
||||
// Old syntax: SUBTRACT/SUBT <p1> FROM/- <p2> GIVING/-> <dest>
|
||||
if paramCount != 6 {
|
||||
return fmt.Errorf("SUBTRACT: wrong number of parameters (%d), expected 6", paramCount)
|
||||
}
|
||||
|
||||
separator1 := strings.ToUpper(params[2])
|
||||
if separator1 != "FROM" && separator1 != "-" {
|
||||
return fmt.Errorf("SUBTRACT: parameter #3 must be 'FROM' or '-', got %q", params[2])
|
||||
}
|
||||
|
||||
separator2 := strings.ToUpper(params[4])
|
||||
if separator2 != "GIVING" && separator2 != "->" {
|
||||
return fmt.Errorf("SUBTRACT: parameter #5 must be 'GIVING' or '->', got %q", params[4])
|
||||
}
|
||||
|
||||
// Parse destination
|
||||
destName := params[5]
|
||||
destSym := ctx.SymbolTable.Lookup(destName, scope)
|
||||
if destSym == nil {
|
||||
return fmt.Errorf("SUBTRACT: unknown variable %q", destName)
|
||||
}
|
||||
if destSym.IsConst() {
|
||||
return fmt.Errorf("SUBTRACT: cannot assign to constant %q", destName)
|
||||
}
|
||||
c.destVarName = destSym.FullName()
|
||||
c.destVarKind = destSym.GetVarKind()
|
||||
|
||||
// Parse parameters based on separator
|
||||
// FROM syntax: SUBTRACT a FROM b means b - a (swap needed)
|
||||
// Minus syntax: SUBT a - b means a - b (no swap)
|
||||
var minuendParam, subtrahendParam string
|
||||
if separator1 == "FROM" {
|
||||
// Swap: position 4 becomes minuend, position 2 becomes subtrahend
|
||||
minuendParam = params[3]
|
||||
subtrahendParam = params[1]
|
||||
} else {
|
||||
// No swap: position 2 is minuend, position 4 is subtrahend
|
||||
minuendParam = params[1]
|
||||
subtrahendParam = params[3]
|
||||
}
|
||||
|
||||
// Parse minuend (param1)
|
||||
var err error
|
||||
c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
|
||||
minuendParam, ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SUBTRACT: minuend: %w", err)
|
||||
}
|
||||
|
||||
// Parse subtrahend (param2)
|
||||
c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
|
||||
subtrahendParam, ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SUBTRACT: subtrahend: %w", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
// New syntax: <dest> = <param1> - <param2>
|
||||
if paramCount != 5 {
|
||||
return fmt.Errorf("SUBTRACT: wrong number of parameters (%d), expected 5", paramCount)
|
||||
}
|
||||
|
||||
if params[1] != "=" {
|
||||
return fmt.Errorf("SUBTRACT: expected '=' at position 2, got %q", params[1])
|
||||
}
|
||||
|
||||
if params[3] != "-" {
|
||||
return fmt.Errorf("SUBTRACT: expected '-' at position 4, got %q", params[3])
|
||||
}
|
||||
|
||||
// Parse destination
|
||||
destName := params[0]
|
||||
destSym := ctx.SymbolTable.Lookup(destName, scope)
|
||||
if destSym == nil {
|
||||
return fmt.Errorf("SUBTRACT: unknown variable %q", destName)
|
||||
}
|
||||
if destSym.IsConst() {
|
||||
return fmt.Errorf("SUBTRACT: cannot assign to constant %q", destName)
|
||||
}
|
||||
c.destVarName = destSym.FullName()
|
||||
c.destVarKind = destSym.GetVarKind()
|
||||
|
||||
// Parse param1 (minuend)
|
||||
var err error
|
||||
c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
|
||||
params[2], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SUBTRACT: param1: %w", err)
|
||||
}
|
||||
|
||||
// Parse param2 (subtrahend)
|
||||
c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
|
||||
params[4], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("SUBTRACT: param2: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *SubtractCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
||||
var asm []string
|
||||
|
||||
// If both params are literals, fold at compile time
|
||||
if !c.param1IsVar && !c.param2IsVar {
|
||||
// param1 - param2 (minuend - subtrahend)
|
||||
result := uint16(int32(c.param1Value) - int32(c.param2Value))
|
||||
lo := uint8(result & 0xFF)
|
||||
hi := uint8((result >> 8) & 0xFF)
|
||||
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||
|
||||
if c.destVarKind == compiler.KindWord {
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||
}
|
||||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
// At least one param is a variable - generate subtract code
|
||||
// SEC sets carry for borrow
|
||||
asm = append(asm, "\tsec")
|
||||
|
||||
// Load minuend (param1)
|
||||
if c.param1IsVar {
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName))
|
||||
} else {
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF)))
|
||||
}
|
||||
|
||||
// Subtract subtrahend (param2)
|
||||
if c.param2IsVar {
|
||||
asm = append(asm, fmt.Sprintf("\tsbc %s", c.param2VarName))
|
||||
} else {
|
||||
asm = append(asm, fmt.Sprintf("\tsbc #$%02x", uint8(c.param2Value&0xFF)))
|
||||
}
|
||||
|
||||
// Store low byte
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||
|
||||
// If destination is word, handle high byte
|
||||
if c.destVarKind == compiler.KindWord {
|
||||
// Load high byte of minuend (param1)
|
||||
if c.param1IsVar {
|
||||
if c.param1VarKind == compiler.KindWord {
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName))
|
||||
} else {
|
||||
asm = append(asm, "\tlda #0")
|
||||
}
|
||||
} else {
|
||||
hi := uint8((c.param1Value >> 8) & 0xFF)
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
|
||||
}
|
||||
|
||||
// Subtract high byte of subtrahend (param2)
|
||||
if c.param2IsVar {
|
||||
if c.param2VarKind == compiler.KindWord {
|
||||
asm = append(asm, fmt.Sprintf("\tsbc %s+1", c.param2VarName))
|
||||
} else {
|
||||
asm = append(asm, "\tsbc #0")
|
||||
}
|
||||
} else {
|
||||
hi := uint8((c.param2Value >> 8) & 0xFF)
|
||||
asm = append(asm, fmt.Sprintf("\tsbc #$%02x", hi))
|
||||
}
|
||||
|
||||
// Store high byte
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||
}
|
||||
|
||||
return asm, nil
|
||||
}
|
||||
510
internal/commands/subtr_test.go
Normal file
510
internal/commands/subtr_test.go
Normal file
|
|
@ -0,0 +1,510 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
)
|
||||
|
||||
func TestSubtractCommand_WillHandle(t *testing.T) {
|
||||
cmd := &SubtractCommand{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
want bool
|
||||
}{
|
||||
{"old syntax SUBTRACT FROM/GIVING", "SUBTRACT a FROM b GIVING c", true},
|
||||
{"old syntax SUBT FROM/arrow", "SUBT a FROM b -> c", true},
|
||||
{"old syntax SUBT minus/arrow", "SUBT a - b -> c", true},
|
||||
{"old syntax SUBTRACT minus/GIVING", "SUBTRACT a - b GIVING c", true},
|
||||
{"new syntax", "result = x - y", true},
|
||||
{"not SUBTRACT", "ADD a TO b GIVING c", false},
|
||||
{"wrong param count", "SUBTRACT a b c", false},
|
||||
{"empty", "", false},
|
||||
{"new syntax wrong op", "result = x + y", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
||||
got := cmd.WillHandle(line)
|
||||
if got != tt.want {
|
||||
t.Errorf("WillHandle() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubtractCommand_OldSyntax_FROM(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
setupVars func(*compiler.SymbolTable)
|
||||
wantAsm []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "SUBTRACT a FROM b (means b-a)",
|
||||
line: "SUBTRACT 5 FROM 10 GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$05",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SUBTRACT byte FROM byte -> byte (variables)",
|
||||
line: "SUBTRACT a FROM b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 10)
|
||||
st.AddVar("b", "", compiler.KindByte, 20)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda b",
|
||||
"\tsbc a",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SUBT a FROM b -> c with arrow",
|
||||
line: "SUBT a FROM b -> result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 10)
|
||||
st.AddVar("b", "", compiler.KindByte, 20)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda b",
|
||||
"\tsbc a",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SUBTRACT literal FROM variable",
|
||||
line: "SUBTRACT 10 FROM a GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 20)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda a",
|
||||
"\tsbc #$0a",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word FROM word -> word",
|
||||
line: "SUBTRACT x FROM y GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("x", "", compiler.KindWord, 0x1000)
|
||||
st.AddVar("y", "", compiler.KindWord, 0x2000)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda y",
|
||||
"\tsbc x",
|
||||
"\tsta result",
|
||||
"\tlda y+1",
|
||||
"\tsbc x+1",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := compiler.NewCompilerContext(&preproc.Pragma{})
|
||||
tt.setupVars(ctx.SymbolTable)
|
||||
|
||||
cmd := &SubtractCommand{}
|
||||
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if !equalAsmSubtr(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubtractCommand_OldSyntax_Minus(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
setupVars func(*compiler.SymbolTable)
|
||||
wantAsm []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "SUBT a - b (means a-b, no swap)",
|
||||
line: "SUBT 10 - 5 GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$05",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SUBT byte - byte -> byte (variables)",
|
||||
line: "SUBT a - b -> result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 20)
|
||||
st.AddVar("b", "", compiler.KindByte, 10)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda a",
|
||||
"\tsbc b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SUBTRACT a - b GIVING c",
|
||||
line: "SUBTRACT a - b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 20)
|
||||
st.AddVar("b", "", compiler.KindByte, 10)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda a",
|
||||
"\tsbc b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SUBT variable - literal",
|
||||
line: "SUBT a - 10 -> result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 20)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda a",
|
||||
"\tsbc #$0a",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word - word -> word",
|
||||
line: "SUBT x - y -> result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("x", "", compiler.KindWord, 0x2000)
|
||||
st.AddVar("y", "", compiler.KindWord, 0x1000)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda x",
|
||||
"\tsbc y",
|
||||
"\tsta result",
|
||||
"\tlda x+1",
|
||||
"\tsbc y+1",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "byte - byte -> word",
|
||||
line: "SUBT a - b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 20)
|
||||
st.AddVar("b", "", compiler.KindByte, 10)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda a",
|
||||
"\tsbc b",
|
||||
"\tsta result",
|
||||
"\tlda #0",
|
||||
"\tsbc #0",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := compiler.NewCompilerContext(&preproc.Pragma{})
|
||||
tt.setupVars(ctx.SymbolTable)
|
||||
|
||||
cmd := &SubtractCommand{}
|
||||
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if !equalAsmSubtr(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubtractCommand_NewSyntax(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
setupVars func(*compiler.SymbolTable)
|
||||
wantAsm []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "byte - byte -> byte",
|
||||
line: "result = a - b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 20)
|
||||
st.AddVar("b", "", compiler.KindByte, 10)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda a",
|
||||
"\tsbc b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "byte - byte -> word",
|
||||
line: "result = a - b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 20)
|
||||
st.AddVar("b", "", compiler.KindByte, 10)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda a",
|
||||
"\tsbc b",
|
||||
"\tsta result",
|
||||
"\tlda #0",
|
||||
"\tsbc #0",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word - word -> word",
|
||||
line: "result = x - y",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("x", "", compiler.KindWord, 0x2000)
|
||||
st.AddVar("y", "", compiler.KindWord, 0x1000)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda x",
|
||||
"\tsbc y",
|
||||
"\tsta result",
|
||||
"\tlda x+1",
|
||||
"\tsbc y+1",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "variable - literal",
|
||||
line: "result = a - 10",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 20)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda a",
|
||||
"\tsbc #$0a",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal - variable",
|
||||
line: "result = 20 - b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("b", "", compiler.KindByte, 10)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda #$14",
|
||||
"\tsbc b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "constant folding",
|
||||
line: "result = 100 - 25",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$4b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "constant folding word",
|
||||
line: "result = $2000 - $1000",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$00",
|
||||
"\tsta result",
|
||||
"\tlda #$10",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "using constant in expression",
|
||||
line: "result = a - OFFSET",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 100)
|
||||
st.AddConst("OFFSET", "", compiler.KindByte, 10)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda a",
|
||||
"\tsbc #$0a",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word - byte -> word",
|
||||
line: "result = wval - bval",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
||||
st.AddVar("bval", "", compiler.KindByte, 0x10)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tsec",
|
||||
"\tlda wval",
|
||||
"\tsbc bval",
|
||||
"\tsta result",
|
||||
"\tlda wval+1",
|
||||
"\tsbc #0",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error: unknown destination",
|
||||
line: "unknown = a - b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error: wrong operator",
|
||||
line: "result = a + b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error: cannot assign to constant",
|
||||
line: "MAXVAL = a - b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
st.AddConst("MAXVAL", "", compiler.KindByte, 255)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := compiler.NewCompilerContext(&preproc.Pragma{})
|
||||
tt.setupVars(ctx.SymbolTable)
|
||||
|
||||
cmd := &SubtractCommand{}
|
||||
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if !equalAsmSubtr(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// equalAsmSubtr compares two assembly slices for equality
|
||||
func equalAsmSubtr(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
237
internal/commands/xor.go
Normal file
237
internal/commands/xor.go
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
"c65gm/internal/utils"
|
||||
)
|
||||
|
||||
// XorCommand handles bitwise XOR operations
|
||||
// Syntax:
|
||||
//
|
||||
// XOR <param1> WITH <param2> GIVING <dest> # old syntax with WITH/GIVING
|
||||
// XOR <param1> WITH <param2> -> <dest> # old syntax with WITH/->
|
||||
// <dest> = <param1> ^ <param2> # new syntax
|
||||
type XorCommand struct {
|
||||
param1VarName string
|
||||
param1VarKind compiler.VarKind
|
||||
param1Value uint16
|
||||
param1IsVar bool
|
||||
|
||||
param2VarName string
|
||||
param2VarKind compiler.VarKind
|
||||
param2Value uint16
|
||||
param2IsVar bool
|
||||
|
||||
destVarName string
|
||||
destVarKind compiler.VarKind
|
||||
}
|
||||
|
||||
func (c *XorCommand) WillHandle(line preproc.Line) bool {
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil || len(params) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
// Old syntax: XOR ... (must have exactly 6 params)
|
||||
if strings.ToUpper(params[0]) == "XOR" && len(params) == 6 {
|
||||
return true
|
||||
}
|
||||
|
||||
// New syntax: <dest> = <param1> ^ <param2>
|
||||
if len(params) == 5 && params[1] == "=" && params[3] == "^" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *XorCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
|
||||
// Clear state
|
||||
c.param1VarName = ""
|
||||
c.param1IsVar = false
|
||||
c.param1Value = 0
|
||||
c.param2VarName = ""
|
||||
c.param2IsVar = false
|
||||
c.param2Value = 0
|
||||
c.destVarName = ""
|
||||
|
||||
params, err := utils.ParseParams(line.Text)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
paramCount := len(params)
|
||||
scope := ctx.CurrentScope()
|
||||
|
||||
// Create constant lookup function
|
||||
constLookup := func(name string) (int64, bool) {
|
||||
sym := ctx.SymbolTable.Lookup(name, scope)
|
||||
if sym != nil && sym.IsConst() {
|
||||
return int64(sym.Value), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Determine syntax and parse accordingly
|
||||
if strings.ToUpper(params[0]) == "XOR" {
|
||||
// Old syntax: XOR <param1> WITH <param2> GIVING/-> <dest>
|
||||
if paramCount != 6 {
|
||||
return fmt.Errorf("XOR: wrong number of parameters (%d), expected 6", paramCount)
|
||||
}
|
||||
|
||||
separator1 := strings.ToUpper(params[2])
|
||||
if separator1 != "WITH" {
|
||||
return fmt.Errorf("XOR: parameter #3 must be 'WITH', got %q", params[2])
|
||||
}
|
||||
|
||||
separator2 := strings.ToUpper(params[4])
|
||||
if separator2 != "GIVING" && separator2 != "->" {
|
||||
return fmt.Errorf("XOR: parameter #5 must be 'GIVING' or '->', got %q", params[4])
|
||||
}
|
||||
|
||||
// Parse destination
|
||||
destName := params[5]
|
||||
destSym := ctx.SymbolTable.Lookup(destName, scope)
|
||||
if destSym == nil {
|
||||
return fmt.Errorf("XOR: unknown variable %q", destName)
|
||||
}
|
||||
if destSym.IsConst() {
|
||||
return fmt.Errorf("XOR: cannot assign to constant %q", destName)
|
||||
}
|
||||
c.destVarName = destSym.FullName()
|
||||
c.destVarKind = destSym.GetVarKind()
|
||||
|
||||
// Parse param1
|
||||
var err error
|
||||
c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
|
||||
params[1], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("XOR: param1: %w", err)
|
||||
}
|
||||
|
||||
// Parse param2
|
||||
c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
|
||||
params[3], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("XOR: param2: %w", err)
|
||||
}
|
||||
|
||||
} else {
|
||||
// New syntax: <dest> = <param1> ^ <param2>
|
||||
if paramCount != 5 {
|
||||
return fmt.Errorf("XOR: wrong number of parameters (%d), expected 5", paramCount)
|
||||
}
|
||||
|
||||
if params[1] != "=" {
|
||||
return fmt.Errorf("XOR: expected '=' at position 2, got %q", params[1])
|
||||
}
|
||||
|
||||
if params[3] != "^" {
|
||||
return fmt.Errorf("XOR: expected '^' at position 4, got %q", params[3])
|
||||
}
|
||||
|
||||
// Parse destination
|
||||
destName := params[0]
|
||||
destSym := ctx.SymbolTable.Lookup(destName, scope)
|
||||
if destSym == nil {
|
||||
return fmt.Errorf("XOR: unknown variable %q", destName)
|
||||
}
|
||||
if destSym.IsConst() {
|
||||
return fmt.Errorf("XOR: cannot assign to constant %q", destName)
|
||||
}
|
||||
c.destVarName = destSym.FullName()
|
||||
c.destVarKind = destSym.GetVarKind()
|
||||
|
||||
// Parse param1
|
||||
var err error
|
||||
c.param1VarName, c.param1VarKind, c.param1Value, c.param1IsVar, err = compiler.ParseOperandParam(
|
||||
params[2], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("XOR: param1: %w", err)
|
||||
}
|
||||
|
||||
// Parse param2
|
||||
c.param2VarName, c.param2VarKind, c.param2Value, c.param2IsVar, err = compiler.ParseOperandParam(
|
||||
params[4], ctx.SymbolTable, scope, constLookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("XOR: param2: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *XorCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
|
||||
var asm []string
|
||||
|
||||
// If both params are literals, fold at compile time
|
||||
if !c.param1IsVar && !c.param2IsVar {
|
||||
result := c.param1Value ^ c.param2Value
|
||||
lo := uint8(result & 0xFF)
|
||||
hi := uint8((result >> 8) & 0xFF)
|
||||
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", lo))
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||
|
||||
if c.destVarKind == compiler.KindWord {
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||
}
|
||||
|
||||
return asm, nil
|
||||
}
|
||||
|
||||
// At least one param is a variable - generate XOR code
|
||||
// Load param1
|
||||
if c.param1IsVar {
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s", c.param1VarName))
|
||||
} else {
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", uint8(c.param1Value&0xFF)))
|
||||
}
|
||||
|
||||
// XOR with param2
|
||||
if c.param2IsVar {
|
||||
asm = append(asm, fmt.Sprintf("\teor %s", c.param2VarName))
|
||||
} else {
|
||||
asm = append(asm, fmt.Sprintf("\teor #$%02x", uint8(c.param2Value&0xFF)))
|
||||
}
|
||||
|
||||
// Store low byte
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s", c.destVarName))
|
||||
|
||||
// If destination is word, handle high byte
|
||||
if c.destVarKind == compiler.KindWord {
|
||||
// Load high byte of param1
|
||||
if c.param1IsVar {
|
||||
if c.param1VarKind == compiler.KindWord {
|
||||
asm = append(asm, fmt.Sprintf("\tlda %s+1", c.param1VarName))
|
||||
} else {
|
||||
asm = append(asm, "\tlda #0")
|
||||
}
|
||||
} else {
|
||||
hi := uint8((c.param1Value >> 8) & 0xFF)
|
||||
asm = append(asm, fmt.Sprintf("\tlda #$%02x", hi))
|
||||
}
|
||||
|
||||
// XOR with high byte of param2
|
||||
if c.param2IsVar {
|
||||
if c.param2VarKind == compiler.KindWord {
|
||||
asm = append(asm, fmt.Sprintf("\teor %s+1", c.param2VarName))
|
||||
} else {
|
||||
asm = append(asm, "\teor #0")
|
||||
}
|
||||
} else {
|
||||
hi := uint8((c.param2Value >> 8) & 0xFF)
|
||||
asm = append(asm, fmt.Sprintf("\teor #$%02x", hi))
|
||||
}
|
||||
|
||||
// Store high byte
|
||||
asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName))
|
||||
}
|
||||
|
||||
return asm, nil
|
||||
}
|
||||
426
internal/commands/xor_test.go
Normal file
426
internal/commands/xor_test.go
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"c65gm/internal/compiler"
|
||||
"c65gm/internal/preproc"
|
||||
)
|
||||
|
||||
func TestXorCommand_WillHandle(t *testing.T) {
|
||||
cmd := &XorCommand{}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
want bool
|
||||
}{
|
||||
{"old syntax WITH/GIVING", "XOR a WITH b GIVING c", true},
|
||||
{"old syntax WITH/arrow", "XOR a WITH b -> c", true},
|
||||
{"new syntax", "result = x ^ y", true},
|
||||
{"not XOR", "ADD a TO b GIVING c", false},
|
||||
{"wrong param count", "XOR a b c", false},
|
||||
{"empty", "", false},
|
||||
{"new syntax wrong op", "result = x + y", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
||||
got := cmd.WillHandle(line)
|
||||
if got != tt.want {
|
||||
t.Errorf("WillHandle() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestXorCommand_OldSyntax(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
setupVars func(*compiler.SymbolTable)
|
||||
wantAsm []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "byte XOR byte -> byte (variables)",
|
||||
line: "XOR a WITH b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("b", "", compiler.KindByte, 0xAA)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\teor b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "byte XOR byte -> word",
|
||||
line: "XOR a WITH b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("b", "", compiler.KindByte, 0xAA)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\teor b",
|
||||
"\tsta result",
|
||||
"\tlda #0",
|
||||
"\teor #0",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word XOR word -> word",
|
||||
line: "XOR x WITH y GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("x", "", compiler.KindWord, 0x1234)
|
||||
st.AddVar("y", "", compiler.KindWord, 0x5678)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda x",
|
||||
"\teor y",
|
||||
"\tsta result",
|
||||
"\tlda x+1",
|
||||
"\teor y+1",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "byte XOR literal -> byte",
|
||||
line: "XOR a WITH $AA GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\teor #$aa",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "literal XOR byte -> byte",
|
||||
line: "XOR 255 WITH b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("b", "", compiler.KindByte, 0xAA)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$ff",
|
||||
"\teor b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "constant folding: 255 XOR 170 -> byte",
|
||||
line: "XOR 255 WITH 170 GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$55",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "constant folding: $FFFF XOR $AAAA -> word",
|
||||
line: "XOR $FFFF WITH $AAAA GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$55",
|
||||
"\tsta result",
|
||||
"\tlda #$55",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "arrow syntax",
|
||||
line: "XOR a WITH b -> result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\teor b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word XOR byte -> byte",
|
||||
line: "XOR wval WITH bval GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
||||
st.AddVar("bval", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda wval",
|
||||
"\teor bval",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word XOR byte -> word",
|
||||
line: "XOR wval WITH bval GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("wval", "", compiler.KindWord, 0x1234)
|
||||
st.AddVar("bval", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda wval",
|
||||
"\teor bval",
|
||||
"\tsta result",
|
||||
"\tlda wval+1",
|
||||
"\teor #0",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error: unknown destination variable",
|
||||
line: "XOR a WITH b GIVING unknown",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error: wrong separator",
|
||||
line: "XOR a TO b GIVING result",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error: cannot assign to constant",
|
||||
line: "XOR a WITH b GIVING MAXVAL",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
st.AddConst("MAXVAL", "", compiler.KindByte, 255)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := compiler.NewCompilerContext(&preproc.Pragma{})
|
||||
tt.setupVars(ctx.SymbolTable)
|
||||
|
||||
cmd := &XorCommand{}
|
||||
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if !equalAsmXor(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestXorCommand_NewSyntax(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
setupVars func(*compiler.SymbolTable)
|
||||
wantAsm []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "byte ^ byte -> byte",
|
||||
line: "result = a ^ b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("b", "", compiler.KindByte, 0xAA)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\teor b",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "byte ^ byte -> word",
|
||||
line: "result = a ^ b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("b", "", compiler.KindByte, 0xAA)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\teor b",
|
||||
"\tsta result",
|
||||
"\tlda #0",
|
||||
"\teor #0",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "word ^ word -> word",
|
||||
line: "result = x ^ y",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("x", "", compiler.KindWord, 0x1234)
|
||||
st.AddVar("y", "", compiler.KindWord, 0x5678)
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda x",
|
||||
"\teor y",
|
||||
"\tsta result",
|
||||
"\tlda x+1",
|
||||
"\teor y+1",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "variable ^ literal",
|
||||
line: "result = a ^ $AA",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\teor #$aa",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "constant folding",
|
||||
line: "result = 255 ^ 170",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$55",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "constant folding word",
|
||||
line: "result = $FFFF ^ $AAAA",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("result", "", compiler.KindWord, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda #$55",
|
||||
"\tsta result",
|
||||
"\tlda #$55",
|
||||
"\tsta result+1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "using constant in expression",
|
||||
line: "result = a ^ MASK",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0xFF)
|
||||
st.AddConst("MASK", "", compiler.KindByte, 0xAA)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantAsm: []string{
|
||||
"\tlda a",
|
||||
"\teor #$aa",
|
||||
"\tsta result",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error: unknown destination",
|
||||
line: "unknown = a ^ b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "error: wrong operator",
|
||||
line: "result = a + b",
|
||||
setupVars: func(st *compiler.SymbolTable) {
|
||||
st.AddVar("a", "", compiler.KindByte, 0)
|
||||
st.AddVar("b", "", compiler.KindByte, 0)
|
||||
st.AddVar("result", "", compiler.KindByte, 0)
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := compiler.NewCompilerContext(&preproc.Pragma{})
|
||||
tt.setupVars(ctx.SymbolTable)
|
||||
|
||||
cmd := &XorCommand{}
|
||||
line := preproc.Line{Text: tt.line, Kind: preproc.Source}
|
||||
|
||||
err := cmd.Interpret(line, ctx)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Error("expected error, got nil")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("Interpret() error = %v", err)
|
||||
}
|
||||
|
||||
asm, err := cmd.Generate(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Generate() error = %v", err)
|
||||
}
|
||||
|
||||
if !equalAsmXor(asm, tt.wantAsm) {
|
||||
t.Errorf("Generate() mismatch\ngot:\n%s\nwant:\n%s",
|
||||
strings.Join(asm, "\n"),
|
||||
strings.Join(tt.wantAsm, "\n"))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// equalAsmXor compares two assembly slices for equality
|
||||
func equalAsmXor(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := range a {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package compiler
|
||||
|
||||
import (
|
||||
"c65gm/internal/utils"
|
||||
"fmt"
|
||||
|
||||
"c65gm/internal/preproc"
|
||||
|
|
@ -57,3 +58,47 @@ type UnhandledLineError struct {
|
|||
func (e *UnhandledLineError) Error() string {
|
||||
return fmt.Sprintf("%s:%d: unhandled line: %s", e.Line.Filename, e.Line.LineNo, e.Line.Text)
|
||||
}
|
||||
|
||||
// ParseOperandParam parses a command parameter that can be a variable or expression
|
||||
// Returns: varName, varKind, value, isVar, error
|
||||
func ParseOperandParam(
|
||||
param string,
|
||||
symTable *SymbolTable,
|
||||
scope []string,
|
||||
constLookup utils.ConstantLookup,
|
||||
) (varName string, varKind VarKind, value uint16, isVar bool, err error) {
|
||||
// Try variable lookup first
|
||||
sym := symTable.Lookup(param, scope)
|
||||
if sym != nil {
|
||||
varKind = sym.GetVarKind()
|
||||
value = sym.Value
|
||||
|
||||
// Constants are treated as literals (inlined), not variables
|
||||
if sym.IsConst() {
|
||||
varName = sym.FullName() // Preserve name for documentation
|
||||
isVar = false
|
||||
return
|
||||
}
|
||||
|
||||
// It's a variable
|
||||
varName = sym.FullName()
|
||||
isVar = true
|
||||
return
|
||||
}
|
||||
|
||||
// Not a variable, must be an expression
|
||||
val, evalErr := utils.EvaluateExpression(param, constLookup)
|
||||
if evalErr != nil {
|
||||
err = fmt.Errorf("not a valid variable or expression: %w", evalErr)
|
||||
return
|
||||
}
|
||||
|
||||
if val < 0 || val > 65535 {
|
||||
err = fmt.Errorf("value %d out of range (0-65535)", val)
|
||||
return
|
||||
}
|
||||
|
||||
value = uint16(val)
|
||||
isVar = false
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -384,3 +384,11 @@ func GenerateVariables(st *SymbolTable) []string {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVarKind extracts VarKind from Symbol
|
||||
func (s *Symbol) GetVarKind() VarKind {
|
||||
if s.IsByte() {
|
||||
return KindByte
|
||||
}
|
||||
return KindWord
|
||||
}
|
||||
|
|
|
|||
4
main.go
4
main.go
|
|
@ -79,6 +79,10 @@ func registerCommands(comp *compiler.Compiler) {
|
|||
comp.Registry().Register(&commands.ByteCommand{})
|
||||
comp.Registry().Register(&commands.WordCommand{})
|
||||
comp.Registry().Register(&commands.AddCommand{})
|
||||
comp.Registry().Register(&commands.AndCommand{})
|
||||
comp.Registry().Register(&commands.OrCommand{})
|
||||
comp.Registry().Register(&commands.XorCommand{})
|
||||
comp.Registry().Register(&commands.SubtractCommand{})
|
||||
}
|
||||
|
||||
func writeOutput(filename string, lines []string) error {
|
||||
|
|
|
|||
Loading…
Reference in a new issue