//===- W65816AsmParser.cpp - Parse W65816 assembly to MCInst instructions -===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Scaffold AsmParser for the WDC 65816 / Apple IIgs target.  Modeled after
// MSP430AsmParser.cpp but stripped of register-operand and addressing-mode
// handling that does not apply to the 65816:
//
//   * The 65816 MC operand set is value-typed only (immediate, address,
//     PC-relative).  Instructions implicitly operate on A, X, Y, S, DBR, PBR,
//     and D, so the parser never builds register operands.  The ", x" / ", y"
//     suffixes on indexed addressing modes are tokens, not registers.
//
//   * There is no ".b" / ".w" mnemonic suffix; the 65816 encodes operand
//     width in the opcode byte itself, and DP/Abs/Long are chosen by value
//     fit, not by mnemonic spelling.
//
// KNOWN LIMITATION (AsmString vs parser):
//
// W65816InstrFormats.td bakes the literal text ", x" and ", y" into the
// AsmString for indexed forms, e.g. the AsmString for LDA_AbsX is
// "lda\t$addr, x".  TableGen's AsmMatcher expects distinct operand classes
// for each addressing variant rather than literal sub-operand text, so the
// generated matcher may fail to route "lda $1000, x" to LDA_AbsX even
// though the parser produces (token "lda", addr $1000, token "x").
//
// The correct long-term fix is to split the operand classes by indexing
// (addrAbsIdxX, addrAbsIdxY, addrDPIdxX, addrDPIdxY, ...) and either give
// them custom parser methods that consume ", x" / ", y" or let the matcher
// own those tokens via ParserMethod/SuperClasses.  That is out of scope for
// this scaffolding task; for now we accept the matcher miss on indexed
// forms while the non-indexed and immediate forms match correctly.
//
//===----------------------------------------------------------------------===//

#include "MCTargetDesc/W65816MCTargetDesc.h"
#include "TargetInfo/W65816TargetInfo.h"

#include "llvm/ADT/APInt.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/MC/MCContext.h"
#include "llvm/MC/MCExpr.h"
#include "llvm/MC/MCInst.h"
#include "llvm/MC/MCInstrInfo.h"
#include "llvm/MC/MCParser/AsmLexer.h"
#include "llvm/MC/MCParser/MCParsedAsmOperand.h"
#include "llvm/MC/MCParser/MCTargetAsmParser.h"
#include "llvm/MC/MCStreamer.h"
#include "llvm/MC/MCSubtargetInfo.h"
#include "llvm/MC/MCSymbol.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/SMLoc.h"

#define DEBUG_TYPE "w65816-asm-parser"

using namespace llvm;

namespace {

/// A parsed W65816 assembly operand.
///
/// The 65816 MC layer sees only three operand flavors: tokens (mnemonic and
/// the literal ", x" / ", y" suffixes baked into AsmStrings), immediates
/// (prefixed with '#' in source), and values used as addresses or branch
/// targets.  There are no register operands on this target; all register
/// use is implicit in the opcode.
class W65816Operand : public MCParsedAsmOperand {
  enum KindTy {
    k_Tok,
    k_Reg,
    k_Imm,
    k_Addr
  } Kind;

  struct TokTy {
    const char *Data;
    unsigned Length;
  };

  union {
    TokTy Tok;
    unsigned RegNum;
    const MCExpr *Imm;
    const MCExpr *Addr;
  };

  SMLoc Start, End;

  // Set only for k_Addr operands carrying an explicit force-long prefix
  // (`>expr`).  When true the operand matches ONLY the 24-bit absolute-long
  // addressing class (AddrLong), regardless of how small the value is, so a
  // bank-explicit `sta >$000071` reaches STA_Long (0x8F) instead of being
  // narrowed to STA_DP/STA_Abs by value fit.
  bool ForceLong = false;

public:
  W65816Operand(StringRef T, SMLoc S)
      : Kind(k_Tok), Tok({T.data(), (unsigned)T.size()}), Start(S),
        End(SMLoc::getFromPointer(T.data() + T.size())) {}
  W65816Operand(unsigned R, SMLoc S, SMLoc E)
      : Kind(k_Reg), RegNum(R), Start(S), End(E) {}
  W65816Operand(KindTy K, const MCExpr *E, SMLoc S, SMLoc E2,
                bool ForceLong = false)
      : Kind(K), Start(S), End(E2), ForceLong(ForceLong) {
    if (K == k_Imm)
      Imm = E;
    else
      Addr = E;
  }

