package commands import ( "fmt" "strings" "c65gm/internal/compiler" "c65gm/internal/preproc" "c65gm/internal/utils" ) // AddCommand handles ADD operations // Syntax: // // ADD TO GIVING # old syntax with TO/GIVING // ADD + -> # old syntax with +/-> // = + # new syntax type AddCommand 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 *AddCommand) WillHandle(line preproc.Line) bool { params, err := utils.ParseParams(line.Text) if err != nil || len(params) == 0 { return false } // Old syntax: ADD ... (must have exactly 6 params) if strings.ToUpper(params[0]) == "ADD" && len(params) == 6 { return true } // New syntax: = + if len(params) == 5 && params[1] == "=" && params[3] == "+" { return true } return false } func (c *AddCommand) 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]) == "ADD" { // Old syntax: ADD TO/+ GIVING/-> if paramCount != 6 { return fmt.Errorf("ADD: wrong number of parameters (%d), expected 6", paramCount) } separator1 := strings.ToUpper(params[2]) if separator1 != "TO" && separator1 != "+" { return fmt.Errorf("ADD: parameter #3 must be 'TO' or '+', got %q", params[2]) } separator2 := strings.ToUpper(params[4]) if separator2 != "GIVING" && separator2 != "->" { return fmt.Errorf("ADD: 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("ADD: unknown variable %q", destName) } if destSym.IsConst() { return fmt.Errorf("ADD: cannot assign to constant %q", destName) } c.destVarName = destSym.FullName() c.destVarKind = getVarKind(destSym) // Parse param1 if err := c.parseParam(params[1], &c.param1VarName, &c.param1VarKind, &c.param1Value, &c.param1IsVar, ctx, scope, constLookup); 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 { return fmt.Errorf("ADD: param2: %w", err) } } else { // New syntax: = + if paramCount != 5 { return fmt.Errorf("ADD: wrong number of parameters (%d), expected 5", paramCount) } if params[1] != "=" { return fmt.Errorf("ADD: expected '=' at position 2, got %q", params[1]) } if params[3] != "+" { return fmt.Errorf("ADD: 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("ADD: unknown variable %q", destName) } if destSym.IsConst() { return fmt.Errorf("ADD: cannot assign to constant %q", destName) } c.destVarName = destSym.FullName() c.destVarKind = getVarKind(destSym) // Parse param1 if err := c.parseParam(params[2], &c.param1VarName, &c.param1VarKind, &c.param1Value, &c.param1IsVar, ctx, scope, constLookup); 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 { return fmt.Errorf("ADD: param2: %w", err) } } 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 // If both params are literals, fold at compile time if !c.param1IsVar && !c.param2IsVar { sum := uint32(c.param1Value) + uint32(c.param2Value) lo := uint8(sum & 0xFF) hi := uint8((sum >> 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 add code asm = append(asm, "\tclc") // 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))) } // Add param2 if c.param2IsVar { asm = append(asm, fmt.Sprintf("\tadc %s", c.param2VarName)) } else { asm = append(asm, fmt.Sprintf("\tadc #$%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)) } // Add high byte of param2 if c.param2IsVar { if c.param2VarKind == compiler.KindWord { asm = append(asm, fmt.Sprintf("\tadc %s+1", c.param2VarName)) } else { asm = append(asm, "\tadc #0") } } else { hi := uint8((c.param2Value >> 8) & 0xFF) asm = append(asm, fmt.Sprintf("\tadc #$%02x", hi)) } // Store high byte asm = append(asm, fmt.Sprintf("\tsta %s+1", c.destVarName)) } return asm, nil } // getVarKind extracts VarKind from Symbol func getVarKind(sym *compiler.Symbol) compiler.VarKind { if sym.IsByte() { return compiler.KindByte } return compiler.KindWord }