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", "")]);

}
```

Reply via email to