  // --- Factory helpers ----------------------------------------------------

  static std::unique_ptr<W65816Operand> createToken(StringRef Str, SMLoc S) {
    return std::make_unique<W65816Operand>(Str, S);
  }

  static std::unique_ptr<W65816Operand> createReg(unsigned R, SMLoc S,
                                                  SMLoc E) {
    return std::make_unique<W65816Operand>(R, S, E);
  }

  static std::unique_ptr<W65816Operand> createImm(const MCExpr *Val, SMLoc S,
                                                  SMLoc E) {
    return std::make_unique<W65816Operand>(k_Imm, Val, S, E);
  }

  static std::unique_ptr<W65816Operand> createAddr(const MCExpr *Val, SMLoc S,
                                                   SMLoc E,
                                                   bool ForceLong = false) {
    return std::make_unique<W65816Operand>(k_Addr, Val, S, E, ForceLong);
  }

  // --- MCParsedAsmOperand hooks ------------------------------------------

  bool isReg()   const override { return Kind == k_Reg; }
  bool isImm()   const override { return Kind == k_Imm; }
  bool isToken() const override { return Kind == k_Tok; }
  bool isMem()   const override { return false; }

  MCRegister getReg() const override {
    assert(Kind == k_Reg && "not a register");
    return MCRegister(RegNum);
  }

  StringRef getToken() const {
    assert(Kind == k_Tok && "not a token");
    return StringRef(Tok.Data, Tok.Length);
  }

  SMLoc getStartLoc() const override { return Start; }
  SMLoc getEndLoc()   const override { return End; }

  // --- AsmMatcher operand-class predicates -------------------------------
  //
  // The matcher picks the narrowest instruction variant whose operand
  // predicates all match.  Constants narrow by value fit; symbolic
  // expressions (labels, globals) default to the canonical 16-bit form
  // for absolute addresses and PC-relative branches, falling through
  // narrower variants only when the value is a constant that fits.
  // 24-bit `long` and `pcrel16` are opt-in (only constants in the wider
  // range, or future explicit addressing-mode syntax).

  static bool constFitsUnsigned(const MCExpr *Expr, unsigned width) {
    if (const auto *CE = dyn_cast<MCConstantExpr>(Expr)) {
      int64_t V = CE->getValue();
      uint64_t mask = width >= 64 ? ~uint64_t(0) : ((uint64_t(1) << width) - 1);
      if (V >= 0)
        return uint64_t(V) <= mask;
      int64_t minSigned = width >= 64 ? INT64_MIN
                                      : -(int64_t(1) << (width - 1));
      return V >= minSigned;
    }
    return false;
  }

  static bool isConstant(const MCExpr *Expr) {
    return Expr && isa<MCConstantExpr>(Expr);
  }

  // Accept symbolic expressions in BOTH isImm8 and isImm16 — for symbols the
  // value isn't known at parse time, so we let the matcher's M/X-flag
  // Predicate (HasMHigh vs HasMLow) pick the right width; the encoder emits
  // a 1-byte fixup_8 or 2-byte fixup_16 accordingly, and link816 resolves it.
  // For constants, gate by the value width so out-of-range constants fail
  // loudly (e.g. `.a8 ; lda #0x100` has no valid form -> error).
  bool isImm8()  const {
    return isImm() && (!isConstant(Imm) || constFitsUnsigned(Imm, 8));
  }
  bool isImm16() const {
    return isImm() && (!isConstant(Imm) || constFitsUnsigned(Imm, 16));
  }

  bool isAddrDP()   const {
    return Kind == k_Addr && !ForceLong &&
           isConstant(Addr) && constFitsUnsigned(Addr, 8);
  }
  bool isAddrAbs()  const {
    return Kind == k_Addr && !ForceLong &&
           (!isConstant(Addr) || constFitsUnsigned(Addr, 16));
  }
  bool isAddrLong() const {
    if (Kind != k_Addr)
      return false;
    // Explicit `>expr` prefix forces the long form for any value that fits
    // in 24 bits (or any symbol), and the narrower AddrDP/AddrAbs predicates
    // above bail, so the matcher has only the long variant to pick.
    if (ForceLong)
      return !isConstant(Addr) || constFitsUnsigned(Addr, 24);
    if (!isConstant(Addr))
      // Symbolic — accept; if a same-mnemonic narrower form exists (Abs)
      // the matcher will prefer it.
      return true;
    // Constant — only match if it doesn't already fit in 16 bits, so
    // pure constants in the 0..0xFFFF range route to AddrAbs.
    return constFitsUnsigned(Addr, 24) && !constFitsUnsigned(Addr, 16);
  }

