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 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 { // Clear state 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) // Validate parameter count 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() // Create constant lookup function constLookup := func(name string) (int64, bool) { sym := ctx.SymbolTable.Lookup(name, ctx.CurrentScope()) if sym != nil && sym.IsConst() { return int64(sym.Value), true } return 0, false } switch paramCount { case 2: // BYTE varname varName = params[1] value = 0 if !utils.ValidateIdentifier(varName) { return fmt.Errorf("BYTE: invalid identifier %q", varName) } err = ctx.SymbolTable.AddVar(varName, scope, compiler.KindByte, uint16(value)) case 4: // BYTE varname = value OR BYTE varname @ address 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 == "=" { // BYTE varname = value if value < 0 || value > 255 { return fmt.Errorf("BYTE: init value %d out of range (0-255)", value) } err = ctx.SymbolTable.AddVar(varName, scope, compiler.KindByte, uint16(value)) } else if operator == "@" { // BYTE varname @ address 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)) } else { return fmt.Errorf("BYTE: expected '=' or '@', got %q", operator) } case 5: // BYTE CONST varname = value 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)) } 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 }