"""
Macintosh C/C++ ABI Demangler

Parses C++ symbols mangled according to the Macintosh C/C++ ABI Standard
Specification (Rev 1.3, Dec 1996, Section 3.4.1) and converts them into the
intermediate representation used by mangle.py, enabling re-mangling into
the Itanium ABI.

The Macintosh mangling grammar is fundamentally length-prefixed and
context-sensitive, so we use a hand-written recursive-descent parser
rather than a generated one.  A lark grammar is provided as structured
documentation and for validation of the grammar rules, but the actual
parsing is done procedurally.

Usage:
    from mac_demangle import mac_demangle, mac_to_itanium

    # Demangle to human-readable signature string
    print(mac_demangle("procWallMsg__8ActCrowdFP4PikiP7MsgWall"))

    # Translate a Macintosh-mangled symbol directly to Itanium mangling
    print(mac_to_itanium("foo__Fv"))
"""

from lark import Lark

from mangle import (
    # Builtin types
    BuiltinVoid, BuiltinBool, BuiltinChar, BuiltinShort, BuiltinInt,
    BuiltinFloat, BuiltinDouble, BuiltinEllipses,
    # Decorators
    OperatorUnsigned, OperatorSigned, OperatorLong,
    OperatorConst, OperatorPointer, OperatorReference,
    # Complex classes
    OperatorNamespace, OperatorTemplateArgs, OperatorFunctionArgs,
    # Special tokens
    SpecialTokenCtor, SpecialTokenDtor,
    SpecialTokenVTable, SpecialTokenRTTI,
    SyntaxToken,
    # Special operators
    SpecialOperatorNew, SpecialOperatorNewArray,
    SpecialOperatorDelete, SpecialOperatorDeleteArray,
    SpecialOperatorAdd, SpecialOperatorSubtract,
    SpecialOperatorMultiply, SpecialOperatorDivide, SpecialOperatorModulo,
    SpecialOperatorBitwiseXOR, SpecialOperatorAssignDivide,
    SpecialOperatorBitwiseAND, SpecialOperatorBitwiseOR,
    SpecialOperatorBitwiseNOT, SpecialOperatorLogicalNOT,
    SpecialOperatorAssign,
    SpecialOperatorLesserThan, SpecialOperatorGreaterThan,
    SpecialOperatorAssignAdd, SpecialOperatorAssignSubtract,
    SpecialOperatorAssignMultiply, SpecialOperatorAssignModulo,
    SpecialOperatorAssignBitwiseXOR, SpecialOperatorAssignBitwiseAND,
    SpecialOperatorAssignBitwiseOR,
    SpecialOperatorLeftShift, SpecialOperatorRightShift,
    SpecialOperatorAssignRightShift, SpecialOperatorAssignLeftShift,
    SpecialOperatorEqual, SpecialOperatorNotEqual,
    SpecialOperatorLesserThanOrEqual, SpecialOperatorGreaterThanOrEqual,
    SpecialOperatorLogicalAND, SpecialOperatorLogicalOR,
    SpecialOperatorIncrement, SpecialOperatorDecrement,
    SpecialOperatorArraySubscript,
    # Mangling infrastructure
    Signature, ItaniumSymbolDictionary, MangleError,
)


# ---------------------------------------------------------------------------
# Lark grammar (Section 3.4.1.3) -- for reference & validation only
# ---------------------------------------------------------------------------
# This grammar documents the Macintosh mangling format in PEG notation.
# Actual parsing is done by the recursive-descent parser below because
# length-prefixed names cannot be expressed in a context-free grammar.
# ---------------------------------------------------------------------------