  bool isPCRel8()  const {
    // Branch targets are typically symbols (resolved by the assembler /
    // linker into the final 8-bit signed offset).  Accept any address
    // expression — constant in-range, or symbolic.  Constants outside
    // 8 bits are rejected so they fall through to PCRel16 / longer
    // forms instead of silently overflowing.
    return Kind == k_Addr &&
           (!isConstant(Addr) || constFitsUnsigned(Addr, 8));
  }
  bool isPCRel16() const {
    return Kind == k_Addr &&
           (!isConstant(Addr) || constFitsUnsigned(Addr, 16));
  }

  // --- addXxxOperands used by the generated matcher ----------------------

  void addExprOperand(MCInst &Inst, const MCExpr *Expr) const {
    if (!Expr)
      Inst.addOperand(MCOperand::createImm(0));
    else if (const MCConstantExpr *CE = dyn_cast<MCConstantExpr>(Expr))
      Inst.addOperand(MCOperand::createImm(CE->getValue()));
    else
      Inst.addOperand(MCOperand::createExpr(Expr));
  }

  void addImmOperands(MCInst &Inst, unsigned N) const {
    assert(N == 1 && "invalid number of operands");
    assert(Kind == k_Imm && "not an immediate");
    addExprOperand(Inst, Imm);
  }

  void addRegOperands(MCInst &Inst, unsigned N) const {
    assert(N == 1 && "invalid number of operands");
    assert(Kind == k_Reg && "not a register");
    Inst.addOperand(MCOperand::createReg(MCRegister(RegNum)));
  }

  // Address operand classes (DP/Abs/Long) and PC-relative branch targets
  // all render through the same path: emit either a constant or an
  // MCExpr.  The width-specific encoding is the encoder's job, not ours.
  void addAddrDPOperands(MCInst &Inst, unsigned N) const {
    assert(N == 1 && "invalid number of operands");
    assert(Kind == k_Addr && "not an address");
    addExprOperand(Inst, Addr);
  }
  void addAddrAbsOperands(MCInst &Inst, unsigned N) const {
    addAddrDPOperands(Inst, N);
  }
  void addAddrLongOperands(MCInst &Inst, unsigned N) const {
    addAddrDPOperands(Inst, N);
  }
  void addPCRel8Operands(MCInst &Inst, unsigned N) const {
    addAddrDPOperands(Inst, N);
  }
  void addPCRel16Operands(MCInst &Inst, unsigned N) const {
    addAddrDPOperands(Inst, N);
  }

  void print(raw_ostream &O, const MCAsmInfo &MAI) const override {
    switch (Kind) {
    case k_Tok:
      O << "Token " << StringRef(Tok.Data, Tok.Length);
      break;
    case k_Imm:
      O << "Immediate ";
      MAI.printExpr(O, *Imm);
      break;
    case k_Addr:
      O << "Address ";
      MAI.printExpr(O, *Addr);
      break;
    }
  }
};

/// Parses W65816 assembly from a stream.
class W65816AsmParser : public MCTargetAsmParser {
  MCAsmParser &Parser;

  bool matchAndEmitInstruction(SMLoc IDLoc, unsigned &Opcode,
                               OperandVector &Operands, MCStreamer &Out,
                               uint64_t &ErrorInfo,
                               bool MatchingInlineAsm) override;

  // The 65816 has no register operands.  Both register hooks always
  // report no-match; the lexer-driven operand parser never asks for one
  // either.  We still have to implement them because MCTargetAsmParser
  // declares them pure virtual / abstract.
  bool parseRegister(MCRegister &Reg, SMLoc &StartLoc, SMLoc &EndLoc) override;
  ParseStatus tryParseRegister(MCRegister &Reg, SMLoc &StartLoc,
                               SMLoc &EndLoc) override;

  bool parseInstruction(ParseInstructionInfo &Info, StringRef Name,
                        SMLoc NameLoc, OperandVector &Operands) override;

