Skip to main content

asm_rs/
parser.rs

1//! Multi-architecture assembly parser.
2//!
3//! Converts a stream of `Token`s from the lexer into a `Statement` list.
4//! Handles instructions, labels, directives, memory operands, size hints, and prefixes.
5//! Architecture-aware register parsing resolves naming conflicts (e.g. `r8` is
6//! x86-64 R8 vs ARM R8) based on the target `Arch`.
7
8use alloc::boxed::Box;
9use alloc::collections::BTreeMap;
10use alloc::string::String;
11use alloc::string::ToString;
12use alloc::vec;
13use alloc::vec::Vec;
14
15use crate::error::{AsmError, Span};
16use crate::ir::*;
17use crate::lexer::{Token, TokenKind};
18
19/// Zero-allocation ASCII-lowercase into a caller-provided stack buffer.
20/// Returns `&str` of the lowered text. Inputs longer than `buf` are truncated.
21#[inline]
22fn to_lower_buf<'b>(s: &str, buf: &'b mut [u8]) -> &'b str {
23    let len = s.len().min(buf.len());
24    buf[..len].copy_from_slice(&s.as_bytes()[..len]);
25    buf[..len].make_ascii_lowercase();
26    // Input was valid UTF-8 and ASCII lowercase preserves validity,
27    // so from_utf8 is infallible here.
28    core::str::from_utf8(&buf[..len]).unwrap_or("")
29}
30
31/// Parse a token stream into a list of IR statements.
32///
33/// # Errors
34///
35/// Returns `Err(AsmError)` if the token stream contains an unexpected token,
36/// a malformed directive, or an invalid instruction syntax.
37pub fn parse(tokens: &[Token<'_>]) -> Result<Vec<Statement>, AsmError> {
38    parse_with_arch(tokens, Arch::X86_64)
39}
40
41/// Parse with explicit architecture for register-name disambiguation.
42pub fn parse_with_arch(tokens: &[Token<'_>], arch: Arch) -> Result<Vec<Statement>, AsmError> {
43    parse_with_syntax(tokens, arch, Syntax::Intel)
44}
45
46/// Parse with explicit architecture and syntax dialect.
47pub fn parse_with_syntax(
48    tokens: &[Token<'_>],
49    arch: Arch,
50    syntax: Syntax,
51) -> Result<Vec<Statement>, AsmError> {
52    let mut parser = Parser::new(tokens, arch, syntax);
53    parser.parse_program()
54}
55
56/// Parse statements one at a time, handing each to `sink` as it is produced.
57///
58/// This is the streaming counterpart to [`parse_with_syntax`], and the form
59/// the assembler uses. `Statement` is a large value — the operand list is
60/// stored inline to keep encoding allocation-free — so collecting a whole
61/// program into a `Vec` costs hundreds of bytes per instruction and makes peak
62/// memory proportional to the *source* rather than to the output. Consuming
63/// each statement immediately keeps that at one statement.
64///
65/// `sink` may return an error to abort parsing early.
66///
67/// # Errors
68///
69/// Returns [`AsmError`] on a syntax error, or whatever `sink` returns.
70pub fn parse_streaming<F>(
71    tokens: &[Token<'_>],
72    arch: Arch,
73    syntax: Syntax,
74    mut sink: F,
75) -> Result<(), AsmError>
76where
77    F: FnMut(Statement) -> Result<(), AsmError>,
78{
79    let mut parser = Parser::new(tokens, arch, syntax);
80    parser.skip_newlines();
81    while !parser.at_end() {
82        if let Some(stmt) = parser.parse_statement()? {
83            sink(stmt)?;
84        }
85        parser.skip_newlines();
86    }
87    Ok(())
88}
89
90struct Parser<'a> {
91    tokens: &'a [Token<'a>],
92    pos: usize,
93    /// Target architecture — controls register name resolution.
94    arch: Arch,
95    /// Syntax dialect — Intel (default) or AT&T/GAS.
96    syntax: Syntax,
97    /// Constants defined so far (via `.equ` or `NAME = expr`) — available to
98    /// `parse_const_expr` so that directive arguments can reference previously
99    /// defined constants.
100    constants: BTreeMap<String, i128>,
101}
102
103impl<'a> Parser<'a> {
104    fn new(tokens: &'a [Token<'a>], arch: Arch, syntax: Syntax) -> Self {
105        Self {
106            tokens,
107            pos: 0,
108            arch,
109            syntax,
110            constants: BTreeMap::new(),
111        }
112    }
113
114    #[inline]
115    fn peek(&self) -> &Token<'a> {
116        &self.tokens[self.pos.min(self.tokens.len() - 1)]
117    }
118
119    #[inline]
120    fn advance(&mut self) -> &Token<'a> {
121        let tok = &self.tokens[self.pos.min(self.tokens.len() - 1)];
122        if self.pos < self.tokens.len() {
123            self.pos += 1;
124        }
125        tok
126    }
127
128    #[inline]
129    fn at_end(&self) -> bool {
130        self.pos >= self.tokens.len() || self.peek().kind == TokenKind::Eof
131    }
132
133    fn expect_ident(&mut self) -> Result<(String, Span), AsmError> {
134        let tok = self.advance();
135        if tok.kind == TokenKind::Ident {
136            Ok((tok.text.to_string(), tok.span))
137        } else {
138            Err(AsmError::Syntax {
139                msg: alloc::format!("expected identifier, found '{}'", tok.text),
140                span: tok.span,
141            })
142        }
143    }
144
145    #[inline]
146    fn skip_newlines(&mut self) {
147        while !self.at_end() && self.peek().kind == TokenKind::Newline {
148            self.advance();
149        }
150    }
151
152    fn parse_program(&mut self) -> Result<Vec<Statement>, AsmError> {
153        // Heuristic: ~3 tokens per statement on average.
154        let mut stmts = Vec::with_capacity(self.tokens.len() / 3 + 1);
155        self.skip_newlines();
156        while !self.at_end() {
157            if let Some(stmt) = self.parse_statement()? {
158                stmts.push(stmt);
159            }
160            self.skip_newlines();
161        }
162        Ok(stmts)
163    }
164
165    fn parse_statement(&mut self) -> Result<Option<Statement>, AsmError> {
166        let tok = self.peek().clone();
167
168        match &tok.kind {
169            TokenKind::Eof => Ok(None),
170            TokenKind::Newline => {
171                self.advance();
172                Ok(None)
173            }
174
175            // Label definition
176            TokenKind::LabelDef => {
177                self.advance();
178                Ok(Some(Statement::Label(tok.text.to_string(), tok.span)))
179            }
180
181            // Numeric label definition
182            TokenKind::NumericLabelDef(n) => {
183                self.advance();
184                let name = alloc::format!("{}", n);
185                Ok(Some(Statement::Label(name, tok.span)))
186            }
187
188            // Directive
189            TokenKind::Directive => self.parse_directive(),
190
191            // Instruction or prefix
192            TokenKind::Ident => self.parse_instruction_or_prefix(),
193
194            _ => Err(AsmError::Syntax {
195                msg: alloc::format!("unexpected token '{}'", tok.text),
196                span: tok.span,
197            }),
198        }
199    }
200
201    fn parse_directive(&mut self) -> Result<Option<Statement>, AsmError> {
202        let tok = self.advance().clone();
203        let mut dir_buf = [0u8; 32];
204        let dir = to_lower_buf(&tok.text, &mut dir_buf);
205        let span = tok.span;
206
207        match dir {
208            // Data directives
209            ".byte" | ".db" => self.parse_data_directive(DataSize::Byte, span),
210            ".word" | ".dw" | ".short" => self.parse_data_directive(DataSize::Word, span),
211            ".long" | ".dd" | ".int" => self.parse_data_directive(DataSize::Long, span),
212            ".quad" | ".dq" => self.parse_data_directive(DataSize::Quad, span),
213
214            // String directives
215            ".ascii" => self.parse_string_directive(false, span),
216            ".asciz" | ".string" => self.parse_string_directive(true, span),
217
218            // Constant
219            ".equ" | ".set" => self.parse_equ_directive(span),
220
221            // Alignment
222            ".align" | ".balign" | ".p2align" => {
223                let is_p2 = dir == ".p2align";
224                self.parse_align_directive(is_p2, span)
225            }
226
227            // Fill
228            ".fill" => self.parse_fill_directive(span),
229
230            // Space/skip
231            ".space" | ".skip" => self.parse_space_directive(span),
232
233            // Org
234            ".org" => self.parse_org_directive(span),
235
236            // Global/extern (accepted but ignored for pure code generation)
237            ".global" | ".globl" | ".extern" => {
238                // Consume the symbol name
239                if !self.at_end() && self.peek().kind == TokenKind::Ident {
240                    self.advance();
241                }
242                Ok(None)
243            }
244
245            // Section directives (accepted but ignored)
246            ".text" | ".data" | ".bss" | ".rodata" | ".section" => {
247                // Skip to end of line
248                while !self.at_end()
249                    && self.peek().kind != TokenKind::Newline
250                    && self.peek().kind != TokenKind::Eof
251                {
252                    self.advance();
253                }
254                Ok(None)
255            }
256
257            // Code mode switching
258            ".code16" => Ok(Some(Statement::CodeMode(crate::ir::X86Mode::Mode16, span))),
259            ".code32" => Ok(Some(Statement::CodeMode(crate::ir::X86Mode::Mode32, span))),
260            ".code64" => Ok(Some(Statement::CodeMode(crate::ir::X86Mode::Mode64, span))),
261
262            // Literal pool flush
263            ".ltorg" | ".pool" => Ok(Some(Statement::Ltorg(span))),
264
265            // ARM/Thumb mode switching
266            ".thumb" => Ok(Some(Statement::ThumbMode(true, span))),
267            ".arm" => Ok(Some(Statement::ThumbMode(false, span))),
268            ".thumb_func" => Ok(Some(Statement::ThumbFunc(span))),
269
270            // Syntax switching: .syntax att / .syntax intel
271            ".syntax" => {
272                let next = self.peek().clone();
273                if next.kind == TokenKind::Ident {
274                    self.advance();
275                    if next.text.eq_ignore_ascii_case("att") {
276                        self.syntax = Syntax::Att;
277                        Ok(None)
278                    } else if next.text.eq_ignore_ascii_case("intel") {
279                        self.syntax = Syntax::Intel;
280                        Ok(None)
281                    } else {
282                        Err(AsmError::Syntax {
283                            msg: alloc::format!(
284                                "unknown syntax '{}' (expected 'att' or 'intel')",
285                                next.text
286                            ),
287                            span: next.span,
288                        })
289                    }
290                } else {
291                    Err(AsmError::Syntax {
292                        msg: String::from("expected 'att' or 'intel' after .syntax"),
293                        span: next.span,
294                    })
295                }
296            }
297
298            // RISC-V options: .option rvc / .option norvc
299            ".option" => {
300                let next = self.peek().clone();
301                if next.kind == TokenKind::Ident {
302                    self.advance();
303                    if next.text.eq_ignore_ascii_case("rvc") {
304                        Ok(Some(Statement::OptionRvc(true, span)))
305                    } else if next.text.eq_ignore_ascii_case("norvc") {
306                        Ok(Some(Statement::OptionRvc(false, span)))
307                    } else {
308                        Err(AsmError::Syntax {
309                            msg: alloc::format!(
310                                "unknown option '{}' (expected 'rvc' or 'norvc')",
311                                next.text
312                            ),
313                            span: next.span,
314                        })
315                    }
316                } else {
317                    Err(AsmError::Syntax {
318                        msg: String::from("expected 'rvc' or 'norvc' after .option"),
319                        span: next.span,
320                    })
321                }
322            }
323
324            _ => Err(AsmError::Syntax {
325                msg: alloc::format!("unknown directive '{}'", dir),
326                span,
327            }),
328        }
329    }
330
331    fn parse_data_directive(
332        &mut self,
333        size: DataSize,
334        span: Span,
335    ) -> Result<Option<Statement>, AsmError> {
336        let mut values = Vec::new();
337        loop {
338            let val = self.parse_data_value()?;
339            values.push(val);
340            if self.peek().kind == TokenKind::Comma {
341                self.advance();
342            } else {
343                break;
344            }
345        }
346        Ok(Some(Statement::Data(DataDecl { size, values, span })))
347    }
348
349    fn parse_data_value(&mut self) -> Result<DataValue, AsmError> {
350        let tok = self.peek().clone();
351        match &tok.kind {
352            TokenKind::Number(n) => {
353                self.advance();
354                Ok(DataValue::Integer(*n))
355            }
356            TokenKind::CharLit(ch) => {
357                self.advance();
358                Ok(DataValue::Integer(*ch as i128))
359            }
360            TokenKind::Ident => {
361                self.advance();
362                let label = tok.text.to_string();
363                // Parse optional addend: label + N or label - N
364                let addend = if self.peek().kind == TokenKind::Plus {
365                    self.advance();
366                    let n = self.parse_const_expr()?;
367                    n as i64
368                } else if self.peek().kind == TokenKind::Minus {
369                    self.advance();
370                    let n = self.parse_const_expr()?;
371                    -(n as i64)
372                } else {
373                    0
374                };
375                Ok(DataValue::Label(label, addend))
376            }
377            TokenKind::Minus => {
378                self.advance();
379                let next = self.peek().clone();
380                if let TokenKind::Number(n) = next.kind {
381                    self.advance();
382                    Ok(DataValue::Integer(-n))
383                } else {
384                    Err(AsmError::Syntax {
385                        msg: String::from("expected number after '-'"),
386                        span: tok.span,
387                    })
388                }
389            }
390            _ => Err(AsmError::Syntax {
391                msg: alloc::format!("expected data value, found '{}'", tok.text),
392                span: tok.span,
393            }),
394        }
395    }
396
397    fn parse_string_directive(
398        &mut self,
399        null_terminate: bool,
400        span: Span,
401    ) -> Result<Option<Statement>, AsmError> {
402        let tok = self.advance().clone();
403        if tok.kind != TokenKind::StringLit {
404            return Err(AsmError::Syntax {
405                msg: String::from("expected string literal"),
406                span: tok.span,
407            });
408        }
409        let mut bytes: Vec<u8> = tok.text.as_bytes().to_vec();
410        if null_terminate {
411            bytes.push(0);
412        }
413        Ok(Some(Statement::Data(DataDecl {
414            size: DataSize::Byte,
415            values: vec![DataValue::Bytes(bytes)],
416            span,
417        })))
418    }
419
420    fn parse_equ_directive(&mut self, span: Span) -> Result<Option<Statement>, AsmError> {
421        let (name, _) = self.expect_ident()?;
422        // Expect comma
423        if self.peek().kind == TokenKind::Comma {
424            self.advance();
425        }
426        let value = self.parse_const_expr()?;
427        self.constants.insert(name.clone(), value);
428        Ok(Some(Statement::Const(ConstDef { name, value, span })))
429    }
430
431    fn parse_align_directive(
432        &mut self,
433        is_p2: bool,
434        span: Span,
435    ) -> Result<Option<Statement>, AsmError> {
436        let raw = self.parse_const_expr()? as u32;
437        let alignment = if is_p2 { 1u32 << raw } else { raw };
438
439        // Validate alignment is a power of two (0 and 1 are no-ops)
440        if alignment > 1 && !alignment.is_power_of_two() {
441            return Err(AsmError::Syntax {
442                msg: alloc::format!("alignment must be a power of 2, got {alignment}"),
443                span,
444            });
445        }
446
447        let fill = if self.peek().kind == TokenKind::Comma {
448            self.advance();
449            // Try to parse a constant expression for the fill byte
450            if matches!(
451                self.peek().kind,
452                TokenKind::Number(_) | TokenKind::Minus | TokenKind::Ident
453            ) {
454                Some(self.parse_const_expr()? as u8)
455            } else {
456                None
457            }
458        } else {
459            None
460        };
461
462        let max_skip = if self.peek().kind == TokenKind::Comma {
463            self.advance();
464            if matches!(
465                self.peek().kind,
466                TokenKind::Number(_) | TokenKind::Minus | TokenKind::Ident
467            ) {
468                Some(self.parse_const_expr()? as u32)
469            } else {
470                None
471            }
472        } else {
473            None
474        };
475
476        Ok(Some(Statement::Align(AlignDirective {
477            alignment,
478            fill,
479            max_skip,
480            span,
481        })))
482    }
483
484    fn parse_fill_directive(&mut self, span: Span) -> Result<Option<Statement>, AsmError> {
485        let count = self.parse_const_expr()? as u32;
486        let mut size = 1u8;
487        let mut value = 0i64;
488        if self.peek().kind == TokenKind::Comma {
489            self.advance();
490            size = self.parse_const_expr()? as u8;
491            if self.peek().kind == TokenKind::Comma {
492                self.advance();
493                value = self.parse_const_expr()? as i64;
494            }
495        }
496        Ok(Some(Statement::Fill(FillDirective {
497            count,
498            size,
499            value,
500            span,
501        })))
502    }
503
504    fn parse_space_directive(&mut self, span: Span) -> Result<Option<Statement>, AsmError> {
505        let size = self.parse_const_expr()? as u32;
506        let fill = if self.peek().kind == TokenKind::Comma {
507            self.advance();
508            self.parse_const_expr()? as u8
509        } else {
510            0
511        };
512        Ok(Some(Statement::Space(SpaceDirective { size, fill, span })))
513    }
514
515    fn parse_org_directive(&mut self, span: Span) -> Result<Option<Statement>, AsmError> {
516        let offset = self.parse_const_expr()? as u64;
517        // Optional fill byte: .org offset, fill
518        let fill = if self.peek().kind == TokenKind::Comma {
519            self.advance(); // skip comma
520            self.parse_const_expr()? as u8
521        } else {
522            0x00
523        };
524        Ok(Some(Statement::Org(OrgDirective { offset, fill, span })))
525    }
526
527    /// Parse a constant expression with full operator support.
528    ///
529    /// Supports (from lowest to highest precedence):
530    /// - `|` (bitwise OR)
531    /// - `^` (bitwise XOR)
532    /// - `&` (bitwise AND)
533    /// - `<<`, `>>` (shifts)
534    /// - `+`, `-` (additive)
535    /// - `*`, `/`, `%` (multiplicative)
536    /// - Unary: `-`, `~` (negate, complement)
537    /// - Atoms: numbers, identifiers (constants), parenthesized sub-expressions
538    fn parse_const_expr(&mut self) -> Result<i128, AsmError> {
539        self.const_expr_or()
540    }
541
542    // ── Precedence levels ──────────────────────────────────
543
544    /// Bitwise OR: `a | b`
545    fn const_expr_or(&mut self) -> Result<i128, AsmError> {
546        let mut val = self.const_expr_xor()?;
547        while self.peek().kind == TokenKind::Pipe {
548            self.advance();
549            val |= self.const_expr_xor()?;
550        }
551        Ok(val)
552    }
553
554    /// Bitwise XOR: `a ^ b`
555    fn const_expr_xor(&mut self) -> Result<i128, AsmError> {
556        let mut val = self.const_expr_and()?;
557        while self.peek().kind == TokenKind::Caret {
558            self.advance();
559            val ^= self.const_expr_and()?;
560        }
561        Ok(val)
562    }
563
564    /// Bitwise AND: `a & b`
565    fn const_expr_and(&mut self) -> Result<i128, AsmError> {
566        let mut val = self.const_expr_shift()?;
567        while self.peek().kind == TokenKind::Ampersand {
568            self.advance();
569            val &= self.const_expr_shift()?;
570        }
571        Ok(val)
572    }
573
574    /// Shifts: `a << b`, `a >> b`
575    fn const_expr_shift(&mut self) -> Result<i128, AsmError> {
576        let mut val = self.const_expr_add()?;
577        loop {
578            match self.peek().kind {
579                TokenKind::LShift => {
580                    self.advance();
581                    let rhs = self.const_expr_add()?;
582                    val = val.wrapping_shl(rhs as u32);
583                }
584                TokenKind::RShift => {
585                    self.advance();
586                    let rhs = self.const_expr_add()?;
587                    val = val.wrapping_shr(rhs as u32);
588                }
589                _ => break,
590            }
591        }
592        Ok(val)
593    }
594
595    /// Addition/subtraction: `a + b`, `a - b`
596    fn const_expr_add(&mut self) -> Result<i128, AsmError> {
597        let mut val = self.const_expr_mul()?;
598        loop {
599            match self.peek().kind {
600                TokenKind::Plus => {
601                    self.advance();
602                    val = val.wrapping_add(self.const_expr_mul()?);
603                }
604                TokenKind::Minus => {
605                    self.advance();
606                    val = val.wrapping_sub(self.const_expr_mul()?);
607                }
608                _ => break,
609            }
610        }
611        Ok(val)
612    }
613
614    /// Multiplication/division/modulo: `a * b`, `a / b`, `a % b`
615    fn const_expr_mul(&mut self) -> Result<i128, AsmError> {
616        let mut val = self.const_expr_unary()?;
617        loop {
618            match self.peek().kind {
619                TokenKind::Star => {
620                    self.advance();
621                    val = val.wrapping_mul(self.const_expr_unary()?);
622                }
623                TokenKind::Slash => {
624                    let span = self.peek().span;
625                    self.advance();
626                    let rhs = self.const_expr_unary()?;
627                    if rhs == 0 {
628                        return Err(AsmError::Syntax {
629                            msg: String::from("division by zero in constant expression"),
630                            span,
631                        });
632                    }
633                    val /= rhs;
634                }
635                TokenKind::Percent => {
636                    let span = self.peek().span;
637                    self.advance();
638                    let rhs = self.const_expr_unary()?;
639                    if rhs == 0 {
640                        return Err(AsmError::Syntax {
641                            msg: String::from("modulo by zero in constant expression"),
642                            span,
643                        });
644                    }
645                    val %= rhs;
646                }
647                _ => break,
648            }
649        }
650        Ok(val)
651    }
652
653    /// Unary operators: `-x`, `~x`
654    fn const_expr_unary(&mut self) -> Result<i128, AsmError> {
655        match self.peek().kind {
656            TokenKind::Minus => {
657                self.advance();
658                Ok(-self.const_expr_unary()?)
659            }
660            TokenKind::Tilde => {
661                self.advance();
662                Ok(!self.const_expr_unary()?)
663            }
664            _ => self.const_expr_atom(),
665        }
666    }
667
668    /// Atoms: numbers, identifiers (constant lookup), and `(expr)`.
669    fn const_expr_atom(&mut self) -> Result<i128, AsmError> {
670        let tok = self.peek().clone();
671        match &tok.kind {
672            TokenKind::Number(n) => {
673                self.advance();
674                Ok(*n)
675            }
676            TokenKind::Ident => {
677                if let Some(&val) = self.constants.get(&*tok.text) {
678                    self.advance();
679                    Ok(val)
680                } else {
681                    Err(AsmError::Syntax {
682                        msg: alloc::format!(
683                            "expected constant expression, found undefined identifier '{}'",
684                            tok.text
685                        ),
686                        span: tok.span,
687                    })
688                }
689            }
690            TokenKind::OpenParen => {
691                self.advance(); // skip '('
692                let val = self.parse_const_expr()?;
693                if self.peek().kind != TokenKind::CloseParen {
694                    return Err(AsmError::Syntax {
695                        msg: String::from("expected ')' in constant expression"),
696                        span: self.peek().span,
697                    });
698                }
699                self.advance(); // skip ')'
700                Ok(val)
701            }
702            _ => Err(AsmError::Syntax {
703                msg: alloc::format!("expected constant expression, found '{}'", tok.text),
704                span: tok.span,
705            }),
706        }
707    }
708
709    fn parse_instruction_or_prefix(&mut self) -> Result<Option<Statement>, AsmError> {
710        let tok = self.peek().clone();
711
712        // Check for `name = expression` constant assignment syntax
713        if self.pos + 1 < self.tokens.len() && self.tokens[self.pos + 1].kind == TokenKind::Equals {
714            let name = tok.text.to_string();
715            let span = tok.span;
716            self.advance(); // consume name
717            self.advance(); // consume '='
718            let value = self.parse_const_expr()?;
719            self.constants.insert(name.clone(), value);
720            return Ok(Some(Statement::Const(ConstDef { name, value, span })));
721        }
722
723        // Check for prefixes (case-insensitive, zero extra allocations).
724        // We track the token position so we can lowercase the final mnemonic
725        // only once, after the prefix loop.
726        let mut prefixes = PrefixList::new();
727        let mut mnemonic_pos = self.pos;
728        let mut current_span = tok.span;
729
730        loop {
731            let prefix = {
732                let text = &*self.tokens[mnemonic_pos].text;
733                if text.eq_ignore_ascii_case("lock") {
734                    Some(Prefix::Lock)
735                } else if text.eq_ignore_ascii_case("rep")
736                    || text.eq_ignore_ascii_case("repe")
737                    || text.eq_ignore_ascii_case("repz")
738                {
739                    Some(Prefix::Rep)
740                } else if text.eq_ignore_ascii_case("repne") || text.eq_ignore_ascii_case("repnz") {
741                    Some(Prefix::Repne)
742                } else {
743                    None
744                }
745            };
746            match prefix {
747                Some(p) => {
748                    prefixes.push(p);
749                    self.advance();
750                }
751                None => break,
752            }
753            if self.at_end() || self.peek().kind != TokenKind::Ident {
754                // Standalone prefix — treat as instruction with no operands
755                return Ok(Some(Statement::Instruction(Instruction {
756                    mnemonic: {
757                        let mut lbuf = [0u8; 32];
758                        Mnemonic::from(to_lower_buf(&self.tokens[mnemonic_pos].text, &mut lbuf))
759                    },
760                    operands: OperandList::new(),
761                    size_hint: None,
762                    prefixes,
763                    opmask: None,
764                    zeroing: false,
765                    broadcast: None,
766                    span: current_span,
767                })));
768            }
769            mnemonic_pos = self.pos;
770            current_span = self.tokens[mnemonic_pos].span;
771        }
772
773        // Now lowercase the mnemonic into stack-allocated Mnemonic (zero alloc).
774        let mut mnemonic = {
775            let mut lbuf = [0u8; 32];
776            Mnemonic::from(to_lower_buf(&self.tokens[mnemonic_pos].text, &mut lbuf))
777        };
778        let mnemonic_span = current_span;
779        self.advance(); // consume mnemonic
780
781        // Parse operands
782        let mut operands = OperandList::new();
783        let mut size_hint = None;
784        let mut opmask: Option<Register> = None;
785        let mut zeroing = false;
786        let mut broadcast: Option<BroadcastMode> = None;
787
788        // AT&T syntax: strip size suffix from mnemonic (movq→mov, addl→add, etc.)
789        if self.syntax == Syntax::Att {
790            if let Some((base, sz)) = strip_att_suffix(&mnemonic) {
791                mnemonic = base;
792                size_hint = Some(sz);
793            }
794        }
795
796        if !self.at_end() && !self.is_statement_end() {
797            let (op, hint) = self.parse_operand()?;
798            // ARM writeback: `R0!` after a register → wrap as Memory(PreIndex, base=R0)
799            let op = if self.peek().kind == TokenKind::Bang {
800                if let Operand::Register(r) = &op {
801                    if r.is_arm() || r.is_aarch64() {
802                        self.advance(); // consume '!'
803                        Operand::Memory(Box::new(MemoryOperand {
804                            base: Some(*r),
805                            index: None,
806                            scale: 1,
807                            disp: 0,
808                            disp_label: None,
809                            segment: None,
810                            size: None,
811                            addr_mode: AddrMode::PreIndex,
812                            index_subtract: false,
813                        }))
814                    } else {
815                        op
816                    }
817                } else {
818                    op
819                }
820            } else {
821                op
822            };
823            push_operand(&mut operands, op, &mnemonic, mnemonic_span)?;
824            if hint.is_some() && size_hint.is_none() {
825                size_hint = hint;
826            }
827
828            // ── AVX-512 decorators after first operand: {k1}, {k1}{z} ──
829            if self.arch == Arch::X86_64 || self.arch == Arch::X86 {
830                self.parse_evex_decorators(&mut opmask, &mut zeroing, &mut broadcast)?;
831            }
832
833            while self.peek().kind == TokenKind::Comma {
834                self.advance(); // skip comma
835                let (op, hint) = self.parse_operand()?;
836                push_operand(&mut operands, op, &mnemonic, mnemonic_span)?;
837                if hint.is_some() && size_hint.is_none() {
838                    size_hint = hint;
839                }
840                // ── AVX-512 decorators after subsequent operands: {1to8} etc. ──
841                if self.arch == Arch::X86_64 || self.arch == Arch::X86 {
842                    self.parse_evex_decorators(&mut opmask, &mut zeroing, &mut broadcast)?;
843                }
844            }
845        }
846
847        // AT&T syntax: reverse operand order (src, dst → dst, src)
848        // Only for instructions with 2 or 3 operands.
849        // Skip for instructions that don't reverse (e.g., 0 or 1 operand).
850        if self.syntax == Syntax::Att && operands.len() >= 2 {
851            operands.reverse();
852        }
853
854        Ok(Some(Statement::Instruction(Instruction {
855            mnemonic,
856            operands,
857            size_hint,
858            prefixes,
859            opmask,
860            zeroing,
861            broadcast,
862            span: mnemonic_span,
863        })))
864    }
865
866    fn is_statement_end(&self) -> bool {
867        matches!(self.peek().kind, TokenKind::Newline | TokenKind::Eof)
868    }
869
870    /// Parse one operand. Returns the operand and optional size hint.
871    /// Parse a single atom in an expression after `+` or `-`:
872    /// either a numeric literal or an identifier (label / constant name).
873    fn parse_expr_atom(&mut self, ctx_tok: &Token<'a>) -> Result<Expr, AsmError> {
874        let next = self.peek().clone();
875        match &next.kind {
876            TokenKind::Number(n) => {
877                self.advance();
878                Ok(Expr::Num(*n))
879            }
880            TokenKind::Ident => {
881                self.advance();
882                // If identifier is a known constant, substitute immediately
883                if let Some(&val) = self.constants.get(&*next.text) {
884                    Ok(Expr::Num(val))
885                } else {
886                    Ok(Expr::Label(next.text.to_string()))
887                }
888            }
889            _ => Err(AsmError::Syntax {
890                msg: alloc::format!(
891                    "expected number or identifier after '+'/'-' near '{}'",
892                    ctx_tok.text
893                ),
894                span: next.span,
895            }),
896        }
897    }
898
899    /// Parse AVX-512 EVEX decorators: `{k1}`, `{k1}{z}`, `{1to2}`, `{1to4}`, `{1to8}`, `{1to16}`.
900    ///
901    /// Called after each operand to pick up decorators attached to that operand.
902    /// Opmask and zeroing typically follow the destination, broadcast follows a memory operand.
903    fn parse_evex_decorators(
904        &mut self,
905        opmask: &mut Option<Register>,
906        zeroing: &mut bool,
907        broadcast: &mut Option<BroadcastMode>,
908    ) -> Result<(), AsmError> {
909        while self.peek().kind == TokenKind::OpenBrace {
910            let brace_span = self.peek().span;
911            self.advance(); // skip '{'
912            let tok = self.peek().clone();
913            match tok.kind {
914                TokenKind::Ident => {
915                    let mut lbuf = [0u8; 32];
916                    let lower = to_lower_buf(&tok.text, &mut lbuf);
917                    if lower == "z" {
918                        *zeroing = true;
919                        self.advance();
920                    } else if let Some(kreg) = parse_register_lower(lower, Arch::X86_64) {
921                        if kreg.is_opmask() {
922                            *opmask = Some(kreg);
923                            self.advance();
924                        } else {
925                            return Err(AsmError::Syntax {
926                                msg: String::from("expected opmask register k0-k7"),
927                                span: tok.span,
928                            });
929                        }
930                    } else {
931                        return Err(AsmError::Syntax {
932                            msg: String::from("unexpected identifier in AVX-512 decorator"),
933                            span: tok.span,
934                        });
935                    }
936                }
937                TokenKind::Number(_) => {
938                    // {1to2}, {1to4}, {1to8}, {1to16}
939                    if tok.text == "1" {
940                        self.advance(); // skip '1'
941                                        // Expect identifier "to2", "to4", "to8", or "to16"
942                        let next = self.peek().clone();
943                        if next.kind == TokenKind::Ident {
944                            let mut lbuf = [0u8; 32];
945                            let nlower = to_lower_buf(&next.text, &mut lbuf);
946                            let mode = match nlower {
947                                "to2" => Some(BroadcastMode::OneToTwo),
948                                "to4" => Some(BroadcastMode::OneToFour),
949                                "to8" => Some(BroadcastMode::OneToEight),
950                                "to16" => Some(BroadcastMode::OneToSixteen),
951                                _ => None,
952                            };
953                            if let Some(m) = mode {
954                                *broadcast = Some(m);
955                                self.advance();
956                            } else {
957                                return Err(AsmError::Syntax {
958                                    msg: String::from("expected 1to2, 1to4, 1to8, or 1to16"),
959                                    span: next.span,
960                                });
961                            }
962                        } else {
963                            return Err(AsmError::Syntax {
964                                msg: String::from("expected broadcast specifier (1to2/4/8/16)"),
965                                span: next.span,
966                            });
967                        }
968                    } else {
969                        return Err(AsmError::Syntax {
970                            msg: String::from("unexpected number in AVX-512 decorator"),
971                            span: tok.span,
972                        });
973                    }
974                }
975                _ => {
976                    // Not an AVX-512 decorator — this is a regular brace (ARM register list etc.)
977                    // Put back by not advancing and returning. But we already consumed '{'.
978                    // This shouldn't happen in normal x86 flow. Return error.
979                    return Err(AsmError::Syntax {
980                        msg: String::from("unexpected token in AVX-512 decorator"),
981                        span: brace_span,
982                    });
983                }
984            }
985            // Expect closing '}'
986            if self.peek().kind == TokenKind::CloseBrace {
987                self.advance();
988            } else {
989                return Err(AsmError::Syntax {
990                    msg: String::from("expected '}' after AVX-512 decorator"),
991                    span: self.peek().span,
992                });
993            }
994        }
995        Ok(())
996    }
997
998    fn parse_operand(&mut self) -> Result<(Operand, Option<OperandSize>), AsmError> {
999        let tok = self.peek().clone();
1000
1001        // Check for size hint: byte/word/dword/qword [ptr]
1002        if tok.kind == TokenKind::Ident {
1003            let mut lbuf = [0u8; 32];
1004            let lower = to_lower_buf(&tok.text, &mut lbuf);
1005            if let Some(sz) = self.try_parse_size_hint(lower) {
1006                // Must be followed by a memory operand or ptr keyword
1007                if self.peek().kind == TokenKind::Ident
1008                    && self.peek().text.eq_ignore_ascii_case("ptr")
1009                {
1010                    self.advance(); // skip "ptr"
1011                }
1012                let (op, _) = self.parse_operand_inner()?;
1013                return Ok((op, Some(sz)));
1014            }
1015        }
1016
1017        self.parse_operand_inner()
1018    }
1019
1020    /// Parse the amount following a shift/extend keyword.
1021    ///
1022    /// The amount is optional: `rrx` never takes one, and a bare extend such
1023    /// as `uxtb` defaults to a shift of zero.
1024    fn parse_shift_amount(&mut self) -> Result<ShiftAmount, AsmError> {
1025        let tok = self.peek().clone();
1026        match &tok.kind {
1027            TokenKind::Number(n) => {
1028                self.advance();
1029                Ok(ShiftAmount::Immediate(clamp_shift(*n)))
1030            }
1031            TokenKind::Minus => {
1032                self.advance();
1033                match self.peek().kind {
1034                    TokenKind::Number(n) => {
1035                        self.advance();
1036                        Ok(ShiftAmount::Immediate(clamp_shift(-n)))
1037                    }
1038                    _ => Err(AsmError::Syntax {
1039                        msg: String::from("expected a number after '-' in shift amount"),
1040                        span: self.peek().span,
1041                    }),
1042                }
1043            }
1044            TokenKind::Ident => {
1045                let mut lbuf = [0u8; 32];
1046                let lower = to_lower_buf(&tok.text, &mut lbuf);
1047                match parse_register_lower(lower, self.arch) {
1048                    Some(reg) => {
1049                        self.advance();
1050                        Ok(ShiftAmount::Register(reg))
1051                    }
1052                    // Not a register — the shift stands alone (`rrx`, `uxtb`)
1053                    // and this identifier belongs to whatever follows.
1054                    None => Ok(ShiftAmount::None),
1055                }
1056            }
1057            _ => Ok(ShiftAmount::None),
1058        }
1059    }
1060
1061    /// Case-insensitive size hint parsing (zero allocations).
1062    fn try_parse_size_hint(&mut self, ident: &str) -> Option<OperandSize> {
1063        if ident.eq_ignore_ascii_case("byte") {
1064            self.advance();
1065            Some(OperandSize::Byte)
1066        } else if ident.eq_ignore_ascii_case("word") {
1067            self.advance();
1068            Some(OperandSize::Word)
1069        } else if ident.eq_ignore_ascii_case("dword") {
1070            self.advance();
1071            Some(OperandSize::Dword)
1072        } else if ident.eq_ignore_ascii_case("qword") {
1073            self.advance();
1074            Some(OperandSize::Qword)
1075        } else if ident.eq_ignore_ascii_case("xmmword") || ident.eq_ignore_ascii_case("oword") {
1076            self.advance();
1077            Some(OperandSize::Xmmword)
1078        } else if ident.eq_ignore_ascii_case("ymmword") {
1079            self.advance();
1080            Some(OperandSize::Ymmword)
1081        } else if ident.eq_ignore_ascii_case("zmmword") {
1082            self.advance();
1083            Some(OperandSize::Zmmword)
1084        } else {
1085            None
1086        }
1087    }
1088
1089    fn parse_operand_inner(&mut self) -> Result<(Operand, Option<OperandSize>), AsmError> {
1090        // AT&T syntax: dispatch to AT&T operand parser
1091        if self.syntax == Syntax::Att {
1092            return self.parse_att_operand();
1093        }
1094
1095        let tok = self.peek().clone();
1096
1097        match &tok.kind {
1098            // ARM register list: {R0, R1, R4, LR}
1099            // SVE braced vector register: {z0.s}
1100            // NEON braced vector register: {v0.4s}
1101            TokenKind::OpenBrace => {
1102                self.advance(); // consume '{'
1103                                // Check for SVE/NEON braced vector register: {z0.s}, {v0.4s}
1104                let first = self.peek().clone();
1105                if let TokenKind::Ident = &first.kind {
1106                    let mut lbuf = [0u8; 32];
1107                    let lower = to_lower_buf(&first.text, &mut lbuf);
1108                    if let Some(dot_pos) = lower.find('.') {
1109                        let reg_part = &lower[..dot_pos];
1110                        let arr_part = &lower[dot_pos + 1..];
1111                        if let Some(reg) = parse_register_lower(reg_part, self.arch) {
1112                            if reg.is_a64_sve_z() || reg.is_a64_vector() {
1113                                if let Some(arr) = VectorArrangement::parse(arr_part) {
1114                                    self.advance(); // consume 'z0.s' / 'v0.4s'
1115                                    if self.peek().kind != TokenKind::CloseBrace {
1116                                        return Err(AsmError::Syntax {
1117                                            msg: String::from("expected '}' after vector register"),
1118                                            span: self.peek().span,
1119                                        });
1120                                    }
1121                                    self.advance(); // consume '}'
1122                                    return Ok((Operand::VectorRegister(reg, arr), None));
1123                                }
1124                            }
1125                        }
1126                    }
1127                }
1128                // Fall through to regular register list parsing
1129                let mut regs = Vec::new();
1130                loop {
1131                    let rtok = self.peek().clone();
1132                    if rtok.kind == TokenKind::CloseBrace {
1133                        self.advance();
1134                        break;
1135                    }
1136                    if rtok.kind == TokenKind::Comma {
1137                        self.advance();
1138                        continue;
1139                    }
1140                    if let TokenKind::Ident = &rtok.kind {
1141                        let mut lbuf = [0u8; 32];
1142                        let lower = to_lower_buf(&rtok.text, &mut lbuf);
1143                        if let Some(reg) = parse_register_lower(lower, self.arch) {
1144                            self.advance();
1145                            // Range form: `{r0-r7}` is shorthand for every
1146                            // register from r0 to r7 inclusive.
1147                            if self.peek().kind == TokenKind::Minus {
1148                                self.advance(); // consume '-'
1149                                let etok = self.peek().clone();
1150                                let mut ebuf = [0u8; 32];
1151                                let elower = to_lower_buf(&etok.text, &mut ebuf);
1152                                let end = match (&etok.kind, parse_register_lower(elower, self.arch))
1153                                {
1154                                    (TokenKind::Ident, Some(r)) => r,
1155                                    _ => {
1156                                        return Err(AsmError::Syntax {
1157                                            msg: alloc::format!(
1158                                                "expected register after '-' in register range, found '{}'",
1159                                                etok.text
1160                                            ),
1161                                            span: etok.span,
1162                                        })
1163                                    }
1164                                };
1165                                self.advance();
1166                                expand_register_range(reg, end, &mut regs, etok.span)?;
1167                                continue;
1168                            }
1169                            regs.push(reg);
1170                            continue;
1171                        }
1172                    }
1173                    return Err(AsmError::Syntax {
1174                        msg: alloc::format!(
1175                            "expected register in register list, found '{}'",
1176                            rtok.text
1177                        ),
1178                        span: rtok.span,
1179                    });
1180                }
1181                Ok((Operand::RegisterList(regs), None))
1182            }
1183
1184            // Memory operand
1185            TokenKind::OpenBracket => {
1186                let mem = self.parse_memory_operand()?;
1187                Ok((Operand::Memory(Box::new(mem)), None))
1188            }
1189
1190            // Literal pool value: =imm (ARM/AArch64 LDR Rn, =value)
1191            TokenKind::Equals => {
1192                self.advance(); // consume '='
1193                let next = self.peek().clone();
1194                match next.kind {
1195                    TokenKind::Number(n) => {
1196                        self.advance();
1197                        Ok((Operand::LiteralPoolValue(n), None))
1198                    }
1199                    TokenKind::Minus => {
1200                        self.advance();
1201                        if let TokenKind::Number(n) = self.peek().kind {
1202                            self.advance();
1203                            Ok((Operand::LiteralPoolValue(-n), None))
1204                        } else {
1205                            Err(AsmError::Syntax {
1206                                msg: String::from("expected number after '=-'"),
1207                                span: next.span,
1208                            })
1209                        }
1210                    }
1211                    _ => Err(AsmError::Syntax {
1212                        msg: alloc::format!("expected number after '=', found '{}'", next.text),
1213                        span: next.span,
1214                    }),
1215                }
1216            }
1217
1218            // RISC-V bare (reg) memory operand — equivalent to 0(reg)
1219            TokenKind::OpenParen if matches!(self.arch, Arch::Rv32 | Arch::Rv64) => {
1220                self.parse_riscv_mem_operand(0)
1221            }
1222
1223            // Immediate — or RISC-V memory operand: offset(reg)
1224            TokenKind::Number(n) => {
1225                let val = *n;
1226                self.advance();
1227                // RISC-V: 0(sp), 8(a0), etc.
1228                if matches!(self.arch, Arch::Rv32 | Arch::Rv64)
1229                    && self.peek().kind == TokenKind::OpenParen
1230                {
1231                    return self.parse_riscv_mem_operand(val);
1232                }
1233                Ok((Operand::Immediate(val), None))
1234            }
1235
1236            // Negative immediate — or RISC-V memory operand: -offset(reg)
1237            TokenKind::Minus => {
1238                self.advance();
1239                let next = self.peek().clone();
1240                if let TokenKind::Number(n) = next.kind {
1241                    self.advance();
1242                    let val = -n;
1243                    // RISC-V: -4(sp), -8(s0), etc.
1244                    if matches!(self.arch, Arch::Rv32 | Arch::Rv64)
1245                        && self.peek().kind == TokenKind::OpenParen
1246                    {
1247                        return self.parse_riscv_mem_operand(val);
1248                    }
1249                    Ok((Operand::Immediate(val), None))
1250                } else {
1251                    Err(AsmError::Syntax {
1252                        msg: String::from("expected number after '-'"),
1253                        span: tok.span,
1254                    })
1255                }
1256            }
1257
1258            // Character literal as immediate
1259            TokenKind::CharLit(ch) => {
1260                self.advance();
1261                Ok((Operand::Immediate(*ch as i128), None))
1262            }
1263
1264            // Identifier: register or label reference
1265            TokenKind::Ident => {
1266                let mut lbuf = [0u8; 32];
1267                let lower = to_lower_buf(&tok.text, &mut lbuf);
1268
1269                // Check for segment:memory pair (e.g., fs:[rax])
1270                if is_segment_name(lower) {
1271                    let seg = match parse_segment(lower) {
1272                        Some(s) => s,
1273                        None => {
1274                            return Err(AsmError::Syntax {
1275                                msg: alloc::format!("unknown segment register: {}", lower),
1276                                span: tok.span,
1277                            });
1278                        }
1279                    };
1280                    // Check if followed by colon and bracket
1281                    if self.pos + 1 < self.tokens.len()
1282                        && self.tokens[self.pos + 1].kind == TokenKind::Colon
1283                    {
1284                        self.advance(); // consume seg name
1285                        self.advance(); // consume ':'
1286
1287                        if self.peek().kind == TokenKind::OpenBracket {
1288                            let mut mem = self.parse_memory_operand()?;
1289                            mem.segment = Some(seg);
1290                            return Ok((Operand::Memory(Box::new(mem)), None));
1291                        }
1292                    }
1293                }
1294
1295                // ARM32 program status register with a field selector:
1296                // `msr cpsr_f, r0`. Checked before the register table so the
1297                // suffix is not lost.
1298                if matches!(self.arch, Arch::Arm | Arch::Thumb) {
1299                    if let Some(psr) = PsrField::from_lower(lower) {
1300                        self.advance();
1301                        return Ok((Operand::PsrField(psr), None));
1302                    }
1303                }
1304
1305                // Barrel-shift / register-extend modifier: `lsl #3`, `asr r3`,
1306                // `rrx`, `uxtw #2`. Only in the UAL dialects — `lsl` is also a
1307                // mnemonic, but a mnemonic has already been consumed by the
1308                // time operands are parsed, so there is no ambiguity here.
1309                if matches!(self.arch, Arch::Arm | Arch::Thumb | Arch::Aarch64) {
1310                    if let Some(shift_op) = ShiftOp::from_lower(lower) {
1311                        self.advance();
1312                        let amount = self.parse_shift_amount()?;
1313                        return Ok((Operand::Shift(shift_op, amount), None));
1314                    }
1315                }
1316
1317                // Try register (possibly with vector arrangement: v0.4s, z0.s, p0.b)
1318                if let Some(dot_pos) = lower.find('.') {
1319                    // Check for vector register with arrangement specifier
1320                    let reg_part = &lower[..dot_pos];
1321                    let arr_part = &lower[dot_pos + 1..];
1322                    if let Some(reg) = parse_register_lower(reg_part, self.arch) {
1323                        // Lane access: `v0.s[0]` names one element rather than
1324                        // an arrangement, so it is checked first — `.s` alone
1325                        // is not a valid arrangement, but `.s[0]` is a lane.
1326                        if reg.is_a64_vector() {
1327                            if let Some(size) = ElementSize::from_lower(arr_part) {
1328                                if self.tokens.get(self.pos + 1).map(|t| &t.kind)
1329                                    == Some(&TokenKind::OpenBracket)
1330                                {
1331                                    self.advance(); // register + size
1332                                    self.advance(); // '['
1333                                    let idx_tok = self.peek().clone();
1334                                    let index = match idx_tok.kind {
1335                                        TokenKind::Number(n)
1336                                            if (0..i128::from(size.lane_count())).contains(&n) =>
1337                                        {
1338                                            n as u8
1339                                        }
1340                                        TokenKind::Number(n) => {
1341                                            return Err(AsmError::Syntax {
1342                                                msg: alloc::format!(
1343                                                    "lane index {} out of range for .{} (0-{})",
1344                                                    n,
1345                                                    size,
1346                                                    size.lane_count() - 1
1347                                                ),
1348                                                span: idx_tok.span,
1349                                            })
1350                                        }
1351                                        _ => {
1352                                            return Err(AsmError::Syntax {
1353                                                msg: String::from("expected a lane index"),
1354                                                span: idx_tok.span,
1355                                            })
1356                                        }
1357                                    };
1358                                    self.advance(); // index
1359                                    if self.peek().kind != TokenKind::CloseBracket {
1360                                        return Err(AsmError::Syntax {
1361                                            msg: String::from("expected ']' after lane index"),
1362                                            span: self.peek().span,
1363                                        });
1364                                    }
1365                                    self.advance(); // ']'
1366                                    return Ok((Operand::VectorElement(reg, size, index), None));
1367                                }
1368                            }
1369                        }
1370                        if reg.is_a64_vector() || reg.is_a64_sve_z() || reg.is_a64_sve_p() {
1371                            if let Some(arr) = VectorArrangement::parse(arr_part) {
1372                                self.advance();
1373                                return Ok((Operand::VectorRegister(reg, arr), None));
1374                            }
1375                        }
1376                        // RISC-V V extension: v0.t → mask operand (bare register)
1377                        if reg.is_riscv_vec() && arr_part == "t" {
1378                            self.advance();
1379                            return Ok((Operand::Register(reg), None));
1380                        }
1381                    }
1382                }
1383
1384                if let Some(reg) = parse_register_lower(lower, self.arch) {
1385                    self.advance();
1386                    // SVE predicate qualifier: p0/m or p0/z
1387                    if reg.is_a64_sve_p() && self.peek().kind == TokenKind::Slash {
1388                        let next_pos = self.pos + 1;
1389                        if next_pos < self.tokens.len() {
1390                            let qual_text = &self.tokens[next_pos].text;
1391                            let qual = if qual_text.eq_ignore_ascii_case("m") {
1392                                Some(SvePredQual::Merging)
1393                            } else if qual_text.eq_ignore_ascii_case("z") {
1394                                Some(SvePredQual::Zeroing)
1395                            } else {
1396                                None
1397                            };
1398                            if let Some(q) = qual {
1399                                self.advance(); // consume '/'
1400                                self.advance(); // consume 'm' or 'z'
1401                                return Ok((Operand::SvePredicate(reg, q), None));
1402                            }
1403                        }
1404                    }
1405                    return Ok((Operand::Register(reg), None));
1406                }
1407
1408                // Check if it's a previously-defined constant (bare identifier → Immediate)
1409                if let Some(&val) = self.constants.get(&*tok.text) {
1410                    self.advance();
1411                    // Check for trailing +/- chains (e.g., CONST + 5)
1412                    let mut result = val;
1413                    loop {
1414                        if self.peek().kind == TokenKind::Plus {
1415                            self.advance();
1416                            let next = self.peek().clone();
1417                            match &next.kind {
1418                                TokenKind::Number(n) => {
1419                                    self.advance();
1420                                    result += n;
1421                                }
1422                                TokenKind::Ident => {
1423                                    if let Some(&v) = self.constants.get(&*next.text) {
1424                                        self.advance();
1425                                        result += v;
1426                                    } else {
1427                                        break;
1428                                    }
1429                                }
1430                                _ => break,
1431                            }
1432                        } else if self.peek().kind == TokenKind::Minus {
1433                            self.advance();
1434                            let next = self.peek().clone();
1435                            match &next.kind {
1436                                TokenKind::Number(n) => {
1437                                    self.advance();
1438                                    result -= n;
1439                                }
1440                                TokenKind::Ident => {
1441                                    if let Some(&v) = self.constants.get(&*next.text) {
1442                                        self.advance();
1443                                        result -= v;
1444                                    } else {
1445                                        break;
1446                                    }
1447                                }
1448                                _ => break,
1449                            }
1450                        } else {
1451                            break;
1452                        }
1453                    }
1454                    return Ok((Operand::Immediate(result), None));
1455                }
1456
1457                // Label reference
1458                self.advance();
1459                // Build an expression tree for label+offset, label-offset,
1460                // label+ident, etc.
1461                let mut expr: Expr = Expr::Label(tok.text.to_string());
1462                let mut is_expression = false;
1463                loop {
1464                    if self.peek().kind == TokenKind::Plus {
1465                        self.advance();
1466                        let rhs = self.parse_expr_atom(&tok)?;
1467                        expr = Expr::Add(Box::new(expr), Box::new(rhs));
1468                        is_expression = true;
1469                    } else if self.peek().kind == TokenKind::Minus {
1470                        self.advance();
1471                        let rhs = self.parse_expr_atom(&tok)?;
1472                        expr = Expr::Sub(Box::new(expr), Box::new(rhs));
1473                        is_expression = true;
1474                    } else {
1475                        break;
1476                    }
1477                }
1478
1479                if is_expression {
1480                    // Try to resolve all-constant expressions eagerly
1481                    expr.resolve_constants(|name| self.constants.get(name).copied());
1482                    if let Some(val) = expr.eval() {
1483                        return Ok((Operand::Immediate(val), None));
1484                    }
1485                    return Ok((Operand::Expression(expr), None));
1486                }
1487
1488                Ok((Operand::Label(tok.text.to_string()), None))
1489            }
1490
1491            // Numeric label references
1492            TokenKind::NumericLabelFwd(n) => {
1493                self.advance();
1494                Ok((Operand::Label(alloc::format!("{}f", n)), None))
1495            }
1496            TokenKind::NumericLabelBwd(n) => {
1497                self.advance();
1498                Ok((Operand::Label(alloc::format!("{}b", n)), None))
1499            }
1500
1501            _ => Err(AsmError::Syntax {
1502                msg: alloc::format!("expected operand, found '{}'", tok.text),
1503                span: tok.span,
1504            }),
1505        }
1506    }
1507
1508    // ── AT&T / GAS syntax operand parsing ────────────────────────────────
1509
1510    /// Parse a single AT&T-syntax operand.
1511    ///
1512    /// Forms:
1513    /// - `$imm` or `$-imm` or `$label` — immediate / label reference
1514    /// - `%reg` — register
1515    /// - `%seg:disp(%base, %index, scale)` — memory with segment override
1516    /// - `disp(%base, %index, scale)` — memory
1517    /// - `(%base)` — memory (zero displacement)
1518    /// - `label` or `label+offset` — label reference (for branches)
1519    fn parse_att_operand(&mut self) -> Result<(Operand, Option<OperandSize>), AsmError> {
1520        let tok = self.peek().clone();
1521
1522        match &tok.kind {
1523            // $imm — immediate
1524            TokenKind::Dollar => {
1525                self.advance(); // consume '$'
1526                let next = self.peek().clone();
1527                match &next.kind {
1528                    TokenKind::Number(n) => {
1529                        let val = *n;
1530                        self.advance();
1531                        Ok((Operand::Immediate(val), None))
1532                    }
1533                    TokenKind::Minus => {
1534                        self.advance(); // consume '-'
1535                        let num_tok = self.peek().clone();
1536                        if let TokenKind::Number(n) = num_tok.kind {
1537                            self.advance();
1538                            Ok((Operand::Immediate(-n), None))
1539                        } else {
1540                            Err(AsmError::Syntax {
1541                                msg: String::from("expected number after '$-'"),
1542                                span: num_tok.span,
1543                            })
1544                        }
1545                    }
1546                    TokenKind::Ident => {
1547                        let name = next.text.to_string();
1548                        self.advance();
1549                        // Check if it's a known constant
1550                        if let Some(&val) = self.constants.get(&name) {
1551                            Ok((Operand::Immediate(val), None))
1552                        } else {
1553                            // Label reference as immediate
1554                            Ok((Operand::Label(name), None))
1555                        }
1556                    }
1557                    _ => Err(AsmError::Syntax {
1558                        msg: alloc::format!(
1559                            "expected number or identifier after '$', found '{}'",
1560                            next.text
1561                        ),
1562                        span: next.span,
1563                    }),
1564                }
1565            }
1566
1567            // %reg or %seg:... — register or segment-prefixed memory
1568            TokenKind::Percent => {
1569                self.advance(); // consume '%'
1570                let reg_tok = self.peek().clone();
1571                if reg_tok.kind != TokenKind::Ident {
1572                    return Err(AsmError::Syntax {
1573                        msg: alloc::format!(
1574                            "expected register name after '%', found '{}'",
1575                            reg_tok.text
1576                        ),
1577                        span: reg_tok.span,
1578                    });
1579                }
1580                let mut lbuf = [0u8; 32];
1581                let lower = to_lower_buf(&reg_tok.text, &mut lbuf);
1582
1583                // Check for segment register followed by ':'
1584                if is_segment_name(lower) {
1585                    // SAFETY: is_segment_name() matches the exact same set as
1586                    // parse_segment(), so unwrap is unreachable.
1587                    let seg = parse_segment(lower).unwrap();
1588                    // Peek ahead: if next is ':', this is a segment-prefixed memory
1589                    if self.pos + 1 < self.tokens.len()
1590                        && self.tokens[self.pos + 1].kind == TokenKind::Colon
1591                    {
1592                        self.advance(); // consume segment name
1593                        self.advance(); // consume ':'
1594                                        // Parse the rest as memory: could be disp(%base,...), (%base,...), or bare disp
1595                        let (seg_disp, seg_disp_label) = match self.peek().kind {
1596                            TokenKind::Number(n) => {
1597                                let val = n;
1598                                self.advance();
1599                                (val as i64, None)
1600                            }
1601                            TokenKind::Minus => {
1602                                self.advance();
1603                                if let TokenKind::Number(n) = self.peek().kind {
1604                                    let val = n;
1605                                    self.advance();
1606                                    (-(val as i64), None)
1607                                } else {
1608                                    (0, None)
1609                                }
1610                            }
1611                            _ => (0, None),
1612                        };
1613                        let mut mem = self.parse_att_memory_operand(seg_disp, seg_disp_label)?;
1614                        mem.segment = Some(seg);
1615                        return Ok((Operand::Memory(Box::new(mem)), None));
1616                    }
1617                }
1618
1619                // Regular register
1620                if let Some(reg) = parse_register_lower(lower, self.arch) {
1621                    self.advance();
1622                    Ok((Operand::Register(reg), None))
1623                } else {
1624                    Err(AsmError::Syntax {
1625                        msg: alloc::format!("unknown register: %{}", lower),
1626                        span: reg_tok.span,
1627                    })
1628                }
1629            }
1630
1631            // (%base, ...) — memory with zero displacement
1632            TokenKind::OpenParen => {
1633                let mem = self.parse_att_memory_operand(0, None)?;
1634                Ok((Operand::Memory(Box::new(mem)), None))
1635            }
1636
1637            // number — could be displacement for memory or standalone immediate (for branches)
1638            TokenKind::Number(n) => {
1639                let val = *n;
1640                self.advance();
1641                // If followed by '(', this is a memory operand: disp(%base, %index, scale)
1642                if self.peek().kind == TokenKind::OpenParen {
1643                    let mem = self.parse_att_memory_operand(val as i64, None)?;
1644                    Ok((Operand::Memory(Box::new(mem)), None))
1645                } else {
1646                    // Standalone number (e.g., for branch targets, port numbers)
1647                    Ok((Operand::Immediate(val), None))
1648                }
1649            }
1650
1651            // -number — negative displacement for memory
1652            TokenKind::Minus => {
1653                self.advance(); // consume '-'
1654                let next = self.peek().clone();
1655                if let TokenKind::Number(n) = next.kind {
1656                    self.advance();
1657                    let val = -n;
1658                    if self.peek().kind == TokenKind::OpenParen {
1659                        let mem = self.parse_att_memory_operand(val as i64, None)?;
1660                        Ok((Operand::Memory(Box::new(mem)), None))
1661                    } else {
1662                        Ok((Operand::Immediate(val), None))
1663                    }
1664                } else {
1665                    Err(AsmError::Syntax {
1666                        msg: String::from("expected number after '-' in AT&T operand"),
1667                        span: tok.span,
1668                    })
1669                }
1670            }
1671
1672            // Identifier — label reference (for branches: jmp label, call label)
1673            TokenKind::Ident => {
1674                let name = tok.text.to_string();
1675                self.advance();
1676                // Check if it's a known constant
1677                if let Some(&val) = self.constants.get(&name) {
1678                    // If followed by '(', treat as memory displacement
1679                    if self.peek().kind == TokenKind::OpenParen {
1680                        let mem = self.parse_att_memory_operand(val as i64, None)?;
1681                        return Ok((Operand::Memory(Box::new(mem)), None));
1682                    }
1683                    return Ok((Operand::Immediate(val), None));
1684                }
1685                // Build expression with optional +/- offset
1686                let mut expr = Expr::Label(name.clone());
1687                let mut has_offset = false;
1688                loop {
1689                    if self.peek().kind == TokenKind::Plus {
1690                        self.advance();
1691                        let atom = self.parse_expr_atom(&tok)?;
1692                        expr = Expr::Add(Box::new(expr), Box::new(atom));
1693                        has_offset = true;
1694                    } else if self.peek().kind == TokenKind::Minus {
1695                        self.advance();
1696                        let atom = self.parse_expr_atom(&tok)?;
1697                        expr = Expr::Sub(Box::new(expr), Box::new(atom));
1698                        has_offset = true;
1699                    } else {
1700                        break;
1701                    }
1702                }
1703                if has_offset {
1704                    Ok((Operand::Expression(expr), None))
1705                } else {
1706                    Ok((Operand::Label(name), None))
1707                }
1708            }
1709
1710            // Numeric label forward/backward references
1711            TokenKind::NumericLabelFwd(n) => {
1712                let n = *n;
1713                self.advance();
1714                Ok((Operand::Label(alloc::format!("{}f", n)), None))
1715            }
1716            TokenKind::NumericLabelBwd(n) => {
1717                let n = *n;
1718                self.advance();
1719                Ok((Operand::Label(alloc::format!("{}b", n)), None))
1720            }
1721
1722            // Star — indirect jump/call: *%rax, *(%rax), *label
1723            TokenKind::Star => {
1724                self.advance(); // consume '*'
1725                                // The suboperand is the actual target — parse recursively
1726                self.parse_att_operand()
1727            }
1728
1729            _ => Err(AsmError::Syntax {
1730                msg: alloc::format!("unexpected token in AT&T operand: '{}'", tok.text),
1731                span: tok.span,
1732            }),
1733        }
1734    }
1735
1736    /// Parse AT&T memory operand: `(%base)`, `(%base, %index)`,
1737    /// `(%base, %index, scale)`, `disp(%base, ...)`.
1738    /// `disp` has already been parsed; it's passed as the `disp` param.
1739    fn parse_att_memory_operand(
1740        &mut self,
1741        disp: i64,
1742        disp_label: Option<String>,
1743    ) -> Result<MemoryOperand, AsmError> {
1744        let open = self.peek().clone();
1745        if open.kind != TokenKind::OpenParen {
1746            return Err(AsmError::Syntax {
1747                msg: alloc::format!("expected '(' in AT&T memory operand, found '{}'", open.text),
1748                span: open.span,
1749            });
1750        }
1751        self.advance(); // consume '('
1752
1753        let mut base = None;
1754        let mut index = None;
1755        let mut scale: u8 = 1;
1756
1757        // Parse base register: %reg
1758        if self.peek().kind == TokenKind::Percent {
1759            self.advance(); // consume '%'
1760            let reg_tok = self.peek().clone();
1761            let mut lbuf = [0u8; 32];
1762            let lower = to_lower_buf(&reg_tok.text, &mut lbuf);
1763            base =
1764                Some(
1765                    parse_register_lower(lower, self.arch).ok_or_else(|| AsmError::Syntax {
1766                        msg: alloc::format!("unknown register: %{}", lower),
1767                        span: reg_tok.span,
1768                    })?,
1769                );
1770            self.advance();
1771        }
1772
1773        // Comma → index register
1774        if self.peek().kind == TokenKind::Comma {
1775            self.advance(); // consume ','
1776            if self.peek().kind == TokenKind::Percent {
1777                self.advance(); // consume '%'
1778                let reg_tok = self.peek().clone();
1779                let mut lbuf = [0u8; 32];
1780                let lower = to_lower_buf(&reg_tok.text, &mut lbuf);
1781                index = Some(parse_register_lower(lower, self.arch).ok_or_else(|| {
1782                    AsmError::Syntax {
1783                        msg: alloc::format!("unknown register: %{}", lower),
1784                        span: reg_tok.span,
1785                    }
1786                })?);
1787                self.advance();
1788            }
1789
1790            // Comma → scale factor
1791            if self.peek().kind == TokenKind::Comma {
1792                self.advance(); // consume ','
1793                let scale_tok = self.peek().clone();
1794                if let TokenKind::Number(n) = scale_tok.kind {
1795                    scale = n as u8;
1796                    self.advance();
1797                } else {
1798                    return Err(AsmError::Syntax {
1799                        msg: alloc::format!(
1800                            "expected scale factor (1,2,4,8), found '{}'",
1801                            scale_tok.text
1802                        ),
1803                        span: scale_tok.span,
1804                    });
1805                }
1806            }
1807        }
1808
1809        // Expect closing ')'
1810        let close = self.peek().clone();
1811        if close.kind != TokenKind::CloseParen {
1812            return Err(AsmError::Syntax {
1813                msg: alloc::format!(
1814                    "expected ')' in AT&T memory operand, found '{}'",
1815                    close.text
1816                ),
1817                span: close.span,
1818            });
1819        }
1820        self.advance();
1821
1822        Ok(MemoryOperand {
1823            base,
1824            index,
1825            scale,
1826            disp,
1827            disp_label,
1828            segment: None,
1829            size: None,
1830            addr_mode: AddrMode::Offset,
1831            index_subtract: false,
1832        })
1833    }
1834
1835    /// Parse a RISC-V memory operand: `offset(reg)`.
1836    /// Called after the offset has been consumed. Expects `(` reg `)`.
1837    fn parse_riscv_mem_operand(
1838        &mut self,
1839        offset: i128,
1840    ) -> Result<(Operand, Option<OperandSize>), AsmError> {
1841        let open_tok = self.advance().clone(); // consume '('
1842        debug_assert_eq!(open_tok.kind, TokenKind::OpenParen);
1843
1844        let reg_tok = self.peek().clone();
1845        let mut lbuf = [0u8; 32];
1846        let lower = to_lower_buf(&reg_tok.text, &mut lbuf);
1847        let reg = if let Some(r) = parse_register_lower(lower, self.arch) {
1848            self.advance();
1849            r
1850        } else {
1851            return Err(AsmError::Syntax {
1852                msg: alloc::format!(
1853                    "expected register in memory operand, found '{}'",
1854                    reg_tok.text
1855                ),
1856                span: reg_tok.span,
1857            });
1858        };
1859
1860        // Expect closing ')'
1861        let close = self.peek().clone();
1862        if close.kind != TokenKind::CloseParen {
1863            return Err(AsmError::Syntax {
1864                msg: alloc::format!("expected ')' after register, found '{}'", close.text),
1865                span: close.span,
1866            });
1867        }
1868        self.advance();
1869
1870        let mem = MemoryOperand {
1871            base: Some(reg),
1872            disp: offset as i64,
1873            ..Default::default()
1874        };
1875        Ok((Operand::Memory(Box::new(mem)), None))
1876    }
1877
1878    /// Parse a memory operand: `[base + index*scale + disp]`
1879    fn parse_memory_operand(&mut self) -> Result<MemoryOperand, AsmError> {
1880        let open = self.advance().clone(); // consume '['
1881        debug_assert_eq!(open.kind, TokenKind::OpenBracket);
1882
1883        let mut mem = MemoryOperand::default();
1884        let mut _expect_term = true;
1885        let mut sign: i64 = 1;
1886
1887        while self.peek().kind != TokenKind::CloseBracket {
1888            if self.at_end() {
1889                return Err(AsmError::Syntax {
1890                    msg: String::from("unterminated memory operand, expected ']'"),
1891                    span: open.span,
1892                });
1893            }
1894
1895            let tok = self.peek().clone();
1896
1897            match &tok.kind {
1898                TokenKind::Plus | TokenKind::Comma => {
1899                    // Comma inside brackets is ARM/AArch64 syntax: [Rn, #offset]
1900                    self.advance();
1901                    sign = 1;
1902                    _expect_term = true;
1903                    continue;
1904                }
1905                TokenKind::Minus => {
1906                    self.advance();
1907                    sign = -1;
1908                    _expect_term = true;
1909                    continue;
1910                }
1911                TokenKind::Ident => {
1912                    let mut lbuf = [0u8; 32];
1913                    let lower = to_lower_buf(&tok.text, &mut lbuf);
1914                    if let Some(reg) = parse_register_lower(lower, self.arch) {
1915                        self.advance();
1916                        // Check if this register is multiplied by a scale
1917                        if self.peek().kind == TokenKind::Star {
1918                            self.advance(); // consume '*'
1919                            let scale_tok = self.peek().clone();
1920                            if let TokenKind::Number(s) = scale_tok.kind {
1921                                if !matches!(s, 1 | 2 | 4 | 8) {
1922                                    return Err(AsmError::Syntax {
1923                                        msg: String::from("scale factor must be 1, 2, 4, or 8"),
1924                                        span: scale_tok.span,
1925                                    });
1926                                }
1927                                self.advance();
1928                                mem.index = Some(reg);
1929                                mem.scale = s as u8;
1930                                mem.index_subtract = sign < 0;
1931                            } else {
1932                                return Err(AsmError::Syntax {
1933                                    msg: String::from("expected scale factor (1, 2, 4, or 8)"),
1934                                    span: scale_tok.span,
1935                                });
1936                            }
1937                        } else if mem.base.is_none() {
1938                            mem.base = Some(reg);
1939                        } else if mem.index.is_none() {
1940                            mem.index = Some(reg);
1941                            mem.scale = 1;
1942                            mem.index_subtract = sign < 0;
1943                        } else {
1944                            return Err(AsmError::Syntax {
1945                                msg: String::from("too many registers in memory operand"),
1946                                span: tok.span,
1947                            });
1948                        }
1949                    } else {
1950                        // Label in memory operand
1951                        self.advance();
1952                        mem.disp_label = Some(tok.text.to_string());
1953                    }
1954                    _expect_term = false;
1955                }
1956                TokenKind::Number(n) => {
1957                    self.advance();
1958                    // Check if number*register (e.g., 4*rbx)
1959                    if self.peek().kind == TokenKind::Star {
1960                        self.advance(); // consume '*'
1961                        let reg_tok = self.peek().clone();
1962                        if reg_tok.kind == TokenKind::Ident {
1963                            let mut lbuf = [0u8; 32];
1964                            let lower = to_lower_buf(&reg_tok.text, &mut lbuf);
1965                            if let Some(reg) = parse_register_lower(lower, self.arch) {
1966                                if !matches!(*n, 1 | 2 | 4 | 8) {
1967                                    return Err(AsmError::Syntax {
1968                                        msg: String::from("scale factor must be 1, 2, 4, or 8"),
1969                                        span: tok.span,
1970                                    });
1971                                }
1972                                self.advance();
1973                                mem.index = Some(reg);
1974                                mem.scale = *n as u8;
1975                                mem.index_subtract = sign < 0;
1976                                _expect_term = false;
1977                                continue;
1978                            }
1979                        }
1980                        return Err(AsmError::Syntax {
1981                            msg: String::from("expected register after scale factor"),
1982                            span: reg_tok.span,
1983                        });
1984                    }
1985                    mem.disp = mem.disp.wrapping_add(sign * (*n as i64));
1986                    _expect_term = false;
1987                }
1988                _ => {
1989                    return Err(AsmError::Syntax {
1990                        msg: alloc::format!("unexpected token '{}' in memory operand", tok.text),
1991                        span: tok.span,
1992                    });
1993                }
1994            }
1995        }
1996
1997        self.advance(); // consume ']'
1998
1999        // ARM/AArch64 writeback: [Rn, #imm]!  → pre-index
2000        if self.peek().kind == TokenKind::Bang {
2001            self.advance(); // consume '!'
2002            mem.addr_mode = AddrMode::PreIndex;
2003        }
2004
2005        // Validate: RSP/ESP/SP cannot be used as a SIB index register.
2006        // In the SIB byte, index code 0b100 (RSP's base_code) means "no index."
2007        // R12 (base_code 4 + REX.X) IS valid because REX.X distinguishes it.
2008        if let Some(idx) = mem.index {
2009            if idx.base_code() == 4 && !idx.is_extended() {
2010                return Err(AsmError::Syntax {
2011                    msg: String::from("RSP/ESP/SP cannot be used as a SIB index register"),
2012                    span: open.span,
2013                });
2014            }
2015        }
2016
2017        Ok(mem)
2018    }
2019}
2020
2021/// Parse a register name — **case-insensitive**, zero heap allocations.
2022///
2023/// Architecture-aware: conflicting names like `r8`-`r15`, `sp` are resolved
2024/// based on the target architecture.
2025pub fn parse_register(name: &str, arch: Arch) -> Option<Register> {
2026    // Stack-based lowercase (register names are at most ~5 chars; 16 is plenty).
2027    let mut buf = [0u8; 16];
2028    let name = to_lower_buf(name, &mut buf);
2029    parse_register_lower(name, arch)
2030}
2031
2032/// Inner register parser — expects **already-lowered** input.
2033/// Append a parsed operand, reporting a diagnostic instead of panicking when
2034/// the instruction names more operands than any encoding takes.
2035///
2036/// The count comes straight from user input, so the fixed-capacity list has to
2037/// be filled fallibly — `OperandList::push` panics, which for a library
2038/// assembling untrusted text would be a denial of service.
2039fn push_operand(
2040    operands: &mut OperandList,
2041    op: Operand,
2042    mnemonic: &str,
2043    span: Span,
2044) -> Result<(), AsmError> {
2045    if operands.try_push(op) {
2046        Ok(())
2047    } else {
2048        Err(AsmError::InvalidOperands {
2049            detail: alloc::format!(
2050                "'{}' has more than {} operands",
2051                mnemonic,
2052                OperandList::MAX_LEN
2053            ),
2054            span,
2055        })
2056    }
2057}
2058
2059/// Narrow a lexed shift amount to the width [`ShiftAmount`] stores.
2060///
2061/// Saturating rather than truncating: an out-of-range amount must stay
2062/// out of range so the encoder still rejects it, instead of wrapping into a
2063/// value that happens to encode.
2064fn clamp_shift(n: i128) -> i32 {
2065    n.clamp(i128::from(i32::MIN), i128::from(i32::MAX)) as i32
2066}
2067
2068/// Expand an inclusive register range such as `r0-r7` into `regs`.
2069///
2070/// Only meaningful for ARM32/Thumb general-purpose registers, which are the
2071/// registers that appear in `{...}` push/pop/LDM/STM lists.
2072fn expand_register_range(
2073    start: Register,
2074    end: Register,
2075    regs: &mut Vec<Register>,
2076    span: Span,
2077) -> Result<(), AsmError> {
2078    if !start.is_arm() || !end.is_arm() {
2079        return Err(AsmError::Syntax {
2080            msg: String::from("register ranges are only supported for ARM32/Thumb registers"),
2081            span,
2082        });
2083    }
2084    let (lo, hi) = (start.arm_reg_num(), end.arm_reg_num());
2085    if lo > hi {
2086        return Err(AsmError::Syntax {
2087            msg: alloc::format!("register range r{}-r{} is inverted", lo, hi),
2088            span,
2089        });
2090    }
2091    for n in lo..=hi {
2092        if let Some(r) = Register::from_arm_reg_num(n) {
2093            regs.push(r);
2094        }
2095    }
2096    Ok(())
2097}
2098
2099fn parse_register_lower(name: &str, arch: Arch) -> Option<Register> {
2100    use Register::*;
2101
2102    // Architecture-specific fast path for ARM / AArch64 / RISC-V
2103    match arch {
2104        Arch::Arm | Arch::Thumb => return parse_register_arm(name),
2105        Arch::Aarch64 => return parse_register_aarch64(name),
2106        Arch::Rv32 | Arch::Rv64 => return parse_register_riscv(name),
2107        _ => {}
2108    }
2109
2110    // x86 / x86-64 register names
2111    match name {
2112        // 64-bit GP
2113        "rax" => Some(Rax),
2114        "rcx" => Some(Rcx),
2115        "rdx" => Some(Rdx),
2116        "rbx" => Some(Rbx),
2117        "rsp" => Some(Rsp),
2118        "rbp" => Some(Rbp),
2119        "rsi" => Some(Rsi),
2120        "rdi" => Some(Rdi),
2121        "r8" => Some(R8),
2122        "r9" => Some(R9),
2123        "r10" => Some(R10),
2124        "r11" => Some(R11),
2125        "r12" => Some(R12),
2126        "r13" => Some(R13),
2127        "r14" => Some(R14),
2128        "r15" => Some(R15),
2129        // 32-bit GP
2130        "eax" => Some(Eax),
2131        "ecx" => Some(Ecx),
2132        "edx" => Some(Edx),
2133        "ebx" => Some(Ebx),
2134        "esp" => Some(Esp),
2135        "ebp" => Some(Ebp),
2136        "esi" => Some(Esi),
2137        "edi" => Some(Edi),
2138        "r8d" => Some(R8d),
2139        "r9d" => Some(R9d),
2140        "r10d" => Some(R10d),
2141        "r11d" => Some(R11d),
2142        "r12d" => Some(R12d),
2143        "r13d" => Some(R13d),
2144        "r14d" => Some(R14d),
2145        "r15d" => Some(R15d),
2146        // 16-bit GP
2147        "ax" => Some(Ax),
2148        "cx" => Some(Cx),
2149        "dx" => Some(Dx),
2150        "bx" => Some(Bx),
2151        "sp" => Some(Sp),
2152        "bp" => Some(Bp),
2153        "si" => Some(Si),
2154        "di" => Some(Di),
2155        "r8w" => Some(R8w),
2156        "r9w" => Some(R9w),
2157        "r10w" => Some(R10w),
2158        "r11w" => Some(R11w),
2159        "r12w" => Some(R12w),
2160        "r13w" => Some(R13w),
2161        "r14w" => Some(R14w),
2162        "r15w" => Some(R15w),
2163        // 8-bit GP
2164        "al" => Some(Al),
2165        "cl" => Some(Cl),
2166        "dl" => Some(Dl),
2167        "bl" => Some(Bl),
2168        "spl" => Some(Spl),
2169        "bpl" => Some(Bpl),
2170        "sil" => Some(Sil),
2171        "dil" => Some(Dil),
2172        "ah" => Some(Ah),
2173        "ch" => Some(Ch),
2174        "dh" => Some(Dh),
2175        "bh" => Some(Bh),
2176        "r8b" => Some(R8b),
2177        "r9b" => Some(R9b),
2178        "r10b" => Some(R10b),
2179        "r11b" => Some(R11b),
2180        "r12b" => Some(R12b),
2181        "r13b" => Some(R13b),
2182        "r14b" => Some(R14b),
2183        "r15b" => Some(R15b),
2184        // Special
2185        "rip" => Some(Rip),
2186        "eip" => Some(Eip),
2187        // Segment
2188        "cs" => Some(Cs),
2189        "ds" => Some(Ds),
2190        "es" => Some(Es),
2191        "fs" => Some(Fs),
2192        "gs" => Some(Gs),
2193        "ss" => Some(Ss),
2194        // XMM
2195        "xmm0" => Some(Xmm0),
2196        "xmm1" => Some(Xmm1),
2197        "xmm2" => Some(Xmm2),
2198        "xmm3" => Some(Xmm3),
2199        "xmm4" => Some(Xmm4),
2200        "xmm5" => Some(Xmm5),
2201        "xmm6" => Some(Xmm6),
2202        "xmm7" => Some(Xmm7),
2203        "xmm8" => Some(Xmm8),
2204        "xmm9" => Some(Xmm9),
2205        "xmm10" => Some(Xmm10),
2206        "xmm11" => Some(Xmm11),
2207        "xmm12" => Some(Xmm12),
2208        "xmm13" => Some(Xmm13),
2209        "xmm14" => Some(Xmm14),
2210        "xmm15" => Some(Xmm15),
2211        // YMM
2212        "ymm0" => Some(Ymm0),
2213        "ymm1" => Some(Ymm1),
2214        "ymm2" => Some(Ymm2),
2215        "ymm3" => Some(Ymm3),
2216        "ymm4" => Some(Ymm4),
2217        "ymm5" => Some(Ymm5),
2218        "ymm6" => Some(Ymm6),
2219        "ymm7" => Some(Ymm7),
2220        "ymm8" => Some(Ymm8),
2221        "ymm9" => Some(Ymm9),
2222        "ymm10" => Some(Ymm10),
2223        "ymm11" => Some(Ymm11),
2224        "ymm12" => Some(Ymm12),
2225        "ymm13" => Some(Ymm13),
2226        "ymm14" => Some(Ymm14),
2227        "ymm15" => Some(Ymm15),
2228        // ZMM
2229        "zmm0" => Some(Zmm0),
2230        "zmm1" => Some(Zmm1),
2231        "zmm2" => Some(Zmm2),
2232        "zmm3" => Some(Zmm3),
2233        "zmm4" => Some(Zmm4),
2234        "zmm5" => Some(Zmm5),
2235        "zmm6" => Some(Zmm6),
2236        "zmm7" => Some(Zmm7),
2237        "zmm8" => Some(Zmm8),
2238        "zmm9" => Some(Zmm9),
2239        "zmm10" => Some(Zmm10),
2240        "zmm11" => Some(Zmm11),
2241        "zmm12" => Some(Zmm12),
2242        "zmm13" => Some(Zmm13),
2243        "zmm14" => Some(Zmm14),
2244        "zmm15" => Some(Zmm15),
2245        "zmm16" => Some(Zmm16),
2246        "zmm17" => Some(Zmm17),
2247        "zmm18" => Some(Zmm18),
2248        "zmm19" => Some(Zmm19),
2249        "zmm20" => Some(Zmm20),
2250        "zmm21" => Some(Zmm21),
2251        "zmm22" => Some(Zmm22),
2252        "zmm23" => Some(Zmm23),
2253        "zmm24" => Some(Zmm24),
2254        "zmm25" => Some(Zmm25),
2255        "zmm26" => Some(Zmm26),
2256        "zmm27" => Some(Zmm27),
2257        "zmm28" => Some(Zmm28),
2258        "zmm29" => Some(Zmm29),
2259        "zmm30" => Some(Zmm30),
2260        "zmm31" => Some(Zmm31),
2261        // Opmask
2262        "k0" => Some(K0),
2263        "k1" => Some(K1),
2264        "k2" => Some(K2),
2265        "k3" => Some(K3),
2266        "k4" => Some(K4),
2267        "k5" => Some(K5),
2268        "k6" => Some(K6),
2269        "k7" => Some(K7),
2270        _ => None,
2271    }
2272}
2273
2274fn is_segment_name(name: &str) -> bool {
2275    name.eq_ignore_ascii_case("cs")
2276        || name.eq_ignore_ascii_case("ds")
2277        || name.eq_ignore_ascii_case("es")
2278        || name.eq_ignore_ascii_case("fs")
2279        || name.eq_ignore_ascii_case("gs")
2280        || name.eq_ignore_ascii_case("ss")
2281}
2282
2283fn parse_segment(name: &str) -> Option<Register> {
2284    if name.eq_ignore_ascii_case("cs") {
2285        Some(Register::Cs)
2286    } else if name.eq_ignore_ascii_case("ds") {
2287        Some(Register::Ds)
2288    } else if name.eq_ignore_ascii_case("es") {
2289        Some(Register::Es)
2290    } else if name.eq_ignore_ascii_case("fs") {
2291        Some(Register::Fs)
2292    } else if name.eq_ignore_ascii_case("gs") {
2293        Some(Register::Gs)
2294    } else if name.eq_ignore_ascii_case("ss") {
2295        Some(Register::Ss)
2296    } else {
2297        None
2298    }
2299}
2300
2301/// ARM32 register name parser.
2302fn parse_register_arm(name: &str) -> Option<Register> {
2303    use Register::*;
2304    match name {
2305        "r0" => Some(ArmR0),
2306        "r1" => Some(ArmR1),
2307        "r2" => Some(ArmR2),
2308        "r3" => Some(ArmR3),
2309        "r4" => Some(ArmR4),
2310        "r5" => Some(ArmR5),
2311        "r6" => Some(ArmR6),
2312        "r7" => Some(ArmR7),
2313        "r8" => Some(ArmR8),
2314        "r9" => Some(ArmR9),
2315        "r10" => Some(ArmR10),
2316        "r11" | "fp" => Some(ArmR11),
2317        "r12" | "ip" => Some(ArmR12),
2318        "r13" | "sp" => Some(ArmSp),
2319        "r14" | "lr" => Some(ArmLr),
2320        "r15" | "pc" => Some(ArmPc),
2321        "cpsr" => Some(ArmCpsr),
2322        _ => None,
2323    }
2324}
2325
2326/// AArch64 register name parser.
2327fn parse_register_aarch64(name: &str) -> Option<Register> {
2328    use Register::*;
2329    match name {
2330        "x0" => Some(A64X0),
2331        "x1" => Some(A64X1),
2332        "x2" => Some(A64X2),
2333        "x3" => Some(A64X3),
2334        "x4" => Some(A64X4),
2335        "x5" => Some(A64X5),
2336        "x6" => Some(A64X6),
2337        "x7" => Some(A64X7),
2338        "x8" => Some(A64X8),
2339        "x9" => Some(A64X9),
2340        "x10" => Some(A64X10),
2341        "x11" => Some(A64X11),
2342        "x12" => Some(A64X12),
2343        "x13" => Some(A64X13),
2344        "x14" => Some(A64X14),
2345        "x15" => Some(A64X15),
2346        "x16" => Some(A64X16),
2347        "x17" => Some(A64X17),
2348        "x18" => Some(A64X18),
2349        "x19" => Some(A64X19),
2350        "x20" => Some(A64X20),
2351        "x21" => Some(A64X21),
2352        "x22" => Some(A64X22),
2353        "x23" => Some(A64X23),
2354        "x24" => Some(A64X24),
2355        "x25" => Some(A64X25),
2356        "x26" => Some(A64X26),
2357        "x27" => Some(A64X27),
2358        "x28" => Some(A64X28),
2359        "x29" => Some(A64X29),
2360        "x30" => Some(A64X30),
2361        "fp" => Some(A64X29),
2362        "lr" => Some(A64X30),
2363        "sp" => Some(A64Sp),
2364        "xzr" => Some(A64Xzr),
2365        "w0" => Some(A64W0),
2366        "w1" => Some(A64W1),
2367        "w2" => Some(A64W2),
2368        "w3" => Some(A64W3),
2369        "w4" => Some(A64W4),
2370        "w5" => Some(A64W5),
2371        "w6" => Some(A64W6),
2372        "w7" => Some(A64W7),
2373        "w8" => Some(A64W8),
2374        "w9" => Some(A64W9),
2375        "w10" => Some(A64W10),
2376        "w11" => Some(A64W11),
2377        "w12" => Some(A64W12),
2378        "w13" => Some(A64W13),
2379        "w14" => Some(A64W14),
2380        "w15" => Some(A64W15),
2381        "w16" => Some(A64W16),
2382        "w17" => Some(A64W17),
2383        "w18" => Some(A64W18),
2384        "w19" => Some(A64W19),
2385        "w20" => Some(A64W20),
2386        "w21" => Some(A64W21),
2387        "w22" => Some(A64W22),
2388        "w23" => Some(A64W23),
2389        "w24" => Some(A64W24),
2390        "w25" => Some(A64W25),
2391        "w26" => Some(A64W26),
2392        "w27" => Some(A64W27),
2393        "w28" => Some(A64W28),
2394        "w29" => Some(A64W29),
2395        "w30" => Some(A64W30),
2396        "wzr" => Some(A64Wzr),
2397        "wsp" => Some(A64Wsp),
2398        // SIMD/FP vector registers (V0–V31)
2399        "v0" => Some(A64V0),
2400        "v1" => Some(A64V1),
2401        "v2" => Some(A64V2),
2402        "v3" => Some(A64V3),
2403        "v4" => Some(A64V4),
2404        "v5" => Some(A64V5),
2405        "v6" => Some(A64V6),
2406        "v7" => Some(A64V7),
2407        "v8" => Some(A64V8),
2408        "v9" => Some(A64V9),
2409        "v10" => Some(A64V10),
2410        "v11" => Some(A64V11),
2411        "v12" => Some(A64V12),
2412        "v13" => Some(A64V13),
2413        "v14" => Some(A64V14),
2414        "v15" => Some(A64V15),
2415        "v16" => Some(A64V16),
2416        "v17" => Some(A64V17),
2417        "v18" => Some(A64V18),
2418        "v19" => Some(A64V19),
2419        "v20" => Some(A64V20),
2420        "v21" => Some(A64V21),
2421        "v22" => Some(A64V22),
2422        "v23" => Some(A64V23),
2423        "v24" => Some(A64V24),
2424        "v25" => Some(A64V25),
2425        "v26" => Some(A64V26),
2426        "v27" => Some(A64V27),
2427        "v28" => Some(A64V28),
2428        "v29" => Some(A64V29),
2429        "v30" => Some(A64V30),
2430        "v31" => Some(A64V31),
2431        // SIMD/FP scalar quad registers (Q0–Q31)
2432        "q0" => Some(A64Q0),
2433        "q1" => Some(A64Q1),
2434        "q2" => Some(A64Q2),
2435        "q3" => Some(A64Q3),
2436        "q4" => Some(A64Q4),
2437        "q5" => Some(A64Q5),
2438        "q6" => Some(A64Q6),
2439        "q7" => Some(A64Q7),
2440        "q8" => Some(A64Q8),
2441        "q9" => Some(A64Q9),
2442        "q10" => Some(A64Q10),
2443        "q11" => Some(A64Q11),
2444        "q12" => Some(A64Q12),
2445        "q13" => Some(A64Q13),
2446        "q14" => Some(A64Q14),
2447        "q15" => Some(A64Q15),
2448        "q16" => Some(A64Q16),
2449        "q17" => Some(A64Q17),
2450        "q18" => Some(A64Q18),
2451        "q19" => Some(A64Q19),
2452        "q20" => Some(A64Q20),
2453        "q21" => Some(A64Q21),
2454        "q22" => Some(A64Q22),
2455        "q23" => Some(A64Q23),
2456        "q24" => Some(A64Q24),
2457        "q25" => Some(A64Q25),
2458        "q26" => Some(A64Q26),
2459        "q27" => Some(A64Q27),
2460        "q28" => Some(A64Q28),
2461        "q29" => Some(A64Q29),
2462        "q30" => Some(A64Q30),
2463        "q31" => Some(A64Q31),
2464        // SIMD/FP scalar double registers (D0–D31)
2465        "d0" => Some(A64D0),
2466        "d1" => Some(A64D1),
2467        "d2" => Some(A64D2),
2468        "d3" => Some(A64D3),
2469        "d4" => Some(A64D4),
2470        "d5" => Some(A64D5),
2471        "d6" => Some(A64D6),
2472        "d7" => Some(A64D7),
2473        "d8" => Some(A64D8),
2474        "d9" => Some(A64D9),
2475        "d10" => Some(A64D10),
2476        "d11" => Some(A64D11),
2477        "d12" => Some(A64D12),
2478        "d13" => Some(A64D13),
2479        "d14" => Some(A64D14),
2480        "d15" => Some(A64D15),
2481        "d16" => Some(A64D16),
2482        "d17" => Some(A64D17),
2483        "d18" => Some(A64D18),
2484        "d19" => Some(A64D19),
2485        "d20" => Some(A64D20),
2486        "d21" => Some(A64D21),
2487        "d22" => Some(A64D22),
2488        "d23" => Some(A64D23),
2489        "d24" => Some(A64D24),
2490        "d25" => Some(A64D25),
2491        "d26" => Some(A64D26),
2492        "d27" => Some(A64D27),
2493        "d28" => Some(A64D28),
2494        "d29" => Some(A64D29),
2495        "d30" => Some(A64D30),
2496        "d31" => Some(A64D31),
2497        // SIMD/FP scalar single registers (S0–S31)
2498        "s0" => Some(A64S0),
2499        "s1" => Some(A64S1),
2500        "s2" => Some(A64S2),
2501        "s3" => Some(A64S3),
2502        "s4" => Some(A64S4),
2503        "s5" => Some(A64S5),
2504        "s6" => Some(A64S6),
2505        "s7" => Some(A64S7),
2506        "s8" => Some(A64S8),
2507        "s9" => Some(A64S9),
2508        "s10" => Some(A64S10),
2509        "s11" => Some(A64S11),
2510        "s12" => Some(A64S12),
2511        "s13" => Some(A64S13),
2512        "s14" => Some(A64S14),
2513        "s15" => Some(A64S15),
2514        "s16" => Some(A64S16),
2515        "s17" => Some(A64S17),
2516        "s18" => Some(A64S18),
2517        "s19" => Some(A64S19),
2518        "s20" => Some(A64S20),
2519        "s21" => Some(A64S21),
2520        "s22" => Some(A64S22),
2521        "s23" => Some(A64S23),
2522        "s24" => Some(A64S24),
2523        "s25" => Some(A64S25),
2524        "s26" => Some(A64S26),
2525        "s27" => Some(A64S27),
2526        "s28" => Some(A64S28),
2527        "s29" => Some(A64S29),
2528        "s30" => Some(A64S30),
2529        "s31" => Some(A64S31),
2530        // SIMD/FP scalar half registers (H0–H31)
2531        "h0" => Some(A64H0),
2532        "h1" => Some(A64H1),
2533        "h2" => Some(A64H2),
2534        "h3" => Some(A64H3),
2535        "h4" => Some(A64H4),
2536        "h5" => Some(A64H5),
2537        "h6" => Some(A64H6),
2538        "h7" => Some(A64H7),
2539        "h8" => Some(A64H8),
2540        "h9" => Some(A64H9),
2541        "h10" => Some(A64H10),
2542        "h11" => Some(A64H11),
2543        "h12" => Some(A64H12),
2544        "h13" => Some(A64H13),
2545        "h14" => Some(A64H14),
2546        "h15" => Some(A64H15),
2547        "h16" => Some(A64H16),
2548        "h17" => Some(A64H17),
2549        "h18" => Some(A64H18),
2550        "h19" => Some(A64H19),
2551        "h20" => Some(A64H20),
2552        "h21" => Some(A64H21),
2553        "h22" => Some(A64H22),
2554        "h23" => Some(A64H23),
2555        "h24" => Some(A64H24),
2556        "h25" => Some(A64H25),
2557        "h26" => Some(A64H26),
2558        "h27" => Some(A64H27),
2559        "h28" => Some(A64H28),
2560        "h29" => Some(A64H29),
2561        "h30" => Some(A64H30),
2562        "h31" => Some(A64H31),
2563        // SIMD/FP scalar byte registers (B0–B31)
2564        "b0" => Some(A64B0),
2565        "b1" => Some(A64B1),
2566        "b2" => Some(A64B2),
2567        "b3" => Some(A64B3),
2568        "b4" => Some(A64B4),
2569        "b5" => Some(A64B5),
2570        "b6" => Some(A64B6),
2571        "b7" => Some(A64B7),
2572        "b8" => Some(A64B8),
2573        "b9" => Some(A64B9),
2574        "b10" => Some(A64B10),
2575        "b11" => Some(A64B11),
2576        "b12" => Some(A64B12),
2577        "b13" => Some(A64B13),
2578        "b14" => Some(A64B14),
2579        "b15" => Some(A64B15),
2580        "b16" => Some(A64B16),
2581        "b17" => Some(A64B17),
2582        "b18" => Some(A64B18),
2583        "b19" => Some(A64B19),
2584        "b20" => Some(A64B20),
2585        "b21" => Some(A64B21),
2586        "b22" => Some(A64B22),
2587        "b23" => Some(A64B23),
2588        "b24" => Some(A64B24),
2589        "b25" => Some(A64B25),
2590        "b26" => Some(A64B26),
2591        "b27" => Some(A64B27),
2592        "b28" => Some(A64B28),
2593        "b29" => Some(A64B29),
2594        "b30" => Some(A64B30),
2595        "b31" => Some(A64B31),
2596        // SVE scalable vector registers (Z0–Z31)
2597        "z0" => Some(A64Z0),
2598        "z1" => Some(A64Z1),
2599        "z2" => Some(A64Z2),
2600        "z3" => Some(A64Z3),
2601        "z4" => Some(A64Z4),
2602        "z5" => Some(A64Z5),
2603        "z6" => Some(A64Z6),
2604        "z7" => Some(A64Z7),
2605        "z8" => Some(A64Z8),
2606        "z9" => Some(A64Z9),
2607        "z10" => Some(A64Z10),
2608        "z11" => Some(A64Z11),
2609        "z12" => Some(A64Z12),
2610        "z13" => Some(A64Z13),
2611        "z14" => Some(A64Z14),
2612        "z15" => Some(A64Z15),
2613        "z16" => Some(A64Z16),
2614        "z17" => Some(A64Z17),
2615        "z18" => Some(A64Z18),
2616        "z19" => Some(A64Z19),
2617        "z20" => Some(A64Z20),
2618        "z21" => Some(A64Z21),
2619        "z22" => Some(A64Z22),
2620        "z23" => Some(A64Z23),
2621        "z24" => Some(A64Z24),
2622        "z25" => Some(A64Z25),
2623        "z26" => Some(A64Z26),
2624        "z27" => Some(A64Z27),
2625        "z28" => Some(A64Z28),
2626        "z29" => Some(A64Z29),
2627        "z30" => Some(A64Z30),
2628        "z31" => Some(A64Z31),
2629        // SVE predicate registers (P0–P15)
2630        "p0" => Some(A64P0),
2631        "p1" => Some(A64P1),
2632        "p2" => Some(A64P2),
2633        "p3" => Some(A64P3),
2634        "p4" => Some(A64P4),
2635        "p5" => Some(A64P5),
2636        "p6" => Some(A64P6),
2637        "p7" => Some(A64P7),
2638        "p8" => Some(A64P8),
2639        "p9" => Some(A64P9),
2640        "p10" => Some(A64P10),
2641        "p11" => Some(A64P11),
2642        "p12" => Some(A64P12),
2643        "p13" => Some(A64P13),
2644        "p14" => Some(A64P14),
2645        "p15" => Some(A64P15),
2646        _ => None,
2647    }
2648}
2649
2650/// Parse a RISC-V register name, supporting both hardware names (x0–x31) and
2651/// ABI names (zero, ra, sp, gp, tp, t0–t6, s0–s11, a0–a7, fp).
2652fn parse_register_riscv(name: &str) -> Option<Register> {
2653    use Register::*;
2654    match name {
2655        // Hardware names
2656        "x0" => Some(RvX0),
2657        "x1" => Some(RvX1),
2658        "x2" => Some(RvX2),
2659        "x3" => Some(RvX3),
2660        "x4" => Some(RvX4),
2661        "x5" => Some(RvX5),
2662        "x6" => Some(RvX6),
2663        "x7" => Some(RvX7),
2664        "x8" => Some(RvX8),
2665        "x9" => Some(RvX9),
2666        "x10" => Some(RvX10),
2667        "x11" => Some(RvX11),
2668        "x12" => Some(RvX12),
2669        "x13" => Some(RvX13),
2670        "x14" => Some(RvX14),
2671        "x15" => Some(RvX15),
2672        "x16" => Some(RvX16),
2673        "x17" => Some(RvX17),
2674        "x18" => Some(RvX18),
2675        "x19" => Some(RvX19),
2676        "x20" => Some(RvX20),
2677        "x21" => Some(RvX21),
2678        "x22" => Some(RvX22),
2679        "x23" => Some(RvX23),
2680        "x24" => Some(RvX24),
2681        "x25" => Some(RvX25),
2682        "x26" => Some(RvX26),
2683        "x27" => Some(RvX27),
2684        "x28" => Some(RvX28),
2685        "x29" => Some(RvX29),
2686        "x30" => Some(RvX30),
2687        "x31" => Some(RvX31),
2688        // ABI names
2689        "zero" => Some(RvX0),
2690        "ra" => Some(RvX1),
2691        "sp" => Some(RvX2),
2692        "gp" => Some(RvX3),
2693        "tp" => Some(RvX4),
2694        "t0" => Some(RvX5),
2695        "t1" => Some(RvX6),
2696        "t2" => Some(RvX7),
2697        "s0" => Some(RvX8),
2698        "fp" => Some(RvX8), // fp is alias for s0
2699        "s1" => Some(RvX9),
2700        "a0" => Some(RvX10),
2701        "a1" => Some(RvX11),
2702        "a2" => Some(RvX12),
2703        "a3" => Some(RvX13),
2704        "a4" => Some(RvX14),
2705        "a5" => Some(RvX15),
2706        "a6" => Some(RvX16),
2707        "a7" => Some(RvX17),
2708        "s2" => Some(RvX18),
2709        "s3" => Some(RvX19),
2710        "s4" => Some(RvX20),
2711        "s5" => Some(RvX21),
2712        "s6" => Some(RvX22),
2713        "s7" => Some(RvX23),
2714        "s8" => Some(RvX24),
2715        "s9" => Some(RvX25),
2716        "s10" => Some(RvX26),
2717        "s11" => Some(RvX27),
2718        "t3" => Some(RvX28),
2719        "t4" => Some(RvX29),
2720        "t5" => Some(RvX30),
2721        "t6" => Some(RvX31),
2722        // FP hardware names
2723        "f0" => Some(RvF0),
2724        "f1" => Some(RvF1),
2725        "f2" => Some(RvF2),
2726        "f3" => Some(RvF3),
2727        "f4" => Some(RvF4),
2728        "f5" => Some(RvF5),
2729        "f6" => Some(RvF6),
2730        "f7" => Some(RvF7),
2731        "f8" => Some(RvF8),
2732        "f9" => Some(RvF9),
2733        "f10" => Some(RvF10),
2734        "f11" => Some(RvF11),
2735        "f12" => Some(RvF12),
2736        "f13" => Some(RvF13),
2737        "f14" => Some(RvF14),
2738        "f15" => Some(RvF15),
2739        "f16" => Some(RvF16),
2740        "f17" => Some(RvF17),
2741        "f18" => Some(RvF18),
2742        "f19" => Some(RvF19),
2743        "f20" => Some(RvF20),
2744        "f21" => Some(RvF21),
2745        "f22" => Some(RvF22),
2746        "f23" => Some(RvF23),
2747        "f24" => Some(RvF24),
2748        "f25" => Some(RvF25),
2749        "f26" => Some(RvF26),
2750        "f27" => Some(RvF27),
2751        "f28" => Some(RvF28),
2752        "f29" => Some(RvF29),
2753        "f30" => Some(RvF30),
2754        "f31" => Some(RvF31),
2755        // FP ABI names
2756        "ft0" => Some(RvF0),
2757        "ft1" => Some(RvF1),
2758        "ft2" => Some(RvF2),
2759        "ft3" => Some(RvF3),
2760        "ft4" => Some(RvF4),
2761        "ft5" => Some(RvF5),
2762        "ft6" => Some(RvF6),
2763        "ft7" => Some(RvF7),
2764        "fs0" => Some(RvF8),
2765        "fs1" => Some(RvF9),
2766        "fa0" => Some(RvF10),
2767        "fa1" => Some(RvF11),
2768        "fa2" => Some(RvF12),
2769        "fa3" => Some(RvF13),
2770        "fa4" => Some(RvF14),
2771        "fa5" => Some(RvF15),
2772        "fa6" => Some(RvF16),
2773        "fa7" => Some(RvF17),
2774        "fs2" => Some(RvF18),
2775        "fs3" => Some(RvF19),
2776        "fs4" => Some(RvF20),
2777        "fs5" => Some(RvF21),
2778        "fs6" => Some(RvF22),
2779        "fs7" => Some(RvF23),
2780        "fs8" => Some(RvF24),
2781        "fs9" => Some(RvF25),
2782        "fs10" => Some(RvF26),
2783        "fs11" => Some(RvF27),
2784        "ft8" => Some(RvF28),
2785        "ft9" => Some(RvF29),
2786        "ft10" => Some(RvF30),
2787        "ft11" => Some(RvF31),
2788        // Vector registers (V extension) — hardware names
2789        "v0" => Some(RvV0),
2790        "v1" => Some(RvV1),
2791        "v2" => Some(RvV2),
2792        "v3" => Some(RvV3),
2793        "v4" => Some(RvV4),
2794        "v5" => Some(RvV5),
2795        "v6" => Some(RvV6),
2796        "v7" => Some(RvV7),
2797        "v8" => Some(RvV8),
2798        "v9" => Some(RvV9),
2799        "v10" => Some(RvV10),
2800        "v11" => Some(RvV11),
2801        "v12" => Some(RvV12),
2802        "v13" => Some(RvV13),
2803        "v14" => Some(RvV14),
2804        "v15" => Some(RvV15),
2805        "v16" => Some(RvV16),
2806        "v17" => Some(RvV17),
2807        "v18" => Some(RvV18),
2808        "v19" => Some(RvV19),
2809        "v20" => Some(RvV20),
2810        "v21" => Some(RvV21),
2811        "v22" => Some(RvV22),
2812        "v23" => Some(RvV23),
2813        "v24" => Some(RvV24),
2814        "v25" => Some(RvV25),
2815        "v26" => Some(RvV26),
2816        "v27" => Some(RvV27),
2817        "v28" => Some(RvV28),
2818        "v29" => Some(RvV29),
2819        "v30" => Some(RvV30),
2820        "v31" => Some(RvV31),
2821        _ => None,
2822    }
2823}
2824
2825/// Convenience: parse assembly text directly into statements.
2826pub fn parse_str(source: &str) -> Result<Vec<Statement>, AsmError> {
2827    let tokens = crate::lexer::tokenize(source)?;
2828    parse(&tokens)
2829}
2830
2831/// Strip AT&T mnemonic size suffix and return (base_mnemonic, size_hint).
2832///
2833/// GAS convention: `movq` → `mov` + Qword, `addl` → `add` + Dword, etc.
2834/// Also handles AT&T-specific mnemonics that have no Intel equivalent via
2835/// simple suffix stripping (e.g., `movzbl` → `movzx`, `cltq` → `cdqe`).
2836fn strip_att_suffix(mnemonic: &str) -> Option<(Mnemonic, OperandSize)> {
2837    // AT&T-specific mnemonic translations — these have completely different
2838    // naming conventions from Intel and cannot be derived by suffix stripping.
2839    let att_translations: &[(&str, &str, OperandSize)] = &[
2840        // movzx variants: movz{src_size}{dst_size}
2841        ("movzbl", "movzx", OperandSize::Dword),
2842        ("movzbw", "movzx", OperandSize::Word),
2843        ("movzbq", "movzx", OperandSize::Qword),
2844        ("movzwl", "movzx", OperandSize::Dword),
2845        ("movzwq", "movzx", OperandSize::Qword),
2846        // movsx variants: movs{src_size}{dst_size}
2847        ("movsbl", "movsx", OperandSize::Dword),
2848        ("movsbw", "movsx", OperandSize::Word),
2849        ("movsbq", "movsx", OperandSize::Qword),
2850        ("movswl", "movsx", OperandSize::Dword),
2851        ("movswq", "movsx", OperandSize::Qword),
2852        ("movslq", "movsxd", OperandSize::Qword),
2853        // Sign/zero-extend accumulator
2854        ("cbtw", "cbw", OperandSize::Word),
2855        ("cwtl", "cwde", OperandSize::Dword),
2856        ("cwtd", "cwd", OperandSize::Word),
2857        ("cltd", "cdq", OperandSize::Dword),
2858        ("cltq", "cdqe", OperandSize::Qword),
2859        ("cqto", "cqo", OperandSize::Qword),
2860    ];
2861
2862    for &(att, intel, size) in att_translations {
2863        if mnemonic == att {
2864            return Some((Mnemonic::from(intel), size));
2865        }
2866    }
2867
2868    if mnemonic.len() < 2 {
2869        return None;
2870    }
2871
2872    // Mnemonics that should NOT be stripped — they end in b/w/l/q naturally
2873    // and are not size-suffixed GAS mnemonics.
2874    let no_strip = [
2875        "call",
2876        "jmp",
2877        "ret",
2878        "nop",
2879        "hlt",
2880        "int",
2881        "syscall",
2882        "sysenter",
2883        "sysexit",
2884        "cpuid",
2885        "rdtsc",
2886        "rdtscp",
2887        "ud2",
2888        "leave",
2889        "enter",
2890        "pushf",
2891        "popf",
2892        "pushfq",
2893        "popfq",
2894        "lahf",
2895        "sahf",
2896        "clc",
2897        "stc",
2898        "cmc",
2899        "cld",
2900        "std",
2901        "cli",
2902        "sti",
2903        "rep",
2904        "repe",
2905        "repne",
2906        "repz",
2907        "repnz",
2908        "lock",
2909        "pause",
2910        "mfence",
2911        "lfence",
2912        "sfence",
2913        "endbr64",
2914        "endbr32",
2915        "iretq",
2916        "cdq",
2917        "cqo",
2918        "cbw",
2919        "cwde",
2920        "cdqe",
2921        "cwd",
2922        "xlat",
2923        "xlatb",
2924        "swapgs",
2925        "wrmsr",
2926        "rdmsr",
2927        "invd",
2928        "wbinvd",
2929        "clts",
2930        "monitor",
2931        "mwait",
2932        "rdrand",
2933        "rdseed",
2934        "xtest",
2935        "xend",
2936        "vzeroall",
2937        "vzeroupper",
2938        "int3",
2939        // setcc and cmovcc mnemonics — they end in condition codes, not size suffixes
2940        "setal",
2941        "setbl",
2942        "setcl",
2943        "setgl",
2944        "setol",
2945        "setnl",
2946        "setpl",
2947        // condition code endings
2948        "jal",
2949        "jbl",
2950        "jcl",
2951        "jgl",
2952        "jol",
2953        "jnl",
2954        "jpl",
2955        // string ops — movsb/w/d/q, stosb/w/d/q, lodsb/w/d/q, scasb/w/d/q, cmpsb/w/d/q
2956        "movsb",
2957        "movsw",
2958        "movsd",
2959        "movsq",
2960        "stosb",
2961        "stosw",
2962        "stosd",
2963        "stosq",
2964        "lodsb",
2965        "lodsw",
2966        "lodsd",
2967        "lodsq",
2968        "scasb",
2969        "scasw",
2970        "scasd",
2971        "scasq",
2972        "cmpsb",
2973        "cmpsw",
2974        "cmpsd",
2975        "cmpsq",
2976        "insb",
2977        "insw",
2978        "insd",
2979        "outsb",
2980        "outsw",
2981        "outsd",
2982        // IN/OUT with size are their own mnemonics (inb, outb, etc.)
2983        "inb",
2984        "inw",
2985        "inl",
2986        "outb",
2987        "outw",
2988        "outl",
2989        // Loop family
2990        "loop",
2991        "loope",
2992        "loopne",
2993        "loopz",
2994        "loopnz",
2995        "jecxz",
2996        "jrcxz",
2997        // cmpxchg family
2998        "cmpxchg8b",
2999        "cmpxchg16b",
3000        // bswap, xchg, cmpxchg, xadd
3001        "bswap",
3002    ];
3003
3004    let suffix = mnemonic.as_bytes()[mnemonic.len() - 1];
3005    let size = match suffix {
3006        b'b' => OperandSize::Byte,
3007        b'w' => OperandSize::Word,
3008        b'l' => OperandSize::Dword,
3009        b'q' => OperandSize::Qword,
3010        _ => return None,
3011    };
3012
3013    if no_strip.contains(&mnemonic) {
3014        return None;
3015    }
3016
3017    let base = &mnemonic[..mnemonic.len() - 1];
3018
3019    // Don't strip if the base would be empty
3020    if base.is_empty() {
3021        return None;
3022    }
3023
3024    // Known base mnemonics that commonly accept size suffixes
3025    let known_bases = [
3026        "mov", "add", "sub", "adc", "sbb", "and", "or", "xor", "cmp", "test", "push", "pop", "inc",
3027        "dec", "neg", "not", "mul", "imul", "div", "idiv", "lea", "xchg", "cmpxchg", "xadd",
3028        "movzx", "movsx", "movsxd", "shl", "shr", "sar", "rol", "ror", "rcl", "rcr", "bt", "bts",
3029        "btr", "btc", "bsf", "bsr", "set", "cmov", // + condition code suffix
3030        "in", "out", "movabs",
3031    ];
3032
3033    // Direct match or prefix match for setcc/cmovcc
3034    if known_bases.contains(&base) {
3035        return Some((Mnemonic::from(base), size));
3036    }
3037
3038    // cmovcc: cmovnel → cmovne (base = cmovne, suffix was 'l')
3039    // setcc: setnel → setne (won't happen typically, but handle it)
3040    // Jcc: jnel → jne (won't happen due to no_strip, but be safe)
3041    if base.starts_with("cmov") || base.starts_with("set") || base.starts_with('j') {
3042        return Some((Mnemonic::from(base), size));
3043    }
3044
3045    // Fallback: strip and return — GAS accepts suffixes on most mnemonics
3046    Some((Mnemonic::from(base), size))
3047}
3048
3049#[cfg(test)]
3050mod tests {
3051    use super::*;
3052
3053    fn parse_one(src: &str) -> Statement {
3054        let stmts = parse_str(src).unwrap();
3055        assert_eq!(
3056            stmts.len(),
3057            1,
3058            "expected 1 statement, got {}: {:?}",
3059            stmts.len(),
3060            stmts
3061        );
3062        stmts.into_iter().next().unwrap()
3063    }
3064
3065    fn parse_instr(src: &str) -> Instruction {
3066        match parse_one(src) {
3067            Statement::Instruction(i) => i,
3068            s => panic!("expected instruction, got {:?}", s),
3069        }
3070    }
3071
3072    // === Basic Instructions ===
3073
3074    #[test]
3075    fn parse_nop() {
3076        let i = parse_instr("nop");
3077        assert_eq!(i.mnemonic, "nop");
3078        assert!(i.operands.is_empty());
3079    }
3080
3081    #[test]
3082    fn parse_ret() {
3083        let i = parse_instr("ret");
3084        assert_eq!(i.mnemonic, "ret");
3085    }
3086
3087    #[test]
3088    fn parse_syscall() {
3089        let i = parse_instr("syscall");
3090        assert_eq!(i.mnemonic, "syscall");
3091    }
3092
3093    // === Register-Register ===
3094
3095    #[test]
3096    fn parse_mov_reg_reg() {
3097        let i = parse_instr("mov rax, rbx");
3098        assert_eq!(i.mnemonic, "mov");
3099        assert_eq!(i.operands.len(), 2);
3100        assert_eq!(i.operands[0], Operand::Register(Register::Rax));
3101        assert_eq!(i.operands[1], Operand::Register(Register::Rbx));
3102    }
3103
3104    #[test]
3105    fn parse_add_r32() {
3106        let i = parse_instr("add eax, ecx");
3107        assert_eq!(i.mnemonic, "add");
3108        assert_eq!(i.operands[0], Operand::Register(Register::Eax));
3109        assert_eq!(i.operands[1], Operand::Register(Register::Ecx));
3110    }
3111
3112    #[test]
3113    fn parse_xor_r8() {
3114        let i = parse_instr("xor al, bl");
3115        assert_eq!(i.operands[0], Operand::Register(Register::Al));
3116        assert_eq!(i.operands[1], Operand::Register(Register::Bl));
3117    }
3118
3119    // === Register-Immediate ===
3120
3121    #[test]
3122    fn parse_mov_reg_imm() {
3123        let i = parse_instr("mov rax, 42");
3124        assert_eq!(i.operands[0], Operand::Register(Register::Rax));
3125        assert_eq!(i.operands[1], Operand::Immediate(42));
3126    }
3127
3128    #[test]
3129    fn parse_mov_reg_hex() {
3130        let i = parse_instr("mov rdi, 0xDEAD");
3131        assert_eq!(i.operands[1], Operand::Immediate(0xDEAD));
3132    }
3133
3134    #[test]
3135    fn parse_add_imm_negative() {
3136        let i = parse_instr("add rsp, -8");
3137        assert_eq!(i.operands[1], Operand::Immediate(-8));
3138    }
3139
3140    #[test]
3141    fn parse_char_immediate() {
3142        let i = parse_instr("mov al, 'A'");
3143        assert_eq!(i.operands[1], Operand::Immediate(65));
3144    }
3145
3146    // === Memory Operands ===
3147
3148    #[test]
3149    fn parse_mem_base() {
3150        let i = parse_instr("mov rax, [rbx]");
3151        assert_eq!(i.operands[0], Operand::Register(Register::Rax));
3152        match &i.operands[1] {
3153            Operand::Memory(m) => {
3154                assert_eq!(m.base, Some(Register::Rbx));
3155                assert_eq!(m.index, None);
3156                assert_eq!(m.disp, 0);
3157            }
3158            _ => panic!("expected memory operand"),
3159        }
3160    }
3161
3162    #[test]
3163    fn parse_mem_base_disp() {
3164        let i = parse_instr("mov rax, [rbp + 8]");
3165        match &i.operands[1] {
3166            Operand::Memory(m) => {
3167                assert_eq!(m.base, Some(Register::Rbp));
3168                assert_eq!(m.disp, 8);
3169            }
3170            _ => panic!("expected memory operand"),
3171        }
3172    }
3173
3174    #[test]
3175    fn parse_mem_base_neg_disp() {
3176        let i = parse_instr("mov rax, [rbp - 0x10]");
3177        match &i.operands[1] {
3178            Operand::Memory(m) => {
3179                assert_eq!(m.base, Some(Register::Rbp));
3180                assert_eq!(m.disp, -16);
3181            }
3182            _ => panic!("expected memory operand"),
3183        }
3184    }
3185
3186    #[test]
3187    fn parse_mem_base_index() {
3188        let i = parse_instr("mov rax, [rbx + rcx]");
3189        match &i.operands[1] {
3190            Operand::Memory(m) => {
3191                assert_eq!(m.base, Some(Register::Rbx));
3192                assert_eq!(m.index, Some(Register::Rcx));
3193                assert_eq!(m.scale, 1);
3194            }
3195            _ => panic!("expected memory operand"),
3196        }
3197    }
3198
3199    #[test]
3200    fn parse_mem_base_index_scale() {
3201        let i = parse_instr("lea rax, [rbx + rcx*8]");
3202        match &i.operands[1] {
3203            Operand::Memory(m) => {
3204                assert_eq!(m.base, Some(Register::Rbx));
3205                assert_eq!(m.index, Some(Register::Rcx));
3206                assert_eq!(m.scale, 8);
3207            }
3208            _ => panic!("expected memory operand"),
3209        }
3210    }
3211
3212    #[test]
3213    fn parse_mem_full() {
3214        let i = parse_instr("mov rax, [rbx + rcx*4 + 16]");
3215        match &i.operands[1] {
3216            Operand::Memory(m) => {
3217                assert_eq!(m.base, Some(Register::Rbx));
3218                assert_eq!(m.index, Some(Register::Rcx));
3219                assert_eq!(m.scale, 4);
3220                assert_eq!(m.disp, 16);
3221            }
3222            _ => panic!("expected memory operand"),
3223        }
3224    }
3225
3226    #[test]
3227    fn parse_mem_disp_only() {
3228        let i = parse_instr("mov rax, [0x1000]");
3229        match &i.operands[1] {
3230            Operand::Memory(m) => {
3231                assert_eq!(m.base, None);
3232                assert_eq!(m.disp, 0x1000);
3233            }
3234            _ => panic!("expected memory operand"),
3235        }
3236    }
3237
3238    // === Size Hints ===
3239
3240    #[test]
3241    fn parse_byte_ptr() {
3242        let i = parse_instr("mov byte ptr [rax], 0");
3243        assert_eq!(i.size_hint, Some(OperandSize::Byte));
3244        match &i.operands[0] {
3245            Operand::Memory(m) => assert_eq!(m.base, Some(Register::Rax)),
3246            _ => panic!("expected memory operand"),
3247        }
3248    }
3249
3250    #[test]
3251    fn parse_qword_no_ptr() {
3252        let i = parse_instr("mov qword [rax], 0");
3253        assert_eq!(i.size_hint, Some(OperandSize::Qword));
3254    }
3255
3256    #[test]
3257    fn parse_dword_ptr() {
3258        let i = parse_instr("add dword ptr [rbp - 4], 1");
3259        assert_eq!(i.size_hint, Some(OperandSize::Dword));
3260    }
3261
3262    // === Labels ===
3263
3264    #[test]
3265    fn parse_label_def() {
3266        let stmt = parse_one("start:");
3267        match stmt {
3268            Statement::Label(name, _) => assert_eq!(name, "start"),
3269            _ => panic!("expected label"),
3270        }
3271    }
3272
3273    #[test]
3274    fn parse_label_ref() {
3275        let i = parse_instr("jmp loop");
3276        assert_eq!(i.operands[0], Operand::Label(String::from("loop")));
3277    }
3278
3279    #[test]
3280    fn parse_call_label() {
3281        let i = parse_instr("call printf");
3282        assert_eq!(i.operands[0], Operand::Label(String::from("printf")));
3283    }
3284
3285    #[test]
3286    fn parse_label_with_offset() {
3287        let i = parse_instr("lea rax, data + 4");
3288        match &i.operands[1] {
3289            Operand::Expression(Expr::Add(l, r)) => {
3290                assert_eq!(**l, Expr::Label(String::from("data")));
3291                assert_eq!(**r, Expr::Num(4));
3292            }
3293            _ => panic!("expected expression operand"),
3294        }
3295    }
3296
3297    // === Prefixes ===
3298
3299    #[test]
3300    fn parse_lock_prefix() {
3301        let i = parse_instr("lock add [rax], 1");
3302        assert_eq!(i.prefixes, vec![Prefix::Lock]);
3303        assert_eq!(i.mnemonic, "add");
3304    }
3305
3306    #[test]
3307    fn parse_rep_prefix() {
3308        let i = parse_instr("rep movsb");
3309        assert_eq!(i.prefixes, vec![Prefix::Rep]);
3310        assert_eq!(i.mnemonic, "movsb");
3311    }
3312
3313    // === Directives ===
3314
3315    #[test]
3316    fn parse_byte_directive() {
3317        let stmt = parse_one(".byte 0x90, 0xCC");
3318        match stmt {
3319            Statement::Data(d) => {
3320                assert_eq!(d.size, DataSize::Byte);
3321                assert_eq!(
3322                    d.values,
3323                    vec![DataValue::Integer(0x90), DataValue::Integer(0xCC)]
3324                );
3325            }
3326            _ => panic!("expected data"),
3327        }
3328    }
3329
3330    #[test]
3331    fn parse_word_directive() {
3332        let stmt = parse_one(".word 0x1234");
3333        match stmt {
3334            Statement::Data(d) => {
3335                assert_eq!(d.size, DataSize::Word);
3336                assert_eq!(d.values, vec![DataValue::Integer(0x1234)]);
3337            }
3338            _ => panic!("expected data"),
3339        }
3340    }
3341
3342    #[test]
3343    fn parse_ascii_directive() {
3344        let stmt = parse_one(".ascii \"hello\"");
3345        match stmt {
3346            Statement::Data(d) => {
3347                assert_eq!(d.size, DataSize::Byte);
3348                assert_eq!(d.values, vec![DataValue::Bytes(b"hello".to_vec())]);
3349            }
3350            _ => panic!("expected data"),
3351        }
3352    }
3353
3354    #[test]
3355    fn parse_asciz_null_terminates() {
3356        let stmt = parse_one(".asciz \"ok\"");
3357        match stmt {
3358            Statement::Data(d) => {
3359                assert_eq!(d.values, vec![DataValue::Bytes(b"ok\0".to_vec())]);
3360            }
3361            _ => panic!("expected data"),
3362        }
3363    }
3364
3365    #[test]
3366    fn parse_equ_directive() {
3367        let stmt = parse_one(".equ SYS_WRITE, 1");
3368        match stmt {
3369            Statement::Const(c) => {
3370                assert_eq!(c.name, "SYS_WRITE");
3371                assert_eq!(c.value, 1);
3372            }
3373            _ => panic!("expected const"),
3374        }
3375    }
3376
3377    #[test]
3378    fn parse_align_directive() {
3379        let stmt = parse_one(".align 16");
3380        match stmt {
3381            Statement::Align(a) => {
3382                assert_eq!(a.alignment, 16);
3383                assert_eq!(a.fill, None);
3384            }
3385            _ => panic!("expected align"),
3386        }
3387    }
3388
3389    #[test]
3390    fn parse_p2align_directive() {
3391        let stmt = parse_one(".p2align 4");
3392        match stmt {
3393            Statement::Align(a) => {
3394                assert_eq!(a.alignment, 16); // 2^4 = 16
3395            }
3396            _ => panic!("expected align"),
3397        }
3398    }
3399
3400    #[test]
3401    fn parse_fill_directive() {
3402        let stmt = parse_one(".fill 10, 1, 0x90");
3403        match stmt {
3404            Statement::Fill(f) => {
3405                assert_eq!(f.count, 10);
3406                assert_eq!(f.size, 1);
3407                assert_eq!(f.value, 0x90);
3408            }
3409            _ => panic!("expected fill"),
3410        }
3411    }
3412
3413    #[test]
3414    fn parse_space_directive() {
3415        let stmt = parse_one(".space 64");
3416        match stmt {
3417            Statement::Space(s) => {
3418                assert_eq!(s.size, 64);
3419                assert_eq!(s.fill, 0);
3420            }
3421            _ => panic!("expected space"),
3422        }
3423    }
3424
3425    #[test]
3426    fn parse_org_directive() {
3427        let stmt = parse_one(".org 0x1000");
3428        match stmt {
3429            Statement::Org(o) => {
3430                assert_eq!(o.offset, 0x1000);
3431                assert_eq!(o.fill, 0x00);
3432            }
3433            _ => panic!("expected org"),
3434        }
3435    }
3436
3437    #[test]
3438    fn parse_org_with_fill() {
3439        let stmt = parse_one(".org 0x100, 0xFF");
3440        match stmt {
3441            Statement::Org(o) => {
3442                assert_eq!(o.offset, 0x100);
3443                assert_eq!(o.fill, 0xFF);
3444            }
3445            _ => panic!("expected org"),
3446        }
3447    }
3448
3449    // === Multi-statement ===
3450
3451    #[test]
3452    fn parse_multi_line() {
3453        let stmts = parse_str("nop\nret").unwrap();
3454        assert_eq!(stmts.len(), 2);
3455        match (&stmts[0], &stmts[1]) {
3456            (Statement::Instruction(i1), Statement::Instruction(i2)) => {
3457                assert_eq!(i1.mnemonic, "nop");
3458                assert_eq!(i2.mnemonic, "ret");
3459            }
3460            _ => panic!("expected two instructions"),
3461        }
3462    }
3463
3464    #[test]
3465    fn parse_label_and_instruction() {
3466        let stmts = parse_str("start:\n  mov rax, 1").unwrap();
3467        assert_eq!(stmts.len(), 2);
3468        assert!(matches!(&stmts[0], Statement::Label(name, _) if name == "start"));
3469        assert!(matches!(&stmts[1], Statement::Instruction(_)));
3470    }
3471
3472    #[test]
3473    fn parse_semicolon_separated() {
3474        let stmts = parse_str("nop; ret").unwrap();
3475        assert_eq!(stmts.len(), 2);
3476    }
3477
3478    // === Case Insensitivity ===
3479
3480    #[test]
3481    fn case_insensitive_mnemonic() {
3482        let i = parse_instr("MOV RAX, RBX");
3483        assert_eq!(i.mnemonic, "mov");
3484        assert_eq!(i.operands[0], Operand::Register(Register::Rax));
3485    }
3486
3487    #[test]
3488    fn case_insensitive_register() {
3489        let i = parse_instr("xor EAX, eax");
3490        assert_eq!(i.operands[0], Operand::Register(Register::Eax));
3491        assert_eq!(i.operands[1], Operand::Register(Register::Eax));
3492    }
3493
3494    // === Extended Registers ===
3495
3496    #[test]
3497    fn parse_extended_reg() {
3498        let i = parse_instr("mov r8, r15");
3499        assert_eq!(i.operands[0], Operand::Register(Register::R8));
3500        assert_eq!(i.operands[1], Operand::Register(Register::R15));
3501    }
3502
3503    #[test]
3504    fn parse_extended_reg_dword() {
3505        let i = parse_instr("mov r8d, r15d");
3506        assert_eq!(i.operands[0], Operand::Register(Register::R8d));
3507        assert_eq!(i.operands[1], Operand::Register(Register::R15d));
3508    }
3509
3510    // === Edge Cases ===
3511
3512    #[test]
3513    fn parse_push_pop() {
3514        let i = parse_instr("push rbp");
3515        assert_eq!(i.mnemonic, "push");
3516        assert_eq!(i.operands[0], Operand::Register(Register::Rbp));
3517    }
3518
3519    #[test]
3520    fn parse_lea() {
3521        let i = parse_instr("lea rdi, [rip + 0x10]");
3522        assert_eq!(i.mnemonic, "lea");
3523        match &i.operands[1] {
3524            Operand::Memory(m) => {
3525                assert_eq!(m.base, Some(Register::Rip));
3526                assert_eq!(m.disp, 0x10);
3527            }
3528            _ => panic!("expected memory"),
3529        }
3530    }
3531
3532    #[test]
3533    fn parse_three_operand_imul() {
3534        let i = parse_instr("imul rax, rbx, 10");
3535        assert_eq!(i.operands.len(), 3);
3536        assert_eq!(i.operands[0], Operand::Register(Register::Rax));
3537        assert_eq!(i.operands[1], Operand::Register(Register::Rbx));
3538        assert_eq!(i.operands[2], Operand::Immediate(10));
3539    }
3540
3541    #[test]
3542    fn global_directive_ignored() {
3543        let stmts = parse_str(".global main\nmov rax, 1").unwrap();
3544        // .global is ignored, should get 1 instruction
3545        assert_eq!(stmts.len(), 1);
3546    }
3547
3548    #[test]
3549    fn section_directive_ignored() {
3550        let stmts = parse_str(".section .text\nnop").unwrap();
3551        assert_eq!(stmts.len(), 1);
3552    }
3553
3554    #[test]
3555    fn empty_input() {
3556        let stmts = parse_str("").unwrap();
3557        assert!(stmts.is_empty());
3558    }
3559
3560    #[test]
3561    fn only_labels() {
3562        let stmts = parse_str("start:\nend:").unwrap();
3563        assert_eq!(stmts.len(), 2);
3564        assert!(matches!(&stmts[0], Statement::Label(n, _) if n == "start"));
3565        assert!(matches!(&stmts[1], Statement::Label(n, _) if n == "end"));
3566    }
3567
3568    #[test]
3569    fn mem_with_label() {
3570        let i = parse_instr("mov rax, [msg]");
3571        match &i.operands[1] {
3572            Operand::Memory(m) => {
3573                assert_eq!(m.base, None);
3574                assert_eq!(m.disp_label, Some(String::from("msg")));
3575            }
3576            _ => panic!("expected memory operand with label"),
3577        }
3578    }
3579
3580    #[test]
3581    fn xmm_registers() {
3582        let i = parse_instr("movaps xmm0, xmm1");
3583        assert_eq!(i.operands[0], Operand::Register(Register::Xmm0));
3584        assert_eq!(i.operands[1], Operand::Register(Register::Xmm1));
3585    }
3586
3587    #[test]
3588    fn segment_override_mem() {
3589        let i = parse_instr("mov rax, fs:[0x28]");
3590        match &i.operands[1] {
3591            Operand::Memory(m) => {
3592                assert_eq!(m.segment, Some(Register::Fs));
3593                assert_eq!(m.disp, 0x28);
3594            }
3595            _ => panic!("expected segment memory operand"),
3596        }
3597    }
3598
3599    // === name = expression syntax ===
3600
3601    #[test]
3602    fn parse_name_equals_constant() {
3603        let stmt = parse_one("EXIT = 60");
3604        match stmt {
3605            Statement::Const(c) => {
3606                assert_eq!(c.name, "EXIT");
3607                assert_eq!(c.value, 60);
3608            }
3609            _ => panic!("expected const, got {:?}", stmt),
3610        }
3611    }
3612
3613    #[test]
3614    fn parse_name_equals_hex() {
3615        let stmt = parse_one("MAGIC = 0xDEAD");
3616        match stmt {
3617            Statement::Const(c) => {
3618                assert_eq!(c.name, "MAGIC");
3619                assert_eq!(c.value, 0xDEAD);
3620            }
3621            _ => panic!("expected const"),
3622        }
3623    }
3624
3625    #[test]
3626    fn parse_name_equals_negative() {
3627        let stmt = parse_one("NEG = -1");
3628        match stmt {
3629            Statement::Const(c) => {
3630                assert_eq!(c.name, "NEG");
3631                assert_eq!(c.value, -1);
3632            }
3633            _ => panic!("expected const"),
3634        }
3635    }
3636
3637    #[test]
3638    fn parse_set_directive() {
3639        let stmt = parse_one(".set COUNT, 42");
3640        match stmt {
3641            Statement::Const(c) => {
3642                assert_eq!(c.name, "COUNT");
3643                assert_eq!(c.value, 42);
3644            }
3645            _ => panic!("expected const"),
3646        }
3647    }
3648
3649    #[test]
3650    fn name_equals_used_in_program() {
3651        let stmts = parse_str("EXIT = 60\nmov eax, EXIT").unwrap();
3652        assert_eq!(stmts.len(), 2);
3653        assert!(matches!(&stmts[0], Statement::Const(_)));
3654        assert!(matches!(&stmts[1], Statement::Instruction(_)));
3655    }
3656
3657    #[test]
3658    fn parse_const_expr_with_identifier() {
3659        // .equ SIZE, 10 then .fill SIZE, 1, 0
3660        let stmts = parse_str("SIZE = 10\n.fill SIZE, 1, 0").unwrap();
3661        assert_eq!(stmts.len(), 2);
3662        match &stmts[1] {
3663            Statement::Fill(f) => assert_eq!(f.count, 10),
3664            _ => panic!("expected Fill"),
3665        }
3666    }
3667
3668    #[test]
3669    fn parse_const_chain() {
3670        // Constants referencing earlier constants
3671        let stmts = parse_str("A = 5\nB = A + 3\n.space B, 0").unwrap();
3672        assert_eq!(stmts.len(), 3);
3673        match &stmts[2] {
3674            Statement::Space(s) => assert_eq!(s.size, 8),
3675            _ => panic!("expected Space"),
3676        }
3677    }
3678
3679    #[test]
3680    fn parse_equ_identifier_in_const_expr() {
3681        let stmts = parse_str(".equ BASE, 100\n.equ TOTAL, BASE + 50").unwrap();
3682        match &stmts[1] {
3683            Statement::Const(c) => assert_eq!(c.value, 150),
3684            _ => panic!("expected Const"),
3685        }
3686    }
3687
3688    #[test]
3689    fn parse_label_plus_identifier_expression() {
3690        // When OFFSET is a constant, label+OFFSET should produce Expression
3691        // that gets partially resolved at parse time
3692        let stmts = parse_str("OFF = 8\nmov rax, data + OFF").unwrap();
3693        match &stmts[1] {
3694            Statement::Instruction(i) => {
3695                match &i.operands[1] {
3696                    // The parser resolves OFF→8, so this becomes Expression(label+8)
3697                    Operand::Expression(Expr::Add(l, r)) => {
3698                        assert_eq!(**l, Expr::Label(String::from("data")));
3699                        assert_eq!(**r, Expr::Num(8));
3700                    }
3701                    other => panic!("expected Expression, got {:?}", other),
3702                }
3703            }
3704            _ => panic!("expected Instruction"),
3705        }
3706    }
3707
3708    #[test]
3709    fn parse_all_constants_resolve_to_immediate() {
3710        // When all parts are constants, expression should collapse to Immediate
3711        let stmts = parse_str("BASE = 100\nOFF = 8\nmov eax, BASE + OFF").unwrap();
3712        match &stmts[2] {
3713            Statement::Instruction(i) => {
3714                assert_eq!(i.operands[1], Operand::Immediate(108));
3715            }
3716            _ => panic!("expected Instruction"),
3717        }
3718    }
3719
3720    #[test]
3721    fn parse_align_with_constant() {
3722        let stmts = parse_str("ALIGN_VAL = 8\n.align ALIGN_VAL").unwrap();
3723        match &stmts[1] {
3724            Statement::Align(a) => assert_eq!(a.alignment, 8),
3725            _ => panic!("expected Align"),
3726        }
3727    }
3728
3729    #[test]
3730    fn parse_label_minus_offset() {
3731        let i = parse_instr("jmp target - 8");
3732        match &i.operands[0] {
3733            Operand::Expression(Expr::Sub(l, r)) => {
3734                assert_eq!(**l, Expr::Label(String::from("target")));
3735                assert_eq!(**r, Expr::Num(8));
3736            }
3737            _ => panic!("expected Sub expression"),
3738        }
3739    }
3740
3741    #[test]
3742    fn parse_const_negation_precedence() {
3743        // -A + B should evaluate as (-A) + B, not -(A + B)
3744        let stmts = parse_str("A = 10\nB = 3\nX = -A + B\nmov eax, X").unwrap();
3745        match &stmts[3] {
3746            Statement::Instruction(i) => {
3747                assert_eq!(
3748                    i.operands[1],
3749                    Operand::Immediate(-7),
3750                    "-10 + 3 should be -7, not -13"
3751                );
3752            }
3753            _ => panic!("expected Instruction"),
3754        }
3755    }
3756
3757    #[test]
3758    fn parse_const_negation_only() {
3759        // -A should evaluate as -10
3760        let stmts = parse_str("A = 10\nX = -A\nmov eax, X").unwrap();
3761        match &stmts[2] {
3762            Statement::Instruction(i) => {
3763                assert_eq!(i.operands[1], Operand::Immediate(-10));
3764            }
3765            _ => panic!("expected Instruction"),
3766        }
3767    }
3768
3769    #[test]
3770    fn parse_const_negation_sub_chain() {
3771        // -A - B should be (-A) - B = -10 - 3 = -13
3772        let stmts = parse_str("A = 10\nB = 3\nX = -A - B\nmov eax, X").unwrap();
3773        match &stmts[3] {
3774            Statement::Instruction(i) => {
3775                assert_eq!(
3776                    i.operands[1],
3777                    Operand::Immediate(-13),
3778                    "-10 - 3 should be -13"
3779                );
3780            }
3781            _ => panic!("expected Instruction"),
3782        }
3783    }
3784
3785    #[test]
3786    fn parse_align_rejects_non_power_of_2() {
3787        let result = crate::parser::parse_str(".align 3");
3788        assert!(result.is_err(), ".align 3 should be rejected");
3789        let err = result.unwrap_err();
3790        let msg = alloc::format!("{err:?}");
3791        assert!(
3792            msg.contains("power of 2"),
3793            "error should mention power of 2, got: {msg}"
3794        );
3795    }
3796
3797    #[test]
3798    fn parse_align_accepts_power_of_2() {
3799        // These should all succeed
3800        for val in &["1", "2", "4", "8", "16", "32", "64", "4096"] {
3801            let src = alloc::format!(".align {val}");
3802            let result = crate::parser::parse_str(&src);
3803            assert!(
3804                result.is_ok(),
3805                ".align {val} should be accepted, got: {result:?}"
3806            );
3807        }
3808    }
3809
3810    // === 8th Audit: RSP/ESP as SIB index rejection ===
3811
3812    #[test]
3813    fn parse_rsp_as_index_rejects() {
3814        // [rbx + rsp*2] should fail — RSP cannot be SIB index
3815        let result = crate::parser::parse_str("mov rax, [rbx + rsp*2]");
3816        assert!(result.is_err(), "RSP as SIB index should be rejected");
3817    }
3818
3819    #[test]
3820    fn parse_esp_as_index_rejects() {
3821        // [ebx + esp*1] should fail — ESP cannot be SIB index
3822        let result = crate::parser::parse_str("mov eax, [ebx + esp*1]");
3823        assert!(result.is_err(), "ESP as SIB index should be rejected");
3824    }
3825
3826    #[test]
3827    fn parse_r12_as_index_accepts() {
3828        // [rbx + r12*2] should succeed — R12 IS valid as SIB index (uses REX.X)
3829        let result = crate::parser::parse_str("mov rax, [rbx + r12*2]");
3830        assert!(
3831            result.is_ok(),
3832            "R12 as SIB index should be accepted, got: {result:?}"
3833        );
3834    }
3835
3836    // === AT&T / GAS Syntax ===
3837
3838    fn parse_att(src: &str) -> Vec<Statement> {
3839        let tokens = crate::lexer::tokenize(src).unwrap();
3840        parse_with_syntax(&tokens, Arch::X86_64, Syntax::Att).unwrap()
3841    }
3842
3843    fn parse_att_instr(src: &str) -> Instruction {
3844        let stmts = parse_att(src);
3845        assert_eq!(stmts.len(), 1, "expected 1 statement, got {:?}", stmts);
3846        match stmts.into_iter().next().unwrap() {
3847            Statement::Instruction(i) => i,
3848            s => panic!("expected instruction, got {s:?}"),
3849        }
3850    }
3851
3852    #[test]
3853    fn att_register_operand() {
3854        let i = parse_att_instr("nop");
3855        assert_eq!(i.mnemonic, "nop");
3856        assert!(i.operands.is_empty());
3857    }
3858
3859    #[test]
3860    fn att_mov_imm_to_reg() {
3861        let i = parse_att_instr("movq $42, %rax");
3862        assert_eq!(i.mnemonic, "mov");
3863        assert_eq!(i.size_hint, Some(OperandSize::Qword));
3864        // Operands reversed: AT&T src,dst → Intel dst,src
3865        assert_eq!(i.operands.len(), 2);
3866        assert_eq!(i.operands[0], Operand::Register(Register::Rax));
3867        assert_eq!(i.operands[1], Operand::Immediate(42));
3868    }
3869
3870    #[test]
3871    fn att_mov_reg_to_reg() {
3872        let i = parse_att_instr("movl %eax, %ecx");
3873        assert_eq!(i.mnemonic, "mov");
3874        assert_eq!(i.size_hint, Some(OperandSize::Dword));
3875        assert_eq!(i.operands[0], Operand::Register(Register::Ecx));
3876        assert_eq!(i.operands[1], Operand::Register(Register::Eax));
3877    }
3878
3879    #[test]
3880    fn att_add_imm_to_reg() {
3881        let i = parse_att_instr("addl $0x10, %eax");
3882        assert_eq!(i.mnemonic, "add");
3883        assert_eq!(i.size_hint, Some(OperandSize::Dword));
3884        assert_eq!(i.operands[0], Operand::Register(Register::Eax));
3885        assert_eq!(i.operands[1], Operand::Immediate(0x10));
3886    }
3887
3888    #[test]
3889    fn att_negative_immediate() {
3890        let i = parse_att_instr("addq $-1, %rax");
3891        assert_eq!(i.mnemonic, "add");
3892        assert_eq!(i.operands[1], Operand::Immediate(-1));
3893    }
3894
3895    #[test]
3896    fn att_byte_suffix() {
3897        let i = parse_att_instr("movb $0x41, %al");
3898        assert_eq!(i.mnemonic, "mov");
3899        assert_eq!(i.size_hint, Some(OperandSize::Byte));
3900        assert_eq!(i.operands[0], Operand::Register(Register::Al));
3901    }
3902
3903    #[test]
3904    fn att_word_suffix() {
3905        let i = parse_att_instr("movw $0x1234, %ax");
3906        assert_eq!(i.mnemonic, "mov");
3907        assert_eq!(i.size_hint, Some(OperandSize::Word));
3908        assert_eq!(i.operands[0], Operand::Register(Register::Ax));
3909    }
3910
3911    #[test]
3912    fn att_memory_base_only() {
3913        let i = parse_att_instr("movq (%rax), %rbx");
3914        assert_eq!(i.mnemonic, "mov");
3915        // After reversal: [0]=rbx (dst), [1]=(%rax) (src)
3916        assert_eq!(i.operands[0], Operand::Register(Register::Rbx));
3917        if let Operand::Memory(m) = &i.operands[1] {
3918            assert_eq!(m.base, Some(Register::Rax));
3919            assert_eq!(m.disp, 0);
3920            assert!(m.index.is_none());
3921        } else {
3922            panic!("expected memory operand");
3923        }
3924    }
3925
3926    #[test]
3927    fn att_memory_disp_base() {
3928        let i = parse_att_instr("movl 8(%rsp), %eax");
3929        assert_eq!(i.mnemonic, "mov");
3930        if let Operand::Memory(m) = &i.operands[1] {
3931            assert_eq!(m.base, Some(Register::Rsp));
3932            assert_eq!(m.disp, 8);
3933        } else {
3934            panic!("expected memory operand");
3935        }
3936    }
3937
3938    #[test]
3939    fn att_memory_negative_disp() {
3940        let i = parse_att_instr("movq -16(%rbp), %rax");
3941        if let Operand::Memory(m) = &i.operands[1] {
3942            assert_eq!(m.base, Some(Register::Rbp));
3943            assert_eq!(m.disp, -16);
3944        } else {
3945            panic!("expected memory operand");
3946        }
3947    }
3948
3949    #[test]
3950    fn att_memory_base_index() {
3951        let i = parse_att_instr("movl (%rax, %rcx), %edx");
3952        if let Operand::Memory(m) = &i.operands[1] {
3953            assert_eq!(m.base, Some(Register::Rax));
3954            assert_eq!(m.index, Some(Register::Rcx));
3955            assert_eq!(m.scale, 1);
3956        } else {
3957            panic!("expected memory operand");
3958        }
3959    }
3960
3961    #[test]
3962    fn att_memory_base_index_scale() {
3963        let i = parse_att_instr("movq (%rax, %rcx, 4), %rdx");
3964        if let Operand::Memory(m) = &i.operands[1] {
3965            assert_eq!(m.base, Some(Register::Rax));
3966            assert_eq!(m.index, Some(Register::Rcx));
3967            assert_eq!(m.scale, 4);
3968            assert_eq!(m.disp, 0);
3969        } else {
3970            panic!("expected memory operand");
3971        }
3972    }
3973
3974    #[test]
3975    fn att_memory_disp_base_index_scale() {
3976        let i = parse_att_instr("movl 16(%rbx, %rsi, 8), %eax");
3977        if let Operand::Memory(m) = &i.operands[1] {
3978            assert_eq!(m.base, Some(Register::Rbx));
3979            assert_eq!(m.index, Some(Register::Rsi));
3980            assert_eq!(m.scale, 8);
3981            assert_eq!(m.disp, 16);
3982        } else {
3983            panic!("expected memory operand");
3984        }
3985    }
3986
3987    #[test]
3988    fn att_segment_override() {
3989        let i = parse_att_instr("movq %fs:0x28(%rax), %rbx");
3990        if let Operand::Memory(m) = &i.operands[1] {
3991            assert_eq!(m.segment, Some(Register::Fs));
3992            assert_eq!(m.base, Some(Register::Rax));
3993            assert_eq!(m.disp, 0x28);
3994        } else {
3995            panic!("expected memory operand");
3996        }
3997    }
3998
3999    #[test]
4000    fn att_push_pop() {
4001        let i = parse_att_instr("pushq %rbp");
4002        assert_eq!(i.mnemonic, "push");
4003        assert_eq!(i.operands[0], Operand::Register(Register::Rbp));
4004
4005        let i2 = parse_att_instr("popq %rbp");
4006        assert_eq!(i2.mnemonic, "pop");
4007        assert_eq!(i2.operands[0], Operand::Register(Register::Rbp));
4008    }
4009
4010    #[test]
4011    fn att_xor_reg_reg() {
4012        let i = parse_att_instr("xorl %eax, %eax");
4013        assert_eq!(i.mnemonic, "xor");
4014        // After reversal: both operands are eax
4015        assert_eq!(i.operands[0], Operand::Register(Register::Eax));
4016        assert_eq!(i.operands[1], Operand::Register(Register::Eax));
4017    }
4018
4019    #[test]
4020    fn att_call_label() {
4021        let i = parse_att_instr("call func");
4022        assert_eq!(i.mnemonic, "call");
4023        assert_eq!(i.operands[0], Operand::Label(String::from("func")));
4024    }
4025
4026    #[test]
4027    fn att_jmp_label() {
4028        let i = parse_att_instr("jmp done");
4029        assert_eq!(i.mnemonic, "jmp");
4030        assert_eq!(i.operands[0], Operand::Label(String::from("done")));
4031    }
4032
4033    #[test]
4034    fn att_jcc_label() {
4035        let i = parse_att_instr("jne loop");
4036        assert_eq!(i.mnemonic, "jne");
4037        assert_eq!(i.operands[0], Operand::Label(String::from("loop")));
4038    }
4039
4040    #[test]
4041    fn att_ret() {
4042        let i = parse_att_instr("ret");
4043        assert_eq!(i.mnemonic, "ret");
4044        assert!(i.operands.is_empty());
4045    }
4046
4047    #[test]
4048    fn att_syscall() {
4049        let i = parse_att_instr("syscall");
4050        assert_eq!(i.mnemonic, "syscall");
4051    }
4052
4053    #[test]
4054    fn att_lock_prefix() {
4055        let i = parse_att_instr("lock xchgl %eax, (%rbx)");
4056        assert_eq!(i.mnemonic, "xchg");
4057        assert!(i.prefixes.contains(&Prefix::Lock));
4058    }
4059
4060    #[test]
4061    fn att_lea() {
4062        let i = parse_att_instr("leaq 8(%rsp), %rax");
4063        assert_eq!(i.mnemonic, "lea");
4064        // After reversal: [0]=rax, [1]=8(%rsp)
4065        assert_eq!(i.operands[0], Operand::Register(Register::Rax));
4066        if let Operand::Memory(m) = &i.operands[1] {
4067            assert_eq!(m.base, Some(Register::Rsp));
4068            assert_eq!(m.disp, 8);
4069        } else {
4070            panic!("expected memory operand");
4071        }
4072    }
4073
4074    #[test]
4075    fn att_imm_label_ref() {
4076        let i = parse_att_instr("movq $myvar, %rax");
4077        assert_eq!(i.mnemonic, "mov");
4078        assert_eq!(i.operands[1], Operand::Label(String::from("myvar")));
4079    }
4080
4081    #[test]
4082    fn att_no_suffix_no_size_hint() {
4083        // No suffix → no size_hint
4084        let i = parse_att_instr("nop");
4085        assert!(i.size_hint.is_none());
4086    }
4087
4088    #[test]
4089    fn att_int_not_stripped() {
4090        // "int" should not have suffix stripped to "in" + Dword
4091        let i = parse_att_instr("int $0x80");
4092        assert_eq!(i.mnemonic, "int");
4093        assert_eq!(i.operands[0], Operand::Immediate(0x80));
4094    }
4095
4096    #[test]
4097    fn att_string_ops_not_stripped() {
4098        let i = parse_att_instr("movsb");
4099        assert_eq!(i.mnemonic, "movsb");
4100        let i = parse_att_instr("stosq");
4101        assert_eq!(i.mnemonic, "stosq");
4102    }
4103
4104    #[test]
4105    fn att_rep_prefix() {
4106        let i = parse_att_instr("rep movsb");
4107        assert_eq!(i.mnemonic, "movsb");
4108        assert!(i.prefixes.contains(&Prefix::Rep));
4109    }
4110
4111    #[test]
4112    fn att_cmp_operand_order() {
4113        // AT&T: cmpl $0, %eax → Intel: cmp eax, 0
4114        let i = parse_att_instr("cmpl $0, %eax");
4115        assert_eq!(i.mnemonic, "cmp");
4116        assert_eq!(i.operands[0], Operand::Register(Register::Eax));
4117        assert_eq!(i.operands[1], Operand::Immediate(0));
4118    }
4119
4120    #[test]
4121    fn att_test_operand_order() {
4122        // AT&T: testl %eax, %eax → Intel: test eax, eax
4123        let i = parse_att_instr("testl %eax, %eax");
4124        assert_eq!(i.mnemonic, "test");
4125        assert_eq!(i.operands[0], Operand::Register(Register::Eax));
4126        assert_eq!(i.operands[1], Operand::Register(Register::Eax));
4127    }
4128
4129    #[test]
4130    fn att_sub_mem_to_reg() {
4131        let i = parse_att_instr("subq 8(%rbp), %rax");
4132        assert_eq!(i.mnemonic, "sub");
4133        assert_eq!(i.operands[0], Operand::Register(Register::Rax));
4134        if let Operand::Memory(m) = &i.operands[1] {
4135            assert_eq!(m.base, Some(Register::Rbp));
4136            assert_eq!(m.disp, 8);
4137        } else {
4138            panic!("expected memory operand");
4139        }
4140    }
4141
4142    #[test]
4143    fn att_push_immediate() {
4144        let i = parse_att_instr("pushq $42");
4145        assert_eq!(i.mnemonic, "push");
4146        assert_eq!(i.operands[0], Operand::Immediate(42));
4147    }
4148
4149    #[test]
4150    fn att_numeric_label_fwd() {
4151        let i = parse_att_instr("jmp 1f");
4152        assert_eq!(i.mnemonic, "jmp");
4153        assert_eq!(i.operands[0], Operand::Label(String::from("1f")));
4154    }
4155
4156    #[test]
4157    fn att_numeric_label_bwd() {
4158        let i = parse_att_instr("jne 1b");
4159        assert_eq!(i.mnemonic, "jne");
4160        assert_eq!(i.operands[0], Operand::Label(String::from("1b")));
4161    }
4162
4163    #[test]
4164    fn att_syntax_directive_switches_mode() {
4165        let src = ".syntax att\nmovq $1, %rax";
4166        // Start with Intel, switch to AT&T via directive
4167        let tokens = crate::lexer::tokenize(src).unwrap();
4168        let stmts = parse_with_syntax(&tokens, Arch::X86_64, Syntax::Intel).unwrap();
4169        // Should have parsed the mov in AT&T mode after .syntax att
4170        let instr = stmts
4171            .iter()
4172            .find_map(|s| {
4173                if let Statement::Instruction(i) = s {
4174                    Some(i)
4175                } else {
4176                    None
4177                }
4178            })
4179            .expect("no instruction found");
4180        assert_eq!(instr.mnemonic, "mov");
4181        // Operand order should be reversed (AT&T)
4182        assert_eq!(instr.operands[0], Operand::Register(Register::Rax));
4183        assert_eq!(instr.operands[1], Operand::Immediate(1));
4184    }
4185
4186    #[test]
4187    fn att_star_indirect_reg() {
4188        let i = parse_att_instr("jmp *%rax");
4189        assert_eq!(i.mnemonic, "jmp");
4190        assert_eq!(i.operands[0], Operand::Register(Register::Rax));
4191    }
4192
4193    #[test]
4194    fn att_star_indirect_mem() {
4195        let i = parse_att_instr("call *(%rax)");
4196        assert_eq!(i.mnemonic, "call");
4197        if let Operand::Memory(m) = &i.operands[0] {
4198            assert_eq!(m.base, Some(Register::Rax));
4199        } else {
4200            panic!("expected memory operand");
4201        }
4202    }
4203
4204    // ── Literal pool parsing ────────────────────────────────
4205
4206    fn parse_aarch64(src: &str) -> Vec<Statement> {
4207        let tokens = crate::lexer::tokenize(src).unwrap();
4208        parse_with_syntax(&tokens, Arch::Aarch64, Syntax::Ual).unwrap()
4209    }
4210
4211    #[test]
4212    fn parse_ldr_literal_pool_x_reg() {
4213        let stmts = parse_aarch64("ldr x0, =0x12345678");
4214        assert_eq!(stmts.len(), 1);
4215        if let Statement::Instruction(instr) = &stmts[0] {
4216            assert_eq!(instr.mnemonic, "ldr");
4217            assert_eq!(instr.operands.len(), 2);
4218            assert!(matches!(
4219                &instr.operands[0],
4220                Operand::Register(Register::A64X0)
4221            ));
4222            assert!(matches!(
4223                &instr.operands[1],
4224                Operand::LiteralPoolValue(0x12345678)
4225            ));
4226        } else {
4227            panic!("expected instruction");
4228        }
4229    }
4230
4231    #[test]
4232    fn parse_ldr_literal_pool_w_reg() {
4233        let stmts = parse_aarch64("ldr w5, =42");
4234        assert_eq!(stmts.len(), 1);
4235        if let Statement::Instruction(instr) = &stmts[0] {
4236            assert_eq!(instr.mnemonic, "ldr");
4237            assert!(matches!(&instr.operands[1], Operand::LiteralPoolValue(42)));
4238        } else {
4239            panic!("expected instruction");
4240        }
4241    }
4242
4243    #[test]
4244    fn parse_ldr_literal_pool_negative() {
4245        let stmts = parse_aarch64("ldr x1, =-1");
4246        if let Statement::Instruction(instr) = &stmts[0] {
4247            assert!(matches!(&instr.operands[1], Operand::LiteralPoolValue(-1)));
4248        } else {
4249            panic!("expected instruction");
4250        }
4251    }
4252
4253    #[test]
4254    fn parse_ldr_literal_pool_hex_large() {
4255        let stmts = parse_aarch64("ldr x0, =0xDEADBEEFCAFEBABE");
4256        if let Statement::Instruction(instr) = &stmts[0] {
4257            if let Operand::LiteralPoolValue(v) = &instr.operands[1] {
4258                assert_eq!(*v, 0xDEADBEEFCAFEBABEu64 as i128);
4259            } else {
4260                panic!("expected LiteralPoolValue");
4261            }
4262        } else {
4263            panic!("expected instruction");
4264        }
4265    }
4266
4267    #[test]
4268    fn parse_ltorg_directive() {
4269        let stmts = parse_aarch64("ldr x0, =1\n.ltorg");
4270        assert_eq!(stmts.len(), 2);
4271        assert!(matches!(&stmts[1], Statement::Ltorg(_)));
4272    }
4273
4274    #[test]
4275    fn parse_pool_directive() {
4276        let stmts = parse_aarch64("ldr x0, =1\n.pool");
4277        assert_eq!(stmts.len(), 2);
4278        assert!(matches!(&stmts[1], Statement::Ltorg(_)));
4279    }
4280
4281    // ── SIMD/FP register parsing ────────────────────────────
4282
4283    #[test]
4284    fn parse_simd_v_registers() {
4285        // V0–V31 should parse as AArch64 vector registers
4286        for i in 0..32 {
4287            let src = alloc::format!("fmov v{}, v0", i);
4288            let stmts = parse_aarch64(&src);
4289            assert_eq!(stmts.len(), 1, "parsing 'fmov v{}, v0' failed", i);
4290            if let Statement::Instruction(instr) = &stmts[0] {
4291                if let Operand::Register(r) = &instr.operands[0] {
4292                    assert!(r.is_a64_simd_fp(), "v{} should be SIMD/FP", i);
4293                    assert_eq!(r.a64_reg_num(), i as u8, "v{} reg num", i);
4294                    assert_eq!(r.a64_simd_fp_bits(), 128, "v{} should be 128 bits", i);
4295                } else {
4296                    panic!("expected register for v{}", i);
4297                }
4298            }
4299        }
4300    }
4301
4302    #[test]
4303    fn parse_simd_q_registers() {
4304        for i in 0..32 {
4305            let src = alloc::format!("mov q{}, q0", i);
4306            let stmts = parse_aarch64(&src);
4307            if let Statement::Instruction(instr) = &stmts[0] {
4308                if let Operand::Register(r) = &instr.operands[0] {
4309                    assert!(r.is_a64_simd_fp(), "q{} should be SIMD/FP", i);
4310                    assert_eq!(r.a64_reg_num(), i as u8);
4311                    assert_eq!(r.a64_simd_fp_bits(), 128);
4312                }
4313            }
4314        }
4315    }
4316
4317    #[test]
4318    fn parse_simd_d_registers() {
4319        for i in 0..32 {
4320            let src = alloc::format!("fmov d{}, d0", i);
4321            let stmts = parse_aarch64(&src);
4322            if let Statement::Instruction(instr) = &stmts[0] {
4323                if let Operand::Register(r) = &instr.operands[0] {
4324                    assert!(r.is_a64_simd_fp(), "d{} should be SIMD/FP", i);
4325                    assert_eq!(r.a64_reg_num(), i as u8);
4326                    assert_eq!(r.a64_simd_fp_bits(), 64);
4327                }
4328            }
4329        }
4330    }
4331
4332    #[test]
4333    fn parse_simd_s_registers() {
4334        for i in 0..32 {
4335            let src = alloc::format!("fmov s{}, s0", i);
4336            let stmts = parse_aarch64(&src);
4337            if let Statement::Instruction(instr) = &stmts[0] {
4338                if let Operand::Register(r) = &instr.operands[0] {
4339                    assert!(r.is_a64_simd_fp(), "s{} should be SIMD/FP", i);
4340                    assert_eq!(r.a64_reg_num(), i as u8);
4341                    assert_eq!(r.a64_simd_fp_bits(), 32);
4342                }
4343            }
4344        }
4345    }
4346
4347    #[test]
4348    fn parse_simd_h_registers() {
4349        for i in 0..32 {
4350            let src = alloc::format!("fmov h{}, h0", i);
4351            let stmts = parse_aarch64(&src);
4352            if let Statement::Instruction(instr) = &stmts[0] {
4353                if let Operand::Register(r) = &instr.operands[0] {
4354                    assert!(r.is_a64_simd_fp(), "h{} should be SIMD/FP", i);
4355                    assert_eq!(r.a64_reg_num(), i as u8);
4356                    assert_eq!(r.a64_simd_fp_bits(), 16);
4357                }
4358            }
4359        }
4360    }
4361
4362    #[test]
4363    fn parse_simd_b_registers() {
4364        for i in 0..32 {
4365            let src = alloc::format!("fmov b{}, b0", i);
4366            let stmts = parse_aarch64(&src);
4367            if let Statement::Instruction(instr) = &stmts[0] {
4368                if let Operand::Register(r) = &instr.operands[0] {
4369                    assert!(r.is_a64_simd_fp(), "b{} should be SIMD/FP", i);
4370                    assert_eq!(r.a64_reg_num(), i as u8);
4371                    assert_eq!(r.a64_simd_fp_bits(), 8);
4372                }
4373            }
4374        }
4375    }
4376
4377    // ── Vector arrangement specifier parsing ──────────────────────────
4378    #[test]
4379    fn parse_vector_arrangement_all_specifiers() {
4380        let cases = [
4381            ("add v0.8b, v1.8b, v2.8b", VectorArrangement::B8),
4382            ("add v0.16b, v1.16b, v2.16b", VectorArrangement::B16),
4383            ("add v0.4h, v1.4h, v2.4h", VectorArrangement::H4),
4384            ("add v0.8h, v1.8h, v2.8h", VectorArrangement::H8),
4385            ("add v0.2s, v1.2s, v2.2s", VectorArrangement::S2),
4386            ("add v0.4s, v1.4s, v2.4s", VectorArrangement::S4),
4387            ("add v0.1d, v1.1d, v2.1d", VectorArrangement::D1),
4388            ("add v0.2d, v1.2d, v2.2d", VectorArrangement::D2),
4389        ];
4390        for (src, expected_arr) in &cases {
4391            let stmts = parse_aarch64(src);
4392            if let Statement::Instruction(instr) = &stmts[0] {
4393                assert_eq!(instr.operands.len(), 3, "source: {}", src);
4394                for (j, op) in instr.operands.iter().enumerate() {
4395                    match op {
4396                        Operand::VectorRegister(_, arr) => {
4397                            assert_eq!(arr, expected_arr, "source: {}, operand {}", src, j);
4398                        }
4399                        other => panic!(
4400                            "expected VectorRegister, got {:?} for source: {}, operand {}",
4401                            other, src, j
4402                        ),
4403                    }
4404                }
4405            } else {
4406                panic!("expected instruction for source: {}", src);
4407            }
4408        }
4409    }
4410
4411    #[test]
4412    fn parse_vector_arrangement_register_numbers() {
4413        // Verify that various register numbers parse correctly with arrangement
4414        for i in [0u32, 1, 7, 15, 16, 31] {
4415            let src = alloc::format!("add v{}.4s, v0.4s, v0.4s", i);
4416            let stmts = parse_aarch64(&src);
4417            if let Statement::Instruction(instr) = &stmts[0] {
4418                match &instr.operands[0] {
4419                    Operand::VectorRegister(reg, arr) => {
4420                        assert_eq!(reg.a64_reg_num(), i as u8, "v{}.4s reg num", i);
4421                        assert_eq!(*arr, VectorArrangement::S4);
4422                        assert!(reg.is_a64_vector());
4423                    }
4424                    other => panic!("expected VectorRegister, got {:?}", other),
4425                }
4426            }
4427        }
4428    }
4429
4430    #[test]
4431    fn parse_vector_arrangement_case_insensitive() {
4432        // Arrangement specifiers should be case insensitive
4433        let cases = ["add v0.4S, v1.4S, v2.4S", "add V0.4s, V1.4s, V2.4s"];
4434        for src in &cases {
4435            let stmts = parse_aarch64(src);
4436            if let Statement::Instruction(instr) = &stmts[0] {
4437                for op in &instr.operands {
4438                    match op {
4439                        Operand::VectorRegister(_, arr) => {
4440                            assert_eq!(*arr, VectorArrangement::S4, "source: {}", src);
4441                        }
4442                        other => panic!(
4443                            "expected VectorRegister, got {:?} for source: {}",
4444                            other, src
4445                        ),
4446                    }
4447                }
4448            }
4449        }
4450    }
4451
4452    #[test]
4453    fn parse_vector_reg_without_arrangement() {
4454        // V register without arrangement specifier should parse as plain Register
4455        let stmts = parse_aarch64("mov v0, v1");
4456        if let Statement::Instruction(instr) = &stmts[0] {
4457            match &instr.operands[0] {
4458                Operand::Register(reg) => {
4459                    assert!(reg.is_a64_vector());
4460                    assert_eq!(reg.a64_reg_num(), 0);
4461                }
4462                other => panic!("expected Register, got {:?}", other),
4463            }
4464        }
4465    }
4466
4467    #[test]
4468    fn parse_vector_arrangement_display() {
4469        let stmts = parse_aarch64("add v3.2d, v4.2d, v5.2d");
4470        if let Statement::Instruction(instr) = &stmts[0] {
4471            if let Operand::VectorRegister(reg, arr) = &instr.operands[0] {
4472                assert_eq!(reg.a64_reg_num(), 3);
4473                assert_eq!(*arr, VectorArrangement::D2);
4474                let display = alloc::format!("{}", instr.operands[0]);
4475                // Display format is "{register_debug_lower}.{arrangement_display}"
4476                assert!(
4477                    display.contains("2D") || display.contains("2d"),
4478                    "Display should contain arrangement: {}",
4479                    display
4480                );
4481            } else {
4482                panic!("expected VectorRegister");
4483            }
4484        }
4485    }
4486
4487    #[test]
4488    fn parse_vector_arrangement_element_properties() {
4489        // Verify element_bits, total_bits, lane_count through parsed arrangements
4490        let cases = [
4491            ("add v0.8b, v0.8b, v0.8b", 8u32, 64u32, 8u32),
4492            ("add v0.16b, v0.16b, v0.16b", 8, 128, 16),
4493            ("add v0.4h, v0.4h, v0.4h", 16, 64, 4),
4494            ("add v0.8h, v0.8h, v0.8h", 16, 128, 8),
4495            ("add v0.2s, v0.2s, v0.2s", 32, 64, 2),
4496            ("add v0.4s, v0.4s, v0.4s", 32, 128, 4),
4497            ("add v0.1d, v0.1d, v0.1d", 64, 64, 1),
4498            ("add v0.2d, v0.2d, v0.2d", 64, 128, 2),
4499        ];
4500        for (src, elem_bits, total_bits, lanes) in &cases {
4501            let stmts = parse_aarch64(src);
4502            if let Statement::Instruction(instr) = &stmts[0] {
4503                if let Operand::VectorRegister(_, arr) = &instr.operands[0] {
4504                    assert_eq!(arr.element_bits(), *elem_bits, "{}", src);
4505                    assert_eq!(arr.total_bits(), *total_bits, "{}", src);
4506                    assert_eq!(arr.lane_count(), *lanes, "{}", src);
4507                }
4508            }
4509        }
4510    }
4511
4512    // === RISC-V Register Parsing ===
4513
4514    fn parse_rv64(src: &str) -> Vec<Statement> {
4515        let tokens = crate::lexer::tokenize(src).unwrap();
4516        parse_with_syntax(&tokens, Arch::Rv64, Syntax::RiscV).unwrap()
4517    }
4518
4519    fn parse_rv64_instr(src: &str) -> Instruction {
4520        let stmts = parse_rv64(src);
4521        assert_eq!(stmts.len(), 1, "expected 1 statement, got {}", stmts.len());
4522        match stmts.into_iter().next().unwrap() {
4523            Statement::Instruction(i) => i,
4524            s => panic!("expected instruction, got {:?}", s),
4525        }
4526    }
4527
4528    #[test]
4529    fn riscv_fp_register_hardware_names() {
4530        // Verify all hardware names f0–f31 parse correctly
4531        for i in 0u8..32 {
4532            let src = alloc::format!("fadd.d f{}, f{}, f{}", i, i, i);
4533            let instr = parse_rv64_instr(&src);
4534            assert_eq!(instr.mnemonic, "fadd.d");
4535            assert_eq!(instr.operands.len(), 3);
4536            for op in &instr.operands {
4537                if let Operand::Register(reg) = op {
4538                    assert!(reg.is_riscv_fp(), "f{} should be FP register", i);
4539                    assert_eq!(reg.rv_fp_reg_num(), i, "f{} should map to reg {}", i, i);
4540                } else {
4541                    panic!("expected register operand for f{}", i);
4542                }
4543            }
4544        }
4545    }
4546
4547    #[test]
4548    fn riscv_fp_register_abi_ft() {
4549        // ft0–ft7 → f0–f7, ft8–ft11 → f28–f31
4550        let mapping: &[(&str, u8)] = &[
4551            ("ft0", 0),
4552            ("ft1", 1),
4553            ("ft2", 2),
4554            ("ft3", 3),
4555            ("ft4", 4),
4556            ("ft5", 5),
4557            ("ft6", 6),
4558            ("ft7", 7),
4559            ("ft8", 28),
4560            ("ft9", 29),
4561            ("ft10", 30),
4562            ("ft11", 31),
4563        ];
4564        for &(name, expected_num) in mapping {
4565            let src = alloc::format!("fmv.d {}, {}", name, name);
4566            let instr = parse_rv64_instr(&src);
4567            if let Operand::Register(reg) = &instr.operands[0] {
4568                assert!(reg.is_riscv_fp(), "{} should be FP", name);
4569                assert_eq!(
4570                    reg.rv_fp_reg_num(),
4571                    expected_num,
4572                    "{} → f{}",
4573                    name,
4574                    expected_num
4575                );
4576            } else {
4577                panic!("expected register for {}", name);
4578            }
4579        }
4580    }
4581
4582    #[test]
4583    fn riscv_fp_register_abi_fs() {
4584        // fs0–fs1 → f8–f9, fs2–fs11 → f18–f27
4585        let mapping: &[(&str, u8)] = &[
4586            ("fs0", 8),
4587            ("fs1", 9),
4588            ("fs2", 18),
4589            ("fs3", 19),
4590            ("fs4", 20),
4591            ("fs5", 21),
4592            ("fs6", 22),
4593            ("fs7", 23),
4594            ("fs8", 24),
4595            ("fs9", 25),
4596            ("fs10", 26),
4597            ("fs11", 27),
4598        ];
4599        for &(name, expected_num) in mapping {
4600            let src = alloc::format!("fmv.d {}, {}", name, name);
4601            let instr = parse_rv64_instr(&src);
4602            if let Operand::Register(reg) = &instr.operands[0] {
4603                assert!(reg.is_riscv_fp(), "{} should be FP", name);
4604                assert_eq!(
4605                    reg.rv_fp_reg_num(),
4606                    expected_num,
4607                    "{} → f{}",
4608                    name,
4609                    expected_num
4610                );
4611            } else {
4612                panic!("expected register for {}", name);
4613            }
4614        }
4615    }
4616
4617    #[test]
4618    fn riscv_fp_register_abi_fa() {
4619        // fa0–fa7 → f10–f17
4620        let mapping: &[(&str, u8)] = &[
4621            ("fa0", 10),
4622            ("fa1", 11),
4623            ("fa2", 12),
4624            ("fa3", 13),
4625            ("fa4", 14),
4626            ("fa5", 15),
4627            ("fa6", 16),
4628            ("fa7", 17),
4629        ];
4630        for &(name, expected_num) in mapping {
4631            let src = alloc::format!("fmv.d {}, {}", name, name);
4632            let instr = parse_rv64_instr(&src);
4633            if let Operand::Register(reg) = &instr.operands[0] {
4634                assert!(reg.is_riscv_fp(), "{} should be FP", name);
4635                assert_eq!(
4636                    reg.rv_fp_reg_num(),
4637                    expected_num,
4638                    "{} → f{}",
4639                    name,
4640                    expected_num
4641                );
4642            } else {
4643                panic!("expected register for {}", name);
4644            }
4645        }
4646    }
4647
4648    #[test]
4649    fn riscv_fp_mixed_with_integer_regs() {
4650        // FP load: flw ft0, 0(sp)  — FP dest, integer base
4651        let instr = parse_rv64_instr("flw ft0, 0(sp)");
4652        assert_eq!(instr.mnemonic, "flw");
4653        if let Operand::Register(reg) = &instr.operands[0] {
4654            assert!(reg.is_riscv_fp());
4655            assert_eq!(reg.rv_fp_reg_num(), 0);
4656        } else {
4657            panic!("expected FP register");
4658        }
4659    }
4660
4661    #[test]
4662    fn riscv_fp_not_integer() {
4663        // FP registers should not report as integer RISC-V registers
4664        let instr = parse_rv64_instr("fadd.d fa0, fa1, fa2");
4665        for op in &instr.operands {
4666            if let Operand::Register(reg) = op {
4667                assert!(reg.is_riscv_fp());
4668                assert!(!reg.is_riscv());
4669            }
4670        }
4671    }
4672
4673    #[test]
4674    fn riscv_integer_not_fp() {
4675        // Integer registers should not report as FP
4676        let instr = parse_rv64_instr("add a0, a1, a2");
4677        for op in &instr.operands {
4678            if let Operand::Register(reg) = op {
4679                assert!(reg.is_riscv());
4680                assert!(!reg.is_riscv_fp());
4681            }
4682        }
4683    }
4684}