MAC_GRAMMAR_REFERENCE = r"""
start: mangled_name

mangled_name: entity_name "__" class_name? type

entity_name: IDENT | special_name

special_name: "__ct" | "__dt"
            | "_vtbl" | "_rttivtbl" | "_vbtbl"
            | "__rtti" | "__ti" | "___ti"
            | "__op" type | "__" OP

OP: "nw" | "nwa" | "dl" | "dla" | "pl" | "mi" | "ml" | "dv"
  | "md" | "er" | "adv" | "ad" | "or" | "co" | "nt" | "as"
  | "lt" | "gt" | "apl" | "ami" | "amu" | "amd" | "aer"
  | "aad" | "aor" | "ls" | "rs" | "ars" | "als" | "eq"
  | "ne" | "le" | "ge" | "aa" | "oo" | "pp" | "mm" | "cl"
  | "vc" | "rf" | "cm" | "rm"

class_name: qualified_name
qualified_name: "Q" DIGIT qual_name_list | qual_name
qual_name_list: qual_name+
qual_name: INT CHARS_OF_LEN_INT

type: basic_type | qualified_type | class_name | array_type | function_type
basic_type: int_type | other_base_type
int_type: SIGN_MOD? INT_BASE_TYPE
SIGN_MOD: "S" | "U"
INT_BASE_TYPE: "b" | "c" | "s" | "i" | "l" | "x" | "w"
other_base_type: "f" | "d" | "r" | "v" | "e"

qualified_type: CV? type_qualifier_list type
CV: "C" | "V" | "CV"
type_qualifier_list: type_qualifier+
type_qualifier: "R" | "P" | "M" type

array_type: dimension_list type
dimension_list: dimension+
dimension: "A" INT "_"

function_type: "F" param_list ("_" type)?
param_list: type+

IDENT: /[A-Za-z_][A-Za-z0-9_]*/
INT: /[0-9]+/
DIGIT: /[0-9]/
"""

# NOTE: This grammar is for documentation only. It contains length-prefixed
# constructs (CHARS_OF_LEN_INT) that are inherently context-sensitive and
# cannot be expressed in a CFG, so we do not attempt to build a Lark parser
# from it.  The actual parsing is the recursive-descent code below.


# ---------------------------------------------------------------------------
# Stream helper
# ---------------------------------------------------------------------------

class DemangleError(Exception):
    pass


class _Stream:
    """A string cursor for recursive-descent parsing."""
    __slots__ = ('data', 'pos')

    def __init__(self, data: str):
        self.data = data
        self.pos = 0

    def peek(self, n=1) -> str:
        return self.data[self.pos:self.pos + n]

    def read(self, n=1) -> str:
        s = self.data[self.pos:self.pos + n]
        if len(s) < n:
            raise DemangleError(
                f"Unexpected end of input at pos {self.pos}, "
                f"wanted {n} chars, got {len(s)}"
            )
        self.pos += n
        return s

    def remaining(self) -> int:
        return len(self.data) - self.pos

    def at_end(self) -> bool:
        return self.pos >= len(self.data)

    def startswith(self, prefix: str) -> bool:
        return self.data[self.pos:self.pos + len(prefix)] == prefix

    def __repr__(self):
        return f"_Stream(pos={self.pos}, rest={self.data[self.pos:]!r})"


# ---------------------------------------------------------------------------
# Operator code -> mangle.py class mapping
# ---------------------------------------------------------------------------

_OP_TABLE = {
    "nw":  SpecialOperatorNew,
    "nwa": SpecialOperatorNewArray,
    "dl":  SpecialOperatorDelete,
    "dla": SpecialOperatorDeleteArray,
    "pl":  SpecialOperatorAdd,
    "mi":  SpecialOperatorSubtract,
    "ml":  SpecialOperatorMultiply,
    "dv":  SpecialOperatorDivide,
    "md":  SpecialOperatorModulo,
    "er":  SpecialOperatorBitwiseXOR,
    "adv": SpecialOperatorAssignDivide,
    "ad":  SpecialOperatorBitwiseAND,
    "or":  SpecialOperatorBitwiseOR,
    "co":  SpecialOperatorBitwiseNOT,
    "nt":  SpecialOperatorLogicalNOT,
    "as":  SpecialOperatorAssign,
    "lt":  SpecialOperatorLesserThan,
    "gt":  SpecialOperatorGreaterThan,
    "apl": SpecialOperatorAssignAdd,
    "ami": SpecialOperatorAssignSubtract,
    "amu": SpecialOperatorAssignMultiply,
    "amd": SpecialOperatorAssignModulo,
    "aer": SpecialOperatorAssignBitwiseXOR,
    "aad": SpecialOperatorAssignBitwiseAND,
    "aor": SpecialOperatorAssignBitwiseOR,
    "ls":  SpecialOperatorLeftShift,
    "rs":  SpecialOperatorRightShift,
    "ars": SpecialOperatorAssignRightShift,
    "als": SpecialOperatorAssignLeftShift,
    "eq":  SpecialOperatorEqual,
    "ne":  SpecialOperatorNotEqual,
    "le":  SpecialOperatorLesserThanOrEqual,
    "ge":  SpecialOperatorGreaterThanOrEqual,
    "aa":  SpecialOperatorLogicalAND,
    "oo":  SpecialOperatorLogicalOR,
    "pp":  SpecialOperatorIncrement,
    "mm":  SpecialOperatorDecrement,
    "vc":  SpecialOperatorArraySubscript,
}