  ParseStatus parseDirective(AsmToken DirectiveID) override;
  bool parseLiteralValues(unsigned Size, SMLoc L);

  bool parseOperand(OperandVector &Operands);

  MCAsmParser &getParser() const { return Parser; }
  AsmLexer &getLexer() const { return Parser.getLexer(); }

  // Assembler-side M/X mode state.  Held in a FeatureBitset rather than two
  // bools so a single ComputeAvailableFeatures(CurFeatures) call refreshes
  // the AsmMatcher's mask after any toggle.  Initialised to MLow+XLow in
  // the constructor below, matching the C ABI default; the .a8/.a16 and
  // .i8/.i16 directives flip the relevant pair.
  FeatureBitset CurFeatures;
  void updateMatcherFeatures() {
    setAvailableFeatures(ComputeAvailableFeatures(CurFeatures));
  }
  // Set/reset a (FeatureLow, FeatureHigh) pair to canonical "High" or "Low"
  // state and refresh the matcher mask.  Shared by .a8/.a16/.i8/.i16
  // directive handling and constructor conflict resolution; without it
  // each toggle and conflict-rule was 2-4 lines of bit manipulation
  // duplicated per axis.
  void setModePair(unsigned FeatureLow, unsigned FeatureHigh, bool High) {
    CurFeatures.reset(High ? FeatureLow  : FeatureHigh);
    CurFeatures.set  (High ? FeatureHigh : FeatureLow);
    updateMatcherFeatures();
  }

  /// @name Auto-generated Matcher Functions
  /// {

#define GET_ASSEMBLER_HEADER
#include "W65816GenAsmMatcher.inc"

  /// }

public:
  W65816AsmParser(const MCSubtargetInfo &STI, MCAsmParser &Parser,
                  const MCInstrInfo &MII, const MCTargetOptions &Options)
      : MCTargetAsmParser(Options, STI, MII), Parser(Parser) {
    MCAsmParserExtension::Initialize(Parser);
    // Seed CurFeatures from the Subtarget, then enforce conflict resolution
    // via setModePair: M and X each must be EXACTLY one direction.  If the
    // user explicitly set -mattr=+mhigh on top of the default +mlow, drop
    // +mlow (vice versa for X).  If neither side is set, default to
    // M=16/X=16 (the C ABI) — belt-and-suspenders with the MC-layer
    // Subtarget's CPU=w65816 default.
    CurFeatures = STI.getFeatureBits();
    setModePair(W65816::FeatureMLow, W65816::FeatureMHigh,
                CurFeatures[W65816::FeatureMHigh]);
    setModePair(W65816::FeatureXLow, W65816::FeatureXHigh,
                CurFeatures[W65816::FeatureXHigh]);
  }
};

} // end anonymous namespace

bool W65816AsmParser::matchAndEmitInstruction(SMLoc Loc, unsigned &Opcode,
                                              OperandVector &Operands,
                                              MCStreamer &Out,
                                              uint64_t &ErrorInfo,
                                              bool MatchingInlineAsm) {
  MCInst Inst;
  unsigned MatchResult =
      MatchInstructionImpl(Operands, Inst, ErrorInfo, MatchingInlineAsm);

  switch (MatchResult) {
  case Match_Success:
    Inst.setLoc(Loc);
    Out.emitInstruction(Inst, *STI);
    return false;
  case Match_MnemonicFail:
    return Error(Loc, "invalid instruction mnemonic");
  case Match_InvalidOperand: {
    SMLoc ErrorLoc = Loc;
    if (ErrorInfo != ~0U) {
      if (ErrorInfo >= Operands.size())
        return Error(ErrorLoc, "too few operands for instruction");
      ErrorLoc = ((W65816Operand &)*Operands[ErrorInfo]).getStartLoc();
      if (ErrorLoc == SMLoc())
        ErrorLoc = Loc;
    }
    return Error(ErrorLoc, "invalid operand for instruction");
  }
  default:
    return true;
  }
}

bool W65816AsmParser::parseRegister(MCRegister &Reg, SMLoc &StartLoc,
                                    SMLoc &EndLoc) {
  // No register operands on the 65816.  A true return signals failure.
  return true;
}

ParseStatus W65816AsmParser::tryParseRegister(MCRegister &Reg, SMLoc &StartLoc,
                                              SMLoc &EndLoc) {
  return ParseStatus::NoMatch;
}

