001    package org.bridj.demangling;
002    
003    import org.bridj.ann.Convention.Style;
004    import java.lang.reflect.*;
005    import java.util.ArrayList;
006    import java.util.Collections;
007    import java.util.List;
008    
009    import org.bridj.NativeLibrary;
010    import org.bridj.demangling.Demangler.ClassRef;
011    import org.bridj.demangling.Demangler.DemanglingException;
012    import org.bridj.demangling.Demangler.MemberRef;
013    import org.bridj.demangling.Demangler.NamespaceRef;
014    import org.bridj.demangling.Demangler.Ident;
015    import org.bridj.demangling.Demangler.IdentLike;
016    import org.bridj.demangling.Demangler.TypeRef;
017    import org.bridj.demangling.Demangler.SpecialName;
018    import org.bridj.CLong;
019    import org.bridj.ann.Convention;
020    import java.math.BigInteger;
021    import java.util.Collection;
022    
023    public class VC9Demangler extends Demangler {
024            public VC9Demangler(NativeLibrary library, String str) {
025                    super(library, str);
026            }
027    
028        private AccessLevelAndStorageClass parseAccessLevelAndStorageClass() throws DemanglingException {
029            AccessLevelAndStorageClass ac = new AccessLevelAndStorageClass();
030            switch (consumeChar()) {
031                case 'A':
032                case 'B':
033                    ac.modifiers = Modifier.PRIVATE;
034                    break;
035                case 'C':
036                case 'D':
037                    ac.modifiers = Modifier.PRIVATE | Modifier.STATIC;
038                    break;
039                case 'E':
040                case 'F':
041                    ac.modifiers = Modifier.PRIVATE;
042                    ac.isVirtual = true;
043                    break;
044                case 'G':
045                case 'H':
046                    ac.modifiers = Modifier.PRIVATE;
047                    ac.isThunk = true;
048                    break;
049                case 'I':
050                case 'J':
051                    ac.modifiers = Modifier.PROTECTED;
052                    break;
053                case 'K':
054                case 'L':
055                    ac.modifiers = Modifier.PROTECTED | Modifier.STATIC;
056                    break;
057                case 'M':
058                case 'N':
059                    ac.modifiers = Modifier.PROTECTED;
060                    ac.isVirtual = true;
061                    break;
062                case 'O':
063                case 'P':
064                    ac.modifiers = Modifier.PROTECTED;
065                    ac.isThunk = true;
066                    break;
067                case 'Q':
068                case 'R':
069                    ac.modifiers = Modifier.PUBLIC;
070                    break;
071                case 'S':
072                case 'T':
073                    ac.modifiers = Modifier.PUBLIC | Modifier.STATIC;
074                    break;
075                case 'U':
076                case 'V':
077                    ac.modifiers = Modifier.PUBLIC;
078                    ac.isVirtual = true;
079                    break;
080                case 'W':
081                case 'X':
082                    ac.modifiers = Modifier.PUBLIC;
083                    ac.isThunk = true;
084                    break;
085                case 'Y':
086                case 'Z':
087                    // No modifier, no storage class
088                    ac.modifiers = 0;
089                    break;
090                default:
091                    throw error("Unknown access level + storage class");
092            }
093            return ac;
094        }
095    
096        private ClassRef parseTemplateType() throws DemanglingException {
097            //System.out.println("# START OF parseTemplateParams()");
098            String name = parseNameFragment();
099            //return withEmptyQualifiedNames(new DemanglingOp<ClassRef>() { public ClassRef run() throws DemanglingException {
100            List<TemplateArg> args = parseTemplateParams();
101            
102            List<Object> names = parseNameQualifications();
103                    
104            //String ns = parseNameFragment();
105            
106            //System.out.println("parseTemplateParams() = " + args + ", ns = " + names);
107            ClassRef tr = new ClassRef(new Ident(name, args.toArray(new TemplateArg[args.size()])));
108            tr.setEnclosingType(reverseNamespace(names));
109    
110            addBackRef(tr);
111            
112            return tr;
113                    //}});
114        }
115    
116        private void parseFunctionProperty(MemberRef mr) throws DemanglingException {
117            mr.callingConvention = parseCallingConvention();
118            TypeRef returnType = consumeCharIf('@') ? classType(void.class) : parseType(true);
119    //        allQualifiedNames.clear();
120            //withEmptyQualifiedNames(new DemanglingRunnable() { public void run() throws DemanglingException {
121            List<TypeRef> paramTypes = parseParams();
122            mr.paramTypes = paramTypes.toArray(new TypeRef[paramTypes.size()]);
123            if (!consumeCharIf('Z')) {
124                List<TypeRef> throwTypes = parseParams();
125                mr.throwTypes = throwTypes.toArray(new TypeRef[throwTypes.size()]);
126            }
127    
128            mr.setValueType(returnType);
129                    //}});
130        }
131    
132        static class AnonymousTemplateArg implements TemplateArg {
133            public AnonymousTemplateArg(String v) {
134                this.v = v;
135            }
136            String v;
137    
138            //@Override
139            public boolean matchesParam(Object param, Annotations annotations) {
140                            return true; // TODO wtf ?
141            }
142            @Override
143            public String toString() {
144                return v;
145            }
146    
147        }
148    
149        private TemplateArg parseTemplateParameter() throws DemanglingException {
150            switch (peekChar()) {
151                case '?':
152                    consumeChar();
153                    return new AnonymousTemplateArg("'anonymous template param " + parseNumber(false) + "'");
154                case '$':
155                    consumeChar();
156                    switch (consumeChar()) {
157                        case '0':
158                            return new Constant(parseNumber(true));
159                        case '2':
160                            int a = parseNumber(true);
161                            int b = parseNumber(true);
162                            return new Constant(a * Math.exp(10 * (int)Math.log(b - Math.log10(a) + 1)));
163                        case 'D':
164                            return new AnonymousTemplateArg("'anonymous template param " + parseNumber(false) + "'");
165                        case 'F':
166                            return new AnonymousTemplateArg("'tuple (" + parseNumber(true) + ", " + parseNumber(true) + ")'");
167                        case 'G':
168                            return new AnonymousTemplateArg("'tuple (" + parseNumber(true) + ", " + parseNumber(true) + ", " + parseNumber(true) + ")'");
169                        case 'Q':
170                            return new AnonymousTemplateArg("'anonymous non-type template param " + parseNumber(false) + "'");
171                    }
172                    break;
173                default:
174                    //error("Unexpected template param value");
175            }
176            return parseType(true);
177        }
178        static class AccessLevelAndStorageClass {
179            int modifiers;
180            boolean isVirtual = false, isThunk = false;
181                
182        }
183            public MemberRef parseSymbol() throws DemanglingException {
184            MemberRef mr = new MemberRef();
185    
186             int iAt = str.indexOf('@');
187            if (iAt >= 0 && consumeCharIf('_')) {
188                if (iAt > 0) {
189                    mr.setMemberName(new Ident(str.substring(1, iAt)));
190                    mr.setArgumentsStackSize(Integer.parseInt(str.substring(iAt + 1)));
191                    return mr;
192                }
193            }
194            if (consumeCharIf('?')) {
195                consumeCharsIf('@', '?');
196    
197                IdentLike memberName = parseFirstQualifiedTypeNameComponent();
198                if (memberName instanceof SpecialName) {
199                    SpecialName specialName = (SpecialName)memberName;
200                    if (!specialName.isFunction())
201                        return null;
202                }
203                mr.setMemberName(memberName);
204                List<Object> qNames = parseNameQualifications();
205                
206                //TypeRef qualifiedName = parseQualifiedTypeName();
207    
208                AccessLevelAndStorageClass ac = parseAccessLevelAndStorageClass();
209                CVClassModifier cvMod = null;
210                if (ac.modifiers != 0 && !Modifier.isStatic(ac.modifiers))
211                    cvMod = parseCVClassModifier();
212    
213                // Function property :
214                //allQualifiedNames.clear(); // TODO fix this !!
215                TypeRef encl;
216                if (cvMod != null && (cvMod.isMember || (memberName instanceof SpecialName) || Modifier.isPublic(ac.modifiers))) {
217                    Object r = qNames.get(0);
218                    ClassRef tr = r instanceof ClassRef ? (ClassRef)r : new ClassRef(new Ident((String)r));
219                    //tr.setSimpleName(qNames.get(0));
220                    qNames.remove(0);
221                    tr.setEnclosingType(reverseNamespace(qNames));
222                    encl = tr;
223                } else {
224                    encl = reverseNamespace(qNames);
225                }
226                
227                addBackRef(encl);
228                mr.setEnclosingType(encl);
229                
230                parseFunctionProperty(mr);
231                
232                if (position != length)
233                    error("Failed to demangle the whole symbol");
234            } else {
235                mr.setMemberName(new Ident(str));
236            }
237            return mr;
238            }
239    
240    
241        TypeRef parseReturnType() throws DemanglingException {
242            TypeRef tr = parseType(true);
243            return tr;
244        }
245        int parseNumber(boolean allowSign) throws DemanglingException {
246            int sign = allowSign && consumeCharIf('?') ? -1 : 1;
247            if (Character.isDigit(peekChar())) {
248                char c = consumeChar();
249                return sign * (int)(c - '0');
250            }
251            if (peekChar() == '@')
252                return 0;
253    
254            char c;
255            StringBuilder b = new StringBuilder();
256            long n = 0;
257            while (((c = consumeChar()) >= 'A' && c <= 'P') && c != '@')
258                n += 16 * (c - 'A');
259            
260            String s = b.toString().trim();
261            if (c != '@' || s.length() == 0)
262                throw error("Expected a number here", -b.length());
263            return sign * Integer.parseInt(s, 16);
264        }
265        TypeRef consumeIfBackRef() throws DemanglingException {
266            char c = peekChar();
267            if (Character.isDigit(c)) {
268                consumeChar();
269                int iBack = (int)(c - '0');
270                return getBackRef(iBack);
271            }
272            return null;
273        }
274            TypeRef parseType(boolean allowVoid) throws DemanglingException {
275            TypeRef backRef = consumeIfBackRef();
276            if (backRef != null)
277                return backRef;
278            
279            char c = consumeChar();
280            switch (c) {
281                    case '_':
282                            TypeRef tr;
283                            switch (consumeChar()) {
284                case 'D': // __int8
285                case 'E': // unsigned __int8
286                    tr = classType(byte.class);
287                                    break;
288                case 'F': // __int16
289                case 'G': // unsigned __int16
290                    tr = classType(short.class);
291                                    break;
292                case 'H': // __int32
293                case 'I': // unsigned __int32
294                    tr = classType(int.class);
295                                    break;
296                case 'J': // __int64
297                case 'K': // unsigned __int64
298                    tr = classType(long.class);
299                                    break;
300                case 'L': // __int128
301                    tr = classType(BigInteger.class);
302                                    break;
303                            case 'N': // bool
304                    tr = classType(boolean.class);
305                                    break;
306                            case '0': // array ??
307                    parseCVClassModifier();
308                    parseType(false);
309                    tr = classType(Object[].class);
310                                    break;
311                            case 'W':
312                                    tr = classType(char.class);//, Wide.class);
313                                    break;
314                            default:
315                                    throw error(-1);
316                            }
317                            addBackRef(tr);
318                            return tr;
319            case 'Z':
320                return classType(Object[].class);
321            case 'O':
322                throw error("'long double' type cannot be mapped !", -1);
323                    case 'C': // signed char
324                    case 'D': // char
325                    case 'E': // unsigned char
326                            return classType(byte.class);
327                    case 'F': // short
328                    case 'G': // unsigned short
329                            return classType(short.class);
330                    case 'H': // int
331                    case 'I': // unsigned int
332                            return classType(int.class);
333                    case 'J': // long
334                    case 'K': // unsigned long
335                            return classType(CLong.class);
336            case 'M': // float
337                return classType(float.class);
338                    case 'N': // double
339                            return classType(double.class);
340            case 'Y':
341                throw error("TODO handle cointerfaces", -1);
342                    case 'X':
343                // TODO handle coclass case
344                if (!allowVoid)
345                    return null;
346                            return classType(void.class);
347            case '?':
348                parseCVClassModifier(); // TODO do something with this !
349                return parseType(allowVoid);
350            case 'A': // reference
351            case 'B': // volatile reference
352            case 'P': // pointer
353            case 'Q': // const pointer
354            case 'R': // volatile pointer
355            case 'S': // const volatile pointer
356                if (!consumeCharsIf('$', 'A')) // __gc
357                    consumeCharsIf('$', 'B');  // __pin
358    
359                CVClassModifier cvMods = parseCVClassModifier();
360                if (cvMods.isVariable) {
361                    if (consumeCharIf('Y')) {
362                        int dimensions = parseNumber(false);
363                        int[] indices = new int[dimensions];
364                        for (int i = 0; i < dimensions; i++)
365                            indices[i] = parseNumber(false);
366                    }
367                    tr = pointerType(parseType(true));
368                } else {
369                    MemberRef mr = new MemberRef();
370                    parseFunctionProperty(mr);
371                    tr = pointerType(new FunctionTypeRef(mr));
372                }
373                addBackRef(tr);
374                return tr;
375            case 'V': // class
376            case 'U': // struct
377            case 'T': // union
378                            //System.out.println("Found struct, class or union");
379                return parseQualifiedTypeName();
380            case 'W': // enum
381                Class<?> cl;
382                switch (consumeChar()) {
383                    case '0':
384                    case '1':
385                        cl = byte.class;
386                        break;
387                    case '2':
388                    case '3':
389                        cl = short.class;
390                        break;
391                    case '4':
392                    case '5':
393                        cl = int.class;
394                        break;
395                    case '6':
396                    case '7': // CLong : int on win32 and win64 !
397                        cl = int.class;
398                        break;
399                    default:
400                        throw error("Unfinished enum", -1);
401                }
402                TypeRef qn = parseQualifiedTypeName();
403                addBackRef(qn);
404                return classType(cl);
405                    default:
406                            throw error(-1);
407                    }
408            }
409        static NamespaceRef reverseNamespace(List<Object> names) {
410            if (names == null || names.isEmpty())
411                return null;
412            Collections.reverse(names);
413            return new NamespaceRef(names.toArray());
414        }
415        List<TypeRef> allQualifiedNames = new ArrayList<TypeRef>();
416        interface DemanglingOp<T> {
417            T run() throws DemanglingException;
418        }
419            <T> T withEmptyQualifiedNames(DemanglingOp<T> action) throws DemanglingException {
420                    List<TypeRef> list = allQualifiedNames;
421            try {
422                    allQualifiedNames = new ArrayList<TypeRef>();
423                    return action.run();
424            } finally {
425                    allQualifiedNames = list;
426            }
427        }
428    
429        IdentLike parseFirstQualifiedTypeNameComponent() throws DemanglingException {
430            if (consumeCharIf('?')) {
431                if (consumeCharIf('$'))
432                    return parseTemplateType().getIdent();
433                else
434                    return parseSpecialName();
435            }
436            else
437                return new Ident(parseNameFragment());
438        }
439        TypeRef parseQualifiedTypeName() throws DemanglingException {
440            TypeRef backRef = consumeIfBackRef();
441            if (backRef != null)
442                return backRef;
443            
444            char c = peekChar();
445            List<Object> names = parseNameQualifications();
446            
447            // TODO fix this :
448            //names.add(0, parseFirstQualifiedTypeNameComponent());
449            Object first = names.get(0);
450                    names.set(0, first instanceof String ? new Ident((String)first) : ((ClassRef)first).getIdent());
451            
452                    if (names.size() == 1 && (names.get(0) instanceof TypeRef)) {
453                            return (TypeRef)names.get(0);
454                    }
455                    
456            /*
457            if (Character.isDigit(c)) {
458                    consumeChar();
459                    int i = (int)(c - '0');
460                    if (i < 0 || i >= allQualifiedNames.size())
461                            throw error("Invalid back reference " + i + " (knows only " + allQualifiedNames + ")", -1);
462                    names = new ArrayList<String>(allQualifiedNames.get(i));
463            } else {
464                    names = parseNames();
465            }*/
466    
467            ClassRef tr = new ClassRef((Ident)names.get(0));
468            names.remove(0);
469            tr.setEnclosingType(reverseNamespace(names));
470            return tr;
471        }
472    
473        public IdentLike parseSpecialName() throws DemanglingException {
474            switch (consumeChar()) {
475            case '0':
476                return SpecialName.Constructor;
477            case '1':
478                return SpecialName.Destructor;
479            case '2':
480                return SpecialName.New;
481            case '3':
482                return SpecialName.Delete;
483            case '4':
484                return SpecialName.OperatorAssign;
485            case '5':
486                return SpecialName.OperatorRShift;
487            case '6':
488                return SpecialName.OperatorLShift;
489            case '7':
490                return SpecialName.OperatorLogicNot;
491            case '8':
492                return SpecialName.OperatorEquals;
493            case '9':
494                return SpecialName.OperatorDifferent;
495            case 'A':
496                return SpecialName.OperatorSquareBrackets;
497            case 'B':
498                return SpecialName.OperatorCast;
499            case 'C':
500                return SpecialName.OperatorArrow;
501            case 'D':
502                return SpecialName.OperatorMultiply;
503            case 'E':
504                return SpecialName.OperatorIncrement;
505            case 'F':
506                return SpecialName.OperatorDecrement;
507            case 'G':
508                return SpecialName.OperatorSubstract;
509            case 'H':
510                return SpecialName.OperatorAdd;
511            case 'I':
512                return SpecialName.OperatorBitAnd;
513            case 'J':
514                return SpecialName.OperatorArrowStar;
515            case 'K':
516                return SpecialName.OperatorDivide;
517            case 'L':
518                return SpecialName.OperatorModulo;
519            case 'M':
520                return SpecialName.OperatorLower;
521            case 'N':
522                return SpecialName.OperatorLowerEquals;
523            case 'O':
524                return SpecialName.OperatorGreater;
525            case 'P':
526                return SpecialName.OperatorGreaterEquals;
527            case 'Q':
528                return SpecialName.OperatorComma;
529            case 'R':
530                return SpecialName.OperatorParenthesis;
531            case 'S':
532                return SpecialName.OperatorBitNot;
533            case 'T':
534                return SpecialName.OperatorXOR;
535            case 'U':
536                return SpecialName.OperatorBitOr;
537            case 'V':
538                return SpecialName.OperatorLogicAnd;
539            case 'W':
540                return SpecialName.OperatorLogicOr;
541            case 'X':
542                return SpecialName.OperatorMultiplyAssign;
543            case 'Y':
544                return SpecialName.OperatorAddAssign;
545            case 'Z':
546                return SpecialName.OperatorSubstractAssign;
547            case '_':
548                switch (consumeChar()) {
549                    case '0':
550                        return SpecialName.OperatorDivideAssign;
551                    case '1':
552                        return SpecialName.OperatorModuloAssign;
553                    case '2':
554                        return SpecialName.OperatorLShiftAssign;
555                    case '3':
556                        return SpecialName.OperatorRShiftAssign;
557                    case '4':
558                        return SpecialName.OperatorBitAndAssign;
559                    case '5':
560                        return SpecialName.OperatorBitOrAssign;
561                    case '6':
562                        return SpecialName.OperatorXORAssign;
563                    case '7':
564                        return SpecialName.VFTable;
565                    case '8':
566                        return SpecialName.VBTable;
567                    case '9':
568                        return SpecialName.VCall;
569                    case 'E':
570                        return SpecialName.VectorDeletingDestructor;
571                    case 'G':
572                        return SpecialName.ScalarDeletingDestructor;
573                    default:
574                        throw error("unhandled extended special name");
575                }
576                
577            default:
578                throw error("Invalid special name");
579            }
580        }
581    
582        private List<TypeRef> parseParams() throws DemanglingException {
583            List<TypeRef> paramTypes = new ArrayList<TypeRef>();
584            if (!consumeCharIf('X')) {
585                char c;
586                            while ((c = peekChar()) != '@' && c != 0 && (c != 'Z' || peekChar(2) == 'Z')) {
587                    TypeRef tr = parseType(false);
588                    if (tr == null)
589                        continue;
590                    paramTypes.add(tr);
591                }
592                if (c == 'Z')
593                    consumeChar();
594                    //break;
595                if (c == '@')
596                    consumeChar();
597            }
598            return paramTypes;
599        }
600        private List<TemplateArg> parseTemplateParams() throws DemanglingException {
601            return withEmptyQualifiedNames(new DemanglingOp<List<TemplateArg>>() { public List<TemplateArg> run() throws DemanglingException {
602                List<TemplateArg> paramTypes = new ArrayList<TemplateArg>();
603                if (!consumeCharIf('X')) {
604                    char c;
605                    while ((c = peekChar()) != '@' && c != 0) {
606                        TemplateArg tr = parseTemplateParameter();
607                        if (tr == null)
608                            continue;
609                        paramTypes.add(tr);
610                    }
611                }
612                return paramTypes;
613            }});
614        }
615    
616        String parseNameFragment() throws DemanglingException {
617                    StringBuilder b = new StringBuilder();
618                    char c;
619    
620                    while ((c = consumeChar()) != '@')
621                            b.append(c);
622    
623            if (b.length() == 0)
624                throw new DemanglingException("Unexpected empty name fragment");
625            
626                    String name = b.toString();
627    //              allQualifiedNames.add(Collections.singletonList(name));
628                    return name;
629            }
630    
631        void addBackRef(TypeRef tr) {
632            if (tr == null || allQualifiedNames.contains(tr))
633                return;
634            
635            allQualifiedNames.add(tr);
636        }
637            
638            
639        TypeRef getBackRef(int i) throws DemanglingException {
640            if (i == allQualifiedNames.size())
641                i--; // TODO fix this !!!
642    
643            if (i < 0 || i >= allQualifiedNames.size())
644                throw error("Invalid back references in name qualifications", -1);
645            return allQualifiedNames.get(i);
646        }
647        private List<Object> parseNameQualifications() throws DemanglingException {
648            List<Object> names = new ArrayList<Object>();
649            
650            if (Character.isDigit(peekChar())) {
651                try {
652                    int i = consumeChar() - '0';
653                    names.add(getBackRef(i));
654                    expectChars('@');
655                    return names;
656                } catch (Exception ex) {
657                    throw error("Invalid back references in name qualifications", -1);
658                }
659            }
660            
661            while (peekChar() != '@') {
662                names.add(parseNameQualification());
663            }
664    
665            expectChars('@');
666            return names;
667        }
668        Object parseNameQualification() throws DemanglingException {
669            if (consumeCharIf('?')) {
670                if (consumeCharIf('$'))
671                    return parseTemplateType();
672                else {
673                    if (peekChar() == 'A')
674                        throw error("Anonymous numbered namespaces not handled yet");
675                    int namespaceNumber = parseNumber(false);
676                    return String.valueOf(namespaceNumber);
677                }
678            } else
679                return parseNameFragment();
680        }
681    
682        Style parseCallingConvention() throws DemanglingException {
683            Convention.Style cc;
684            boolean exported = true;
685            switch (consumeChar()) {
686                case 'A':
687                    exported = false;
688                case 'B':
689                    cc = Convention.Style.CDecl;
690                    break;
691                case 'C':
692                    exported = false;
693                case 'D':
694                    cc = Convention.Style.Pascal;
695                    break;
696                case 'E':
697                    exported = false;
698                case 'F':
699                    cc = Convention.Style.ThisCall;
700                    break;
701                case 'G':
702                    exported = false;
703                case 'H':
704                    cc = Convention.Style.StdCall;
705                    break;
706                case 'I':
707                    exported = false;
708                case 'J':
709                    cc = Convention.Style.FastCall;
710                    break;
711                case 'K':
712                    exported = false;
713                case 'L':
714                    cc = null;
715                    break;
716                case 'N':
717                    cc = Convention.Style.CLRCall;
718                    break;
719                default:
720                    throw error("Unknown calling convention");
721            }
722            return cc;
723        }
724        static class CVClassModifier {
725            boolean isVariable;
726            boolean isMember;
727            boolean isBased;
728        }
729        CVClassModifier parseCVClassModifier() throws DemanglingException {
730            CVClassModifier mod = new CVClassModifier();
731            switch (peekChar()) {
732                case 'E': // __ptr64
733                case 'F': // __unaligned 
734                case 'I': // __restrict
735                    consumeChar();
736                    break;
737            }
738            boolean based = false;
739            switch (consumeChar()) {
740                case 'M': // __based
741                case 'N': // __based
742                case 'O': // __based
743                case 'P': // __based
744                    mod.isBased = true;
745                case 'A':
746                case 'B':
747                case 'J':
748                case 'C':
749                case 'G':
750                case 'K':
751                case 'D':
752                case 'H':
753                case 'L':
754                    mod.isVariable = true;
755                    mod.isMember = false;
756                    break;
757                case '2': // __based
758                case '3': // __based
759                case '4': // __based
760                case '5': // __based
761                    mod.isBased = true;
762                case 'Q':
763                case 'U':
764                case 'Y':
765                case 'R':
766                case 'V':
767                case 'Z':
768                case 'S':
769                case 'W':
770                case '0':
771                case 'T':
772                case 'X':
773                case '1':
774                    mod.isVariable = true;
775                    mod.isMember = true;
776                    break;
777                case '_': // __based
778                    mod.isBased = true;
779                    switch (consumeChar()) {
780                        case 'A':
781                        case 'B':
782                            mod.isVariable = false;
783                            break;
784                        case 'C':
785                        case 'D':
786                            mod.isVariable = false;
787                            mod.isMember = true;
788                            break;
789                        default:
790                            throw error("Unknown extended __based class modifier", -1);
791                    }
792                    break;
793                case '6':
794                case '7':
795                    mod.isVariable = false;
796                    mod.isMember = false;
797                    break;
798                case '8':
799                case '9':
800                    mod.isVariable = false;
801                    mod.isMember = true;
802                    break;
803                default:
804                    throw error("Unknown CV class modifier", -1);
805            }
806            if (mod.isBased) {
807                switch (consumeChar()) {
808                    case '0': // __based(void)
809                        break;
810                    case '2':
811                        parseNameQualifications();
812                        break;
813                    case '5': // no __based() ??
814                        break;
815                }
816            }
817            return mod;
818        }
819    }