# ---------------------------------------------------------------------------
# Factory helpers (bypass constructors that expect MutableString)
# ---------------------------------------------------------------------------

def _mk_sig(items):
    sig = list.__new__(Signature)
    list.__init__(sig, items)
    return sig


def _mk_tmpl(name_token, params):
    obj = list.__new__(OperatorTemplateArgs)
    list.__init__(obj, params)
    obj.lhand = name_token
    return obj


def _mk_fargs(params):
    obj = list.__new__(OperatorFunctionArgs)
    list.__init__(obj, params)
    return obj


def _mk_ns(lhand, rhand):
    obj = OperatorNamespace()
    obj.lhand = lhand
    obj.rhand = rhand
    return obj


# ---------------------------------------------------------------------------
# Recursive-descent parser
# ---------------------------------------------------------------------------

def _parse_int(s: _Stream) -> int:
    """Parse a run of decimal digits."""
    digits = ""
    while not s.at_end() and s.peek().isdigit():
        digits += s.read()
    if not digits:
        raise DemangleError(f"Expected integer at pos {s.pos}")
    return int(digits)


def _parse_l_name(s: _Stream) -> str:
    """Parse <l_name> ::= <int> <id> (length-prefixed identifier).

    In Macintosh ABI, template types are encoded inline as part of the
    length-prefixed name, e.g. ``22IDelegate2<P5Joint,Ul>`` where the
    length 22 covers the entire string including angle brackets and the
    encoded parameter types inside them.
    """
    length = _parse_int(s)
    name = s.read(length)
    return name


def _parse_l_name_to_node(s: _Stream):
    """Parse an l_name and convert to the appropriate mangle.py node.

    If the name contains angle brackets (``<...>``), it is a template
    instantiation.  We parse the template name and the encoded parameters
    inside the brackets and return an OperatorTemplateArgs node.
    Otherwise return a SyntaxToken.
    """
    raw = _parse_l_name(s)

    lt = raw.find('<')
    if lt < 0:
        return SyntaxToken(raw)

    # Template: split into name and parameter blob
    template_name = raw[:lt]
    if not raw.endswith('>'):
        raise DemangleError(f"Template name does not end with '>': {raw!r}")
    params_str = raw[lt + 1:-1]  # between < and >

    params = _parse_template_params(params_str)
    return _mk_tmpl(SyntaxToken(template_name), params)


def _parse_template_params(params_str: str) -> list:
    """Parse comma-separated type encodings inside template angle brackets.

    The encoded types use the same scheme as elsewhere in the mangling.
    Commas separate parameters, but we must respect nested ``<>`` pairs.
    """
    parts = []
    depth = 0
    current = ""
    for ch in params_str:
        if ch == '<':
            depth += 1
            current += ch
        elif ch == '>':
            depth -= 1
            current += ch
        elif ch == ',' and depth == 0:
            parts.append(current)
            current = ""
        else:
            current += ch
    if current:
        parts.append(current)

    result = []
    for part in parts:
        ps = _Stream(part)
        result.append(_parse_type(ps))
    return result