bool W65816AsmParser::parseInstruction(ParseInstructionInfo &Info,
                                       StringRef Name, SMLoc NameLoc,
                                       OperandVector &Operands) {
  // Some 65816 implied-accumulator forms appear in our AsmStrings as a
  // two-word spelling ("inc a", "asl a", etc.).  Fold the " a" back into
  // the mnemonic token here so the matcher sees exactly what the printer
  // emitted.  The bare "inc"/"asl"/... spellings remain routed to their
  // memory variants.
  Operands.push_back(W65816Operand::createToken(Name, NameLoc));

  // If there are no operands, we're done (covers nop, rts, rtl, clc, etc.
  // and the implied-accumulator forms handled above).
  if (getLexer().is(AsmToken::EndOfStatement)) {
    // Bare shorthand for accumulator-implied shift/rotate/inc/dec.
    // The canonical 65816 syntax is `asl a` / `inc a`, but most
    // assemblers accept the bare mnemonic.  Inject the A register
    // operand so the matcher routes to the ASL_A / INA / etc. def.
    StringRef Lower = Name.lower();
    if (Lower == "asl" || Lower == "lsr" || Lower == "rol" ||
        Lower == "ror" || Lower == "inc" || Lower == "dec") {
      SMLoc EOS = getLexer().getTok().getLoc();
      Operands.push_back(W65816Operand::createReg(W65816::A, EOS, EOS));
    }
    Parser.Lex();
    return false;
  }

  // Implied-accumulator form: "<mnem> a" (e.g. "inc a", "asl a").  The
  // AsmString for these uses the A register as an operand, so the matcher
  // expects a register operand here, not a literal token.
  if (getLexer().is(AsmToken::Identifier) &&
      getLexer().getTok().getIdentifier().equals_insensitive("a")) {
    SMLoc S = getLexer().getTok().getLoc();
    SMLoc E = SMLoc::getFromPointer(S.getPointer() + 1);
    Operands.push_back(W65816Operand::createReg(W65816::A, S, E));
    Parser.Lex(); // eat "a"
    if (getLexer().isNot(AsmToken::EndOfStatement)) {
      SMLoc ErrLoc = getLexer().getLoc();
      Parser.eatToEndOfStatement();
      return Error(ErrLoc, "unexpected token");
    }
    Parser.Lex();
    return false;
  }

  // Indirect addressing modes: `(addr)`, `(addr), y`, `(addr, x)`,
  // `(addr, s), y`.  We must intercept the leading `(` here — if it
  // were left to parseExpression it would be treated as expression
  // grouping and the parens would be silently eaten, producing an
  // absolute-mode opcode (caught silently with setjmp/longjmp #88).
  if (getLexer().is(AsmToken::LParen)) {
    SMLoc LParenLoc = getLexer().getTok().getLoc();
    Operands.push_back(W65816Operand::createToken("(", LParenLoc));
    Parser.Lex(); // eat '('

    // Parse the inner address expression.
    SMLoc AddrStart = getLexer().getLoc();
    const MCExpr *Val;
    if (Parser.parseExpression(Val))
      return Error(LParenLoc, "expected address expression after '('");
    SMLoc AddrEnd = getLexer().getLoc();
    Operands.push_back(W65816Operand::createAddr(Val, AddrStart, AddrEnd));

    // Now we expect either:
    //   `, x )`           — DP indexed indirect    e.g. lda (dp, x)
    //   `, s ) , y`       — stack-rel indirect Y   e.g. lda (4, s), y
    //   `)` then optional `, y`  — DP indirect / DP indirect Y
    if (getLexer().is(AsmToken::Comma)) {
      Parser.Lex(); // eat comma inside parens
      if (!getLexer().is(AsmToken::Identifier))
        return Error(getLexer().getLoc(), "expected 'x' or 's' after ','");
      StringRef Ident = getLexer().getTok().getIdentifier();
      SMLoc IdentLoc = getLexer().getTok().getLoc();
      bool isX = Ident.equals_insensitive("x");
      bool isS = Ident.equals_insensitive("s");
      if (!isX && !isS)
        return Error(IdentLoc, "expected 'x' or 's'");
      Parser.Lex(); // eat x / s
      if (!getLexer().is(AsmToken::RParen))
        return Error(getLexer().getLoc(), "expected ')'");
      Parser.Lex(); // eat ')'
      // The matcher expects a SINGLE combined token for the inside-paren
      // index — `x)` for DP-indexed-X and `s)` for stack-rel-indirect-Y
      // (per the asm strings `($addr , x)` and `($off, s), y` which
      // tablegen tokenizes with `x)` / `s)` as one MCK token because
      // there's no whitespace between them).  Pushing `x` + `)` as
      // separate operands fails matching with "invalid operand".
      Operands.push_back(W65816Operand::createToken(isX ? "x)" : "s)",
                                                    IdentLoc));
    } else {
      // Plain `(addr)` — close the parens and fall through to the
      // optional `, y` handling below.
      if (!getLexer().is(AsmToken::RParen))
        return Error(getLexer().getLoc(), "expected ')'");
      SMLoc RParenLoc = getLexer().getTok().getLoc();
      Operands.push_back(W65816Operand::createToken(")", RParenLoc));
      Parser.Lex(); // eat ')'
    }
    // Fall through to the trailing-suffix loop (handles `, y` after
    // the closing paren).
  } else if (getLexer().is(AsmToken::LBrac)) {
    // Long-indirect addressing: `[dp]` or `[dp], y`.
    SMLoc LBracLoc = getLexer().getTok().getLoc();
    Operands.push_back(W65816Operand::createToken("[", LBracLoc));
    Parser.Lex(); // eat '['

    SMLoc AddrStart = getLexer().getLoc();
    const MCExpr *Val;
    if (Parser.parseExpression(Val))
      return Error(LBracLoc, "expected address expression after '['");
    SMLoc AddrEnd = getLexer().getLoc();
    Operands.push_back(W65816Operand::createAddr(Val, AddrStart, AddrEnd));

    if (!getLexer().is(AsmToken::RBrac))
      return Error(getLexer().getLoc(), "expected ']'");
    SMLoc RBracLoc = getLexer().getTok().getLoc();
    Operands.push_back(W65816Operand::createToken("]", RBracLoc));
    Parser.Lex(); // eat ']'
    // Trailing-suffix loop will pick up `, y` if present.
  } else {
    // Parse the first real operand (immediate or address expression).
    if (parseOperand(Operands))
      return true;
  }

  // Handle trailing ", x" / ", y" / ", <expr>" (block-move second operand).
  while (getLexer().is(AsmToken::Comma)) {
    Parser.Lex(); // eat comma

    // Indexed suffix: ", x" / ", y" / ", s".  X and Y are real
    // register operands at the matcher level (the matcher generates
    // MCK_X / MCK_Y from the named registers).  S has no register def
    // — the matcher treats it as a literal token (MCK_s) — so we push
    // it as a token instead of as a register.
    if (getLexer().is(AsmToken::Identifier)) {
      StringRef Ident = getLexer().getTok().getIdentifier();
      if (Ident.equals_insensitive("x") || Ident.equals_insensitive("y")) {
        SMLoc S = getLexer().getTok().getLoc();
        SMLoc E = SMLoc::getFromPointer(S.getPointer() + 1);
        unsigned Reg = Ident.equals_insensitive("x") ? W65816::X
                                                      : W65816::Y;
        Operands.push_back(W65816Operand::createReg(Reg, S, E));
        Parser.Lex();
        continue;
      }
      if (Ident.equals_insensitive("s")) {
        SMLoc S = getLexer().getTok().getLoc();
        Operands.push_back(W65816Operand::createToken("s", S));
        Parser.Lex();
        continue;
      }
    }

    // Otherwise it must be another expression operand (e.g. block-move
    // "mvn dst, src" where both operands are immediate bank bytes).
    if (parseOperand(Operands))
      return true;
  }

  if (getLexer().isNot(AsmToken::EndOfStatement)) {
    SMLoc ErrLoc = getLexer().getLoc();
    Parser.eatToEndOfStatement();
    return Error(ErrLoc, "unexpected token");
  }

  Parser.Lex(); // consume EndOfStatement
  return false;
}

