/******************************************************************************* | |
* Copyright (c) 2010 Nokia and others. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/epl-v10.html | |
* | |
* Contributors: | |
* Nokia - Initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.cdt.debug.edc.internal.symbols.files; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Stack; | |
import java.util.WeakHashMap; | |
import org.eclipse.cdt.debug.edc.symbols.IUnmangler; | |
/** | |
* Unmangler for the ARM/Itanium/etc. EABI (http://www.codesourcery.com/public/cxx-abi/abi.html) | |
* <p> | |
* TODO: <expression> <closure-type-name> <lambda-sig> | |
*/ | |
public class UnmanglerEABI implements IUnmangler { | |
private static boolean DEBUG = false; | |
enum SubstType { | |
PREFIX, | |
TEMPLATE_PREFIX, | |
TYPE, | |
QUAL_TYPE, | |
TEMPLATE_TEMPLATE_PARAM, | |
} | |
public UnmanglerEABI() { | |
} | |
static class UnmangleState { | |
private char[] symbol; | |
private int index; | |
StringBuilder buffer; | |
private Stack<Integer> pushes ; // lengths of buffer when pushed | |
private List<String> substitutions; | |
private Map<Integer, SubstType> substitutionTypes; | |
private int lastTypeNameIndex; | |
private List<String> templateArgs; | |
private int templateArgBase; | |
private Stack<Integer> backtracks ; // grouped entries: index value, lengths of buffer, and substitutions length when pushed | |
private final boolean nameOnly; | |
public UnmangleState(String symbol, boolean nameOnly) { | |
this.symbol = symbol.toCharArray(); | |
this.nameOnly = nameOnly; | |
index = 0; | |
buffer = new StringBuilder(); | |
pushes = new Stack<Integer>(); | |
substitutions = new ArrayList<String>(); | |
substitutionTypes = new HashMap<Integer, UnmanglerEABI.SubstType>(); | |
templateArgs = new ArrayList<String>(); | |
backtracks = new Stack<Integer>(); | |
lastTypeNameIndex = -1; | |
} | |
/* (non-Javadoc) | |
* @see java.lang.Object#toString() | |
*/ | |
@Override | |
public String toString() { | |
String remaining = getRemaining(); | |
if (remaining.length() == 0) | |
remaining = "<<end>>"; | |
return "state: at [" + remaining + "], so far: " + current(); | |
} | |
/** | |
* Push when entering a new decoding context (BNF expression). | |
*/ | |
public void push() { | |
pushes.push(buffer.length()); | |
} | |
/** | |
* Pop the current decoded string and restore context to | |
* the calling context. | |
* @return decoded string | |
*/ | |
public String pop() { | |
int oldpos = pushes.isEmpty() ? 0 : pushes.pop(); | |
String str = buffer.substring(oldpos, buffer.length()); | |
buffer.setLength(oldpos); | |
return str; | |
} | |
/** | |
* Push all state, when entering a possible backtrack scenario. | |
* Use #safePop() if an operation succeeds, or #safeBacktrack() | |
* if it failed and you want to retry. | |
*/ | |
public void safePush() { | |
backtracks.push(index); | |
backtracks.push(lastTypeNameIndex); | |
backtracks.push(buffer.length()); | |
backtracks.push(substitutions.size()); | |
backtracks.push(pushes.size()); | |
} | |
/** | |
* Call when a #safePush() branch has succeeded to discard backtrack state. | |
*/ | |
public void safePop() { | |
backtracks.pop(); | |
backtracks.pop(); | |
backtracks.pop(); | |
backtracks.pop(); | |
backtracks.pop(); | |
} | |
/** | |
* Call when a #safePush() branch has failed to reset backtrack state. | |
* (To perform another backtrack, call #safePush() again) | |
*/ | |
public void safeBacktrack() { | |
int oldSize = backtracks.pop(); | |
pushes.subList(oldSize, pushes.size()).clear(); | |
oldSize = backtracks.pop(); | |
substitutions.subList(oldSize, substitutions.size()).clear(); | |
while (substitutionTypes.size() > oldSize) | |
substitutionTypes.remove(substitutionTypes.size() - 1); | |
buffer.setLength(backtracks.pop()); | |
lastTypeNameIndex = backtracks.pop(); | |
index = backtracks.pop(); | |
} | |
/** | |
* Tell if there is any current string (length > 0) | |
* @return | |
*/ | |
public boolean hasCurrent() { | |
int oldpos = pushes.isEmpty() ? 0 : pushes.peek(); | |
int end = buffer.length(); | |
return end > oldpos; | |
} | |
/** | |
* Get the current constructed string (since the last #push()) | |
* @return | |
*/ | |
public String current() { | |
int oldpos = pushes.isEmpty() ? 0 : pushes.peek(); | |
String str = buffer.substring(oldpos, buffer.length()); | |
return str; | |
} | |
/** | |
* Remember the current constructed string as a substitution. | |
* @param substType | |
*/ | |
public void remember(SubstType substType) { | |
remember(current(), substType); | |
} | |
public boolean lastSubstitutionIsPrefix(SubstType substType) { | |
if (substitutions.size() == 0) | |
return false; | |
String current = current(); | |
if (substitutions.get(substitutions.size() - 1).length() >= current.length()) | |
return false; | |
return lastSubstitution() == substType; | |
} | |
/** | |
* Remember the given string as a substitution. | |
* @param name | |
* @param substType | |
*/ | |
public void remember(String name, SubstType substType) { | |
if (name.length() == 0) | |
return; | |
int num = substitutions.size(); | |
if (num > 0 && substitutions.get(num - 1).equals(name)) | |
return; | |
substitutions.add(name); | |
substitutionTypes.put(num, substType); | |
lastTypeNameIndex = num; | |
if (DEBUG) System.out.println(num+" := " + name + " --> " + substType); | |
} | |
/** | |
* Replace the last substitution. | |
* @param name | |
* @param substType | |
*/ | |
public void rememberInstead(String name, SubstType substType) { | |
int num = substitutions.size() - 1; | |
substitutions.set(num, name); | |
substitutionTypes.put(num, substType); | |
if (DEBUG) System.out.println(num+" ::= " + name + " -- > " + substType); | |
} | |
/** | |
* Pop the current decoded string as in {@link #pop()} | |
* and remember the string as a substitution. | |
* @return String | |
*/ | |
public String popAndRemember(SubstType substType) { | |
String name = pop(); | |
remember(name, substType); | |
return name; | |
} | |
public char peek() { | |
return index < symbol.length ? symbol[index] : 0; | |
} | |
public char peek(int offset) { | |
return index + offset < symbol.length ? symbol[index + offset] : 0; | |
} | |
public void consume(char ch) throws UnmanglingException { | |
if (ch != get()) | |
throw unexpected(); | |
} | |
public char get() { | |
return index < symbol.length ? symbol[index++] : 0; | |
} | |
public void unget() { | |
if (index > 0) index--; | |
} | |
public void skip() { | |
if (index < symbol.length) | |
index++; | |
} | |
public void skip2() { | |
index = Math.min(index + 2, symbol.length); | |
} | |
public boolean done() { | |
return index >= symbol.length; | |
} | |
public UnmanglingException unexpected() { | |
return new UnmanglingException("Unexpected text at " + getRemaining(), buffer.toString()); | |
} | |
public UnmanglingException unexpected(String what) { | |
return new UnmanglingException("Wanted " + what + " but got unexpected text at " + getRemaining(), buffer.toString()); | |
} | |
public UnmanglingException notImplemented() { | |
return new UnmanglingException("Unimplemented at " + getRemaining(), | |
buffer.toString()); | |
} | |
/** | |
* @return | |
*/ | |
private String getRemaining() { | |
if (index >= symbol.length) | |
return ""; | |
return new String(symbol, index, symbol.length - index); | |
} | |
/** | |
* @throws UnmanglingException | |
* | |
*/ | |
public void throwIfDone() throws UnmanglingException { | |
if (done()) | |
throw new UnmanglingException("Unexpected end of symbol", | |
buffer.toString()); | |
} | |
public void updateSubstitution(SubstType substType) { | |
int num = substitutions.size() - 1; | |
substitutionTypes.put(num, substType); | |
if (DEBUG) System.out.println(num + " ::= " + substType); | |
} | |
/** | |
* @return | |
*/ | |
public SubstType lastSubstitution() { | |
return substitutionTypes.get(substitutions.size() - 1); | |
} | |
/** | |
* @param arg | |
*/ | |
public void rememberTemplateArg(String arg) { | |
templateArgs.add(arg); | |
} | |
/** | |
* @param num | |
* @return | |
* @throws UnmanglingException | |
*/ | |
public String getTemplateArg(int num) throws UnmanglingException { | |
num -= templateArgBase; | |
if (num < 0 || num >= templateArgs.size()) | |
throw unexpected("template argument in range 0-" + (templateArgs.size() - templateArgBase)+"; got " + num); | |
return templateArgs.get(num); | |
} | |
public String lastSubstitutedName() { | |
if (lastTypeNameIndex < 0) | |
return ""; | |
return substitutions.get(lastTypeNameIndex); | |
} | |
} | |
private static WeakHashMap<String, String> unmangledMap = new WeakHashMap<String, String>(); | |
private static WeakHashMap<String, String> withoutArgsMap = new WeakHashMap<String, String>(); | |
/* (non-Javadoc) | |
* @see org.eclipse.cdt.debug.edc.internal.symbols.files.IUnmangler#undecorate(java.lang.String) | |
*/ | |
public String undecorate(String symbol) { | |
// symbols may have @@GLIBC... type suffixes | |
int atat = symbol.indexOf("@@"); | |
if (atat > 0) | |
symbol = symbol.substring(0, atat); | |
return symbol; | |
} | |
/* (non-Javadoc) | |
* @see org.eclipse.cdt.debug.edc.internal.symbols.files.IUnmangler#isMangled(java.lang.String) | |
*/ | |
public boolean isMangled(String symbol) { | |
if (symbol == null) | |
return false; | |
if (symbol.startsWith("_Z")) | |
return true; | |
// this is used for enum constants | |
if (symbol.startsWith("__N")) | |
return true; | |
return false; | |
} | |
public String unmangleWithoutArgs(String symbol) throws UnmanglingException { | |
return unmangle(symbol, true); | |
} | |
public String unmangle(String symbol) throws UnmanglingException { | |
return unmangle(symbol, false); | |
} | |
public String unmangleType(String symbol) throws UnmanglingException { | |
if (symbol == null) | |
return null; | |
if (unmangledMap.containsKey(symbol)) | |
return unmangledMap.get(symbol); | |
if (symbol.startsWith("_Z")) { | |
UnmangleState state = new UnmangleState(symbol, false); | |
state.skip2(); | |
String unmangled = ""; | |
if (state.peek() == 'S') { | |
unmangled += unmangleSubstitution(state); | |
} | |
while (!state.done()) { | |
if (state.peek() == 'I') { | |
// unscoped-template-name | |
state.remember(unmangled, SubstType.TEMPLATE_PREFIX); | |
String args = unmangleTemplateArgs(state, false); | |
state.buffer.append(args); | |
unmangled += args; | |
} else { | |
if (unmangled.equals("::std")) | |
unmangled += "::"; | |
unmangled += unmangleType(state); | |
} | |
state.remember(unmangled, SubstType.TYPE); | |
} | |
unmangledMap.put(symbol, unmangled); | |
return unmangled; | |
} | |
return symbol; | |
} | |
public String unmangle(String symbol, boolean skipArgs) throws UnmanglingException { | |
if (symbol == null) | |
return null; | |
String unmangled; | |
if (skipArgs) { | |
if (withoutArgsMap.containsKey(symbol)) | |
unmangled = withoutArgsMap.get(symbol); | |
else { | |
unmangled = doUnmangle(symbol, true); | |
withoutArgsMap.put(symbol, unmangled); | |
} | |
} else if (unmangledMap.containsKey(symbol)) { | |
unmangled = unmangledMap.get(symbol); | |
} else { | |
unmangled = doUnmangle(symbol, skipArgs); | |
unmangledMap.put(symbol, unmangled); | |
do {// for break below if conditionals succeed | |
int paren = unmangled.indexOf('('); | |
if (0 < paren) { | |
String unmangledWithoutArgs = unmangled.substring(0, paren-1); | |
if (unmangledWithoutArgs != null && unmangledWithoutArgs.length() != 0) { | |
withoutArgsMap.put(symbol, unmangledWithoutArgs); | |
break; | |
} } | |
withoutArgsMap.put(symbol, unmangled); | |
} while (false);// allows break above to skip default case | |
} | |
return unmangled; | |
} | |
/** | |
* @param symbol | |
* @return | |
* @throws UnmanglingException | |
*/ | |
private String doUnmangle(String symbol, boolean nameOnly) throws UnmanglingException { | |
/* | |
Entities with C linkage and global namespace variables are not mangled. Mangled names have the general structure: | |
<mangled-name> ::= _Z <encoding> | |
<encoding> ::= <function name> <bare-function-type> | |
::= <data name> | |
::= <special-name> | |
*/ | |
if (symbol.startsWith("_Z")) { | |
String suffix = ""; | |
int idx = symbol.indexOf('@'); | |
if (idx >= 0) { | |
suffix = symbol.substring(idx); | |
symbol = symbol.substring(0, idx); | |
} | |
UnmangleState state = new UnmangleState(symbol, nameOnly); | |
state.skip2(); | |
String unmangled = unmangleEncoding(state); | |
unmangled += suffix; | |
return unmangled; | |
} else if (symbol.startsWith("__N")) { | |
UnmangleState state = new UnmangleState(symbol, true); | |
state.skip2(); | |
String unmangled = unmangleName(state); | |
return unmangled; | |
} else { | |
return symbol; | |
} | |
} | |
/* | |
<encoding> ::= <function name> <bare-function-type> | |
::= <data name> | |
::= <special-name> | |
*/ | |
private String unmangleEncoding(UnmangleState state) throws UnmanglingException { | |
state.push(); | |
String name; | |
// ferret out <special-name> | |
char ch = state.peek(); | |
if (ch == 'T' || ch == 'G') { | |
name = unmangleSpecialName(state); | |
} else { | |
name = unmangleName(state); | |
} | |
if (!state.done() && !state.nameOnly) { | |
boolean isTemplate = name.endsWith(">"); // HACK | |
if (isTemplate) { | |
state.buffer.append(unmangleType(state)); | |
state.buffer.append(' '); | |
} | |
state.buffer.append(name); | |
state.buffer.append(unmangleBareFunctionType(state, false)); | |
} else { | |
state.buffer.append(name); | |
} | |
return state.pop(); | |
} | |
private void unmangleSpecialNameCallOffset(UnmangleState state, char ch) | |
throws UnmanglingException { | |
switch (ch) { | |
case 'h': { | |
// h <nv-offset> _ | |
int offset = doUnmangleNumber(state); | |
state.consume('_'); | |
state.buffer.append("<non-virtual base override at offset "); | |
appendHexNumber(state.buffer, offset); | |
break; | |
} | |
case 'v': { | |
// v <offset number> _ <virtual offset number> _ | |
int offset = doUnmangleNumber(state); | |
state.consume('_'); | |
int voffset = doUnmangleNumber(state); | |
state.consume('_'); | |
state.buffer.append("<virtual base override at offset "); | |
appendHexNumber(state.buffer, offset); | |
state.buffer.append(", vcall offset "); | |
appendHexNumber(state.buffer, voffset); | |
break; | |
} | |
default: | |
throw state.unexpected("special name call-offset"); | |
} | |
} | |
/* | |
<special-name> ::= TV <type> # virtual table | |
::= TT <type> # VTT structure (construction vtable index) | |
::= TI <type> # typeinfo structure | |
::= TS <type> # typeinfo name (null-terminated byte string) | |
<special-name> ::= GV <object name> # Guard variable for one-time initialization | |
# No <type> | |
<special-name> ::= T <call-offset> <base encoding> | |
# base is the nominal target function of thunk | |
<call-offset> ::= h <nv-offset> _ | |
::= v <v-offset> _ | |
<nv-offset> ::= <offset number> | |
# non-virtual base override | |
<v-offset> ::= <offset number> _ <virtual offset number> | |
# virtual base override, with vcall offset | |
<special-name> ::= Tc <call-offset> <call-offset> <base encoding> | |
# base is the nominal target function of thunk | |
# first call-offset is 'this' adjustment | |
# second call-offset is result adjustment | |
*/ | |
private String unmangleSpecialName(UnmangleState state) throws UnmanglingException { | |
state.push(); | |
char ch = state.get(); | |
if (ch == 'T') { | |
String type = null; | |
ch = state.get(); | |
switch (ch) { | |
case 'V': | |
type = unmangleType(state); | |
state.buffer.append("<virtual table for "); | |
state.buffer.append(type); | |
state.buffer.append('>'); | |
break; | |
case 'T': | |
type = unmangleType(state); | |
state.buffer.append("<VTT structure for "); | |
state.buffer.append(type); | |
state.buffer.append('>'); | |
break; | |
case 'I': | |
type = unmangleType(state); | |
state.buffer.append("<typeinfo structure for "); | |
state.buffer.append(type); | |
state.buffer.append('>'); | |
break; | |
case 'S': | |
type = unmangleType(state); | |
state.buffer.append("<typeinfo name for "); | |
state.buffer.append(type); | |
state.buffer.append('>'); | |
break; | |
case 'h': | |
case 'v': | |
unmangleSpecialNameCallOffset(state, ch); | |
state.buffer.append(" for "); | |
state.buffer.append(unmangleEncoding(state)); | |
state.buffer.append('>'); | |
break; | |
case 'c': { | |
// c <call-offset> <call-offset> <base encoding> | |
state.buffer.append("<covariant : 'this' adjustment "); | |
unmangleSpecialNameCallOffset(state, state.get()); | |
state.buffer.append("> result adjustment "); | |
unmangleSpecialNameCallOffset(state, state.get()); | |
state.buffer.append("> for "); | |
state.buffer.append(unmangleEncoding(state)); | |
state.buffer.append('>'); | |
break; | |
} | |
default: | |
throw state.unexpected("special name"); | |
} | |
} else if (ch == 'G') { | |
switch (state.get()) { | |
case 'V': | |
state.buffer.append("<one-time-init guard for "); | |
state.buffer.append(unmangleName(state)); | |
state.buffer.append('>'); | |
break; | |
default: | |
throw state.unexpected("special name"); | |
} | |
} | |
return state.pop(); | |
} | |
private void appendHexNumber(StringBuilder builder, int offset) { | |
if (offset < 0) { | |
builder.append("-0x"); | |
builder.append(Integer.toHexString(-offset)); | |
} else { | |
builder.append("0x"); | |
builder.append(Integer.toHexString(offset)); | |
} | |
} | |
/** | |
* @param state | |
* @param name | |
* @return | |
* @throws UnmanglingException | |
*/ | |
private String doUnmangleFunctionWithName(UnmangleState state, boolean expectReturn, String name) throws UnmanglingException { | |
state.push(); | |
state.consume('F'); | |
if (expectReturn) { | |
state.buffer.append(unmangleType(state)); | |
state.buffer.append(' '); | |
} | |
if (name != null) | |
state.buffer.append(name); | |
state.buffer.append(unmangleBareFunctionType(state, false)); | |
state.consume('E'); | |
return state.pop(); | |
} | |
/** | |
* @param state | |
* @param expectReturn true if a return type precedes argument list | |
* @throws UnmanglingException | |
*/ | |
private String unmangleBareFunctionType(UnmangleState state, boolean expectReturn) throws UnmanglingException { | |
state.push(); | |
if (expectReturn) { | |
state.buffer.append(unmangleType(state)); | |
state.buffer.append(' '); | |
} | |
state.buffer.append('('); | |
if (state.peek() == 'v') { | |
state.skip(); | |
} else { | |
boolean first = true; | |
while (!state.done() && state.peek() != 'E') { | |
if (first) { | |
first = false; | |
} else { | |
state.buffer.append(','); | |
} | |
state.buffer.append(unmangleType(state)); | |
} | |
} | |
state.buffer.append(')'); | |
return state.pop(); | |
} | |
/* | |
<name> ::= <nested-name> = N ... | |
::= <unscoped-name> = number or St ... | |
::= <unscoped-template-name> <template-args> = unscoped | S ... | I ... | |
::= <local-name> # See Scope Encoding below = Z ... | |
*/ | |
private String unmangleName(UnmangleState state) throws UnmanglingException { | |
state.push(); | |
char ch = state.peek(); | |
if (ch == 'N') { | |
state.buffer.append(unmangleNestedName(state, true)); | |
} else if (ch == 'Z') { | |
state.buffer.append(unmangleLocalName(state)); | |
} else if (ch == 0) { | |
state.throwIfDone(); | |
} else { | |
// must be unscoped-name or unscoped-template-name | |
if (ch == 'S' && state.peek(1) == 't') { | |
state.skip2(); | |
state.buffer.append("::std::"); | |
} | |
String name = unmangleUnqualifiedName(state); | |
state.buffer.append(name); | |
if (state.peek() == 'I') { | |
// unscoped-template-name | |
state.remember(name, SubstType.TEMPLATE_PREFIX); | |
String args = unmangleTemplateArgs(state, false); | |
state.buffer.append(args); | |
state.remember(name + args, SubstType.TYPE); | |
} | |
} | |
return state.pop(); | |
} | |
/* | |
<local-name> := Z <function encoding> E <entity name> [<discriminator>] | |
:= Z <function encoding> E s [<discriminator>] | |
<discriminator> := _ <non-negative number> # when number < 10 | |
:= __ <non-negative number> _ # when number >= 10 | |
*/ | |
private String unmangleLocalName(UnmangleState state) throws UnmanglingException { | |
state.push(); | |
state.consume('Z'); | |
state.buffer.append(unmangleEncoding(state)); | |
state.consume('E'); | |
boolean isStringLiteral = false; | |
if (state.peek() == 's') { | |
isStringLiteral = true; | |
state.skip(); | |
if (state.peek() == '_') | |
state.buffer.append("::"); | |
} else { | |
addNameWithColons(state, unmangleName(state)); | |
} | |
if (state.peek() == '_') { | |
state.skip(); | |
int num; | |
if (state.peek() == '_') { | |
// >= 10 | |
num = doUnmangleNonNegativeNumber(state); | |
state.consume('_'); | |
} else { | |
char ch = state.get(); | |
if (ch >= '0' && ch <= '9') { | |
num = ch - '0'; | |
} else { | |
throw state.unexpected("number"); | |
} | |
} | |
if (isStringLiteral) | |
state.buffer.append("string literal"); | |
state.buffer.append("#" + num); | |
} | |
return state.pop(); | |
} | |
/* | |
<source-name> ::= <positive length number> <identifier> | |
<number> ::= [n] <non-negative decimal integer> | |
<identifier> ::= <unqualified source code identifier> | |
*/ | |
private String unmangleSourceName(UnmangleState state) throws UnmanglingException { | |
state.push(); | |
char ch = state.peek(); | |
if (ch >= '0' && ch <= '9') { | |
int length = doUnmangleNumber(state); | |
while (length-- > 0) { | |
state.throwIfDone(); | |
state.buffer.append(state.get()); | |
} | |
return state.pop(); | |
} else { | |
throw state.unexpected(); | |
} | |
} | |
/* | |
* [0-9]+ | |
*/ | |
private int doUnmangleNonNegativeNumber(UnmangleState state) { | |
int number = 0; | |
char ch; | |
while ((ch = state.get()) != 0 && ch >= '0' && ch <= '9') { | |
number = number * 10 + (ch - '0'); | |
} | |
state.unget(); | |
return number; | |
} | |
/* | |
* [n] <non-negative decimal number> | |
*/ | |
private int doUnmangleNumber(UnmangleState state) { | |
boolean neg = false; | |
if (state.peek() == 'n') { | |
state.skip(); | |
neg = true; | |
} | |
int number = doUnmangleNonNegativeNumber(state); | |
return neg ? -number : number; | |
} | |
/* | |
<nested-name> ::= N [<CV-qualifiers>] <prefix> <unqualified-name> E | |
::= N [<CV-qualifiers>] <template-prefix> <template-args> E (args = I...) | |
*/ | |
private String unmangleNestedName(UnmangleState state, boolean allowCV) | |
throws UnmanglingException { | |
state.push(); | |
state.consume('N'); | |
String cvquals = allowCV ? unmangleCVQualifiers(state) : null; | |
state.buffer.append(unmanglePrefix(state, SubstType.PREFIX)); | |
state.consume('E'); | |
if (allowCV && (cvquals != null && cvquals.length() > 0)) { | |
state.buffer.append(' '); | |
state.buffer.append(cvquals); | |
} | |
return state.pop(); | |
} | |
/* | |
<template-args> ::= I <template-arg>+ E | |
*/ | |
private String unmangleTemplateArgs(UnmangleState state, boolean substArg) throws UnmanglingException { | |
state.push(); | |
int origTypeIndex = state.lastTypeNameIndex; | |
String typeName = state.lastSubstitutedName(); | |
if (!substArg || state.peek() == 'I') { | |
state.consume('I'); | |
substArg = false; | |
} | |
state.buffer.append('<'); | |
boolean lastArgWasSubst = false; | |
if (state.peek() != 'E') { | |
boolean first = true; | |
do { | |
if (first) | |
first = false; | |
else | |
state.buffer.append(','); | |
boolean unfinishedTemplateSubst = false; | |
if (state.peek() == 'S') { | |
char ch2 = state.peek(1); | |
if (ch2 == 't') { | |
state.buffer.append("::std::"); | |
state.skip2(); | |
first = true; | |
continue; // more of this arg to come | |
} | |
lastArgWasSubst = true; | |
if (ch2 == 'a' || ch2 == 'b') { | |
unfinishedTemplateSubst = true; | |
} | |
} | |
String arg = unmangleTemplateArg(state); | |
if (unfinishedTemplateSubst) | |
arg += unmangleTemplateArgs(state, false); | |
state.buffer.append(arg); | |
if (lastArgWasSubst && state.done()) | |
break; | |
lastArgWasSubst = false; | |
} while (state.peek() != 'E'); | |
} | |
if (!substArg && !lastArgWasSubst) | |
state.consume('E'); | |
if (state.buffer.lastIndexOf(">") == state.buffer.length() - 1) | |
state.buffer.append(' '); | |
state.buffer.append('>'); | |
if (state.lastSubstitution() == SubstType.TEMPLATE_TEMPLATE_PARAM) | |
state.rememberInstead(typeName + state.current(), SubstType.TEMPLATE_TEMPLATE_PARAM); | |
else if (state.lastTypeNameIndex > origTypeIndex) | |
state.remember(typeName + state.current(), SubstType.TYPE); | |
state.lastTypeNameIndex = origTypeIndex; | |
return state.pop(); | |
} | |
/* | |
<template-arg> ::= <type> # type or template | |
::= X <expression> E # expression | |
::= <expr-primary> # simple expressions ('L') | |
::= I <template-arg>* E # argument pack | |
::= sp <expression> # pack expansion of (C++0x) | |
*/ | |
private String unmangleTemplateArg(UnmangleState state) throws UnmanglingException { | |
state.push(); | |
String arg = null; | |
char ch = state.peek(); | |
if (ch == 'X') { | |
throw state.notImplemented(); | |
} else if (ch == 'I') { | |
arg = unmangleTemplateArgs(state, false); | |
} else if (ch == 's' && state.peek(1) == 'p') { | |
throw state.notImplemented(); | |
} else if (ch == 'L') { | |
arg = unmangleExprPrimary(state); | |
} else { | |
arg = unmangleType(state); | |
} | |
state.rememberTemplateArg(arg); | |
state.buffer.append(arg); | |
return state.pop(); | |
} | |
/** | |
<expr-primary> ::= L <type> <value number> E # integer literal | |
::= L <type> <value float> E # floating literal | |
::= L <string type> E # string literal | |
::= L <nullptr type> E # nullptr literal (i.e., "LDnE") | |
::= L <type> <real-part float> _ <imag-part float> E # complex floating point literal (C 2000) | |
::= L <mangled-name> E # external name | |
* @param state | |
* @return | |
*/ | |
private String unmangleExprPrimary(UnmangleState state) throws UnmanglingException { | |
state.push(); | |
state.consume('L'); | |
try { | |
state.safePush(); | |
String type = null; | |
String suffix = null; | |
switch (state.peek()) { | |
case 'i': // int | |
suffix = ""; | |
break; | |
case 'j': // unsigned int | |
suffix = "U"; | |
break; | |
case 'l': // long | |
suffix = "L"; | |
break; | |
case 'm': // unsigned long | |
suffix = "UL"; | |
break; | |
case 'x': // long long | |
suffix = "LL"; | |
break; | |
case 'y': // unsigned long long | |
suffix = "ULL"; | |
break; | |
} | |
if (suffix != null) { | |
state.skip(); | |
state.buffer.append(doUnmangleNumber(state)); | |
state.buffer.append(suffix); | |
} else { | |
// show other types | |
type = unmangleType(state); | |
state.buffer.append('('); | |
state.buffer.append(type); | |
state.buffer.append(')'); | |
state.buffer.append(doUnmangleNumber(state)); | |
} | |
state.safePop(); | |
} catch (UnmanglingException e) { | |
state.safeBacktrack(); | |
// must be mangled-name or something else | |
state.buffer.append(unmangleName(state)); | |
} | |
state.consume('E'); | |
return state.popAndRemember(SubstType.TEMPLATE_TEMPLATE_PARAM); | |
} | |
/* | |
<template-param> ::= T_ # first template parameter | |
::= T <parameter-2 non-negative number> _ | |
*/ | |
private String unmangleTemplateParam(UnmangleState state) throws UnmanglingException { | |
state.push(); | |
state.consume('T'); | |
int num = doUnmangleBase10(state); | |
state.buffer.append(state.getTemplateArg(num)); | |
return state.popAndRemember(SubstType.TEMPLATE_TEMPLATE_PARAM); | |
} | |
/** | |
* Base-10, where _ = 0 and 1..x = 0..x-1 | |
* @param state | |
* @return | |
* @throws UnmanglingException | |
*/ | |
private int doUnmangleBase10(UnmangleState state) throws UnmanglingException { | |
char ch; | |
if (state.peek() == '_') { | |
state.skip(); | |
return 0; | |
} | |
int num = 0; | |
while ((ch = state.get()) != '_') { | |
state.throwIfDone(); | |
num = (num * 10) + (ch - '0'); | |
} | |
return num + 1; | |
} | |
/* | |
<substitution> ::= S <seq-id> _ | |
::= S_ | |
<substitution> ::= St # ::std:: | |
<substitution> ::= Sa # ::std::allocator | |
<substitution> ::= Sb # ::std::basic_string | |
<substitution> ::= Ss # ::std::basic_string < char, | |
::std::char_traits<char>, | |
::std::allocator<char> > | |
<substitution> ::= Si # ::std::basic_istream<char, std::char_traits<char> > | |
<substitution> ::= So # ::std::basic_ostream<char, std::char_traits<char> > | |
<substitution> ::= Sd # ::std::basic_iostream<char, std::char_traits<char> > | |
*/ | |
private String unmangleSubstitution(UnmangleState state) throws UnmanglingException { | |
state.push(); | |
state.consume('S'); | |
char ch = state.peek(); | |
if (ch == '_' || (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z')) { | |
int num = doUnmangleBase36(state); | |
if (num < 0 || num >= state.substitutions.size()) | |
throw state.unexpected("substitution id in the range 0-"+ state.substitutions.size() + " but got " + num); | |
String val = state.substitutions.get(num); | |
SubstType type = state.substitutionTypes.get(num); | |
switch (type) { | |
case PREFIX: | |
//...? | |
state.buffer.append(val); | |
break; | |
case TEMPLATE_PREFIX: | |
state.buffer.append(val); | |
state.buffer.append(unmangleTemplateArgs(state, true)); | |
break; | |
case TEMPLATE_TEMPLATE_PARAM: | |
state.buffer.append(val); | |
break; | |
case QUAL_TYPE: | |
case TYPE: | |
// ...? | |
state.buffer.append(val); | |
break; | |
} | |
} else { | |
switch (ch) { | |
case 't': | |
state.buffer.append("::std"); break; | |
case 'a': | |
state.buffer.append("::std::allocator"); break; | |
case 'b': | |
state.buffer.append("::std::basic_string"); break; | |
case 's': | |
state.buffer.append("::std::basic_string<char,::std::char_traits<char>,::std::allocator<char> >"); break; | |
case 'i': | |
state.buffer.append("::std::basic_istream<char,::std::char_traits<char> >"); break; | |
case 'o': | |
state.buffer.append("::std::basic_ostream<char,::std::char_traits<char> >"); break; | |
case 'd': | |
state.buffer.append("::std::basic_iostream<char,::std::char_traits<char> >"); break; | |
default: | |
throw state.unexpected("std:: substitution"); | |
} | |
state.skip(); | |
} | |
return state.pop(); | |
} | |
/** | |
* As a special case, the first substitutable entity is encoded as "S_", | |
* i.e. with no number, so the numbered entities are the second one as | |
* "S0_", the third as "S1_", the twelfth as "SA_", the thirty-eighth as | |
* "S10_", etc. | |
* @throws UnmanglingException | |
*/ | |
private int doUnmangleBase36(UnmangleState state) throws UnmanglingException { | |
int num = 0; | |
char ch = state.peek(); | |
if (ch == '_') { | |
state.skip(); | |
return 0; | |
} | |
while ((ch = state.get()) != '_') { | |
state.throwIfDone(); | |
num = (num * 10); | |
if (ch >= '0' && ch <= '9') | |
num += (ch - '0'); | |
else if (ch >= 'A' && ch <= 'Z') | |
num += (ch - 'A') + 10; | |
else | |
throw state.unexpected("BASE-36 number"); | |
} | |
return num + 1; | |
} | |
/* | |
<prefix> ::= <prefix> <unqualified-name> # ... 0-9 | |
::= <template-prefix> <template-args> --> template=T... args=I... | |
::= <template-param> --> T... | |
::= # empty | |
::= <substitution> --> S... | |
::= <prefix> <data-member-prefix> --> name M | |
left-recursion elimination: | |
<prefix> ::= <template-prefix> <template-args> <prefix'> | |
::= <template-param> <prefix'> | |
::= <substitution> <prefix'> | |
::= # empty | |
<prefix'> ::= <unqualified-name> <prefix'> | |
::= <data-member-prefix> M <prefix'> | |
::= #empty | |
*/ | |
private String unmanglePrefix(UnmangleState state, SubstType substType) throws UnmanglingException { | |
state.push(); | |
boolean any = false; | |
boolean lastSubst = false; | |
while (true) { | |
char ch = state.peek(); | |
if (ch == 'E') { | |
break; | |
} | |
String part = null; | |
if (ch == 'T') { | |
part = unmangleTemplateParam(state); | |
state.remember(substType); | |
} | |
else if (ch == 'S') { | |
part = unmangleSubstitution(state); | |
lastSubst = true; | |
} | |
else if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') | |
|| (ch == 'C' || ch == 'D' || ch == 'L')) { | |
part = unmangleUnqualifiedName(state); | |
} | |
else if (ch == 'I') { | |
if (!any) | |
throw state.unexpected(); | |
if (state.hasCurrent()) { | |
state.updateSubstitution(SubstType.TEMPLATE_PREFIX); | |
part = state.current(); | |
} | |
String args = unmangleTemplateArgs(state, false); | |
state.buffer.append(args); | |
continue; | |
} | |
else { | |
throw state.unexpected(); | |
} | |
lastSubst = false; | |
any = true; | |
if (lastSubst) | |
any = true; | |
if (state.hasCurrent()) { | |
addNameWithColons(state, part); | |
} else { | |
state.buffer.append(part); | |
} | |
if (ch != 'S' && state.peek() != 'E') { | |
state.remember(substType); | |
} | |
} | |
return state.pop(); | |
} | |
/** | |
* @param state | |
* @param name | |
*/ | |
private void addNameWithColons(UnmangleState state, String name) { | |
if (state.hasCurrent() && !name.startsWith("::")) | |
state.buffer.append("::"); | |
state.buffer.append(name); | |
} | |
/* | |
<type> ::= <builtin-type> = rVKPROCGU ... | |
::= <function-type> = | |
::= <class-enum-type> | |
::= <array-type> | |
::= <pointer-to-member-type> = M... | |
::= <template-param> | |
::= <template-template-param> <template-args> | |
::= <substitution> # See Compression below | |
<type> ::= <CV-qualifiers> <type> | |
::= P <type> # pointer-to | |
::= R <type> # reference-to | |
::= O <type> # rvalue reference-to (C++0x) | |
::= C <type> # complex pair (C 2000) | |
::= G <type> # imaginary (C 2000) | |
::= U <source-name> <type> # vendor extended type qualifier | |
<CV-qualifiers> ::= [r] [V] [K] # restrict (C99), volatile, const | |
*/ | |
private String unmangleType(UnmangleState state) throws UnmanglingException { | |
state.push(); | |
char ch = state.get(); | |
switch (ch) { | |
// | |
// qualified types | |
// | |
case 'r': | |
case 'V': | |
case 'K': | |
state.unget(); | |
String cvquals = unmangleCVQualifiers(state); | |
state.buffer.append(unmangleType(state)); | |
if (cvquals.length() > 0) { | |
state.buffer.append(' '); | |
state.buffer.append(cvquals); | |
} | |
if (state.lastSubstitutionIsPrefix(SubstType.QUAL_TYPE)) | |
state.remember(SubstType.QUAL_TYPE); | |
return state.popAndRemember(SubstType.QUAL_TYPE); | |
case 'P': | |
state.buffer.append(unmangleType(state)); | |
if (state.lastSubstitutionIsPrefix(SubstType.QUAL_TYPE)) | |
state.remember(SubstType.QUAL_TYPE); | |
ptrOrRefize(state.buffer, "*"); | |
return state.popAndRemember(SubstType.QUAL_TYPE); | |
case 'R': | |
state.buffer.append(unmangleType(state)); | |
if (state.lastSubstitutionIsPrefix(SubstType.QUAL_TYPE)) | |
state.remember(SubstType.QUAL_TYPE); | |
ptrOrRefize(state.buffer, "&"); | |
return state.popAndRemember(SubstType.QUAL_TYPE); | |
case 'O': // rvalue reference-to | |
case 'C': // complex pair | |
case 'G': // imaginary | |
throw state.notImplemented(); | |
case 'U': // vendor extension | |
{ | |
// TODO: assuming the extension precedes the type, | |
// e.g. int __declspec(dllimport) foo(); | |
state.buffer.append(unmangleSourceName(state)); | |
state.buffer.append(' '); | |
state.buffer.append(unmangleType(state)); | |
return state.popAndRemember(SubstType.TYPE); | |
} | |
// | |
// built-in types | |
// | |
case 'v': | |
state.buffer.append("void"); break; | |
case 'w': | |
state.buffer.append("wchar_t"); break; | |
case 'b': | |
state.buffer.append("bool"); break; | |
case 'c': | |
state.buffer.append("char"); break; | |
case 'a': | |
state.buffer.append("signed char"); break; | |
case 'h': | |
state.buffer.append("unsigned char"); break; | |
case 's': | |
state.buffer.append("short"); break; | |
case 't': | |
state.buffer.append("unsigned short"); break; | |
case 'i': | |
state.buffer.append("int"); break; | |
case 'j': | |
state.buffer.append("unsigned int"); break; | |
case 'l': | |
state.buffer.append("long"); break; | |
case 'm': | |
state.buffer.append("unsigned long"); break; | |
case 'x': | |
state.buffer.append("long long"); break; | |
case 'y': | |
state.buffer.append("unsigned long long"); break; | |
case 'n': | |
state.buffer.append("__int128"); break; | |
case 'o': | |
state.buffer.append("unsigned __int128"); break; | |
case 'f': | |
state.buffer.append("float"); break; | |
case 'd': | |
state.buffer.append("double"); break; | |
case 'e': | |
state.buffer.append("long double"); break; | |
case 'g': | |
state.buffer.append("__float128"); break; | |
case 'z': | |
state.buffer.append("..."); break; | |
case 'D': { | |
ch = state.get(); | |
switch (ch) { | |
case 'd': | |
state.buffer.append("::std::decimal::decimal64"); break; | |
case 'e': | |
state.buffer.append("::std::decimal::decimal128"); break; | |
case 'f': | |
state.buffer.append("::std::decimal::decimal32"); break; | |
case 'h': | |
state.buffer.append("::std::decimal::binary16"); break; // TODO: a guess; what's the actual C++ name for the half-float? | |
case 'i': | |
state.buffer.append("char32_t"); break; | |
case 's': | |
state.buffer.append("char16_t"); break; | |
default: | |
// Dp, Dt, DT | |
state.unget(); throw state.notImplemented(); | |
} | |
} | |
case 'u': | |
state.buffer.append(unmangleName(state)); | |
return state.popAndRemember(SubstType.TYPE); | |
// | |
// <class-enum-type> ::= <unqualified-name> | <nested-name> | |
// | |
case 'N': | |
state.unget(); | |
state.buffer.append(unmangleNestedName(state, false)); | |
state.remember(SubstType.TYPE); | |
break; | |
case 'F': | |
// <function-type> ::= F [Y] <bare-function-type> E | |
if (state.peek() == 'Y') { | |
state.skip(); | |
state.buffer.append("extern \"C\" "); | |
} | |
state.buffer.append(unmangleBareFunctionType(state, true)); | |
state.consume('E'); | |
state.remember(SubstType.TYPE); | |
break; | |
case 'M': { | |
state.unget(); | |
String name = unmanglePtm(state); | |
state.buffer.append(name); | |
state.remember(name, SubstType.TYPE); | |
break; | |
} | |
case 'S': | |
state.unget(); | |
state.buffer.append(unmangleSubstitution(state)); | |
break; | |
case 'T': | |
// either <template-param> or <template-template-param> <template-args> | |
state.unget(); | |
state.buffer.append(unmangleTemplateParam(state)); | |
if (state.peek() == 'I') { | |
state.buffer.append(unmangleTemplateArgs(state, false)); | |
} | |
break; | |
case 'A': | |
state.unget(); | |
state.buffer.append(unmangleArrayType(state)); | |
break; | |
default: | |
state.unget(); | |
String unqual = unmangleUnqualifiedName(state); | |
state.buffer.append(unqual); | |
if (state.peek() == 'I') { | |
// unscoped-template-name | |
state.remember(unqual, SubstType.TEMPLATE_PREFIX); | |
state.buffer.append(unmangleTemplateArgs(state, false)); | |
} | |
state.remember(SubstType.TYPE); | |
break; | |
} | |
return state.pop(); | |
} | |
/** | |
* Insert a "*" or "&" into a string. If this is a function type, | |
* insert in front of the argument list, not after. | |
* @param buffer | |
* @param string | |
*/ | |
private void ptrOrRefize(StringBuilder buffer, String string) { | |
char last = buffer.length() > 0 ? buffer.charAt(buffer.length() - 1) : 0; | |
if (last == ')' || last == ']') { | |
char match = last == ')' ? '(' : '['; | |
int stack = 0; | |
int idx = buffer.length() - 1; | |
while (idx > 0) { | |
char ch = buffer.charAt(idx); | |
if (ch == last) | |
stack++; | |
else if (ch == match) { | |
stack--; | |
if (stack == 0) | |
break; | |
} | |
idx--; | |
} | |
buffer.insert(idx, '(' + string + ')'); | |
} else { | |
buffer.append(string); | |
} | |
} | |
/* | |
<array-type> ::= A <positive dimension number> _ <element type> | |
::= A [<dimension expression>] _ <element type> | |
*/ | |
private String unmangleArrayType(UnmangleState state) throws UnmanglingException { | |
state.push(); | |
state.consume('A'); | |
String count; | |
char ch = state.peek(); | |
if (ch >= '0' && ch <= '9') { | |
int num = doUnmangleNonNegativeNumber(state); | |
count = "" + num; | |
} else { | |
throw state.notImplemented(); | |
} | |
state.consume('_'); | |
state.buffer.append(unmangleType(state)); | |
state.buffer.append('['); | |
state.buffer.append(count); | |
state.buffer.append(']'); | |
return state.pop(); | |
} | |
/* | |
<pointer-to-member-type> ::= M <class type> <member type> | |
*/ | |
private String unmanglePtm(UnmangleState state) throws UnmanglingException { | |
state.push(); | |
state.consume('M'); | |
String klass = unmangleType(state); | |
String ptrquals = unmangleCVQualifiers(state); | |
try { | |
state.safePush(); | |
state.buffer.append(doUnmangleFunctionWithName(state, true, '(' + klass + "::*)")); | |
state.safePop(); | |
} catch (UnmanglingException e) { | |
// may be pointer to member (field) | |
state.safeBacktrack(); | |
state.buffer.append(unmangleType(state)); | |
state.buffer.append(' '); | |
state.buffer.append(klass); | |
state.buffer.append("::*"); | |
} | |
if (ptrquals.length() > 0) { | |
state.buffer.append(' '); | |
state.buffer.append(ptrquals); | |
} | |
return state.pop(); | |
} | |
/** | |
* Unmangle any sequence of CV quals | |
* @param state state | |
* @return String | |
*/ | |
private String unmangleCVQualifiers(UnmangleState state) { | |
state.push(); | |
while (true) { | |
boolean matched = true; | |
switch (state.peek()) { | |
case 'r': | |
state.skip(); | |
if (state.hasCurrent()) state.buffer.append(' '); | |
state.buffer.append("restrict"); | |
break; | |
case 'V': | |
state.skip(); | |
if (state.hasCurrent()) state.buffer.append(' '); | |
state.buffer.append("volatile"); | |
break; | |
case 'K': | |
state.skip(); | |
if (state.hasCurrent()) state.buffer.append(' '); | |
state.buffer.append("const"); | |
break; | |
default: | |
matched = false; | |
break; | |
} | |
if (!matched) | |
break; | |
} | |
return state.pop(); | |
} | |
static class Operator { | |
String name; | |
/** for unary or binary ops; other questionable ones are 0 */ | |
int numops; | |
public Operator(String name, int numops) { | |
this.name = name; | |
this.numops = numops; | |
} | |
} | |
static Map<String, Operator> operators = new HashMap<String, Operator>(); | |
private static void registerOperator(String code, String name, int opcnt) { | |
if (operators.containsKey(code)) | |
throw new IllegalStateException(); | |
operators.put(code, new Operator(name, opcnt)); | |
} | |
static { | |
registerOperator("nw", "new", 0); | |
registerOperator("na", "new[]", 0); | |
registerOperator("dl", "delete", 0); | |
registerOperator("da", "delete[]", 0); | |
registerOperator("ps", "+", 1); | |
registerOperator("ng", "-", 1); | |
registerOperator("ad", "&", 1); | |
registerOperator("de", "*", 1); | |
registerOperator("co", "~", 1); | |
registerOperator("pl", "+", 2); | |
registerOperator("mi", "-", 2); | |
registerOperator("ml", "*", 2); | |
registerOperator("dv", "/", 2); | |
registerOperator("rm", "%", 2); | |
registerOperator("an", "&", 2); | |
registerOperator("or", "|", 2); | |
registerOperator("eo", "^", 2); | |
registerOperator("aS", "=", 2); | |
registerOperator("pL", "+=", 2); | |
registerOperator("mI", "-=", 2); | |
registerOperator("mL", "*=", 2); | |
registerOperator("dV", "/=", 2); | |
registerOperator("rM", "%=", 2); | |
registerOperator("aN", "&=", 2); | |
registerOperator("oR", "|=", 2); | |
registerOperator("eO", "^=", 2); | |
registerOperator("ls", "<<", 2); | |
registerOperator("rs", ">>", 2); | |
registerOperator("lS", "<<=", 2); | |
registerOperator("rS", ">>=", 2); | |
registerOperator("eq", "==", 2); | |
registerOperator("ne", "!=", 2); | |
registerOperator("lt", "<", 2); | |
registerOperator("gt", ">", 2); | |
registerOperator("le", "<=", 2); | |
registerOperator("ge", ">=", 2); | |
registerOperator("nt", "!", 1); | |
registerOperator("aa", "&&", 2); | |
registerOperator("oo", "||", 2); | |
registerOperator("pp", "++", 1); | |
registerOperator("mm", "--", 1); | |
registerOperator("cm", ",", 2); | |
registerOperator("pm", "->*", 2); | |
registerOperator("pt", "->", 2); | |
registerOperator("cl", "()", 1); | |
registerOperator("ix", "[]", 2); | |
registerOperator("qu", "?", 3); | |
registerOperator("st", "sizeof ", 0); // type | |
registerOperator("sz", "sizeof", 1); // expression | |
registerOperator("at", "alignof ", 0); // type | |
registerOperator("az", "alignof", 1); // expression | |
registerOperator("cv", "()", 1); | |
} | |
/* | |
<unqualified-name> ::= <operator-name> = lowercase | |
::= <ctor-dtor-name> = C1-3 D0-2 ... | |
::= <source-name> = <number> ... | |
::= <unnamed-type-name> = Ut ... | |
*/ | |
private String unmangleUnqualifiedName(UnmangleState state) throws UnmanglingException { | |
char ch = state.peek(); | |
if (ch >= '0' && ch <= '9') { | |
return unmangleSourceName(state); | |
} | |
else if (ch >= 'a' && ch <= 'z') { | |
return unmangleOperatorName(state); | |
} | |
else if (ch == 'U') { | |
return unmangleUnnamedTypeName(state); | |
} | |
else if (ch == 'C') { | |
state.push(); | |
String last = simpleName(state.lastSubstitutedName()); | |
state.get(); | |
switch (state.get()) { | |
case '1': | |
case '2': | |
case '3': | |
state.buffer.append(last); | |
return state.pop(); | |
default: | |
state.unget(); | |
throw state.unexpected("constructor name"); | |
} | |
} | |
else if (ch == 'D') { | |
state.push(); | |
String last = simpleName(state.lastSubstitutedName()); | |
state.get(); | |
state.buffer.append('~'); | |
state.buffer.append(last); | |
switch (state.get()) { | |
case '0': | |
return state.pop(); | |
case '1': | |
return state.pop(); | |
case '2': | |
return state.pop(); | |
default: | |
state.unget(); | |
throw state.unexpected("destructor name"); | |
} | |
} | |
throw state.unexpected(); | |
} | |
/** | |
* @param name | |
* @return | |
*/ | |
private String simpleName(String name) { | |
int idx = name.lastIndexOf("::"); | |
if (idx >= 0) | |
return name.substring(idx + 2); | |
return name; | |
} | |
/* | |
<unnamed-type-name> ::= Ut [ <nonnegative number> ] _ | |
<unnamed-type-name> ::= <closure-type-name> | |
<closure-type-name> ::= Ul <lambda-sig> E [ <nonnegative number> ] _ | |
*/ | |
private String unmangleUnnamedTypeName(UnmangleState state) throws UnmanglingException { | |
state.push(); | |
state.consume('U'); | |
switch (state.get()) { | |
case 't': | |
state.buffer.append("<unnamed #"); | |
if (state.peek() != '_') { | |
state.buffer.append("" + (doUnmangleNonNegativeNumber(state) + 2)); | |
} else { | |
state.buffer.append("1"); | |
} | |
state.buffer.append('>'); | |
state.consume('_'); | |
break; | |
case 'l': | |
throw state.notImplemented(); | |
default: | |
throw state.unexpected(); | |
} | |
return state.pop(); | |
} | |
/* | |
*/ | |
private String unmangleOperatorName(UnmangleState state) throws UnmanglingException { | |
state.push(); | |
char ch = state.get(); | |
String op = "" + ch; | |
if (ch == 'v') { | |
// vendor type <digit> <source-name> | |
ch = state.get(); | |
if (ch >= '0' && ch <= '9') { | |
int opcount = ch - '0'; | |
op = unmangleSourceName(state); | |
boolean first = true; | |
// pretend it's a function, to differentiate | |
state.buffer.append('('); | |
while (opcount-- > 0) { | |
if (first) | |
first = false; | |
else | |
state.buffer.append(','); | |
} | |
state.buffer.append(')'); | |
} else { | |
throw state.unexpected(); | |
} | |
return state.pop(); | |
} | |
ch = state.get(); | |
if (!Character.isLetter(ch)) { | |
throw state.unexpected(); | |
} | |
op += ch; | |
Operator oper = operators.get(op); | |
if (oper == null) { | |
throw state.unexpected(); | |
} | |
state.buffer.append("operator "); | |
// special cases | |
if (op.equals("cv")) { | |
state.buffer.append(unmangleType(state)); | |
// fall through | |
} | |
state.buffer.append(oper.name); | |
return state.pop(); | |
} | |
} |