def _parse_qualified_name(s: _Stream):
    """
    Parse <qualified_name> ::= 'Q' <count> <qual_name_list> | <qual_name>

    In practice the count after 'Q' is a plain integer (often a single
    digit) without an underscore terminator.  This matches what mangle.py's
    ``macintosh_mangle_ns`` actually produces (e.g. ``Q24name4Test``).

    Returns a SyntaxToken, OperatorTemplateArgs, or nested OperatorNamespace.
    """
    if not s.at_end() and s.peek() == 'Q':
        s.read()  # consume Q
        count = _parse_int(s)
        names = []
        for _ in range(count):
            names.append(_parse_l_name_to_node(s))
        # Nest into OperatorNamespace chain (left to right, outer to inner)
        result = names[0]
        for i in range(1, len(names)):
            result = _mk_ns(result, names[i])
        return result
    else:
        return _parse_l_name_to_node(s)


def _parse_basic_type(s: _Stream):
    """
    Parse <basic_type> ::= <int_type> | <other_base_type>

    Returns None if the next character does not start a basic type.
    """
    if s.at_end():
        return None

    ch = s.peek()

    # Sign modifier: only valid if followed by an int base type char
    sign = None
    if ch in ('S', 'U'):
        if s.remaining() >= 2 and s.peek(2)[1] in 'bcsilxw':
            sign = s.read()
            ch = s.peek()
        else:
            return None

    # Integer base types
    _int_map = {
        'b': lambda: BuiltinBool(),
        'c': lambda: BuiltinChar(),
        's': lambda: BuiltinShort(),
        'i': lambda: BuiltinInt(),
        'l': lambda: _mk_long_int(),
        'x': lambda: _mk_longlong_int(),
        'w': lambda: SyntaxToken("wchar_t"),
    }

    if ch in _int_map:
        s.read()
        result = _int_map[ch]()
        if sign == 'U':
            w = OperatorUnsigned(); w.rhand = result; return w
        elif sign == 'S':
            w = OperatorSigned(); w.rhand = result; return w
        return result

    if sign is not None:
        raise DemangleError(f"Sign modifier '{sign}' not followed by int type (got '{ch}')")

    # Other base types
    _other_map = {
        'f': lambda: BuiltinFloat(),
        'd': lambda: BuiltinDouble(),
        'r': lambda: _mk_long_double(),
        'v': lambda: BuiltinVoid(),
        'e': lambda: BuiltinEllipses(),
    }

    if ch in _other_map:
        s.read()
        return _other_map[ch]()

    return None


def _mk_long_int():
    op = OperatorLong(); op.rhand = BuiltinInt(); return op

def _mk_longlong_int():
    inner = OperatorLong(); inner.rhand = BuiltinInt()
    outer = OperatorLong(); outer.rhand = inner; return outer

def _mk_long_double():
    op = OperatorLong(); op.rhand = BuiltinDouble(); return op


def _can_start_type(s: _Stream) -> bool:
    """Heuristic: can the current position begin a <type> production?"""
    if s.at_end():
        return False
    ch = s.peek()
    return (ch in 'bcsilxwfdrveUSCVPRMAFQ' or ch.isdigit())