bool W65816AsmParser::parseOperand(OperandVector &Operands) {
  SMLoc S = getLexer().getLoc();

  // Immediate form: '#' <expression>.  The generated AsmMatcher tokenises
  // '#' in the AsmString (e.g. "lda\t#$imm") as a separate MCK__HASH_
  // token, so we push it as a literal and then the immediate operand.
  if (getLexer().is(AsmToken::Hash)) {
    SMLoc HashLoc = S;
    Parser.Lex(); // eat '#'
    Operands.push_back(W65816Operand::createToken("#", HashLoc));
    SMLoc ImmStart = getLexer().getLoc();
    const MCExpr *Val;
    if (Parser.parseExpression(Val))
      return Error(HashLoc, "expected immediate expression after '#'");
    SMLoc E = getLexer().getLoc();
    Operands.push_back(W65816Operand::createImm(Val, ImmStart, E));
    return false;
  }

  // Force-long prefix: `>expr` selects the 24-bit absolute-long addressing
  // form even when the value would otherwise fit in DP/abs.  This is the only
  // way to spell, e.g., a bank-explicit `sta >$000071` (opcode 0x8F) — the
  // 65816 encodes width in the opcode, so without the prefix the matcher
  // always narrows by value fit.  ('>' lexes as AsmToken::Greater; it cannot
  // begin an address expression, so consuming it here is unambiguous.)
  bool ForceLong = false;
  if (getLexer().is(AsmToken::Greater)) {
    Parser.Lex(); // eat '>'
    ForceLong = true;
  }

  // Anything else is an address / branch-target / block-move byte.  We
  // rely on the standard LLVM asm expression parser, which accepts
  // identifiers (labels), decimal integers, hex ($... via MCAsmInfo
  // configuration or 0x...), binary (%... when the lexer allows it),
  // and arithmetic.
  const MCExpr *Val;
  if (Parser.parseExpression(Val))
    return Error(S, "expected expression");
  SMLoc E = getLexer().getLoc();
  Operands.push_back(W65816Operand::createAddr(Val, S, E, ForceLong));
  return false;
}

