c65gm/internal/commands/byte.go

182 lines
4.8 KiB
Go

package commands
import (
"fmt"
"strings"
"c65gm/internal/compiler"
"c65gm/internal/preproc"
"c65gm/internal/utils"
)
// ByteCommand handles BYTE variable declarations
// Syntax:
//
// BYTE varname # byte with init = 0
// BYTE varname = value # byte with init value
// BYTE varname @ address # byte at absolute address
// BYTE CONST varname = value # constant byte
// BYTE REGISTER varname # register-hinted byte (function-local only)
// BYTE REGISTER varname = value # register-hinted byte with init value
type ByteCommand struct {
varName string
value uint16
isConst bool
isAbs bool
}
func (c *ByteCommand) WillHandle(line preproc.Line) bool {
params, err := utils.ParseParams(line.Text)
if err != nil || len(params) == 0 {
return false
}
return strings.ToUpper(params[0]) == "BYTE"
}
func (c *ByteCommand) Interpret(line preproc.Line, ctx *compiler.CompilerContext) error {
c.varName = ""
c.value = 0
c.isConst = false
c.isAbs = false
params, err := utils.ParseParams(line.Text)
if err != nil {
return err
}
paramCount := len(params)
// Check for REGISTER keyword
register := false
if paramCount >= 2 && strings.ToUpper(params[1]) == "REGISTER" {
register = true
params = append(params[:1], params[2:]...)
paramCount = len(params)
}
if register {
if paramCount != 2 && paramCount != 4 {
return fmt.Errorf("BYTE REGISTER: expected 'name' or 'name = value', got %d parameters after REGISTER", paramCount)
}
} else {
if paramCount != 2 && paramCount != 4 && paramCount != 5 {
return fmt.Errorf("BYTE: wrong number of parameters (%d)", paramCount)
}
}
var varName string
var value int64
scope := ctx.FunctionHandler.CurrentFunction()
constLookup := ctx.SymbolTable.ConstantLookupFunc(ctx.CurrentScope())
switch paramCount {
case 2:
varName = params[1]
value = 0
if !utils.ValidateIdentifier(varName) {
return fmt.Errorf("BYTE: invalid identifier %q", varName)
}
if register {
if scope == "" {
return fmt.Errorf("BYTE REGISTER %q is only valid inside a FUNC block (remove REGISTER or move inside a function)", varName)
}
err = ctx.SymbolTable.AddRegisterVar(varName, scope, uint16(value), line)
} else {
err = ctx.SymbolTable.AddVar(varName, scope, compiler.KindByte, uint16(value), line)
}
case 4:
varName = params[1]
operator := params[2]
valueStr := params[3]
if !utils.ValidateIdentifier(varName) {
return fmt.Errorf("BYTE: invalid identifier %q", varName)
}
value, err = utils.EvaluateExpression(valueStr, constLookup)
if err != nil {
return fmt.Errorf("BYTE: invalid value %q: %w", valueStr, err)
}
if operator == "=" {
if value < 0 || value > 255 {
return fmt.Errorf("BYTE: init value %d out of range (0-255)", value)
}
if register {
if scope == "" {
return fmt.Errorf("BYTE REGISTER: variable %q must be declared in function scope", varName)
}
err = ctx.SymbolTable.AddRegisterVar(varName, scope, uint16(value), line)
} else {
err = ctx.SymbolTable.AddVar(varName, scope, compiler.KindByte, uint16(value), line)
}
} else if operator == "@" {
if register {
return fmt.Errorf("BYTE REGISTER: @-mapped address is not valid for register variables")
}
if value < 0 || value > 0xFFFF {
return fmt.Errorf("BYTE: absolute address $%X out of range", value)
}
c.isAbs = true
err = ctx.SymbolTable.AddAbsolute(varName, scope, compiler.KindByte, uint16(value), line)
} else {
return fmt.Errorf("BYTE: expected '=' or '@', got %q", operator)
}
case 5:
if register {
return fmt.Errorf("BYTE REGISTER: unexpected additional parameters")
}
constKeyword := strings.ToUpper(params[1])
varName = params[2]
operator := params[3]
valueStr := params[4]
if constKeyword != "CONST" {
return fmt.Errorf("BYTE: expected CONST keyword, got %q", params[1])
}
if operator != "=" {
return fmt.Errorf("BYTE: expected '=', got %q", operator)
}
if !utils.ValidateIdentifier(varName) {
return fmt.Errorf("BYTE: invalid identifier %q", varName)
}
value, err = utils.EvaluateExpression(valueStr, constLookup)
if err != nil {
return fmt.Errorf("BYTE: invalid value %q: %w", valueStr, err)
}
if value < 0 || value > 255 {
return fmt.Errorf("BYTE: const value %d out of range (0-255)", value)
}
c.isConst = true
err = ctx.SymbolTable.AddConst(varName, scope, compiler.KindByte, uint16(value), line)
}
if err != nil {
return fmt.Errorf("BYTE: %w", err)
}
c.varName = varName
c.value = uint16(value)
return nil
}
func (c *ByteCommand) Generate(_ *compiler.CompilerContext) ([]string, error) {
// Variables are rendered by assembleOutput, not by individual commands
return nil, nil
}
func (c *ByteCommand) GetClass() compiler.CmdClass { return compiler.CmdLinear }
func (c *ByteCommand) GetName() string { return "BYTE" }