def _parse_type(s: _Stream):
    """
    Parse <type> ::= [<cv>] [<type_qualifier_list>] <inner>
    where <inner> is basic_type | class_name | array_type | function_type
    """
    if s.at_end():
        raise DemangleError("Unexpected end of input while parsing type")

    # Optional CV qualifiers.  'C' as a cv qualifier only appears before
    # P, R, or M (type qualifiers).  Otherwise it could be part of a class
    # name or something else.
    is_const = False
    is_volatile = False

    if s.startswith("CV"):
        nxt = s.peek(3)[2:3]
        if nxt in ('P', 'R', 'M'):
            s.read(2); is_const = True; is_volatile = True
    elif s.peek() == 'C' and s.remaining() >= 2:
        nxt = s.peek(2)[1]
        if nxt in ('P', 'R', 'M', 'C', 'V'):
            s.read(); is_const = True
    elif s.peek() == 'V' and s.remaining() >= 2:
        nxt = s.peek(2)[1]
        if nxt in ('P', 'R', 'M', 'C', 'V'):
            s.read(); is_volatile = True

    # Type qualifier list: P (pointer), R (reference), M<type> (member ptr)
    qualifiers = []
    while not s.at_end():
        ch = s.peek()
        if ch == 'P':
            s.read(); qualifiers.append('P')
        elif ch == 'R':
            s.read(); qualifiers.append('R')
        elif ch == 'M':
            s.read()
            mt = _parse_type(s)
            qualifiers.append(('M', mt))
        else:
            break

    # Base type
    inner = _parse_basic_type(s)

    if inner is None:
        if not s.at_end() and s.peek() == 'A' and s.remaining() >= 2 and s.peek(2)[1].isdigit():
            inner = _parse_array_type(s)
        elif not s.at_end() and s.peek() == 'F':
            inner = _parse_function_type(s)
        else:
            inner = _parse_qualified_name(s)

    # Apply qualifiers right-to-left (spec says "interpreted from right to left")
    result = inner
    for q in reversed(qualifiers):
        if q == 'P':
            w = OperatorPointer(); w.lhand = result; result = w
        elif q == 'R':
            w = OperatorReference(); w.lhand = result; result = w
        elif isinstance(q, tuple) and q[0] == 'M':
            w = OperatorPointer(); w.lhand = result; result = w

    if is_const:
        w = OperatorConst(); w.lhand = result; result = w

    return result


def _parse_array_type(s: _Stream):
    """Parse <array_type> ::= <dimension_list> <type>"""
    while not s.at_end() and s.peek() == 'A':
        s.read()
        _ = _parse_int(s)
        if not s.at_end() and s.peek() == '_':
            s.read()
    return _parse_type(s)


def _parse_function_type(s: _Stream):
    """Parse <function_type> ::= 'F' <param_list> ['_' <return_type>]"""
    s.read()  # consume F
    params = []
    while not s.at_end() and s.peek() != '_' and _can_start_type(s):
        params.append(_parse_type(s))
    if not s.at_end() and s.peek() == '_':
        s.read()
        _parse_type(s)  # return type (consumed but not used at top level)
    return _mk_fargs(params)


# ---------------------------------------------------------------------------
# Top-level separator logic
# ---------------------------------------------------------------------------

def _find_entity_separator(mangled: str):
    """
    Split a mangled name into ``(entity_name, rest_after_separator)``.

    The entity and the class/type info are separated by ``__``.  Special
    entity names that themselves start with underscores need careful handling.
    """
    _PREFIXES = [
        ("_rttivtbl__", "_rttivtbl"),
        ("_vtbl__",     "_vtbl"),
        ("_vbtbl__",    "_vbtbl"),
        ("__rtti__",    "__rtti"),
        ("___ti",       "___ti"),
        ("__ti",        "__ti"),
        ("__ct__",      "__ct"),
        ("__dt__",      "__dt"),
    ]
    for prefix, entity in _PREFIXES:
        if mangled.startswith(prefix):
            return (entity, mangled[len(prefix):])

    # Operator entities: __<op>__<rest>  or  __<op>F... (free function)
    if mangled.startswith("__"):
        op_codes = sorted(_OP_TABLE.keys(), key=len, reverse=True)
        for op in op_codes:
            # Member operator: __op__<class><func>
            p = "__" + op + "__"
            if mangled.startswith(p):
                return ("__" + op, mangled[len(p):])
        # Free operator function: __<op>F...
        for op in op_codes:
            p = "__" + op
            if mangled == p or (mangled.startswith(p) and mangled[len(p)] == 'F'):
                return ("__" + op, mangled[len(p):])

    # Normal case: scan for "__" followed by valid continuation
    i = 0
    while i < len(mangled) - 1:
        if mangled[i] == '_' and mangled[i + 1] == '_':
            rest = mangled[i + 2:]
            if rest and (rest[0].isdigit() or rest[0] in ('Q', 'F', 'C')):
                return (mangled[:i], rest)
        i += 1

    raise DemangleError(f"Cannot find entity separator in {mangled!r}")


