2377 lines
58 KiB
D
2377 lines
58 KiB
D
package core.demangle;
|
|
|
|
private enum TypeCtor : ushort {
|
|
None = 0,
|
|
//// 'x'
|
|
Const = (1 << 1),
|
|
/// 'y'
|
|
Immutable = (1 << 2),
|
|
/// 'O'
|
|
Shared = (1 << 3),
|
|
///
|
|
InOut = (1 << 4),
|
|
}
|
|
|
|
private immutable ManglingFlagInfo[] typeCtors = [
|
|
ManglingFlagInfo(TypeCtor.Immutable, "immutable"),
|
|
ManglingFlagInfo(TypeCtor.Shared, "shared"),
|
|
ManglingFlagInfo(TypeCtor.InOut, "inout"),
|
|
ManglingFlagInfo(TypeCtor.Const, "const"),
|
|
];
|
|
|
|
private enum FuncAttributes : ushort {
|
|
None = 0,
|
|
//// 'a'
|
|
Pure = (1 << 1),
|
|
//// 'b'
|
|
Nothrow = (1 << 2),
|
|
//// 'c'
|
|
Ref = (1 << 3),
|
|
//// 'd'
|
|
Property = (1 << 4),
|
|
//// 'e'
|
|
Trusted = (1 << 5),
|
|
//// 'f'
|
|
Safe = (1 << 6),
|
|
//// 'i'
|
|
NoGC = (1 << 7),
|
|
//// 'j'
|
|
Return = (1 << 8),
|
|
//// 'l'
|
|
Scope = (1 << 9),
|
|
//// 'm'
|
|
Live = (1 << 10),
|
|
|
|
/// Their order matter
|
|
ReturnScope = (1 << 11),
|
|
ScopeReturn = (1 << 12),
|
|
}
|
|
|
|
// The order in which we process is the same as in compiler/dmd/src/dmangle.d
|
|
private immutable ManglingFlagInfo[] funcAttrs = [
|
|
ManglingFlagInfo(FuncAttributes.Pure, "pure"),
|
|
ManglingFlagInfo(FuncAttributes.Nothrow, "nothrow"),
|
|
ManglingFlagInfo(FuncAttributes.Ref, "ref"),
|
|
ManglingFlagInfo(FuncAttributes.Property, "@property"),
|
|
ManglingFlagInfo(FuncAttributes.NoGC, "@nogc"),
|
|
|
|
ManglingFlagInfo(FuncAttributes.ReturnScope, "return scope"),
|
|
ManglingFlagInfo(FuncAttributes.ScopeReturn, "scope return"),
|
|
|
|
ManglingFlagInfo(FuncAttributes.Return, "return"),
|
|
ManglingFlagInfo(FuncAttributes.Scope, "scope"),
|
|
|
|
ManglingFlagInfo(FuncAttributes.Live, "@live"),
|
|
ManglingFlagInfo(FuncAttributes.Trusted, "@trusted"),
|
|
ManglingFlagInfo(FuncAttributes.Safe, "@safe"),
|
|
];
|
|
|
|
private struct NoHooks
|
|
{
|
|
// supported hooks
|
|
// static bool parseLName(ref Demangle);
|
|
// static char[] parseType(ref Demangle, char[])
|
|
}
|
|
|
|
private struct Demangle(Hooks = NoHooks)
|
|
{
|
|
// NOTE: This implementation currently only works with mangled function
|
|
// names as they exist in an object file. Type names mangled via
|
|
// the .mangleof property are effectively incomplete as far as the
|
|
// ABI is concerned and so are not considered to be mangled symbol
|
|
// names.
|
|
|
|
// NOTE: This implementation builds the demangled buffer in place by
|
|
// writing data as it is decoded and then rearranging it later as
|
|
// needed. In practice this results in very little data movement,
|
|
// and the performance cost is more than offset by the gain from
|
|
// not allocating dynamic memory to assemble the name piecemeal.
|
|
//
|
|
// If the destination buffer is too small, parsing will restart
|
|
// with a larger buffer. Since this generally means only one
|
|
// allocation during the course of a parsing run, this is still
|
|
// faster than assembling the result piecemeal.
|
|
|
|
pure @safe:
|
|
enum AddType { no, yes }
|
|
|
|
|
|
this( return scope const(char)[] buf_, return scope char[] dst_ = null )
|
|
{
|
|
this( buf_, AddType.yes, dst_ );
|
|
}
|
|
|
|
|
|
this( return scope const(char)[] buf_, AddType addType_, return scope char[] dst_ = null )
|
|
{
|
|
buf = buf_;
|
|
addType = addType_;
|
|
dst.dst = dst_;
|
|
}
|
|
|
|
const(char)[] buf = null;
|
|
Buffer dst;
|
|
size_t pos = 0;
|
|
size_t brp = 0; // current back reference pos
|
|
AddType addType = AddType.yes;
|
|
bool mute = false;
|
|
Hooks hooks;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Type Testing and Conversion
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
static bool isAlpha( char val )
|
|
{
|
|
return ('a' <= val && 'z' >= val) ||
|
|
('A' <= val && 'Z' >= val) ||
|
|
(0x80 & val); // treat all unicode as alphabetic
|
|
}
|
|
|
|
|
|
static bool isDigit( char val ) nothrow
|
|
{
|
|
return '0' <= val && '9' >= val;
|
|
}
|
|
|
|
|
|
static bool isHexDigit( char val )
|
|
{
|
|
return ('0' <= val && '9' >= val) ||
|
|
('a' <= val && 'f' >= val) ||
|
|
('A' <= val && 'F' >= val);
|
|
}
|
|
|
|
|
|
static ubyte ascii2hex( out bool errStatus, char val ) nothrow
|
|
{
|
|
if (val >= 'a' && val <= 'f')
|
|
return cast(ubyte)(val - 'a' + 10);
|
|
if (val >= 'A' && val <= 'F')
|
|
return cast(ubyte)(val - 'A' + 10);
|
|
if (val >= '0' && val <= '9')
|
|
return cast(ubyte)(val - '0');
|
|
|
|
errStatus = true;
|
|
return 0;
|
|
}
|
|
|
|
BufSlice shift(scope const BufSlice val) return scope
|
|
{
|
|
if (mute)
|
|
return dst.bslice_empty;
|
|
return dst.shift(val);
|
|
}
|
|
|
|
void putComma(size_t n)
|
|
{
|
|
version (DigitalMars) pragma(inline, false);
|
|
if (n)
|
|
put(", ");
|
|
}
|
|
|
|
void put(char c) return scope
|
|
{
|
|
char[1] val = c;
|
|
put(val[]);
|
|
}
|
|
|
|
void put(scope BufSlice val) return scope
|
|
{
|
|
put(val.getSlice);
|
|
}
|
|
|
|
void put(scope const(char)[] val) return scope nothrow
|
|
{
|
|
if (mute)
|
|
return;
|
|
dst.append(val);
|
|
}
|
|
|
|
|
|
void putAsHex( size_t val, int width = 0 )
|
|
{
|
|
import core.internal.string;
|
|
|
|
UnsignedStringBuf buf = void;
|
|
|
|
auto s = unsignedToTempString!16(val, buf);
|
|
int slen = cast(int)s.length;
|
|
if (slen < width)
|
|
{
|
|
foreach (i; slen .. width)
|
|
put('0');
|
|
}
|
|
put(s);
|
|
}
|
|
|
|
|
|
void pad( const(char)[] val )
|
|
{
|
|
if ( val.length )
|
|
{
|
|
put(" ");
|
|
put( val );
|
|
}
|
|
}
|
|
|
|
|
|
void silent( out bool err_status, void delegate(out bool err_status) pure @safe nothrow dg ) nothrow
|
|
{
|
|
auto n = dst.length;
|
|
dg(err_status);
|
|
if(!err_status)
|
|
dst.len = n;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Parsing Utility
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
@property bool empty()
|
|
{
|
|
return pos >= buf.length;
|
|
}
|
|
|
|
@property char front()
|
|
{
|
|
if ( pos < buf.length )
|
|
return buf[pos];
|
|
return char.init;
|
|
}
|
|
|
|
char peek( size_t n )
|
|
{
|
|
if ( pos + n < buf.length )
|
|
return buf[pos + n];
|
|
return char.init;
|
|
}
|
|
|
|
|
|
bool test( char val ) nothrow
|
|
{
|
|
return val == front;
|
|
}
|
|
|
|
void popFront() nothrow
|
|
{
|
|
if ( pos++ >= buf.length )
|
|
assert(false);
|
|
}
|
|
|
|
|
|
void popFront(int i) nothrow
|
|
{
|
|
while (i--)
|
|
popFront();
|
|
}
|
|
|
|
|
|
bool match( char val ) nothrow
|
|
{
|
|
if (!test(val))
|
|
return false;
|
|
else
|
|
{
|
|
popFront();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool match( const(char)[] val ) nothrow
|
|
{
|
|
foreach (char e; val )
|
|
if (!match( e ))
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void eat( char val )
|
|
{
|
|
if ( val == front )
|
|
popFront();
|
|
}
|
|
|
|
bool isSymbolNameFront(out bool errStatus) nothrow
|
|
{
|
|
char val = front;
|
|
if ( isDigit( val ) || val == '_' )
|
|
return true;
|
|
if ( val != 'Q' )
|
|
return false;
|
|
|
|
// check the back reference encoding after 'Q'
|
|
val = peekBackref();
|
|
if (val == 0)
|
|
{
|
|
// invalid back reference
|
|
errStatus = true;
|
|
return false;
|
|
}
|
|
|
|
return isDigit( val ); // identifier ref
|
|
}
|
|
|
|
// return the first character at the back reference
|
|
char peekBackref() nothrow
|
|
{
|
|
assert( front == 'Q' );
|
|
auto n = decodeBackref!1();
|
|
if (!n || n > pos)
|
|
return 0; // invalid back reference
|
|
|
|
return buf[pos - n];
|
|
}
|
|
|
|
size_t decodeBackref(size_t peekAt = 0)() nothrow
|
|
{
|
|
enum base = 26;
|
|
size_t n = 0;
|
|
for (size_t p; ; p++)
|
|
{
|
|
char t;
|
|
static if (peekAt > 0)
|
|
{
|
|
t = peek(peekAt + p);
|
|
}
|
|
else
|
|
{
|
|
t = front;
|
|
popFront();
|
|
}
|
|
if (t < 'A' || t > 'Z')
|
|
{
|
|
if (t < 'a' || t > 'z')
|
|
return 0; // invalid back reference
|
|
|
|
n = base * n + t - 'a';
|
|
return n;
|
|
}
|
|
n = base * n + t - 'A';
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Parsing Implementation
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
/*
|
|
Number:
|
|
Digit
|
|
Digit Number
|
|
*/
|
|
const(char)[] sliceNumber() return scope
|
|
{
|
|
auto beg = pos;
|
|
|
|
while ( true )
|
|
{
|
|
auto t = front;
|
|
if (t >= '0' && t <= '9')
|
|
popFront();
|
|
else
|
|
return buf[beg .. pos];
|
|
}
|
|
}
|
|
|
|
|
|
size_t decodeNumber(out bool errStatus) scope nothrow
|
|
{
|
|
return decodeNumber( errStatus, sliceNumber() );
|
|
}
|
|
|
|
size_t decodeNumber( out bool errStatus, scope const(char)[] num ) scope nothrow
|
|
{
|
|
size_t val = 0;
|
|
|
|
foreach ( c; num )
|
|
{
|
|
import core.checkedint : mulu, addu;
|
|
|
|
bool overflow = false;
|
|
val = mulu(val, 10, overflow);
|
|
val = addu(val, c - '0', overflow);
|
|
if (overflow)
|
|
{
|
|
errStatus = true;
|
|
return 0;
|
|
}
|
|
}
|
|
return val;
|
|
}
|
|
|
|
void parseReal(out bool errStatus) scope nothrow
|
|
{
|
|
char[64] tbuf = void;
|
|
size_t tlen = 0;
|
|
real val = void;
|
|
|
|
void onError()
|
|
{
|
|
errStatus = true;
|
|
}
|
|
|
|
if ( 'I' == front )
|
|
{
|
|
if (!match("INF"))
|
|
return onError();
|
|
put( "real.infinity" );
|
|
return;
|
|
}
|
|
if ( 'N' == front )
|
|
{
|
|
popFront();
|
|
if ( 'I' == front )
|
|
{
|
|
if (!match("INF"))
|
|
return onError();
|
|
put( "-real.infinity" );
|
|
return;
|
|
}
|
|
if ( 'A' == front )
|
|
{
|
|
if (!match("AN"))
|
|
return onError();
|
|
put( "real.nan" );
|
|
return;
|
|
}
|
|
tbuf[tlen++] = '-';
|
|
}
|
|
|
|
tbuf[tlen++] = '0';
|
|
tbuf[tlen++] = 'X';
|
|
errStatus = !isHexDigit( front );
|
|
if (errStatus)
|
|
return; // Expected hex digit
|
|
|
|
tbuf[tlen++] = front;
|
|
tbuf[tlen++] = '.';
|
|
popFront();
|
|
|
|
while ( isHexDigit( front ) )
|
|
{
|
|
if (tlen >= tbuf.length)
|
|
return onError(); // Too many hex float digits
|
|
tbuf[tlen++] = front;
|
|
popFront();
|
|
}
|
|
if (!match('P'))
|
|
return onError();
|
|
tbuf[tlen++] = 'p';
|
|
if ( 'N' == front )
|
|
{
|
|
tbuf[tlen++] = '-';
|
|
popFront();
|
|
}
|
|
else
|
|
{
|
|
tbuf[tlen++] = '+';
|
|
}
|
|
while ( isDigit( front ) )
|
|
{
|
|
tbuf[tlen++] = front;
|
|
popFront();
|
|
}
|
|
|
|
tbuf[tlen] = 0;
|
|
pureReprintReal( tbuf[] );
|
|
put( tbuf[0 .. tlen] );
|
|
}
|
|
|
|
|
|
/*
|
|
LName:
|
|
Number Name
|
|
|
|
Name:
|
|
Namestart
|
|
Namestart Namechars
|
|
|
|
Namestart:
|
|
_
|
|
Alpha
|
|
|
|
Namechar:
|
|
Namestart
|
|
Digit
|
|
|
|
Namechars:
|
|
Namechar
|
|
Namechar Namechars
|
|
*/
|
|
void parseLName(out string errMsg) scope nothrow
|
|
{
|
|
static if (__traits(hasMember, Hooks, "parseLName"))
|
|
{
|
|
auto r = hooks.parseLName(errMsg, this);
|
|
if (errMsg !is null)
|
|
return;
|
|
if (r) return;
|
|
}
|
|
|
|
void error(string msg)
|
|
{
|
|
errMsg = msg;
|
|
}
|
|
|
|
if ( front == 'Q' )
|
|
{
|
|
// back reference to LName
|
|
auto refPos = pos;
|
|
popFront();
|
|
size_t n = decodeBackref();
|
|
if (!n || n > refPos)
|
|
return error("Invalid LName back reference");
|
|
|
|
if ( !mute )
|
|
{
|
|
auto savePos = pos;
|
|
scope(exit) pos = savePos;
|
|
pos = refPos - n;
|
|
parseLName(errMsg);
|
|
}
|
|
return;
|
|
}
|
|
|
|
bool err_flag;
|
|
auto n = decodeNumber(err_flag);
|
|
if (err_flag)
|
|
return error("Number overflow");
|
|
|
|
if ( n == 0 )
|
|
{
|
|
put( "__anonymous" );
|
|
return;
|
|
}
|
|
if ( n > buf.length || n > buf.length - pos )
|
|
return error("LName must be at least 1 character");
|
|
|
|
if ( '_' != front && !isAlpha( front ) )
|
|
return error("Invalid character in LName");
|
|
|
|
foreach (char e; buf[pos + 1 .. pos + n] )
|
|
{
|
|
if ( '_' != e && !isAlpha( e ) && !isDigit( e ) )
|
|
return error("Invalid character in LName");
|
|
}
|
|
|
|
put( buf[pos .. pos + n] );
|
|
pos += n;
|
|
}
|
|
|
|
|
|
/*
|
|
Type:
|
|
Shared
|
|
Const
|
|
Immutable
|
|
Wild
|
|
TypeArray
|
|
TypeVector
|
|
TypeStaticArray
|
|
TypeAssocArray
|
|
TypePointer
|
|
TypeFunction
|
|
TypeIdent
|
|
TypeClass
|
|
TypeStruct
|
|
TypeEnum
|
|
TypeTypedef
|
|
TypeDelegate
|
|
TypeNone
|
|
TypeVoid
|
|
TypeNoreturn
|
|
TypeByte
|
|
TypeUbyte
|
|
TypeShort
|
|
TypeUshort
|
|
TypeInt
|
|
TypeUint
|
|
TypeLong
|
|
TypeUlong
|
|
TypeCent
|
|
TypeUcent
|
|
TypeFloat
|
|
TypeDouble
|
|
TypeReal
|
|
TypeIfloat
|
|
TypeIdouble
|
|
TypeIreal
|
|
TypeCfloat
|
|
TypeCdouble
|
|
TypeCreal
|
|
TypeBool
|
|
TypeChar
|
|
TypeWchar
|
|
TypeDchar
|
|
TypeTuple
|
|
|
|
Shared:
|
|
O Type
|
|
|
|
Const:
|
|
x Type
|
|
|
|
Immutable:
|
|
y Type
|
|
|
|
Wild:
|
|
Ng Type
|
|
|
|
TypeArray:
|
|
A Type
|
|
|
|
TypeVector:
|
|
Nh Type
|
|
|
|
TypeStaticArray:
|
|
G Number Type
|
|
|
|
TypeAssocArray:
|
|
H Type Type
|
|
|
|
TypePointer:
|
|
P Type
|
|
|
|
TypeFunction:
|
|
CallConvention FuncAttrs Arguments ArgClose Type
|
|
|
|
TypeIdent:
|
|
I LName
|
|
|
|
TypeClass:
|
|
C LName
|
|
|
|
TypeStruct:
|
|
S LName
|
|
|
|
TypeEnum:
|
|
E LName
|
|
|
|
TypeTypedef:
|
|
T LName
|
|
|
|
TypeDelegate:
|
|
D TypeFunction
|
|
|
|
TypeNone:
|
|
n
|
|
|
|
TypeVoid:
|
|
v
|
|
|
|
TypeNoreturn
|
|
Nn
|
|
|
|
TypeByte:
|
|
g
|
|
|
|
TypeUbyte:
|
|
h
|
|
|
|
TypeShort:
|
|
s
|
|
|
|
TypeUshort:
|
|
t
|
|
|
|
TypeInt:
|
|
i
|
|
|
|
TypeUint:
|
|
k
|
|
|
|
TypeLong:
|
|
l
|
|
|
|
TypeUlong:
|
|
m
|
|
|
|
TypeCent
|
|
zi
|
|
|
|
TypeUcent
|
|
zk
|
|
|
|
TypeFloat:
|
|
f
|
|
|
|
TypeDouble:
|
|
d
|
|
|
|
TypeReal:
|
|
e
|
|
|
|
TypeIfloat:
|
|
o
|
|
|
|
TypeIdouble:
|
|
p
|
|
|
|
TypeIreal:
|
|
j
|
|
|
|
TypeCfloat:
|
|
q
|
|
|
|
TypeCdouble:
|
|
r
|
|
|
|
TypeCreal:
|
|
c
|
|
|
|
TypeBool:
|
|
b
|
|
|
|
TypeChar:
|
|
a
|
|
|
|
TypeWchar:
|
|
u
|
|
|
|
TypeDchar:
|
|
w
|
|
|
|
TypeTuple:
|
|
B Number Arguments
|
|
*/
|
|
BufSlice parseType(out bool errStatus) return scope nothrow
|
|
{
|
|
static immutable string[23] primitives = [
|
|
"char", // a
|
|
"bool", // b
|
|
"creal", // c
|
|
"double", // d
|
|
"real", // e
|
|
"float", // f
|
|
"byte", // g
|
|
"ubyte", // h
|
|
"int", // i
|
|
"ireal", // j
|
|
"uint", // k
|
|
"long", // l
|
|
"ulong", // m
|
|
null, // n
|
|
"ifloat", // o
|
|
"idouble", // p
|
|
"cfloat", // q
|
|
"cdouble", // r
|
|
"short", // s
|
|
"ushort", // t
|
|
"wchar", // u
|
|
"void", // v
|
|
"dchar", // w
|
|
];
|
|
|
|
static if (__traits(hasMember, Hooks, "parseType"))
|
|
{
|
|
auto n = hooks.parseType(errStatus, this, null);
|
|
if (errStatus)
|
|
return dst.bslice_empty;
|
|
else
|
|
if (n !is null)
|
|
return BufSlice(n, 0, n.length);
|
|
}
|
|
|
|
auto beg = dst.length;
|
|
auto t = front;
|
|
|
|
BufSlice parseBackrefType(out string errStatus, scope BufSlice delegate(bool err_flag) pure @safe nothrow parseDg) pure @safe nothrow
|
|
{
|
|
if (pos == brp)
|
|
{
|
|
errStatus = "recursive back reference";
|
|
return dst.bslice_empty;
|
|
}
|
|
|
|
auto refPos = pos;
|
|
popFront();
|
|
auto n = decodeBackref();
|
|
if (n == 0 || n > pos)
|
|
{
|
|
errStatus = "invalid back reference";
|
|
return dst.bslice_empty;
|
|
}
|
|
|
|
if ( mute )
|
|
return dst.bslice_empty;
|
|
auto savePos = pos;
|
|
auto saveBrp = brp;
|
|
scope(success) { pos = savePos; brp = saveBrp; }
|
|
pos = refPos - n;
|
|
brp = refPos;
|
|
|
|
bool err_flag;
|
|
auto ret = parseDg(err_flag);
|
|
if (err_flag)
|
|
{
|
|
errStatus = "parseDg error";
|
|
return dst.bslice_empty;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// call parseType() and return error if occured
|
|
enum parseTypeOrF = "parseType(errStatus); if (errStatus) return dst.bslice_empty;";
|
|
|
|
switch ( t )
|
|
{
|
|
case 'Q': // Type back reference
|
|
string errMsg;
|
|
auto r = parseBackrefType(errMsg, (e_flag) => parseType(e_flag));
|
|
if (errMsg !is null)
|
|
return dst.bslice_empty;
|
|
return r;
|
|
case 'O': // Shared (O Type)
|
|
popFront();
|
|
put( "shared(" );
|
|
mixin(parseTypeOrF);
|
|
put( ')' );
|
|
return dst[beg .. $];
|
|
case 'x': // Const (x Type)
|
|
popFront();
|
|
put( "const(" );
|
|
mixin(parseTypeOrF);
|
|
put( ')' );
|
|
return dst[beg .. $];
|
|
case 'y': // Immutable (y Type)
|
|
popFront();
|
|
put( "immutable(" );
|
|
mixin(parseTypeOrF);
|
|
put( ')' );
|
|
return dst[beg .. $];
|
|
case 'N':
|
|
popFront();
|
|
switch ( front )
|
|
{
|
|
case 'n': // Noreturn
|
|
popFront();
|
|
put("noreturn");
|
|
return dst[beg .. $];
|
|
case 'g': // Wild (Ng Type)
|
|
popFront();
|
|
// TODO: Anything needed here?
|
|
put( "inout(" );
|
|
mixin(parseTypeOrF);
|
|
put( ')' );
|
|
return dst[beg .. $];
|
|
case 'h': // TypeVector (Nh Type)
|
|
popFront();
|
|
put( "__vector(" );
|
|
mixin(parseTypeOrF);
|
|
put( ')' );
|
|
return dst[beg .. $];
|
|
default:
|
|
errStatus = true;
|
|
return dst.bslice_empty;
|
|
}
|
|
case 'A': // TypeArray (A Type)
|
|
popFront();
|
|
mixin(parseTypeOrF);
|
|
put( "[]" );
|
|
return dst[beg .. $];
|
|
case 'G': // TypeStaticArray (G Number Type)
|
|
popFront();
|
|
auto num = sliceNumber();
|
|
mixin(parseTypeOrF);
|
|
put( '[' );
|
|
put( num );
|
|
put( ']' );
|
|
return dst[beg .. $];
|
|
case 'H': // TypeAssocArray (H Type Type)
|
|
popFront();
|
|
// skip t1
|
|
auto tx = parseType(errStatus);
|
|
if (errStatus)
|
|
return dst.bslice_empty;
|
|
mixin(parseTypeOrF);
|
|
put( '[' );
|
|
shift(tx);
|
|
put( ']' );
|
|
return dst[beg .. $];
|
|
case 'P': // TypePointer (P Type)
|
|
popFront();
|
|
mixin(parseTypeOrF);
|
|
put( '*' );
|
|
return dst[beg .. $];
|
|
case 'F': case 'U': case 'W': case 'V': case 'R': // TypeFunction
|
|
auto r = parseTypeFunction(errStatus);
|
|
if (errStatus)
|
|
return dst.bslice_empty;
|
|
return r;
|
|
case 'C': // TypeClass (C LName)
|
|
case 'S': // TypeStruct (S LName)
|
|
case 'E': // TypeEnum (E LName)
|
|
case 'T': // TypeTypedef (T LName)
|
|
popFront();
|
|
parseQualifiedName(errStatus);
|
|
if (errStatus)
|
|
return dst.bslice_empty;
|
|
return dst[beg .. $];
|
|
case 'D': // TypeDelegate (D TypeFunction)
|
|
popFront();
|
|
auto modifiers = parseModifier();
|
|
if ( front == 'Q' )
|
|
{
|
|
string errMsg;
|
|
auto r = parseBackrefType(errMsg, (e_flag) => parseTypeFunction(e_flag, IsDelegate.yes));
|
|
if (errMsg !is null)
|
|
return dst.bslice_empty;
|
|
return r;
|
|
}
|
|
else
|
|
{
|
|
parseTypeFunction(errStatus, IsDelegate.yes);
|
|
if (errStatus)
|
|
return dst.bslice_empty;
|
|
}
|
|
|
|
if (modifiers)
|
|
{
|
|
// write modifiers behind the function arguments
|
|
while (auto str = typeCtors.toStringConsume(modifiers))
|
|
{
|
|
put(' ');
|
|
put(str);
|
|
}
|
|
}
|
|
return dst[beg .. $];
|
|
case 'n': // TypeNone (n)
|
|
popFront();
|
|
// TODO: Anything needed here?
|
|
return dst[beg .. $];
|
|
case 'B': // TypeTuple (B Number Arguments)
|
|
popFront();
|
|
// TODO: Handle this.
|
|
return dst[beg .. $];
|
|
case 'Z': // Internal symbol
|
|
// This 'type' is used for untyped internal symbols, i.e.:
|
|
// __array
|
|
// __init
|
|
// __vtbl
|
|
// __Class
|
|
// __Interface
|
|
// __ModuleInfo
|
|
popFront();
|
|
return dst[beg .. $];
|
|
default:
|
|
if (t >= 'a' && t <= 'w')
|
|
{
|
|
popFront();
|
|
put( primitives[cast(size_t)(t - 'a')] );
|
|
return dst[beg .. $];
|
|
}
|
|
else if (t == 'z')
|
|
{
|
|
popFront();
|
|
switch ( front )
|
|
{
|
|
case 'i':
|
|
popFront();
|
|
put( "cent" );
|
|
return dst[beg .. $];
|
|
case 'k':
|
|
popFront();
|
|
put( "ucent" );
|
|
return dst[beg .. $];
|
|
default:
|
|
errStatus = true;
|
|
return dst.bslice_empty;
|
|
}
|
|
}
|
|
errStatus = true;
|
|
return dst.bslice_empty;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
TypeFunction:
|
|
CallConvention FuncAttrs Arguments ArgClose Type
|
|
|
|
CallConvention:
|
|
F // D
|
|
U // C
|
|
W // Windows
|
|
R // C++
|
|
|
|
FuncAttrs:
|
|
FuncAttr
|
|
FuncAttr FuncAttrs
|
|
|
|
FuncAttr:
|
|
empty
|
|
FuncAttrPure
|
|
FuncAttrNothrow
|
|
FuncAttrProperty
|
|
FuncAttrRef
|
|
FuncAttrReturn
|
|
FuncAttrScope
|
|
FuncAttrTrusted
|
|
FuncAttrSafe
|
|
|
|
FuncAttrPure:
|
|
Na
|
|
|
|
FuncAttrNothrow:
|
|
Nb
|
|
|
|
FuncAttrRef:
|
|
Nc
|
|
|
|
FuncAttrProperty:
|
|
Nd
|
|
|
|
FuncAttrTrusted:
|
|
Ne
|
|
|
|
FuncAttrSafe:
|
|
Nf
|
|
|
|
FuncAttrNogc:
|
|
Ni
|
|
|
|
FuncAttrReturn:
|
|
Nj
|
|
|
|
FuncAttrScope:
|
|
Nl
|
|
|
|
Arguments:
|
|
Argument
|
|
Argument Arguments
|
|
|
|
Argument:
|
|
Argument2
|
|
M Argument2 // scope
|
|
|
|
Argument2:
|
|
Type
|
|
J Type // out
|
|
K Type // ref
|
|
L Type // lazy
|
|
|
|
ArgClose
|
|
X // variadic T t,...) style
|
|
Y // variadic T t...) style
|
|
Z // not variadic
|
|
*/
|
|
void parseCallConvention(out bool errStatus) nothrow
|
|
{
|
|
// CallConvention
|
|
switch ( front )
|
|
{
|
|
case 'F': // D
|
|
popFront();
|
|
break;
|
|
case 'U': // C
|
|
popFront();
|
|
put( "extern (C) " );
|
|
break;
|
|
case 'W': // Windows
|
|
popFront();
|
|
put( "extern (Windows) " );
|
|
break;
|
|
case 'R': // C++
|
|
popFront();
|
|
put( "extern (C++) " );
|
|
break;
|
|
default:
|
|
errStatus = true;
|
|
}
|
|
}
|
|
|
|
/// Returns: Flags of `TypeCtor`
|
|
ushort parseModifier()
|
|
{
|
|
TypeCtor res = TypeCtor.None;
|
|
switch ( front )
|
|
{
|
|
case 'y':
|
|
popFront();
|
|
return TypeCtor.Immutable;
|
|
case 'O':
|
|
popFront();
|
|
res |= TypeCtor.Shared;
|
|
if (front == 'x')
|
|
goto case 'x';
|
|
if (front == 'N')
|
|
goto case 'N';
|
|
return TypeCtor.Shared;
|
|
case 'N':
|
|
if (peek( 1 ) != 'g')
|
|
return res;
|
|
popFront();
|
|
popFront();
|
|
res |= TypeCtor.InOut;
|
|
if ( front == 'x' )
|
|
goto case 'x';
|
|
return res;
|
|
case 'x':
|
|
popFront();
|
|
res |= TypeCtor.Const;
|
|
return res;
|
|
default: return TypeCtor.None;
|
|
}
|
|
}
|
|
|
|
ushort parseFuncAttr(out bool errStatus) nothrow
|
|
{
|
|
// FuncAttrs
|
|
ushort result;
|
|
while ('N' == front)
|
|
{
|
|
popFront();
|
|
switch ( front )
|
|
{
|
|
case 'a': // FuncAttrPure
|
|
popFront();
|
|
result |= FuncAttributes.Pure;
|
|
continue;
|
|
case 'b': // FuncAttrNoThrow
|
|
popFront();
|
|
result |= FuncAttributes.Nothrow;
|
|
continue;
|
|
case 'c': // FuncAttrRef
|
|
popFront();
|
|
result |= FuncAttributes.Ref;
|
|
continue;
|
|
case 'd': // FuncAttrProperty
|
|
popFront();
|
|
result |= FuncAttributes.Property;
|
|
continue;
|
|
case 'e': // FuncAttrTrusted
|
|
popFront();
|
|
result |= FuncAttributes.Trusted;
|
|
continue;
|
|
case 'f': // FuncAttrSafe
|
|
popFront();
|
|
result |= FuncAttributes.Safe;
|
|
continue;
|
|
case 'g':
|
|
case 'h':
|
|
case 'k':
|
|
case 'n':
|
|
// NOTE: The inout parameter type is represented as "Ng".
|
|
// The vector parameter type is represented as "Nh".
|
|
// The return parameter type is represented as "Nk".
|
|
// The noreturn parameter type is represented as "Nn".
|
|
// These make it look like a FuncAttr, but infact
|
|
// if we see these, then we know we're really in
|
|
// the parameter list. Rewind and break.
|
|
pos--;
|
|
return result;
|
|
case 'i': // FuncAttrNogc
|
|
popFront();
|
|
result |= FuncAttributes.NoGC;
|
|
continue;
|
|
case 'j': // FuncAttrReturn
|
|
popFront();
|
|
if (this.peek(0) == 'N' && this.peek(1) == 'l')
|
|
{
|
|
result |= FuncAttributes.ReturnScope;
|
|
popFront();
|
|
popFront();
|
|
} else {
|
|
result |= FuncAttributes.Return;
|
|
}
|
|
continue;
|
|
case 'l': // FuncAttrScope
|
|
popFront();
|
|
if (this.peek(0) == 'N' && this.peek(1) == 'j')
|
|
{
|
|
result |= FuncAttributes.ScopeReturn;
|
|
popFront();
|
|
popFront();
|
|
} else {
|
|
result |= FuncAttributes.Scope;
|
|
}
|
|
continue;
|
|
case 'm': // FuncAttrLive
|
|
popFront();
|
|
result |= FuncAttributes.Live;
|
|
continue;
|
|
default:
|
|
errStatus = true;
|
|
return 0;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void parseFuncArguments(out bool errStatus) scope nothrow
|
|
{
|
|
// Arguments
|
|
for ( size_t n = 0; true; n++ )
|
|
{
|
|
switch ( front )
|
|
{
|
|
case 'X': // ArgClose (variadic T t...) style)
|
|
popFront();
|
|
put( "..." );
|
|
return;
|
|
case 'Y': // ArgClose (variadic T t,...) style)
|
|
popFront();
|
|
put( ", ..." );
|
|
return;
|
|
case 'Z': // ArgClose (not variadic)
|
|
popFront();
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
putComma(n);
|
|
|
|
/* Do special return, scope, ref, out combinations
|
|
*/
|
|
int npops;
|
|
if ( 'M' == front && peek(1) == 'N' && peek(2) == 'k')
|
|
{
|
|
const c3 = peek(3);
|
|
if (c3 == 'J')
|
|
{
|
|
put("scope return out "); // MNkJ
|
|
npops = 4;
|
|
}
|
|
else if (c3 == 'K')
|
|
{
|
|
put("scope return ref "); // MNkK
|
|
npops = 4;
|
|
}
|
|
}
|
|
else if ('N' == front && peek(1) == 'k')
|
|
{
|
|
const c2 = peek(2);
|
|
if (c2 == 'J')
|
|
{
|
|
put("return out "); // NkJ
|
|
npops = 3;
|
|
}
|
|
else if (c2 == 'K')
|
|
{
|
|
put("return ref "); // NkK
|
|
npops = 3;
|
|
}
|
|
else if (c2 == 'M')
|
|
{
|
|
const c3 = peek(3);
|
|
if (c3 == 'J')
|
|
{
|
|
put("return scope out "); // NkMJ
|
|
npops = 4;
|
|
}
|
|
else if (c3 == 'K')
|
|
{
|
|
put("return scope ref "); // NkMK
|
|
npops = 4;
|
|
}
|
|
else
|
|
{
|
|
put("return scope "); // NkM
|
|
npops = 3;
|
|
}
|
|
}
|
|
}
|
|
popFront(npops);
|
|
|
|
if ( 'M' == front )
|
|
{
|
|
popFront();
|
|
put( "scope " );
|
|
}
|
|
if ( 'N' == front )
|
|
{
|
|
popFront();
|
|
if ( 'k' == front ) // Return (Nk Parameter2)
|
|
{
|
|
popFront();
|
|
put( "return " );
|
|
}
|
|
else
|
|
pos--;
|
|
}
|
|
|
|
// call parseType() and return error if occured
|
|
enum parseTypeOrF = "parseType(errStatus); if (errStatus) return;";
|
|
|
|
switch ( front )
|
|
{
|
|
case 'I': // in (I Type)
|
|
popFront();
|
|
put("in ");
|
|
if (front == 'K')
|
|
goto case;
|
|
mixin(parseTypeOrF);
|
|
continue;
|
|
case 'K': // ref (K Type)
|
|
popFront();
|
|
put( "ref " );
|
|
mixin(parseTypeOrF);
|
|
continue;
|
|
case 'J': // out (J Type)
|
|
popFront();
|
|
put( "out " );
|
|
mixin(parseTypeOrF);
|
|
continue;
|
|
case 'L': // lazy (L Type)
|
|
popFront();
|
|
put( "lazy " );
|
|
mixin(parseTypeOrF);
|
|
continue;
|
|
default:
|
|
mixin(parseTypeOrF);
|
|
}
|
|
}
|
|
}
|
|
|
|
enum IsDelegate { no, yes }
|
|
|
|
/*
|
|
TypeFunction:
|
|
CallConvention FuncAttrs Arguments ArgClose Type
|
|
*/
|
|
BufSlice parseTypeFunction(out bool errStatus, IsDelegate isdg = IsDelegate.no) return scope nothrow
|
|
{
|
|
auto beg = dst.length;
|
|
|
|
parseCallConvention(errStatus);
|
|
if (errStatus)
|
|
return dst.bslice_empty;
|
|
|
|
auto attributes = parseFuncAttr(errStatus);
|
|
if (errStatus)
|
|
return dst.bslice_empty;
|
|
|
|
auto argbeg = dst.length;
|
|
put(IsDelegate.yes == isdg ? "delegate" : "function");
|
|
put( '(' );
|
|
parseFuncArguments(errStatus);
|
|
if (errStatus)
|
|
return dst.bslice_empty;
|
|
put( ')' );
|
|
if (attributes)
|
|
{
|
|
// write function attributes behind arguments
|
|
while (auto str = funcAttrs.toStringConsume(attributes))
|
|
{
|
|
put(' ');
|
|
put(str);
|
|
}
|
|
}
|
|
|
|
// A function / delegate return type is located at the end of its mangling
|
|
// Write it in order, then shift it back to 'code order'
|
|
// e.g. `delegate(int) @safedouble ' => 'double delegate(int) @safe'
|
|
{
|
|
auto retbeg = dst.length;
|
|
parseType(errStatus);
|
|
if (errStatus)
|
|
return dst.bslice_empty;
|
|
put(' ');
|
|
shift(dst[argbeg .. retbeg]);
|
|
}
|
|
|
|
return dst[beg .. $];
|
|
}
|
|
|
|
static bool isCallConvention( char ch )
|
|
{
|
|
switch ( ch )
|
|
{
|
|
case 'F', 'U', 'V', 'W', 'R':
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
Value:
|
|
n
|
|
Number
|
|
i Number
|
|
N Number
|
|
e HexFloat
|
|
c HexFloat c HexFloat
|
|
A Number Value...
|
|
|
|
HexFloat:
|
|
NAN
|
|
INF
|
|
NINF
|
|
N HexDigits P Exponent
|
|
HexDigits P Exponent
|
|
|
|
Exponent:
|
|
N Number
|
|
Number
|
|
|
|
HexDigits:
|
|
HexDigit
|
|
HexDigit HexDigits
|
|
|
|
HexDigit:
|
|
Digit
|
|
A
|
|
B
|
|
C
|
|
D
|
|
E
|
|
F
|
|
*/
|
|
|
|
void parseValue(out bool errStatus) scope nothrow
|
|
{
|
|
parseValue(errStatus, dst.bslice_empty);
|
|
}
|
|
|
|
void parseValue(out bool errStatus, scope BufSlice name, char type = '\0' ) scope nothrow
|
|
{
|
|
void onError()
|
|
{
|
|
errStatus = true;
|
|
}
|
|
|
|
switch ( front )
|
|
{
|
|
case 'n':
|
|
popFront();
|
|
put( "null" );
|
|
return;
|
|
case 'i':
|
|
popFront();
|
|
if ('0' > front || '9' < front)
|
|
return onError(); // Number expected
|
|
goto case;
|
|
case '0': .. case '9':
|
|
parseIntegerValue( errStatus, name, type );
|
|
return;
|
|
case 'N':
|
|
popFront();
|
|
put( '-' );
|
|
parseIntegerValue( errStatus, name, type );
|
|
return;
|
|
case 'e':
|
|
popFront();
|
|
parseReal(errStatus);
|
|
return;
|
|
case 'c':
|
|
popFront();
|
|
parseReal(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
put( '+' );
|
|
if (!match('c'))
|
|
return onError();
|
|
parseReal(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
put( 'i' );
|
|
return;
|
|
case 'a': case 'w': case 'd':
|
|
char t = front;
|
|
popFront();
|
|
auto n = decodeNumber(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
if (!match('_'))
|
|
return onError();
|
|
put( '"' );
|
|
foreach (i; 0..n)
|
|
{
|
|
auto a = ascii2hex( errStatus, front );
|
|
if (errStatus)
|
|
return;
|
|
popFront();
|
|
|
|
auto b = ascii2hex( errStatus, front );
|
|
if (errStatus)
|
|
return;
|
|
popFront();
|
|
|
|
auto v = cast(char)((a << 4) | b);
|
|
if (' ' <= v && v <= '~') // ASCII printable
|
|
{
|
|
put(v);
|
|
}
|
|
else
|
|
{
|
|
put("\\x");
|
|
putAsHex(v, 2);
|
|
}
|
|
}
|
|
put( '"' );
|
|
if ( 'a' != t )
|
|
put(t);
|
|
return;
|
|
case 'A':
|
|
// NOTE: This is kind of a hack. An associative array literal
|
|
// [1:2, 3:4] is represented as HiiA2i1i2i3i4, so the type
|
|
// is "Hii" and the value is "A2i1i2i3i4". Thus the only
|
|
// way to determine that this is an AA value rather than an
|
|
// array value is for the caller to supply the type char.
|
|
// Hopefully, this will change so that the value is
|
|
// "H2i1i2i3i4", rendering this unnecesary.
|
|
if ( 'H' == type )
|
|
goto LassocArray;
|
|
// A Number Value...
|
|
// An array literal. Value is repeated Number times.
|
|
popFront();
|
|
put( '[' );
|
|
auto n = decodeNumber(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
foreach ( i; 0 .. n )
|
|
{
|
|
putComma(i);
|
|
parseValue(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
}
|
|
put( ']' );
|
|
return;
|
|
case 'H':
|
|
LassocArray:
|
|
// H Number Value...
|
|
// An associative array literal. Value is repeated 2*Number times.
|
|
popFront();
|
|
put( '[' );
|
|
auto n = decodeNumber(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
foreach ( i; 0 .. n )
|
|
{
|
|
putComma(i);
|
|
parseValue(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
put(':');
|
|
parseValue(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
}
|
|
put( ']' );
|
|
return;
|
|
case 'S':
|
|
// S Number Value...
|
|
// A struct literal. Value is repeated Number times.
|
|
popFront();
|
|
if ( name.length )
|
|
put( name );
|
|
put( '(' );
|
|
auto n = decodeNumber(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
foreach ( i; 0 .. n )
|
|
{
|
|
putComma(i);
|
|
parseValue(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
}
|
|
put( ')' );
|
|
return;
|
|
case 'f':
|
|
// f MangledName
|
|
// A function literal symbol
|
|
popFront();
|
|
parseMangledName(errStatus, false, 1);
|
|
return;
|
|
default:
|
|
errStatus = true;
|
|
}
|
|
}
|
|
|
|
void parseIntegerValue( out bool errStatus, scope BufSlice name, char type = '\0' ) scope nothrow
|
|
{
|
|
switch ( type )
|
|
{
|
|
case 'a': // char
|
|
case 'u': // wchar
|
|
case 'w': // dchar
|
|
{
|
|
auto val = sliceNumber();
|
|
auto num = decodeNumber( errStatus, val );
|
|
if (errStatus)
|
|
return;
|
|
|
|
switch ( num )
|
|
{
|
|
case '\'':
|
|
put( "'\\''" );
|
|
return;
|
|
// \", \?
|
|
case '\\':
|
|
put( "'\\\\'" );
|
|
return;
|
|
case '\a':
|
|
put( "'\\a'" );
|
|
return;
|
|
case '\b':
|
|
put( "'\\b'" );
|
|
return;
|
|
case '\f':
|
|
put( "'\\f'" );
|
|
return;
|
|
case '\n':
|
|
put( "'\\n'" );
|
|
return;
|
|
case '\r':
|
|
put( "'\\r'" );
|
|
return;
|
|
case '\t':
|
|
put( "'\\t'" );
|
|
return;
|
|
case '\v':
|
|
put( "'\\v'" );
|
|
return;
|
|
default:
|
|
switch ( type )
|
|
{
|
|
case 'a':
|
|
if ( num >= 0x20 && num < 0x7F )
|
|
{
|
|
put( '\'' );
|
|
put( cast(char)num );
|
|
put( '\'' );
|
|
return;
|
|
}
|
|
put( "\\x" );
|
|
putAsHex( num, 2 );
|
|
return;
|
|
case 'u':
|
|
put( "'\\u" );
|
|
putAsHex( num, 4 );
|
|
put( '\'' );
|
|
return;
|
|
case 'w':
|
|
put( "'\\U" );
|
|
putAsHex( num, 8 );
|
|
put( '\'' );
|
|
return;
|
|
default:
|
|
assert( 0 );
|
|
}
|
|
}
|
|
}
|
|
case 'b': // bool
|
|
auto d = decodeNumber(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
put( d ? "true" : "false" );
|
|
return;
|
|
case 'h', 't', 'k': // ubyte, ushort, uint
|
|
put( sliceNumber() );
|
|
put( 'u' );
|
|
return;
|
|
case 'l': // long
|
|
put( sliceNumber() );
|
|
put( 'L' );
|
|
return;
|
|
case 'm': // ulong
|
|
put( sliceNumber() );
|
|
put( "uL" );
|
|
return;
|
|
default:
|
|
put( sliceNumber() );
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
TemplateArgs:
|
|
TemplateArg
|
|
TemplateArg TemplateArgs
|
|
|
|
TemplateArg:
|
|
TemplateArgX
|
|
H TemplateArgX
|
|
|
|
TemplateArgX:
|
|
T Type
|
|
V Type Value
|
|
S Number_opt QualifiedName
|
|
X ExternallyMangledName
|
|
*/
|
|
void parseTemplateArgs(out bool errStatus) scope nothrow
|
|
{
|
|
L_nextArg:
|
|
for ( size_t n = 0; true; n++ )
|
|
{
|
|
if ( front == 'H' )
|
|
popFront();
|
|
|
|
switch ( front )
|
|
{
|
|
case 'T':
|
|
popFront();
|
|
putComma(n);
|
|
parseType(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
continue;
|
|
case 'V':
|
|
popFront();
|
|
putComma(n);
|
|
// NOTE: In the few instances where the type is actually
|
|
// desired in the output it should precede the value
|
|
// generated by parseValue, so it is safe to simply
|
|
// decrement len and let put/append do its thing.
|
|
char t = front; // peek at type for parseValue
|
|
if ( t == 'Q' )
|
|
{
|
|
t = peekBackref();
|
|
if (t == 0)
|
|
{
|
|
// invalid back reference
|
|
errStatus = true;
|
|
return;
|
|
}
|
|
}
|
|
BufSlice name = dst.bslice_empty;
|
|
silent( errStatus, delegate void(out bool e_flg) nothrow { name = parseType(e_flg); } );
|
|
if (errStatus)
|
|
return;
|
|
parseValue( errStatus, name, t );
|
|
if (errStatus)
|
|
return;
|
|
continue;
|
|
case 'S':
|
|
popFront();
|
|
putComma(n);
|
|
|
|
if ( mayBeMangledNameArg() )
|
|
{
|
|
auto l = dst.length;
|
|
auto p = pos;
|
|
auto b = brp;
|
|
|
|
if (parseMangledNameArg())
|
|
continue;
|
|
dst.len = l;
|
|
pos = p;
|
|
brp = b;
|
|
}
|
|
if ( isDigit( front ) && isDigit( peek( 1 ) ) )
|
|
{
|
|
// ambiguity: length followed by qualified name (starting with number)
|
|
// try all possible pairs of numbers
|
|
auto qlen = decodeNumber(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
|
|
qlen /= 10; // last digit needed for QualifiedName
|
|
pos--;
|
|
auto l = dst.length;
|
|
auto p = pos;
|
|
auto b = brp;
|
|
while ( qlen > 0 )
|
|
{
|
|
errStatus = false;
|
|
parseQualifiedName(errStatus);
|
|
|
|
if (!errStatus)
|
|
{
|
|
if ( pos == p + qlen )
|
|
continue L_nextArg;
|
|
}
|
|
|
|
qlen /= 10; // retry with one digit less
|
|
pos = --p;
|
|
dst.len = l;
|
|
brp = b;
|
|
}
|
|
}
|
|
|
|
parseQualifiedName(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
continue;
|
|
case 'X':
|
|
popFront();
|
|
putComma(n);
|
|
{
|
|
string errMsg;
|
|
parseLName(errMsg);
|
|
if (errMsg)
|
|
return;
|
|
}
|
|
continue;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool mayBeMangledNameArg() nothrow
|
|
{
|
|
bool errStatus;
|
|
auto p = pos;
|
|
scope(exit) pos = p;
|
|
|
|
if ( isDigit( buf[pos] ) )
|
|
{
|
|
auto n = decodeNumber(errStatus);
|
|
|
|
return !errStatus && n >= 4 &&
|
|
pos < buf.length && '_' == buf[pos++] &&
|
|
pos < buf.length && 'D' == buf[pos++] &&
|
|
isDigit( buf[pos] );
|
|
}
|
|
else
|
|
{
|
|
const isSNF = isSymbolNameFront(errStatus);
|
|
|
|
return !errStatus &&
|
|
pos < buf.length && '_' == buf[pos++] &&
|
|
pos < buf.length && 'D' == buf[pos++] &&
|
|
isSNF;
|
|
}
|
|
}
|
|
|
|
bool parseMangledNameArg() nothrow
|
|
{
|
|
bool errStatus;
|
|
|
|
size_t n = 0;
|
|
if ( isDigit( front ) )
|
|
{
|
|
n = decodeNumber(errStatus);
|
|
|
|
if (errStatus)
|
|
return false;
|
|
}
|
|
|
|
parseMangledName(errStatus, false, n );
|
|
|
|
return !errStatus;
|
|
}
|
|
|
|
/*
|
|
TemplateInstanceName:
|
|
Number __T LName TemplateArgs Z
|
|
*/
|
|
void parseTemplateInstanceName(out bool errStatus, bool hasNumber) scope nothrow
|
|
{
|
|
auto sav = pos;
|
|
auto saveBrp = brp;
|
|
|
|
void onError()
|
|
{
|
|
errStatus = true;
|
|
pos = sav;
|
|
brp = saveBrp;
|
|
}
|
|
|
|
size_t n = 0;
|
|
if (hasNumber)
|
|
{
|
|
n = decodeNumber(errStatus);
|
|
if (errStatus)
|
|
return onError();
|
|
}
|
|
|
|
auto beg = pos;
|
|
errStatus = !match( "__T" );
|
|
if (errStatus)
|
|
return onError();
|
|
|
|
{
|
|
string errMsg;
|
|
parseLName(errMsg);
|
|
if (errMsg !is null)
|
|
return onError();
|
|
}
|
|
|
|
put( "!(" );
|
|
|
|
parseTemplateArgs(errStatus);
|
|
if (errStatus)
|
|
return onError();
|
|
|
|
if (!match('Z'))
|
|
return onError();
|
|
|
|
if ( hasNumber && pos - beg != n )
|
|
{
|
|
// Template name length mismatch
|
|
return onError();
|
|
}
|
|
|
|
put( ')' );
|
|
}
|
|
|
|
|
|
bool mayBeTemplateInstanceName() scope nothrow
|
|
{
|
|
auto p = pos;
|
|
scope(exit) pos = p;
|
|
|
|
bool errStatus;
|
|
auto n = decodeNumber(errStatus);
|
|
if (errStatus)
|
|
return false;
|
|
|
|
return n >= 5 &&
|
|
pos < buf.length && '_' == buf[pos++] &&
|
|
pos < buf.length && '_' == buf[pos++] &&
|
|
pos < buf.length && 'T' == buf[pos++];
|
|
}
|
|
|
|
|
|
/*
|
|
SymbolName:
|
|
LName
|
|
TemplateInstanceName
|
|
*/
|
|
void parseSymbolName(out bool errStatus) scope nothrow
|
|
{
|
|
// LName -> Number
|
|
// TemplateInstanceName -> Number "__T"
|
|
switch ( front )
|
|
{
|
|
case '_':
|
|
// no length encoding for templates for new mangling
|
|
parseTemplateInstanceName(errStatus, false);
|
|
return;
|
|
|
|
case '0': .. case '9':
|
|
if ( mayBeTemplateInstanceName() )
|
|
{
|
|
auto t = dst.length;
|
|
|
|
parseTemplateInstanceName(errStatus, true);
|
|
if (!errStatus)
|
|
return;
|
|
else
|
|
{
|
|
dst.len = t;
|
|
}
|
|
}
|
|
goto case;
|
|
case 'Q':
|
|
string errMsg;
|
|
parseLName(errMsg);
|
|
errStatus = errMsg !is null;
|
|
return;
|
|
default:
|
|
errStatus = true;
|
|
}
|
|
}
|
|
|
|
// parse optional function arguments as part of a symbol name, i.e without return type
|
|
// if keepAttr, the calling convention and function attributes are not discarded, but returned
|
|
BufSlice parseFunctionTypeNoReturn( bool keepAttr = false ) return scope nothrow
|
|
{
|
|
// try to demangle a function, in case we are pointing to some function local
|
|
auto prevpos = pos;
|
|
auto prevlen = dst.length;
|
|
auto prevbrp = brp;
|
|
|
|
if ( 'M' == front )
|
|
{
|
|
// do not emit "needs this"
|
|
popFront();
|
|
auto modifiers = parseModifier();
|
|
while (auto str = typeCtors.toStringConsume(modifiers))
|
|
{
|
|
put(str);
|
|
put(' ');
|
|
}
|
|
}
|
|
if ( isCallConvention( front ) )
|
|
{
|
|
BufSlice attr = dst.bslice_empty;
|
|
// we don't want calling convention and attributes in the qualified name
|
|
bool errStatus;
|
|
parseCallConvention(errStatus);
|
|
if (!errStatus)
|
|
{
|
|
auto attributes = parseFuncAttr(errStatus);
|
|
if (!errStatus)
|
|
{
|
|
if (keepAttr) {
|
|
while (auto str = funcAttrs.toStringConsume(attributes))
|
|
{
|
|
put(str);
|
|
put(' ');
|
|
}
|
|
attr = dst[prevlen .. $];
|
|
}
|
|
|
|
put( '(' );
|
|
parseFuncArguments(errStatus);
|
|
if (errStatus)
|
|
return dst.bslice_empty;
|
|
put( ')' );
|
|
return attr;
|
|
}
|
|
}
|
|
|
|
// not part of a qualified name, so back up
|
|
pos = prevpos;
|
|
dst.len = prevlen;
|
|
brp = prevbrp;
|
|
}
|
|
|
|
return dst.bslice_empty;
|
|
}
|
|
|
|
/*
|
|
QualifiedName:
|
|
SymbolName
|
|
SymbolName QualifiedName
|
|
*/
|
|
void parseQualifiedName(out bool errStatus) return scope nothrow
|
|
{
|
|
size_t n = 0;
|
|
bool is_sym_name_front;
|
|
|
|
do
|
|
{
|
|
if ( n++ )
|
|
put( '.' );
|
|
|
|
parseSymbolName(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
|
|
parseFunctionTypeNoReturn();
|
|
|
|
is_sym_name_front = isSymbolNameFront(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
|
|
} while ( is_sym_name_front );
|
|
}
|
|
|
|
|
|
/*
|
|
MangledName:
|
|
_D QualifiedName Type
|
|
_D QualifiedName M Type
|
|
*/
|
|
void parseMangledName( out bool errStatus, bool displayType, size_t n = 0 ) scope nothrow
|
|
{
|
|
BufSlice name = dst.bslice_empty;
|
|
|
|
auto end = pos + n;
|
|
|
|
eat( '_' );
|
|
errStatus = !match( 'D' );
|
|
if (errStatus)
|
|
return;
|
|
|
|
do
|
|
{
|
|
size_t beg = dst.length;
|
|
size_t nameEnd = dst.length;
|
|
BufSlice attr = dst.bslice_empty;
|
|
bool is_sym_name_front;
|
|
|
|
do
|
|
{
|
|
if ( attr.length )
|
|
dst.remove(attr); // dump attributes of parent symbols
|
|
if (beg != dst.length)
|
|
put( '.' );
|
|
|
|
parseSymbolName(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
|
|
nameEnd = dst.length;
|
|
attr = parseFunctionTypeNoReturn( displayType );
|
|
|
|
is_sym_name_front = isSymbolNameFront(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
} while (is_sym_name_front);
|
|
|
|
if ( displayType )
|
|
{
|
|
attr = shift( attr );
|
|
nameEnd = dst.length - attr.length; // name includes function arguments
|
|
}
|
|
name = dst[beg .. nameEnd];
|
|
|
|
if ( 'M' == front )
|
|
popFront(); // has 'this' pointer
|
|
|
|
auto lastlen = dst.length;
|
|
auto type = parseType(errStatus);
|
|
if (errStatus)
|
|
return;
|
|
|
|
if ( displayType )
|
|
{
|
|
if ( type.length )
|
|
put( ' ' );
|
|
// sort (name,attr,type) -> (attr,type,name)
|
|
shift( name );
|
|
}
|
|
else
|
|
{
|
|
// remove type
|
|
assert( attr.length == 0 );
|
|
dst.len = lastlen;
|
|
}
|
|
if ( pos >= buf.length || (n != 0 && pos >= end) )
|
|
return;
|
|
|
|
switch ( front )
|
|
{
|
|
case 'T': // terminators when used as template alias parameter
|
|
case 'V':
|
|
case 'S':
|
|
case 'Z':
|
|
return;
|
|
default:
|
|
}
|
|
put( '.' );
|
|
|
|
} while ( true );
|
|
}
|
|
|
|
void parseMangledName(out bool errStatus) nothrow
|
|
{
|
|
parseMangledName(errStatus, AddType.yes == addType);
|
|
}
|
|
|
|
char[] doDemangle(alias FUNC)() return scope nothrow
|
|
{
|
|
while ( true )
|
|
{
|
|
bool errStatus;
|
|
FUNC(errStatus);
|
|
if (!errStatus)
|
|
{
|
|
return dst[0 .. $].getSlice;
|
|
}
|
|
else
|
|
{
|
|
return dst.copyInput(buf);
|
|
}
|
|
}
|
|
}
|
|
|
|
char[] demangleName() nothrow
|
|
{
|
|
return doDemangle!parseMangledName();
|
|
}
|
|
|
|
char[] demangleType() nothrow
|
|
{
|
|
return doDemangle!parseType();
|
|
}
|
|
}
|
|
|
|
private struct Buffer
|
|
{
|
|
enum size_t minSize = 4000;
|
|
|
|
@safe pure:
|
|
|
|
private char[] dst;
|
|
private size_t len;
|
|
|
|
public alias opDollar = len;
|
|
|
|
public size_t length () const scope @safe pure nothrow @nogc
|
|
{
|
|
return this.len;
|
|
}
|
|
|
|
public BufSlice opSlice (size_t from, size_t to)
|
|
return scope @safe pure nothrow @nogc
|
|
{
|
|
return bslice(from, to);
|
|
}
|
|
|
|
static bool contains(scope const(char)[] a, scope const BufSlice b) @safe nothrow
|
|
{
|
|
return
|
|
b.from < a.length &&
|
|
b.to <= a.length;
|
|
}
|
|
|
|
char[] copyInput(scope const(char)[] buf)
|
|
return scope nothrow
|
|
{
|
|
if (dst.length < buf.length)
|
|
dst.length = buf.length;
|
|
char[] r = dst[0 .. buf.length];
|
|
r[] = buf[];
|
|
return r;
|
|
}
|
|
|
|
private void checkAndStretchBuf(size_t len_to_add) scope nothrow
|
|
{
|
|
const required = len + len_to_add;
|
|
|
|
if (required > dst.length)
|
|
dst.length = dst.length + len_to_add;
|
|
}
|
|
|
|
// move val to the end of the dst buffer
|
|
BufSlice shift(scope const BufSlice val) return scope nothrow
|
|
{
|
|
version (DigitalMars) pragma(inline, false); // tame dmd inliner
|
|
|
|
if (val.length)
|
|
{
|
|
const ptrdiff_t s = val.from;
|
|
const size_t f = len;
|
|
|
|
assert(contains( dst[0 .. len], val ),
|
|
"\ndst=\""~dst[0 .. len]~"\"\n"~
|
|
"val=\""~val.getSlice~"\"\n"
|
|
);
|
|
|
|
checkAndStretchBuf(val.length);
|
|
|
|
// store value temporary over len index
|
|
dst[len .. len + val.length] = val.getSlice();
|
|
|
|
// shift all chars including temporary saved above
|
|
// if buf was allocated above it will be leave for further usage
|
|
for (size_t p = s; p < f; p++)
|
|
dst[p] = dst[p + val.length];
|
|
|
|
return bslice(len - val.length, len);
|
|
}
|
|
|
|
return bslice_empty;
|
|
}
|
|
|
|
// remove val from dst buffer
|
|
void remove(scope BufSlice val) scope nothrow
|
|
{
|
|
version (DigitalMars) pragma(inline, false); // tame dmd inliner
|
|
|
|
if ( val.length )
|
|
{
|
|
assert( contains( dst[0 .. len], val ) );
|
|
|
|
assert( len >= val.length && len <= dst.length );
|
|
len -= val.length;
|
|
for (size_t p = val.from; p < len; p++)
|
|
dst[p] = dst[p + val.length];
|
|
}
|
|
}
|
|
|
|
void append(scope const(char)[] val) scope nothrow
|
|
{
|
|
version (DigitalMars) pragma(inline, false); // tame dmd inliner
|
|
|
|
if (val.length)
|
|
{
|
|
if ( !dst.length )
|
|
dst.length = minSize;
|
|
|
|
checkAndStretchBuf(val.length);
|
|
|
|
// data is already not in place?
|
|
if ( &dst[len] != &val[0] )
|
|
dst[len .. len + val.length] = val[];
|
|
|
|
len += val.length;
|
|
}
|
|
}
|
|
|
|
@nogc:
|
|
|
|
private scope bslice(size_t from, size_t to) nothrow
|
|
{
|
|
return BufSlice(dst, from, to);
|
|
}
|
|
|
|
private static scope bslice_empty() nothrow
|
|
{
|
|
return BufSlice.init;
|
|
}
|
|
}
|
|
|
|
private struct BufSlice
|
|
{
|
|
char[] buf;
|
|
size_t from;
|
|
size_t to;
|
|
|
|
@safe pure nothrow:
|
|
|
|
@disable this();
|
|
|
|
this(return scope char[] buf) scope nothrow @nogc
|
|
{
|
|
this(buf, 0, 0);
|
|
}
|
|
|
|
this(return scope char[] buf, size_t from, size_t to, bool lastArgIsLen = false) scope nothrow @nogc
|
|
{
|
|
this.buf = buf;
|
|
this.from = from;
|
|
|
|
if (lastArgIsLen)
|
|
this.to = from + to;
|
|
else
|
|
this.to = to;
|
|
}
|
|
|
|
invariant
|
|
{
|
|
if (buf is null)
|
|
{
|
|
assert(from == 0);
|
|
assert(to == 0);
|
|
}
|
|
|
|
assert(from <= to);
|
|
}
|
|
|
|
auto getSlice() inout nothrow scope { return buf[from .. to]; }
|
|
size_t length() const scope { return to - from; }
|
|
}
|
|
|
|
/**
|
|
* Demangles a D mangled type.
|
|
*
|
|
* Params:
|
|
* buf = The string to demangle.
|
|
* dst = An optional destination buffer.
|
|
*
|
|
* Returns:
|
|
* The demangled type name or the original string if the name is not a
|
|
* mangled D type.
|
|
*/
|
|
char[] demangleType( const(char)[] buf, char[] dst = null ) nothrow pure @safe
|
|
{
|
|
auto d = Demangle!()(buf, dst);
|
|
return d.demangleType();
|
|
}
|
|
|
|
extern (C) private
|
|
{
|
|
pure @trusted @nogc nothrow pragma(mangle, "fakePureReprintReal") void pureReprintReal(char[] nptr);
|
|
|
|
void fakePureReprintReal(char[] nptr)
|
|
{
|
|
import core.stdc.stdlib : strtold;
|
|
import core.stdc.stdio : snprintf;
|
|
import core.stdc.errno : errno;
|
|
|
|
const err = errno;
|
|
real val = strtold(nptr.ptr, null);
|
|
snprintf(nptr.ptr, nptr.length, "%#Lg", val);
|
|
errno = err;
|
|
}
|
|
}
|