Is there a way to get a template’s parameters and constraints?

Adam Ross Walker adamrosswalker at googlemail.com
Sun Jan 22 12:43:37 UTC 2023


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

}
```


More information about the Digitalmars-d-learn mailing list