def _entity_to_node(name: str):
    """Convert entity name string to a mangle.py IR node."""
    if name == "__ct":
        return SpecialTokenCtor()
    if name == "__dt":
        return SpecialTokenDtor()
    if name in ("_vtbl", "_rttivtbl"):
        return SpecialTokenVTable()
    if name == "__rtti":
        return SpecialTokenRTTI()
    if name.startswith("__") and len(name) > 2:
        op_code = name[2:]
        if op_code in _OP_TABLE:
            return _OP_TABLE[op_code]()
    return SyntaxToken(name)


# ---------------------------------------------------------------------------
# Main parse entry point
# ---------------------------------------------------------------------------

def _parse_mangled(mangled: str) -> Signature:
    """
    Parse a Macintosh ABI mangled name and return a ``Signature`` object
    (from mangle.py) that can be re-mangled to Itanium format.
    """
    entity_str, rest = _find_entity_separator(mangled)
    entity_node = _entity_to_node(entity_str)
    s = _Stream(rest)

    # Type-info special entities (__ti, ___ti)
    if entity_str in ("__ti", "___ti"):
        ty = _parse_type(s)
        rtti = SpecialTokenRTTI()
        if isinstance(ty, SyntaxToken):
            return _mk_sig([_mk_ns(ty, rtti)])
        return _mk_sig([ty])

    # Vtable / RTTI / vbtbl: just a class name, no function type
    if entity_str in ("_vtbl", "_rttivtbl", "_vbtbl", "__rtti"):
        class_qual = _parse_qualified_name(s)
        return _mk_sig([_mk_ns(class_qual, entity_node)])

    # --- Functions and data members ---
    class_qual = None
    if not s.at_end() and (s.peek().isdigit() or s.peek() == 'Q'):
        class_qual = _parse_qualified_name(s)

    # Const method qualifier: 'C' immediately before 'F'
    is_const_method = False
    if not s.at_end() and s.peek() == 'C' and s.remaining() >= 2 and s.peek(2)[1] == 'F':
        s.read()  # consume C
        is_const_method = True

    # Function parameters
    has_func = False
    params = []
    if not s.at_end() and s.peek() == 'F':
        s.read()  # consume F
        has_func = True
        while not s.at_end() and _can_start_type(s):
            params.append(_parse_type(s))

    # Build name node
    if class_qual is not None:
        name_node = _mk_ns(class_qual, entity_node)
    else:
        name_node = entity_node

    # Build the Signature
    if has_func:
        if not params:
            params = [BuiltinVoid()]

        func_args = _mk_fargs(params)

        if is_const_method:
            const_args = OperatorConst()
            const_args.lhand = func_args
            args_node = const_args
        else:
            args_node = func_args

        if isinstance(entity_node, (SpecialTokenCtor, SpecialTokenDtor)):
            return _mk_sig([name_node, args_node])
        else:
            # Return type is not in Mac mangling; Itanium also omits it for
            # top-level functions, so a void placeholder works.
            return _mk_sig([BuiltinVoid(), name_node, args_node])
    else:
        return _mk_sig([name_node])


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

def mac_demangle(mangled: str) -> str:
    """
    Demangle a Macintosh ABI mangled C++ symbol to a human-readable string.

    Returns the demangled string representation.
    Raises DemangleError if the symbol cannot be parsed.
    """
    return str(_parse_mangled(mangled))


def mac_to_itanium(mangled: str) -> str:
    """
    Translate a Macintosh ABI mangled C++ symbol to Itanium ABI mangling.

    Parses the Macintosh mangled name, builds the mangle.py IR, then
    re-mangles using the Itanium mangling rules.

    Returns the Itanium-mangled symbol string.
    Raises DemangleError if the symbol cannot be parsed.
    """
    sig = _parse_mangled(mangled)
    return sig.itanium_mangle(ItaniumSymbolDictionary())