ParseStatus W65816AsmParser::parseDirective(AsmToken DirectiveID) {
  StringRef IDVal = DirectiveID.getIdentifier().lower();

  // M/X processor-status mode directives.  Each toggles the appropriate
  // feature pair on CurFeatures and refreshes the AsmMatcher's mask, so
  // subsequent `lda #imm`/`ldx #imm`/etc. encode with the right operand
  // width.  Both ca65 (.a8/.a16, .i8/.i16) and WDC/Merlin32 (.as/.al,
  // .xs/.xl) spellings are accepted.  No operands; expect EOL.
  bool IsModeDir = true;
  if      (IDVal == ".a8"  || IDVal == ".as")
    setModePair(W65816::FeatureMLow, W65816::FeatureMHigh, /*High=*/true);
  else if (IDVal == ".a16" || IDVal == ".al")
    setModePair(W65816::FeatureMLow, W65816::FeatureMHigh, /*High=*/false);
  else if (IDVal == ".i8"  || IDVal == ".xs")
    setModePair(W65816::FeatureXLow, W65816::FeatureXHigh, /*High=*/true);
  else if (IDVal == ".i16" || IDVal == ".xl")
    setModePair(W65816::FeatureXLow, W65816::FeatureXHigh, /*High=*/false);
  else IsModeDir = false;
  if (IsModeDir) {
    if (!getLexer().is(AsmToken::EndOfStatement))
      return ParseStatus::Failure;
    return ParseStatus::Success;
  }

  unsigned Size = 0;
  if (IDVal == ".long")
    Size = 4;
  else if (IDVal == ".word" || IDVal == ".short")
    Size = 2;
  else if (IDVal == ".byte")
    Size = 1;
  else
    return ParseStatus::NoMatch;

  if (parseLiteralValues(Size, DirectiveID.getLoc()))
    return ParseStatus::Failure;
  return ParseStatus::Success;
}

bool W65816AsmParser::parseLiteralValues(unsigned Size, SMLoc L) {
  auto parseOne = [&]() -> bool {
    const MCExpr *Value;
    if (getParser().parseExpression(Value))
      return true;
    getParser().getStreamer().emitValue(Value, Size, L);
    return false;
  };
  return (parseMany(parseOne));
}

extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeW65816AsmParser() {
  RegisterMCAsmParser<W65816AsmParser> X(getTheW65816Target());
}

#define GET_REGISTER_MATCHER
#define GET_MATCHER_IMPLEMENTATION
#include "W65816GenAsmMatcher.inc"
