1 # see http://en.wikipedia.org/wiki/Microsoft_Visual_C%2B%2B_Name_Mangling
  2
  3 class StupidDict(dict): pass
  4
  5 calling_conventions = StupidDict({
  6     "A": "__cdecl",
  7     "B": "__cdecl", # according to wine's source (http://source.winehq.org/source/dlls/msvcrt/undname.c#L561) - verify?
  8     "G": "__stdcall",
  9     "I": "__fastcall",
 10     "E": "__thiscall",
 11 })
 12
 13 opcodes = {
 14     "?0": "(constructor)",
 15     "?1": "(destructor)",
 16     "?2": "(new)",
 17     "?3": "(delete)",
 18     "?4": "=",
 19     "?5": ">>",
 20     "?6": "<<",
 21     "?7": "!",
 22     "?8": "==",
 23     "?9": "!=",
 24     "?A": "[]",
 25     "?B": "(conversion)",
 26     "?C": "->",
 27     "?D": "*",
 28     "?E": "++",
 29     "?F": "--",
 30     "?G": "-",
 31     "?H": "+",
 32     "?I": "&",
 33     "?J": "->*",
 34     "?K": "/",
 35     "?L": "%",
 36     "?M": "<",
 37     "?N": "<=",
 38     "?O": ">",
 39     "?P": ">=",
 40     "?Q": ",",
 41     "?R": "(call)",
 42     "?S": "~",
 43     "?T": "^",
 44     "?U": "|",
 45     "?V": "&&",
 46     "?W": "||",
 47     "?X": "*=",
 48     "?Y": "+=",
 49     "?Z": "-=",
 50     "?_0": "/=",
 51     "?_1": "%=",
 52     "?_2": ">>=",
 53     "?_3": "<<=",
 54     "?_4": "&=",
 55     "?_5": "|=",
 56     "?_6": "^=",
 57     "?_7": "(vtable)",
 58     "?_C": "(unknown)",
 59     "?_F": "(default constructor closure)",
 60     "?_O": "(unknown)",
 61     "?_R0?AV": "(RTTI Type Descriptor)",
 62     "?_R1A@?OA@EA@": "(RTTI Base Class Descriptor at (0,-1,0,64))",
 63     "?_R2": "(RTTI Base Class Array)",
 64     "?_R3": "(RTTI Class Hierachy Descriptor)",
 65     "?_R4": "(RTTI Complete Object Locator)",
 66     "?_U": "(new[])",
 67     "?_V": "(delete[])",
 68 }
 69
 70 # XXX: ugly, anyway to do this without typechecking or similar?
 71
 72 def getshort_template(name):
 73     """returns the short form of a template object if `name` is a template
 74     for example when given Foo<class bar> returns Foo, if `name` isn't a template
 75     it itself is returned"""
 76     if isinstance(name, Template):
 77         return name.name
 78     else:
 79         return name
 80
 81 opformatters = {
 82     "(constructor)": lambda op: "%s::%s" % (op.name, getshort_template(op.name[-1])),
 83     "(destructor)": lambda op: "%s::~%s" % (op.name, getshort_template(op.name[-1])),
 84     "(new)": lambda op: "%s::operator new" % op.name,
 85     "(delete)": lambda op: "%s::operator delete" % op.name,
 86     "(conversion)": lambda op: "%s::(conversion)" % op.name,
 87     "(call)": lambda op: "%s()()" % op.name,
 88     "(unknown)": lambda op: "(unknown operator %s for %s)" % (op.opcode, op.name),
 89     "(new[])": lambda op: "%s::operator new[]" % op.name,
 90     "(delete[])": lambda op: "%s::operator delete[]" % op.name,
 91 }
 92
 93 # XXX: according to wine there's a "thunk" type, figure out what it is,
 94 # http://source.winehq.org/source/dlls/msvcrt/undname.c#L917
 95 typecodes = {
 96     "A": {"visibility": "private", "type": "method", "special": None},
 97     "C": {"visibility": "private", "type": "method", "special": "static"},
 98     "E": {"visibility": "private", "type": "method", "special": "virtual"},
 99     "I": {"visibility": "protected", "type": "method", "special": None},
100     "K": {"visibility": "protected", "type": "method", "special": "static"},
101     "M": {"visibility": "protected", "type": "method", "special": "virtual"},
102     "Q": {"visibility": "public", "type": "method", "special": None},
103     "S": {"visibility": "public", "type": "method", "special": "static"},
104     "U": {"visibility": "public", "type": "method", "special": "virtual"},
105     "Y": {"type": "function"},
106     "0": {"type": "data", "name": 0},
107     "1": {"type": "data", "name": 1},
108     "2": {"type": "data", "name": 2},
109     "3": {"type": "data", "name": 3},
110     "6": {"type": "compiler_data", "name": 6},
111 }
112
113 datatypecodes = frozenset(("0", "1", "2", "3"))
114 compiler_types = frozenset(("6",))
115 functypecodes = frozenset(typecodes) - datatypecodes - compiler_types
116
117 sdatatypes = {
118     "C": "signed char",
119     "D": "char",
120     "E": "unsigned char",
121     "F": "short",
122     "G": "unsigned short",
123     "H": "int",
124     "I": "unsigned int",
125     "J": "long",
126     "K": "unsigned long",
127     "M": "float",
128     "N": "double",
129     "O": "long double",
130     "_J": "__int64",
131     "_K": "unsigned __int64",
132     "_N": "bool",
133     "_W": "wchar_t",
134 }
135
136 backrefable_sdatatypes = set(("_J", "_K", "_N", "_W"))
137
138 namedatatypes = {
139     "T": "union",
140     "U": "struct",
141     "V": "class",
142     "W4": "enum", # 4-byte
143 }
144
145 # ReferenceOrPointer in the BNF Syntax
146 ptrtypes = {
147     "A": (None, "&"),
148     "P": (None, "*"),
149     "Q": ("const", "*"),
150     "R": ("volatile", "*"),
151 }
152
153 # Pointer in the BNF Syntax
154 # Note this conflicts with the "A|B|C" syntax, but it looks like the
155 # second char of each of these doesn't conflict with SimpleDataType/X
156 # this might change in the future so be weary of breakage
157 ptrtypes2 = {
158     "AP": (None, "*"),
159     "BQ": ("const", "*"),
160     "CR": ("volatile", "*"),
161 }
162
163 storageclasses = {
164     "A": "Normal",
165     "B": "Volatile",
166     "C": "const",
167     "Z": "Executable",
168 }
169
170 # XXX: modifiers: not mentioned on the wikipedia page
171 modifiers = {
172     "A": None,
173     "B": "const",
174     "C": "volatile",
175     "D": "const volatile",
176 }
177
178 digits = {
179     "A": "0",
180     "B": "1",
181     "C": "2",
182     "D": "3",
183     "E": "4",
184     "F": "5",
185     "G": "6",
186     "H": "7",
187     "I": "8",
188     "J": "9",
189     "K": "A",
190     "L": "B",
191     "M": "C",
192     "N": "D",
193     "O": "E",
194     "P": "F",
195 }
196
197 import re
198 import string
199 import collections
200
201 def collapse_name(name, base):
202     """collapses the given name in-place based on `base`"""
203     # make sure base is a name and not an Operator or etc.
204     while isinstance(base, Reference):
205         base = base.to
206     while isinstance(name, Reference):
207         name = name.to
208     if isinstance(name, Name):
209         for i in xrange(len(name)-1):
210             if base[i] == name[0]:
211                 del name[0]
212         # extra pass to remove stuff in containers
213         for part in name:
214             if hasattr(part, "collapse"):
215                 part.collapse(base)
216
217 def collapsed_name(name, base):
218     """returns a new collapsed name based on `base`"""
219     import copy
220     n = copy.deepcopy(name)
221     collapse_name(n, base)
222     return n
223
224 class Args(list):
225     def collapse(self, base):
226         """modifies self in-place collapsing the names in it based on `base`"""
227         for arg in self:
228             collapse_name(arg, base)
229
230     def get_collapsed(self, base):
231         """returns a new Args instance with names collapsed based on `base`"""
232         import copy
233         n = copy.deepcopy(self)
234         n.collapse(base)
235         return n
236
237     def __str__(self):
238         return ", ".join(str(arg) for arg in self)
239
240 class Reference(object):
241     """base Reference class, anything that is mostly just a 1:1 container with special formatting should derive from this"""
242     def __init__(self, to):
243         self.to = to
244
245 class Name(list):
246     def __str__(self):
247         return "::".join(str(s) for s in self)
248
249 class Template(object):
250     def __init__(self, name, args):
251         self.name = name
252         self.args = args
253
254     def __str__(self):
255         return "%s<%s>" % (self.name, self.args)
256
257     def collapse(self, base):
258         self.args.collapse(base)
259
260 class Operator(Reference):
261     def __init__(self, name, opcode):
262         Reference.__init__(self, name)
263         self.name = name
264         self.opcode = opcode
265         self.opstr = opcodes[opcode]
266
267     def __str__(self):
268         if self.opstr in opformatters:
269             return opformatters[self.opstr](self)
270         return "%s::operator%s" % (self.name, self.opstr)
271
272 class NamedDataType(Reference):
273     def __init__(self, typecode, name):
274         Reference.__init__(self, name)
275         self.typecode = typecode
276         self.typestr = namedatatypes[typecode]
277         self.name = name
278
279     def __str__(self):
280         return "%s %s" % (self.typestr, self.name)
281
282 class ModifiedDataType(Reference):
283     def __init__(self, type, modifier):
284         Reference.__init__(self, type)
285         self.type = type
286         self.modifier = modifier
287
288     def __str__(self):
289         return "%s %s" % (self.modifier, self.type)
290
291 class Pointer(Reference):
292     def __init__(self, to, types):
293         Reference.__init__(self, to)
294         self.types = types
295
296     def __str__(self):
297         # XXX: what's the proper pointer syntax? - Think I got it right now (http://articles.techrepublic.com.com/5100-22-1052161.html)
298         # XXX: should these be reversed here or when constructing the object?
299         types = reversed(self.types)
300         pairs = ["%s%s " % (ptrtype, modifier) if modifier else "%s" % ptrtype for modifier, ptrtype in types]
301         #prefixes = [t[0] for t in self.types if t[0]]
302         #ptrs = reversed([t[1] for t in self.types])
303         return "%s %s" % (self.to, "".join(pairs).rstrip())
304
305 class Array(Reference):
306     def __init__(self, of, dimensions):
307         Reference.__init__(self, of)
308         self.of = of
309         self.dimensions = dimensions
310
311     def __str__(self):
312         return "%s %s" % (self.of, "".join("[%d]" % dim for dim in self.dimensions))
313
314 class Function(object):
315     def __init__(self, name, type, callingconv, ret_type, args, storage):
316         self.name = name
317         self.type = type
318         self.callingconv = callingconv
319         self.ret_type = ret_type
320         self.args = args
321         self.storage = storage
322
323     def __str__(self):
324         # XXX: calling convention, storage class, static/visibility?
325         # XXX: do collapsing, should probably be done optionally in __format__ later
326         args = self.args.get_collapsed(self.name)
327         ret_type = collapsed_name(self.ret_type, self.name)
328         return "%s %s(%s)" % (ret_type, self.name, args)
329
330 class Method(Function):
331     def __init__(self, name, type, modifier, callingconv, ret_type, args, storage):
332         Function.__init__(self, name, type, callingconv, ret_type, args, storage)
333         self.modifier = modifier
334
335     def __str__(self):
336         #args = ", ".join(str(arg) for arg in self.args)
337         postfix = " %s" % self.modifier if self.modifier else ""
338         prefix = "%s " % self.type["special"] if self.type["special"] else ""
339         return "%s%s%s" % (prefix, Function.__str__(self), postfix)
340
341 class Variable(object):
342     def __init__(self, name, type, datatype, storage):
343         self.name = name
344         self.type = type
345         self.datatype = datatype
346         self.storage = storage
347
348     def __str__(self):
349         return "%s %s" % (self.datatype, self.name)
350
351 class CompilerData(object):
352     def __init__(self, name, type, modifier, extname):
353         self.name = name
354         self.type = type
355         self.modifier = modifier
356         self.extname = extname
357
358     def __str__(self):
359         prefix = "%s " % self.modifier if self.modifier else ""
360         return "%s%s" % (prefix, self.name)
361
362 class FuncPointer(Pointer):
363     def __str__(self):
364         # TODO: is there any reasonable case where you'd have a pointer to a func pointer?
365         # XXX: include Calling Convention?
366         ptrs = [t[1] for t in self.types]
367         args = self.to.args
368         return "%s (%s%s)(%s)" % (self.to.ret_type, "".join(ptrs), self.to.name, args)
369
370 def get_name(s, backrefs, reverse=True):
371     nbackrefs = backrefs["name"]
372     validc = r"[a-zA-Z_]\w*"
373     identifier = re.compile(r"(?P<backref>\d)|(?P<name>%s)@" % validc)
374     def identname(match):
375         """returns (should_to_backrefs, name)"""
376         gd = match.groupdict()
377         name = None
378         backref = gd.get("backref", None)
379         if backref:
380             name = nbackrefs[int(backref)]
381         else:
382             name = gd["name"]
383         return (False if backref else True, name)
384     curr = s
385     identifiers = []
386     while True:
387         if curr[:2] == "?$":
388             curr = curr[2:]
389             unqmatch = identifier.match(curr)
390             add, unQualifiedName = identname(unqmatch)
391             if add:
392                 nbackrefs.append(unQualifiedName)
393             curr = curr[unqmatch.end():]
394             tbr = collections.defaultdict(list)
395             (args, curr) = get_arglist(curr, tbr)
396             (name, curr) = get_name(curr, backrefs, reverse=False)
397             identifiers.append(Template(unQualifiedName, args))
398             identifiers.extend(name)
399             continue
400         m = identifier.match(curr)
401         if not m:
402             break
403         add, name = identname(m)
404         if add:
405             nbackrefs.append(name)
406         identifiers.append(name)
407         curr = curr[m.end():]
408     if reverse:
409         name = list(reversed(identifiers))
410     else:
411         name = identifiers
412     return (Name(name), curr)
413
414 sdatatype_p = "|".join(re.escape(dt) for dt in sdatatypes)
415 ndatatype_p = "|".join(re.escape(dt) for dt in namedatatypes)
416 simpledtype_re = re.compile(r"(?P<simple>%s)|(?P<named>%s)" % (sdatatype_p, ndatatype_p))
417
418 rop_re = re.compile("|".join(re.escape(ptype) for ptype in ptrtypes))
419 ptrref_re = re.compile("|".join(re.escape(ptype) for ptype in ptrtypes2))
420
421 sane_y_re = re.compile(r"Y\d[%s]" % re.escape("".join(digits)))
422
423 class InvalidDataType(Exception):
424     def __init__(self, s):
425         self.s = s
426
427     def __str__(self):
428         return "Invalid Data Type at the start of %r" % self.s
429
430 class CantParse(Exception):
431     def __init__(self, symbol, scheme):
432         self.symbol = symbol
433         self.scheme = scheme
434
435     def __str__(self):
436         return "Can't parse %r with the %s mangling scheme" % (self.symbol, self.scheme)
437
438 def get_number(curr, backrefs):
439     nstr, sep, curr = curr.partition("@")
440     return (int("".join(digits[c] for c in nstr), 16), curr)
441
442 def get_datatype(s, backrefs, record=True):
443     pbackrefs = backrefs["pointer"]
444     curr = s
445     # SimpleDataType stuff first
446     m = simpledtype_re.match(curr)
447     if m:
448         gd = m.groupdict()
449         curr = curr[m.end():]
450         assert gd.get("simple") or gd.get("named")
451         if gd.get("simple"):
452             dtype = sdatatypes[gd["simple"]]
453             if record:
454                 pbackrefs.append(dtype)
455             return (dtype, curr)
456         elif gd.get("named"):
457             name, curr = get_name(curr, backrefs)
458             assert curr[0] == "@"
459             curr = curr[1:]
460             dtype = NamedDataType(gd["named"], Name(name))
461             if record:
462                 pbackrefs.append(dtype)
463             return (dtype, curr)
464     # XXX: Pointers?
465     m = rop_re.match(curr)
466     if m:
467         types = [ptrtypes[m.group(0)]]
468         curr = curr[m.end():]
469         while True:
470             m = ptrref_re.match(curr)
471             if not m:
472                 break
473             types.append(ptrtypes2[m.group(0)])
474             curr = curr[m.end():]
475         if curr[0] in ("A", "B", "C"):
476             # XXX: looks like A == normal, B == const, C == volatile
477             modifier = modifiers[curr[0]]
478             curr = curr[1:]
479             if curr[0] == "X":
480                 base, curr = "void", curr[1:]
481             else:
482                 base, curr = get_datatype(curr, backrefs, False)
483                 if modifier:
484                     base = ModifiedDataType(base, modifier)
485             dtype = Pointer(base, types)
486         elif curr[0] in ("6", "8"):
487             curr = curr[1:]
488             fdict, curr = get_functype(curr, backrefs)
489             # XXX: anonymous and no typecode, what to do?
490             func = Function("", typecodes["Y"], fdict["cconv"], fdict["ret"], fdict["args"], fdict["storage_class"])
491             dtype = FuncPointer(func, types)
492         pbackrefs.append(dtype)
493         return dtype, curr
494     if curr[0] in string.digits:
495         dtype = pbackrefs[int(curr[0])]
496         curr = curr[1:]
497         return dtype, curr
498     # XXX: wtf, looks like there's a second syntax for Y: Y<amount><firstnum in base10>$$C<modifier><type>
499     if sane_y_re.match(curr):
500         curr = curr[1:]
501         amount = int(curr[0]) + 1
502         curr = curr[1:]
503         dimensions = []
504         for i in xrange(amount):
505             dimension, curr = get_number(curr, backrefs)
506             dimensions.append(dimension)
507         base, curr = get_datatype(curr, backrefs)
508         dtype = Array(base, dimensions)
509         return (dtype, curr)
510     raise InvalidDataType(curr)
511
512 # this is in a separate function than get_datatype because it seems it only applies to the return value
513 def get_modifieddatatype(s, backrefs):
514     curr = s
515     assert curr[0] == "?"
516     curr = curr[1:]
517     modifier = modifiers[curr[0]]
518     curr = curr[1:]
519     dtype, curr = get_datatype(curr, backrefs)
520     if modifier:
521         dtype = ModifiedDataType(dtype, modifier)
522     return (dtype, curr)
523
524 def get_arglist(s, backrefs):
525     if s[0] == "X":
526         return (Args(), s[1:])
527     curr = s
528     args = Args()
529     while True:
530         if curr[0] == "@" or curr[0] == "Z":
531             curr = curr[1:]
532             break
533         try:
534             dt, curr = get_datatype(curr, backrefs)
535         except InvalidDataType:
536             break
537         args.append(dt)
538         #m = simpledtype_re.match(curr)
539         #if not m:
540         # break
541         #gd = m.groupdict()
542         #curr = curr[m.end():]
543         #assert gd.get("simple") or gd.get("named")
544         #if gd.get("simple"):
545         # args.append(sdatatypes[gd["simple"]])
546         #elif gd.get("named"):
547         # name, curr = get_name(curr, backrefs)
548         # assert curr[0] == "@"
549         # curr = curr[1:]
550         # args.append(NamedDataType(gd["named"], Name(name)))
551     return (args, curr)
552
553 ftypecode_p = "|".join(re.escape(typecode) for typecode in functypecodes)
554 dtypecode_p = "|".join(re.escape(typecode) for typecode in datatypecodes)
555 ctypecode_p = "|".join(re.escape(typecode) for typecode in compiler_types)
556 typecodes_re = re.compile(r"(?P<function>%s)|(?P<datatype>%s)|(?P<compilertype>%s)" % (ftypecode_p, dtypecode_p, ctypecode_p))
557
558 def get_typecode(s, backrefs):
559     m = typecodes_re.match(s)
560     curr = s[m.end():]
561     gd = m.groupdict()
562     assert gd["function"] or gd["datatype"] or gd["compilertype"]
563     typecode_d = {}
564     if gd["function"]:
565         typecode_d["type"] = typecodes[gd["function"]]
566         if typecode_d["type"]["type"] == "method":
567             if typecode_d["type"]["special"] not in ("static", "thunk"):
568                 # read the modifier
569                 typecode_d["modifier"] = modifiers[curr[0]]
570                 curr = curr[1:]
571             else:
572                 # static method, has no modifier, fake it though
573                 typecode_d["modifier"] = None
574         functype, curr = get_functype(curr, backrefs)
575         typecode_d.update(functype)
576     elif gd["datatype"]:
577         typecode_d["type"] = typecodes[gd["datatype"]]
578         datatype, curr = get_datatype(curr, backrefs)
579         sclass = storageclasses[curr[0]]
580         curr = curr[1:]
581         typecode_d["datatype"] = datatype
582         typecode_d["storage_class"] = sclass
583     elif gd["compilertype"]:
584         typecode_d["type"] = typecodes[gd["compilertype"]]
585         typecode_d["modifier"] = modifiers[curr[0]]
586         curr = curr[1:]
587         typecode_d["name"], curr = get_name(curr, backrefs)
588     return typecode_d, curr
589
590 callingconvre = re.compile("|".join(re.escape(convention) for convention in calling_conventions))
591
592 def get_functype(s, backrefs):
593     m = callingconvre.match(s)
594     curr = s[m.end():]
595     cconv = calling_conventions[m.group(0)]
596     ret = None
597     if curr[0] in ("X", "@"):
598         curr = curr[1:]
599         ret = "void"
600     elif curr[0] == "?":
601         ret, curr = get_modifieddatatype(curr, backrefs)
602     if ret is None:
603         ret, curr = get_datatype(curr, backrefs)
604     args, curr = get_arglist(curr, backrefs)
605     sclass = storageclasses[curr[0]]
606     curr = curr[1:]
607     ftype_dict = {
608         "cconv": cconv,
609         "ret": ret,
610         "args": args,
611         "storage_class": sclass,
612     }
613     return ftype_dict, curr
614
615 opre = re.compile("|".join(re.escape(opcode) for opcode in opcodes))
616
617 def parse_mangledsymbol(s):
618     backrefs = collections.defaultdict(list)
619     if s[0] != "?":
620         # XXX: Except here?
621         raise CantParse(s, "MSVC++")
622     curr = s[1:]
623     opmatch = opre.match(curr)
624     if opmatch:
625         curr = curr[opmatch.end():]
626     name, curr = get_name(curr, backrefs)
627     if opmatch:
628         name = Operator(name, opmatch.group(0))
629     assert curr[0] == "@"
630     curr = curr[1:]
631     typecode, curr = get_typecode(curr, backrefs)
632     type = typecode["type"]["type"]
633     if type == "data":
634         ret = Variable(name, typecode["type"], typecode["datatype"], typecode["storage_class"])
635     elif type == "method":
636         ret = Method(name, typecode["type"], typecode["modifier"], typecode["cconv"], typecode["ret"], typecode["args"], typecode["storage_class"])
637     elif type == "compiler_data":
638         ret = CompilerData(name, typecode["type"], typecode["modifier"], typecode["name"])
639     elif type == "function":
640         ret = Function(name, typecode["type"], typecode["cconv"], typecode["ret"], typecode["args"], typecode["storage_class"])
641     return ret
642
643 # Just do the basic Separation of parts with pyparsing, don't try to
644 # understand it with it (too hard, it seems to be against me whatever
645 # I try)
646 #from pyparsing import Or, Literal, Word, OneOrMore, alphas, Regex, Optional, ZeroOrMore, Group
647 #from string import digits
648 #ops = Or([Literal(op) for op in opcodes])("opcode")
649 #del op
650
651 #validc = Regex(r"[a-zA-Z_]\w*")
652
653 #anydigit = Or([Literal(digit) for digit in digits])
654 #backref = anydigit
655 #identifier = (validc + "@") ^ backref
656 # XXX: template handling, verify this is correct
657 #name = ("?$" + identifier + (lambda: args)() + OneOrMore(identifier)) ^ OneOrMore(identifier)
658
659 #cconv = Or([Literal(convention) for convention in calling_conventions])
660 #del convention
661
662 #ftypecode = Or([Literal(typecode) for typecode in functypecodes])
663 #datatypecode = Or([Literal(typecode) for typecode in datatypecodes])
664 #del typecode
665
666 #simpledtype = Or([Literal(dtype) for dtype in sdatatypes])
667 #namedtype = Or([Literal(dtype) for dtype in namedatatypes])
668 #simpletype = simpledtype ^ (namedtype + name + "@")
669
670 #rop = Or([Literal(ptype) for ptype in ptrtypes]) # reference or pointer
671 #pointerrefs = ZeroOrMore(Or([Literal(ptype) for ptype in ptrtypes2]))
672 #del ptype
673 #ptrref = anydigit
674 # XXX: FunctionType forms of ptrtype
675 #ptrtype = (rop + pointerrefs + (Literal("A") ^ "B" ^ "C") + (simpletype ^ "X")) ^ (ptrref)
676
677 #dtype = simpletype ^ ptrtype
678
679 #storageclass = Or([Literal(stype) for stype in storageclasses])
680 #del stype
681
682
683 #args = Group("X" ^ (OneOrMore(dtype) + Optional(Literal("@") ^ "Z")))
684 # Note: looks like ReturnValue can be @ for constructors atleast
685 #functype = cconv + (Literal("X") ^ "@" ^ dtype)  + args + storageclass
686
687 #typecode = (ftypecode + functype) ^ (datatypecode + dtype + storageclass)
688
689 #mangledsymbol = "?" + Optional(ops) + name.copy()("name") + "@" + typecode.copy()("TypeCode")
690
691 tests = [
692     "?xorWith@BitSet@xercesc_2_8@@QAEXABV12@@Z",
693     "??0ASCIIRangeFactory@xercesc_2_8@@QAE@XZ",
694     "??0?$XMLHolder@U_RTL_CRITICAL_SECTION@@@xercesc_2_8@@QAE@XZ",
695     "??0ArrayIndexOutOfBoundsException@xercesc_2_8@@QAE@QBDIW4Codes@XMLExcepts@1@QB_W222PAVMemoryManager@1@@Z"
696 ]
697
698 def reorder_ident(parsetree):
699     # kill @'s
700     inreverse = parsetree[::2]
701     # reverse to get (namespace, namespace, class, whatever, etc.)
702     return tuple(reversed(inreverse))
703