def mac_demangle_signature(mangled: str) -> Signature:
    """
    Demangle a Macintosh ABI mangled C++ symbol and return the raw
    mangle.py Signature object for further programmatic use.
    """
    return _parse_mangled(mangled)


# ---------------------------------------------------------------------------
# Test harness
# ---------------------------------------------------------------------------

def _test(mac_mangled, expected_itanium=None):
    try:
        sig = _parse_mangled(mac_mangled)
        demangled = str(sig)
        itanium = sig.itanium_mangle(ItaniumSymbolDictionary())
        it_ok = ""
        if expected_itanium:
            it_ok = "  OK" if itanium == expected_itanium else f"  FAIL expected {expected_itanium}"
        print(f"  {mac_mangled}")
        print(f"      demangled : {demangled}")
        print(f"      itanium   : {itanium}{it_ok}")
    except Exception as e:
        print(f"  {mac_mangled}")
        print(f"      ERROR: {e}")


if __name__ == "__main__":
    print("=" * 72)
    print("  Macintosh ABI -> Itanium ABI  Demangler Tests")
    print("=" * 72)

    print("\n--- Simple free functions ---")
    _test("foo__Fv",  "_Z3foov")
    _test("foo__Fi",  "_Z3fooi")
    _test("foo__Fl",  "_Z3fool")
    _test("foo__Fx",  "_Z3foox")
    _test("foo__Ff",  "_Z3foof")
    _test("foo__Fd",  "_Z3food")
    _test("foo__Fr",  "_Z3fooe")

    print("\n--- Multiple parameters ---")
    _test("foo__Fcis",  "_Z3foocis")

    print("\n--- Pointer / reference parameters ---")
    _test("procWallMsg__8ActCrowdFP4PikiP7MsgWall")

    print("\n--- Qualified (nested) class methods ---")
    _test("MethodA__Q24name4TestFv")
    _test("MethodB__Q24name4TestCFv")

    print("\n--- Unsigned / Signed types ---")
    _test("foo__FUi",  "_Z3fooj")
    _test("foo__FUl",  "_Z3foom")
    _test("foo__FUc",  "_Z3fooh")
    _test("foo__FSc",  "_Z3fooa")

    print("\n--- Constructor / Destructor ---")
    _test("__ct__8MyClassFv")
    _test("__dt__8MyClassFv")

    print("\n--- Vtable ---")
    _test("_vtbl__8MyClass")
    _test("_vtbl__3XXX")

    print("\n--- Template types in parameters ---")
    _test("recTraverseMaterials__9BaseShapeFP5JointP22IDelegate2<P5Joint,Ul>")

    print("\n--- Template types in class qualifier ---")
    _test("foobar__FQ210NamespaceA19TemplateClassA<i,f>")

    print("\n--- Longest symbol of Pikmin 1 ---")
    _test(
        "init__Q23zen17particleGeneratorFPUcP7TextureP7TextureR8Vector3f"
        "PQ23zen18particleMdlManager"
        "PQ23zen37CallBack1<PQ23zen17particleGenerator>"
        "PQ23zen58CallBack2<PQ23zen17particleGenerator,PQ23zen11particleMdl>"
    )

    print("\n--- Operator functions ---")
    _test("__nw__FUi")
    _test("__dt__Q24name4TestFv")
    _test("__eq__5MyClsFi")

    print("\n--- Round-trip: mangle.py -> mac -> parse -> itanium ---")
    from mangle import macintosh_mangle as mac_mangle
    protos = [
        "void foo()",
        "void foo(int)",
        "void foo(long int)",
        "void foo(float)",
        "void name::Test::MethodA()",
        "void name::Test::MethodB() const",
        "void ActCrowd::procWallMsg(Piki*, MsgWall*)",
    ]
    for proto in protos:
        mac = mac_mangle(proto)
        try:
            it = mac_to_itanium(mac)
            print(f"  {proto}")
            print(f"      mac={mac}  ->  itanium={it}")
        except Exception as e:
            print(f"  {proto}")
            print(f"      mac={mac}  ->  ERROR: {e}")

    print()
