There is a way but it's horrible. You can take the `.stringof`
and parse the result. I knocked this up for something but it's
not well tested and there are probably templates that it handles
incorrectly. I'm not claiming this is any good, I just happened
to have it.
```d
enum TemplateParameterType { valueType, aliasType, typeType,
thisType, sequenceType }
public struct TemplateParameter
{
TemplateParameterType type;
string name;
string defaultValue;
}
public auto templateParameters(alias T)()
{
static assert(__traits(isTemplate, T));
import std.algorithm : startsWith;
import std.array : appender;
import std.string : strip;
auto results = appender!(TemplateParameter[]);
auto isMissing = false;
const templateSignature = T.stringof;
const allParametersStart =
findExpectedSource(templateSignature,
'(', isMissing) + 1;
const allParametersEnd = allParametersStart +
findExpectedSource(templateSignature[allParametersStart .. $],
')', isMissing);
if (isMissing)
throw new Exception("{Expected `(` and `)` in template
signature: `" ~ templateSignature ~ "`.");
auto parametersText = templateSignature[allParametersStart ..
allParametersEnd];
auto parameterIndex = -1;
mainParameterLoop:
while (parametersText.length > 0)
{
parameterIndex++;
auto parameterEnd = findExpectedSource(parametersText,
',', isMissing);
if (isMissing)
parameterEnd = parametersText.length;
auto parameterRemainingText = parametersText[0 ..
parameterEnd];
if (parameterEnd < parametersText.length)
parametersText = parametersText[parameterEnd + 1 ..
$];
else
parametersText = "";
auto token = parameterRemainingText.consumeToken;
if (parameterRemainingText.startsWith("..."))
{
results.put(TemplateParameter(TemplateParameterType.sequenceType,
token, ""));
continue mainParameterLoop;
}
auto nextToken = parameterRemainingText.consumeToken;
if (nextToken == "")
{
results.put(TemplateParameter(TemplateParameterType.typeType,
token, ""));
continue mainParameterLoop;
}
TemplateParameterType type;
if (token == "this")
type = TemplateParameterType.thisType;
else if (token == "alias")
type = TemplateParameterType.aliasType;
else if (nextToken == ":" || nextToken == "=")
type = TemplateParameterType.typeType;
else
type = TemplateParameterType.valueType;
if (type != TemplateParameterType.typeType)
{
token = nextToken;
nextToken = parameterRemainingText.consumeToken;
}
while (true)
{
if (nextToken == "")
{
results.put(TemplateParameter(type, token, ""));
continue mainParameterLoop;
}
if (nextToken == ":")
{
const identifierName = token;
while (true)
{
token = parameterRemainingText.consumeToken;
if (token.length == 0)
{
results.put(TemplateParameter(type,
identifierName, ""));
continue mainParameterLoop;
}
else if (token == "=")
{
results.put(TemplateParameter(type,
identifierName, parameterRemainingText.strip));
continue mainParameterLoop;
}
}
results.put(TemplateParameter(type,
identifierName, parameterRemainingText.strip));
continue mainParameterLoop;
}
if (nextToken == "=")
{
const identifierName = token;
results.put(TemplateParameter(type,
identifierName, parameterRemainingText.strip));
continue mainParameterLoop;
}
token = nextToken;
import std.conv : to;
if (token.length == 0)
throw new Exception("Cannot parse parameter " ~
parameterIndex.to!string ~ " in template `" ~ templateSignature ~
"`.");
nextToken = parameterRemainingText.consumeToken;
}
}
return results[];
}
private auto findExpectedCharacter(string source, char character,
out bool isMissing)
{
foreach (offset; 0 .. source.length)
if (source[offset] == character)
return offset;
isMissing = true;
return 0;
}
private auto findExpectedText(string source, string text, out
bool isMissing)
{
import std.algorithm : startsWith;
foreach (offset; 0 .. source.length)
if (source[offset .. $].startsWith(text))
return offset;
isMissing = true;
return 0;
}
private auto findExpectedSource(string source, char character,
out bool isMissing)
{
auto offset = 0L;
while (true)
{
if (offset >= source.length)
isMissing = true;
else if (source[offset] == character)
return offset;
else if (source[offset] == '(')
offset += findExpectedSource(source[offset + 1 .. $],
')', isMissing) + 1;
else if (source[offset] == '[')
offset += findExpectedSource(source[offset + 1 .. $],
']', isMissing) + 1;
else if (source[offset] == '{')
offset += findExpectedSource(source[offset + 1 .. $],
'}', isMissing) + 1;
else if (source[offset] == '"')
offset += findExpectedCharacter(source[offset + 1 ..
$], '"', isMissing) + 1;
else if (source[offset] == '\'')
offset += findExpectedCharacter(source[offset + 1 ..
$], '\'', isMissing) + 1;
else if (source[offset] == '/' && offset + 1 <
source.length && source[offset + 1] == '/')
offset += findExpectedCharacter(source[offset + 1 ..
$], '\n', isMissing) + 1;
else if (source[offset] == '/' && offset + 1 <
source.length && source[offset + 1] == '*')
offset += findExpectedText(source[offset + 1 .. $],
"*/", isMissing) + 1;
if (isMissing)
return 0;
offset++;
}
}
private string consumeToken(ref string text)
{
import std.uni : isWhite, isAlphaNum;
// Chew up any preceding white space.
while (text.length > 0 && text[0].isWhite)
text = text[1 .. $];
auto offset = 0;
while (offset < text.length && (text[offset].isAlphaNum ||
text[offset] == '_'))
offset++;
if (offset == 0 && text.length > 0)
offset++;
const result = text[0 .. offset];
if (offset < text.length)
text = text[offset .. $];
else
text = "";
return result;
}
unittest
{
void testTemplate(alias t, size_t line =
__LINE__)(TemplateParameter[] expected)
{
import std.conv : to;
const parameters = templateParameters!t;
assert(parameters == expected,
"\n\nTest failure on line " ~ line.to!string ~ ": \n"
~
"Parameters: " ~ parameters.to!string ~ "\n" ~
"Expected: " ~ expected.to!string ~ "\n");
}
template t1() { }
testTemplate!t1([]);
template t2(int p) { }
testTemplate!t2([TemplateParameter(TemplateParameterType.valueType, "p", "")]);
template t3(int p = 1) { }
testTemplate!t3([TemplateParameter(TemplateParameterType.valueType, "p", "1")]);
template t4(int p1, string p2) { }
testTemplate!t4([TemplateParameter(TemplateParameterType.valueType, "p1", ""), TemplateParameter(TemplateParameterType.valueType, "p2", "")]);
template t5(int p1 = 123, string p2 = "ABC") { }
testTemplate!t5([TemplateParameter(TemplateParameterType.valueType, "p1", "123"), TemplateParameter(TemplateParameterType.valueType, "p2", "\"ABC\"")]);
template t6(alias p) { }
testTemplate!t6([TemplateParameter(TemplateParameterType.aliasType, "p", "")]);
template t7(alias p = 12.34) { }
testTemplate!t7([TemplateParameter(TemplateParameterType.aliasType, "p", "12.34")]);
class ThisTemplateTest
{
template t8(this p) { }
}
const thisTemplateTest = new ThisTemplateTest;
testTemplate!(thisTemplateTest.t8)([TemplateParameter(TemplateParameterType.thisType, "p", "")]);
template t9(p...) { }
testTemplate!t9([TemplateParameter(TemplateParameterType.sequenceType, "p", "")]);
template t10(T) { }
testTemplate!t10([TemplateParameter(TemplateParameterType.typeType, "T", "")]);
template t11(alias a_b_c = "a_b_c") { }
testTemplate!t11([TemplateParameter(TemplateParameterType.aliasType, "a_b_c", "\"a_b_c\"")]);
class C { }
template t12(T : C) { }
testTemplate!t12([TemplateParameter(TemplateParameterType.typeType, "T", "")]);
}
```