From: teodor Date: Thu, 2 Oct 2008 14:05:59 +0000 (+0000) Subject: Add expressions X-Git-Url: http://sigaev.ru/git/gitweb.cgi?a=commitdiff_plain;h=029b7a56ef910c2430ddb40a7383c7caa640ecc1;p=tedtools.git Add expressions --- diff --git a/data/template.tmpl b/data/template.tmpl index 774a0b3..95e0060 100644 --- a/data/template.tmpl +++ b/data/template.tmpl @@ -1,43 +1,57 @@ id: <% ID %> - simple idhex: <% ID, "0x%08x" %> - HEX -idhexdef: <% ID, "HEX(0x%08x)" || "-1" %> -< +idhexdef: <% ID, "HEX(0x%08x)" # "-1" %> -< ndef: <% ndefID %> -ndef def: <% ndefID || "Wow" %> +ndef def: <% ndefID # "Wow" %> empty <% EmptyId %> -empty def: <% EmptyId || "\"EmptyId\" - default" %> +empty def: <% EmptyId # "\"EmptyId\" - default" %> zero <% zeroID %> -zero def: <% zeroID || "zeroID" %><# COMENT +zero def: <% zeroID # "zeroID" %><# COMENT #> <@ IF ID @>ID-YES<@ ELSE @>ID-NO<@ ENDIF @> -<@ IF DEFINED ID @>DEFINED ID-YES<@ ELSE @>DEFINED ID-NO<@ ENDIF @> -<@ IF NOT ID @>DEFINED ID-YES<@ ELSE @>NOT ID-NO<@ ENDIF @> -<@ IF NOT DEFINED ID @>DEFINED ID-YES<@ ELSE @>NOT DEFINED ID-NO<@ ENDIF @> +<@ IF DEFINED(ID) @>DEFINED ID-YES<@ ELSE @>DEFINED ID-NO<@ ENDIF @> +<@ IF !ID @>DEFINED ID-YES<@ ELSE @>NOT ID-NO<@ ENDIF @> +<@ IF ! DEFINED (ID) @>DEFINED ID-YES<@ ELSE @>NOT DEFINED ID-NO<@ ENDIF @> <@ IF ndefID @>ndefID-YES<@ ELSE @>ndefID-NO<@ ENDIF @> -<@ IF DEFINED ndefID @>DEFINED ndefID-YES<@ ELSE @>DEFINED ndefID-NO<@ ENDIF @> -<@ IF NOT ndefID @>DEFINED ndefID-YES<@ ELSE @>NOT ndefID-NO<@ ENDIF @> -<@ IF NOT DEFINED ndefID @>DEFINED ndefID-YES<@ ELSE @>NOT DEFINED ndefID-NO<@ ENDIF @> +<@ IF DEFINED (ndefID) @>DEFINED ndefID-YES<@ ELSE @>DEFINED ndefID-NO<@ ENDIF @> +<@ IF ! (ndefID) @>DEFINED ndefID-YES<@ ELSE @>NOT ndefID-NO<@ ENDIF @> +<@ IF ! DEFINED (ndefID) @>DEFINED ndefID-YES<@ ELSE @>NOT DEFINED ndefID-NO<@ ENDIF @> <@ IF EmptyId @>EmptyId-YES<@ ELSE @>EmptyId-NO<@ ENDIF @> -<@ IF DEFINED EmptyId @>DEFINED EmptyId-YES<@ ELSE @>DEFINED EmptyId-NO<@ ENDIF @> -<@ IF NOT EmptyId @>DEFINED EmptyId-YES<@ ELSE @>NOT EmptyId-NO<@ ENDIF @> -<@ IF NOT DEFINED EmptyId @>DEFINED EmptyId-YES<@ ELSE @>NOT DEFINED EmptyId-NO<@ ENDIF @> +<@ IF DEFINED (EmptyId) @>DEFINED EmptyId-YES<@ ELSE @>DEFINED EmptyId-NO<@ ENDIF @> +<@ IF ! EmptyId @>DEFINED EmptyId-YES<@ ELSE @>NOT EmptyId-NO<@ ENDIF @> +<@ IF ! DEFINED (EmptyId) @>DEFINED EmptyId-YES<@ ELSE @>NOT DEFINED EmptyId-NO<@ ENDIF @> <@ IF zeroID @>zeroID-YES<@ ELSE @>zeroID-NO<@ ENDIF @> -<@ IF DEFINED zeroID @>DEFINED zeroID-YES<@ ELSE @>DEFINED zeroID-NO<@ ENDIF @> -<@ IF NOT zeroID @>DEFINED zeroID-YES<@ ELSE @>NOT zeroID-NO<@ ENDIF @> -<@ IF NOT DEFINED zeroID @>DEFINED zeroID-YES<@ ELSE @>NOT DEFINED zeroID-NO<@ ENDIF @> +<@ IF DEFINED (zeroID) @>DEFINED zeroID-YES<@ ELSE @>DEFINED zeroID-NO<@ ENDIF @> +<@ IF ! zeroID @>DEFINED zeroID-YES<@ ELSE @>NOT zeroID-NO<@ ENDIF @> +<@ IF ! DEFINED(zeroID) @>DEFINED zeroID-YES<@ ELSE @>NOT DEFINED zeroID-NO<@ ENDIF @> + +id*2 <% ID * 2 %> +id+2 <% ID + 2 %> +(id+2)*2 <% (ID + 2)*2 %> +id+2*2 <% ID + 2*2 %> +id*2+2 <% ID * 2+2 %> +id*3+zeroID <% ID * 2+zeroID %> +length(str) <% length (str) %> +length(str) > 3: <% length(str) > 3 %> + +<@ if length(str) < 10 && id > 16 @>HEH-1<@ endif @> +CallCounter: <% CallCounter() %> + +str ? "yes" : -1 = <% str ? "yes" : -1 %> <@ IF ID @> - <@ IF DEFINED zeroID @> + <@ IF DEFINED(zeroID) @> ID!=0 && defined(zeroID) - right <@ ELSE @> ID!=0 && !defined(zeroID) <@endif@> <@ELSE@> - <@ IF DEFINED zeroID @> + <@ IF DEFINED (zeroID) @> ID==0 && defined(zeroID) <@ ELSE @> ID==0 && && !defined(zeroID) @@ -46,7 +60,7 @@ zero def: <% zeroID || "zeroID" %><# COMENT <@ LOOP outerLoop @> - <% __COUNTER %>/<% __SIZE %>. odd:<% __ODD %> even:<% __EVEN %> <# <@if __FIRST @>FIRST<@endif@> <@if __LAST @>LAST<@endif@> id: <% ^ID %> <% DATA1 %>:<% DATA2 || "Data is absent" %>/<% DATA1 %> #> + <% __COUNTER %>/<% __SIZE %>. odd:<% __ODD %> even:<% __EVEN %> <# <@if __FIRST @>FIRST<@endif@> <@if __LAST @>LAST<@endif@> id: <% ^ID %> <% DATA1 %>:<% DATA2 # "Data is absent" %>/<% DATA1 %> #> <& data/template_include.tmpl &> <@ LOOP innerLoop @> diff --git a/data/template_include.tmpl b/data/template_include.tmpl index e7b52cf..a4a6839 100644 --- a/data/template_include.tmpl +++ b/data/template_include.tmpl @@ -1 +1 @@ -<@if __FIRST @>FIRST<@endif@> <@if __LAST @>LAST<@endif@> id: <% ^ID %> <% DATA1 %>:<% DATA2 || "Data is absent" %>/<% DATA1 %> +<@if __FIRST @>FIRST<@endif@> <@if __LAST @>LAST<@endif@> id: <% ^ID %> <% DATA1 %>:<% DATA2 # "Data is absent" %>/<% DATA1 %> diff --git a/expected/tmpl b/expected/tmpl index d7dd7a7..57095bd 100644 --- a/expected/tmpl +++ b/expected/tmpl @@ -30,6 +30,20 @@ DEFINED zeroID-YES DEFINED zeroID-YES NOT DEFINED zeroID-NO +id*2 34 +id+2 19 +(id+2)*2 38 +id+2*2 21 +id*2+2 36 +id*3+zeroID 34 +length(str) 6 +length(str) > 3: true + +HEH-1 +CallCounter: 1 + +str ? "yes" : -1 = yes + ID!=0 && defined(zeroID) - right @@ -96,6 +110,20 @@ DEFINED zeroID-YES DEFINED zeroID-YES NOT DEFINED zeroID-NO +id*2 46 +id+2 25 +(id+2)*2 50 +id+2*2 27 +id*2+2 48 +id*3+zeroID 46 +length(str) +length(str) > 3: + + +CallCounter: 2 + +str ? "yes" : -1 = -1 + ID!=0 && defined(zeroID) - right diff --git a/template.c b/template.c index 6561ee1..d85a610 100644 --- a/template.c +++ b/template.c @@ -34,6 +34,284 @@ #include "tlog.h" #include "template.h" +/* + * Default operations and functions + */ + +static int +isVariable(VariableValue value) { + if ( value == NULL ) + return 0; + + if ( (value->flags & TND_DEFINED) == 0 ) { + return 0; + } else { + switch (value->type) { + case valueInt: + return value->value.intValue; + case valueString: + if ( value->value.stringValue == NULL || *value->value.stringValue == '\0' ) + return 0; + return 1; + case valueTime: + return ( value->value.timeValue > 0 ) ? 1 : 0; + case valueBool: + return value->value.boolValue; + case valueDouble: + return (value->value.doubleValue == 0) ? 0 : 1; + case valuePointer: + default: + return 0; + } + } + + return 0; +} + +static VariableValue +makeBoolValue(Template tmpl, int v) { + VariableValue outvalue = mcalloc(tmpl->templateContext, sizeof(VariableValueData)); + + outvalue->type = valueBool; + outvalue->flags= TND_DEFINED; + outvalue->value.boolValue = (v) ? 1 : 0; + return outvalue; +} + +static VariableValue +copyValue(Template tmpl, VariableValue in) { + VariableValue out= mcalloc(tmpl->templateContext, sizeof(VariableValueData)); + + if (in) + memcpy(out, in, sizeof(VariableValueData)); + else { + out->type = valueBool; /* something */ + out->flags = 0; + } + return out; +} + +static VariableValue +isDefinedFn(Template tmpl, int n, VariableValue *vals) { + return makeBoolValue(tmpl, vals[0]->flags & TND_DEFINED ); +} + +static int +strmblen(char *str) { + int len = strlen(str); + int totlen = 0, clen; + + mblen(NULL,0); /* reset internal state */ + while( len > 0 ) { + clen = mblen( str, len ); + str += clen; + len -= clen; + totlen++; + } + + return totlen; +} + +static VariableValue +LengthFn(Template tmpl, int n, VariableValue *vals) { + VariableValue outvalue = NULL; + + outvalue = copyValue( tmpl, NULL ); + outvalue->type = valueInt; + + if ( vals[0]->type == valueString && vals[0]->value.stringValue && + (vals[0]->flags & TND_DEFINED) ) { + outvalue->flags |= TND_DEFINED; + outvalue->value.intValue = strmblen( vals[0]->value.stringValue ); + } else + outvalue->flags &= ~TND_DEFINED; + + return outvalue; +} + +static VariableValue +NotFn(Template tmpl, int n, VariableValue *vals) { + return makeBoolValue(tmpl, !isVariable(vals[0])); +} + +static VariableValue +AndFn(Template tmpl, int n, VariableValue *vals) { + return makeBoolValue(tmpl, isVariable(vals[0]) && isVariable(vals[1]) ); +} + +static VariableValue +OrFn(Template tmpl, int n, VariableValue *vals) { + return makeBoolValue(tmpl, isVariable(vals[0]) || isVariable(vals[1]) ); +} + +static VariableValue +CondFn(Template tmpl, int n, VariableValue *vals) { + return isVariable(vals[0]) ? vals[1] : vals[2]; +} + +static VariableValue +ModFn(Template tmpl, int n, VariableValue *vals) { + VariableValue outvalue = copyValue( tmpl, NULL ); + + outvalue->type = valueInt; + + if ( (vals[0]->flags & vals[1]->flags & TND_DEFINED) && + vals[0]->type == valueInt && vals[1]->type == valueInt) { + + outvalue->flags |= TND_DEFINED; + outvalue->value.intValue = vals[0]->value.intValue % vals[1]->value.intValue; + } else + outvalue->flags &= ~TND_DEFINED; + + return outvalue; +} + +static VariableValue +UnaryMinesFn(Template tmpl, int n, VariableValue *vals) { + VariableValue outvalue = copyValue( tmpl, vals[0] ); + + if (outvalue->type == valueInt) + outvalue->value.intValue = -outvalue->value.intValue; + else if (outvalue->type == valueDouble) + outvalue->value.doubleValue = -outvalue->value.doubleValue; + else + outvalue->flags &= ~TND_DEFINED; + + return outvalue; +} + +#define ISNUM(v) ( ((v)->type == valueDouble || (v)->type == valueInt) && ((v)->flags & TND_DEFINED) ) + +#define ARIPHACT(OP) \ + VariableValue outvalue = copyValue( tmpl, NULL ); \ + if ( !(ISNUM(vals[0]) && ISNUM(vals[1])) ) { \ + outvalue->flags &= ~TND_DEFINED; \ + } else if ( vals[0]->type == valueDouble || vals[1]->type == valueDouble ) { \ + outvalue->flags |= TND_DEFINED; \ + outvalue->type = valueDouble; \ + if ( vals[0]->type == valueDouble ) \ + outvalue->value.doubleValue = vals[0]->value.doubleValue; \ + else \ + outvalue->value.doubleValue = vals[0]->value.intValue; \ + if ( vals[1]->type == valueDouble ) \ + outvalue->value.doubleValue OP##= vals[1]->value.doubleValue; \ + else \ + outvalue->value.doubleValue OP##= vals[1]->value.intValue; \ + } else { \ + outvalue->flags |= TND_DEFINED; \ + outvalue->type = valueInt; \ + outvalue->value.intValue = vals[0]->value.intValue OP vals[1]->value.intValue; \ + } + +static VariableValue +PlusFn(Template tmpl, int n, VariableValue *vals) { + ARIPHACT(+) + return outvalue; +} + +static VariableValue +MinesFn(Template tmpl, int n, VariableValue *vals) { + ARIPHACT(-) + return outvalue; +} + +static VariableValue +MulFn(Template tmpl, int n, VariableValue *vals) { + ARIPHACT(*) + return outvalue; +} + +static VariableValue +DivFn(Template tmpl, int n, VariableValue *vals) { + ARIPHACT(/) + return outvalue; +} + +#define CMPACT(OP) \ + VariableValue outvalue = copyValue( tmpl, NULL ); \ + outvalue->type = valueBool; \ + if ( !(ISNUM(vals[0]) && ISNUM(vals[1])) ) { \ + outvalue->flags &= ~TND_DEFINED; \ + } else if ( vals[0]->type == valueDouble || vals[1]->type == valueDouble ) { \ + outvalue->flags |= TND_DEFINED; \ + if ( vals[0]->type == valueDouble && vals[1]->type == valueDouble ) \ + outvalue->value.boolValue = (vals[0]->value.doubleValue OP vals[1]->value.doubleValue) \ + ? 1 : 0; \ + else if ( vals[0]->type == valueDouble ) \ + outvalue->value.boolValue = (vals[0]->value.doubleValue OP vals[1]->value.intValue) \ + ? 1 : 0; \ + else \ + outvalue->value.boolValue = (vals[0]->value.intValue OP vals[1]->value.doubleValue) \ + ? 1 : 0; \ + } else { \ + outvalue->flags |= TND_DEFINED; \ + outvalue->value.boolValue = (vals[0]->value.intValue OP vals[1]->value.intValue) ? 1 : 0; \ + } + +static VariableValue +LtFn(Template tmpl, int n, VariableValue *vals) { + CMPACT(<) + return outvalue; +} + +static VariableValue +LeFn(Template tmpl, int n, VariableValue *vals) { + CMPACT(<=) + return outvalue; +} + +static VariableValue +EqFn(Template tmpl, int n, VariableValue *vals) { + CMPACT(==) + return outvalue; +} + +static VariableValue +GeFn(Template tmpl, int n, VariableValue *vals) { + CMPACT(>=) + return outvalue; +} + +static VariableValue +GtFn(Template tmpl, int n, VariableValue *vals) { + CMPACT(>) + return outvalue; +} + +static VariableValue +NeFn(Template tmpl, int n, VariableValue *vals) { + CMPACT(!=) + return outvalue; +} + +static executeFunctionDescData Functions[] = { + {"defined", 1, isDefinedFn}, + {"length", 1, LengthFn}, + {"+", 2, PlusFn}, + {"-", 2, MinesFn}, + {"*", 2, MulFn}, + {"/", 2, DivFn}, + {"%", 2, ModFn}, + {"-", 1, UnaryMinesFn}, + {"?", 3, CondFn}, + {"||", 2, OrFn}, + {"&&", 2, AndFn}, + {"!", 1, NotFn}, + {"<", 2, LtFn}, + {"<=", 2, LeFn}, + {"==", 2, EqFn}, + {">=", 2, GeFn}, + {">", 2, GtFn}, + {"!=", 2, NeFn}, + {"<>", 2, NeFn}, + {NULL, -1, NULL} +}; + + +/* + * Initialize functions + */ + #define MAXDEPTH (128) typedef struct ParseState { @@ -106,8 +384,11 @@ findIncludes(ParseState *state, TemplateNode *node) { GLCELL_DATA(cell) = chld; } break; - case TextNode: + case ExpressionNode: /* any expression node can not include files */ + case PrintNode: + case ConstNode: case VariableNode: + case TextNode: break; default: tlog(TL_CRIT|TL_EXIT, "unknown node type: %d", (*node)->type); @@ -247,6 +528,27 @@ addLoop(Template tmpl, TemplateNode node, TemplateNode loopParentNode) { return 0; } +static void +findFunction(Template tmpl, TemplateNode node) { + executeFunctionDesc ptr = tmpl->functions; + + tassert(ptr != NULL); + + while(ptr && ptr->name) { + if ( node->nodeData.expression.nargs < 0 || node->nodeData.expression.nargs == ptr->nargs ) { + if ( strcmp( node->nodeData.expression.functionName, ptr->name ) == 0 ) { + node->nodeData.expression.function = ptr; + return; + } + } + + ptr++; + } + tlog(TL_CRIT|TL_EXIT, "Can not find function named '%s' with %d arguments", + node->nodeData.expression.functionName, + node->nodeData.expression.nargs); +} + static int compileTree( Template tmpl, TemplateNode node, TemplateNode loopParentNode ) { GListCell *cell; @@ -266,20 +568,8 @@ compileTree( Template tmpl, TemplateNode node, TemplateNode loopParentNode ) { return 1; break; case ConditionNode: - node->nodeData.condition.flags = checkSpecialVariable( - node->nodeData.condition.flags, - node->nodeData.condition.varName ); - - if ( (node->nodeData.condition.flags & TND_GLOBAL) == 0 ) - node->nodeData.condition.varName = qualifyVarname(tmpl, loopParentNode, - node->nodeData.condition.varName, - &node->nodeData.condition.varNameLength); - - node->nodeData.condition.value = findVariable( tmpl, loopParentNode, - node->nodeData.condition.flags, - node->nodeData.condition.varName, - node->nodeData.condition.varNameLength ); - + if ( compileTree(tmpl, node->nodeData.condition.expressionNode, loopParentNode) ) + return 1; if ( compileTree(tmpl, node->nodeData.condition.ifNode, loopParentNode) ) return 1; if ( compileTree(tmpl, node->nodeData.condition.elseNode, loopParentNode) ) @@ -308,9 +598,25 @@ compileTree( Template tmpl, TemplateNode node, TemplateNode loopParentNode ) { node->nodeData.variable.varName, node->nodeData.variable.varNameLength ); break; + case PrintNode: + if ( compileTree(tmpl, node->nodeData.print.expressionNode, loopParentNode) ) + return 1; + break; + case ExpressionNode: + node->nodeData.expression.nargs = GLIST_LENGTH(node->nodeData.expression.argsNode); + findFunction(tmpl, node); + + node->nodeData.expression.argsValue = mcalloc(tmpl->templateContext, sizeof(VariableValue) * + node->nodeData.expression.nargs ); + + GListForeach(cell, node->nodeData.expression.argsNode) + if ( compileTree(tmpl, (TemplateNode)GLCELL_DATA(cell), loopParentNode) ) + return 1; + break; case IncludeNode: tlog(TL_CRIT|TL_EXIT, "unexpected IncludeNode"); break; + case ConstNode: case TextNode: break; default: @@ -321,7 +627,7 @@ compileTree( Template tmpl, TemplateNode node, TemplateNode loopParentNode ) { } int -initTemplate( Template tmpl, MemoryContext *mc, char *basedir, char *filename ) { +initTemplate( Template tmpl, MemoryContext *mc, executeFunctionDesc functions, char *basedir, char *filename ) { ParseState state; int err; @@ -333,6 +639,22 @@ initTemplate( Template tmpl, MemoryContext *mc, char *basedir, char *filename ) tmpl->templateContext = mc; SFSInit_dp(&tmpl->variables, sizeof(void*), NULL); + if ( functions ) { + executeFunctionDesc ptr = functions; + int n=0; + + while(ptr->name) + ptr++; + + n = ptr - functions; + tmpl->functions = mcalloc(tmpl->templateContext, + sizeof(executeFunctionDescData) * n + sizeof(Functions)); + + memcpy( tmpl->functions, functions, sizeof(executeFunctionDescData) * n ); + memcpy( tmpl->functions + n, Functions, sizeof(Functions)); + } else + tmpl->functions = Functions; + if ( (err=recursiveReadTemplate(&state, NULL, filename))!=0 ) return err; if ( (err=compileTree( tmpl, tmpl->tree, NULL ))!=0 ) @@ -341,6 +663,10 @@ initTemplate( Template tmpl, MemoryContext *mc, char *basedir, char *filename ) return 0; } +/* + * Reset/cleanup + */ + void resetTemplate( Template tmpl ) { SFSDataIO out; @@ -399,12 +725,22 @@ freeNode( TemplateNode node ) { GListFree( node->nodeData.children ); break; case ConditionNode: + freeNode( node->nodeData.condition.expressionNode ); freeNode( node->nodeData.condition.ifNode ); freeNode( node->nodeData.condition.elseNode ); break; - case IncludeNode: + case PrintNode: + freeNode( node->nodeData.print.expressionNode); + break; + case ExpressionNode: + GListForeach(cell, node->nodeData.expression.argsNode) + freeNode( GLCELL_DATA(cell) ); + GListFree( node->nodeData.expression.argsNode ); + break; case VariableNode: + case IncludeNode: case TextNode: + case ConstNode: default: break; } @@ -416,6 +752,10 @@ freeTemplate( Template tmpl ) { freeNode( tmpl->tree ); } +/* + * Set value routines + */ + static void newLoopInstance( Template tmpl, TemplateNode node ) { node->nodeData.loop.listInstance = GListPush( @@ -559,6 +899,10 @@ setTemplateValueUndefined( Template tmpl, char * key ) { return setTemplateValue( tmpl, key ); } +/* + * output routines + */ + static char * printVal( Template tmpl, VariableValue value, int *len, char *format ) { int printedlen; @@ -627,34 +971,49 @@ printVal( Template tmpl, VariableValue value, int *len, char *format ) { return res; } -static int -isVariable(VariableValue value, int flags) { - if ( value == NULL ) - return 0; - if ( flags & TND_DEFINED ) { - return (value->flags & TND_DEFINED); - } else { - switch (value->type) { - case valueInt: - return value->value.intValue; - case valueString: - if ( value->value.stringValue == NULL || *value->value.stringValue == '\0' ) - return 0; - return 1; - case valueTime: - return ( value->value.timeValue > 0 ) ? 1 : 0; - case valueBool: - return value->value.boolValue; - case valueDouble: - return (value->value.doubleValue == 0) ? 0 : 1; - case valuePointer: - default: - return 0; - } +static VariableValue +executeExpression( Template tmpl, TemplateNode node ) { + VariableValue outvalue = NULL; + GListCell *cell; + int i = 0; + + + switch (node->type) { + case ConstNode: + outvalue = &node->nodeData.value; + break; + case VariableNode: + if ( node->nodeData.variable.value->type == valuePointer ) + outvalue = node->nodeData.variable.value->value.ptrValue; + else + outvalue = node->nodeData.variable.value; + break; + case ExpressionNode: + GListForeach(cell, node->nodeData.expression.argsNode) + node->nodeData.expression.argsValue[i++] = + executeExpression( tmpl, (TemplateNode)GLCELL_DATA(cell) ); + + outvalue = node->nodeData.expression.function->execFn(tmpl, + node->nodeData.expression.nargs, + node->nodeData.expression.argsValue); + break; + case TextNode: + case IncludeNode: + case LoopNode: + case ConditionNode: + case CollectionNode: + case PrintNode: + tlog(TL_CRIT|TL_EXIT, "Unexpected node type: %d", node->type); + break; + default: + tlog(TL_CRIT|TL_EXIT, "Unknown node type: %d", node->type); + break; } - return 0; + tassert( outvalue!=NULL ); + + return outvalue; } static void @@ -729,36 +1088,25 @@ printNode( Template tmpl, TemplateNode node ) { } break; case ConditionNode: - value = node->nodeData.condition.value; - if ( value->type == valuePointer ) - value = value->value.ptrValue; - - if ( node->nodeData.condition.flags & TND_NOT ) { - if ( isVariable(value, node->nodeData.condition.flags) ) - printNode( tmpl, node->nodeData.condition.elseNode ); - else - printNode( tmpl, node->nodeData.condition.ifNode ); - } else { - if ( isVariable(value, node->nodeData.condition.flags) ) - printNode( tmpl, node->nodeData.condition.ifNode ); - else - printNode( tmpl, node->nodeData.condition.elseNode ); - } + value = executeExpression( tmpl, node->nodeData.condition.expressionNode ); + + if ( isVariable(value) ) + printNode( tmpl, node->nodeData.condition.ifNode ); + else + printNode( tmpl, node->nodeData.condition.elseNode ); break; case CollectionNode: GListForeach( cell, node->nodeData.children ) printNode( tmpl, (TemplateNode)GLCELL_DATA(cell) ); break; - case VariableNode: - value = node->nodeData.variable.value; - if ( value->type == valuePointer ) - value = value->value.ptrValue; + case PrintNode: + value = executeExpression( tmpl, node->nodeData.condition.expressionNode ); if ( value && (value->flags & TND_DEFINED) != 0 ) { int len; char *res; - res = printVal(tmpl, value, &len, node->nodeData.variable.formatValue); + res = printVal(tmpl, value, &len, node->nodeData.print.formatValue); if ( (node->nodeData.variable.flags & TND_HTMLESCAPE) && tmpl->htmlEscape ) res = tmpl->htmlEscape(res, &len); @@ -769,16 +1117,22 @@ printNode( Template tmpl, TemplateNode node ) { tmpl->printString( res, len ); mcfree(res); } - } else if ( node->nodeData.variable.defaultValue ) { - tmpl->printString( node->nodeData.variable.defaultValue, - strlen( node->nodeData.variable.defaultValue ) ); + } else if ( node->nodeData.print.defaultValue ) { + tmpl->printString( node->nodeData.print.defaultValue, + strlen( node->nodeData.print.defaultValue ) ); } break; case TextNode: tmpl->printString( node->nodeData.text.value, node->nodeData.text.valueLength ); break; case IncludeNode: + case VariableNode: + case ConstNode: + case ExpressionNode: + tlog(TL_CRIT|TL_EXIT, "Unexpected node type: %d", node->type); + break; default: + tlog(TL_CRIT|TL_EXIT, "Unknown node type: %d", node->type); break; } } @@ -793,38 +1147,57 @@ printTemplate( Template tmpl ) { return 0; } +static +void printLevel(int level) { + while(level-- > 0 ) + fputs(" ", stdout); +} static void recursiveDump(Template tmpl, TemplateNode node, int level) { GListCell *cell; + printLevel(level); if (node == NULL ) { - printf("%d void\n", level); + printf("VOID\n"); return; } switch(node->type) { case IncludeNode: - printf("%d IncludeNode\n", level); + printf("IncludeNode\n"); break; case LoopNode: - printf("%d LoopNode\n", level); + printf("LoopNode '%s'\n", node->nodeData.loop.varName); recursiveDump(tmpl, node->nodeData.loop.bodyNode, level+1); break; case ConditionNode: - printf("%d ConditionNode\n", level); + printf("ConditionNode\n"); + recursiveDump(tmpl, node->nodeData.condition.expressionNode, level+1); recursiveDump(tmpl, node->nodeData.condition.ifNode, level+1); recursiveDump(tmpl, node->nodeData.condition.elseNode, level+1); break; case CollectionNode: - printf("%d CollectionNode\n", level); + printf("CollectionNode\n"); GListForeach(cell, node->nodeData.children) recursiveDump(tmpl, (TemplateNode)GLCELL_DATA(cell), level+1); break; case TextNode: - printf("%d TextNode len:%d\n", level, node->nodeData.text.valueLength); + printf("TextNode len:%d\n", node->nodeData.text.valueLength); break; case VariableNode: - printf("%d VariableNode '%s'\n", level, node->nodeData.variable.varName); + printf("VariableNode '%s'\n", node->nodeData.variable.varName); + break; + case ExpressionNode: + printf("ExpressionNode '%s'\n", node->nodeData.expression.functionName); + GListForeach(cell, node->nodeData.expression.argsNode) + recursiveDump( tmpl, GLCELL_DATA(cell) ,level + 1); + break; + case PrintNode: + printf("PrintNode\n"); + recursiveDump( tmpl, node->nodeData.print.expressionNode, level + 1 ); + break; + case ConstNode: + printf("ConstNode\n"); break; default: tlog(TL_CRIT|TL_EXIT, "unknown node type: %d", node->type); diff --git a/template.h b/template.h index 72a57e5..0e12052 100644 --- a/template.h +++ b/template.h @@ -30,23 +30,75 @@ /****************************************************************************** * SYNTAX * ****************************************************************************** - * <% [^]VARNAME[, "FORMAT"] [||"DEFAULTVALUE"] [|(h|u)]%> - * - '^' mark means global variable. + * <% EXPRESSION , "FORMAT"] [# "DEFAULTVALUE"] [|(h|u)]%> * - format value should be as in strftime for time value and printf * for all other. Currently, bool values have only "true"/"false" * string values - * <@IF [NOT] [DEFINED] [^]VARNAME@> + * <@IF EXPRESSION @> * <@ELSE@> * <@ENDIF@> * - * <@LOOP MARKNAME@> + * Expression is classical with support following: + * - ['^'] VARNAME + * variable defined from C-code. Mark '^' means global + * variable, not local in loop. + * - expression (+|-|*|/|%) expression + * ariphmetic operations + * - expression ( || | && ) expression + * ! expression + * logical OR, AND and NOT + * - expression ( < | <= | == | >= | > | != | <> ) expression + * compare expression + * - LENGTH(expression) + * computes length of string + * - DEFINED(expression) + * returns true if expression is defined + * - expression ? expression : expression + * - ( expression ) + * - USERDEFINEDFUNCTION( [expression[,expression[...]]] ) + * User defined function call. Function should be defined at + * C-level. * + * <@LOOP MARKNAME@> * <@ENDLOOP@> + * Loop has predefined variables: + * __FIRST - true for first iteration + * __LAST - true for last iteration + * __COUNTER - iteration's number + * __SIZE - number of iterations + * __ODD - true for odd iteraion + * __EVEN - true for even iteraion * * <& FILENAME &> * * <# comment #> * + ****************************************************************************** + * C-Interface + ****************************************************************************** + * - setTemplateValueInt + * setTemplateValueString + * setTemplateValueTime + * setTemplateValueBool + * setTemplateValueDouble + * setTemplateValueUndefined + * Sets varibale's value + * - addTemplateRow + * Add one iteration to the pointed loop. Local variable in loop should + * pointed with predecence loop's mark(s) separated by dot. Example: + * HTML: + * <@Loop outerLoop@> + * <% var1 %> + * <@Loop innerLoop@> + * <% var2 %> + * <@endloop@> + * <@endloop@> + * C: + * addTemplateRow("outerLoop"); + * setTemplateValueBool("outerLoop.var1"); + * addTemplateRow("innerLoop"); + * setTemplateValueBool("outerLoop.innerLoop.var2"); + * ******************************************************************************/ #ifndef __TEMPLATE_H__ @@ -66,6 +118,9 @@ typedef enum TemplateNodeType { LoopNode, ConditionNode, CollectionNode, + ExpressionNode, + PrintNode, + ConstNode, /* value's types of variables */ valueInt = 200, /* smallest of any values type */ @@ -76,20 +131,23 @@ typedef enum TemplateNodeType { valuePointer } TemplateNodeType; -#define TND_DEFINED (0x0001) -#define TND_NOT (0x0002) -#define TND_HTMLESCAPE (0x0004) -#define TND_URLESCAPE (0x0008) -#define TND_GLOBAL (0x0010) -#define TND___FIRST (0x0020) -#define TND___LAST (0x0040) -#define TND___COUNTER (0x0080) -#define TND___SIZE (0x0100) -#define TND___ODD (0x0200) -#define TND___EVEN (0x0400) +#define TND_HTMLESCAPE (0x0001) +#define TND_URLESCAPE (0x0002) +#define TND_GLOBAL (0x0004) + +#define TND___FIRST (0x0008) +#define TND___LAST (0x0010) +#define TND___COUNTER (0x0020) +#define TND___SIZE (0x0040) +#define TND___ODD (0x0080) +#define TND___EVEN (0x0100) + +#define TND_DEFINED (0x0200) #define TND__SPECIALMASK (TND___FIRST | TND___LAST | TND___COUNTER | TND___SIZE | TND___ODD | TND___EVEN) -struct TemplateNodeData; + +typedef struct TemplateData *Template; + typedef struct TemplateNodeData *TemplateNode; typedef struct VariableValueData * VariableValue; @@ -107,6 +165,14 @@ typedef struct VariableValueData { } value; } VariableValueData; + +typedef struct executeFunctionDescData *executeFunctionDesc; +typedef struct executeFunctionDescData { + char *name; + int nargs; /* -1 - variable number */ + VariableValue (*execFn)(Template, int, VariableValue*); +} executeFunctionDescData; + typedef struct LoopInstanceData * LoopInstance; typedef struct LoopInstanceData { int nrow; @@ -129,10 +195,29 @@ typedef struct TemplateNodeData { int varNameLength; VariableValue value; int flags; - char *formatValue; - char *defaultValue; } variable; + /* ExpressionNode */ + struct { + VariableValue *argsValue; + int nargs; + char *functionName; + executeFunctionDesc function; + GList *argsNode; /* list of nodes after parsing + but before compile */ + } expression; + + /* ConstNode */ + VariableValueData value; + + /* PrintNode */ + struct { + TemplateNode expressionNode; + char *formatValue; + char *defaultValue; + int flags; + } print; + /* IncludeNode */ char *includeFile; @@ -149,10 +234,7 @@ typedef struct TemplateNodeData { /* ConditionNode */ struct { - int flags; - char *varName; - int varNameLength; - VariableValue value; + TemplateNode expressionNode; TemplateNode ifNode; TemplateNode elseNode; } condition; @@ -170,18 +252,17 @@ typedef char* (*urlEscapeFn)(char *, int * /* in/out */); typedef char* (*htmlEscapeFn)(char *, int * /* in/out */); typedef struct TemplateData { - TemplateNode tree; - MemoryContext *templateContext; - SFSTree variables; - outFn printString; - urlEscapeFn urlEscape; - htmlEscapeFn htmlEscape; + TemplateNode tree; + MemoryContext *templateContext; + SFSTree variables; + outFn printString; + urlEscapeFn urlEscape; + htmlEscapeFn htmlEscape; + executeFunctionDesc functions; } TemplateData; -typedef struct TemplateData *Template; - int parseTemplateFile(Template tmpl, char* filename ); /* return non-zero if error */ -int initTemplate( Template tmpl, MemoryContext *mc, char *basedir, char *filename ); +int initTemplate( Template tmpl, MemoryContext *mc, executeFunctionDesc functions, char *basedir, char *filename ); void freeTemplate( Template tmpl ); void resetTemplate( Template tmpl ); int printTemplate( Template tmpl ); diff --git a/tmpl_gram.y b/tmpl_gram.y index 3730660..3335903 100644 --- a/tmpl_gram.y +++ b/tmpl_gram.y @@ -16,30 +16,33 @@ void startTemplateLex(Template tmpl, FILE *in); static Template curTmpl; extern int tmpl_yylineno; +static TemplateNode newExpressionNode(char *op, GList *args); +static GList *makeList2(void *a, void *b); + %} %union { - char *str; - char punct; - int varname; - int flags; - TemplateNode node; + char *str; + char punct; + int flags; + int intval; + double floatval; + TemplateNode node; + GList *list; } %type node %type listnodes %type template -%type condition -%type condition_varname +%type expression +%type list_expression %type varname %type opt_escape -%type opt_global -%type opt_default %type opt_format +%type opt_default -%token OR_P %token STRING %token FILENAME %token TEXT_P @@ -47,7 +50,22 @@ extern int tmpl_yylineno; %token VAR_OPEN VAR_CLOSE EXPR_OPEN EXPR_CLOSE INCLUDE_OPEN INCLUDE_CLOSE %token HTMLESCAPE URLESCAPE IF_P ELSE_P LOOP_P ENDIF_P ENDLOOP_P - NOT_P DEFINED_P +%token CMP_P + +%token INTEGER +%token DOUBLE + + +%left '+' '-' +%left '*' '/' '%' +%left '?' ':' +%left NEG + +%left OR_P +%left AND_P +%left NOT_P + +%left CMP_P %% @@ -80,8 +98,6 @@ varname: | LOOP_P | ENDIF_P | ENDLOOP_P - | NOT_P - | DEFINED_P ; opt_escape: @@ -90,52 +106,103 @@ opt_escape: | { $$=0; } ; -opt_global: - '^' { $$=TND_GLOBAL; } - | { $$=0; } - ; - opt_format: ',' STRING { $$=$2; } | { $$=NULL; } ; opt_default: - OR_P STRING { $$=$2; } + '#' STRING { $$=$2; } | { $$=NULL; } ; -condition_varname: - varname { - $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); - $$->type = ConditionNode; - $$->nodeData.condition.varName = $1; - $$->nodeData.condition.varNameLength = strlen($1); - } - | '^' varname { - $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); - $$->type = ConditionNode; - $$->nodeData.condition.flags = TND_GLOBAL; - $$->nodeData.condition.varName = $2; - $$->nodeData.condition.varNameLength = strlen($2); +list_expression: + expression ',' expression { + $$ = makeList2($1, $3); } + | list_expression ',' expression { + $$ = GListPush($$, $3); + } ; -condition: - condition_varname { - $$ = $1; +expression: + varname { + $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); + $$->type = VariableNode; + $$->nodeData.variable.varName = $1; + $$->nodeData.variable.varNameLength = strlen($1); + } + | '^' varname { + $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); + $$->type = VariableNode; + $$->nodeData.variable.flags = TND_GLOBAL; + $$->nodeData.variable.varName = $2; + $$->nodeData.variable.varNameLength = strlen($2); + } + | STRING { + $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); + $$->type = ConstNode; + $$->nodeData.value.type = valueString; + $$->nodeData.value.flags = TND_DEFINED; + $$->nodeData.value.value.stringValue = $1; + } + | INTEGER { + $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); + $$->type = ConstNode; + $$->nodeData.value.type = valueInt; + $$->nodeData.value.flags = TND_DEFINED; + $$->nodeData.value.value.intValue = $1; + } + | DOUBLE { + $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); + $$->type = ConstNode; + $$->nodeData.value.type = valueDouble; + $$->nodeData.value.flags = TND_DEFINED; + $$->nodeData.value.value.doubleValue = $1; } - | DEFINED_P condition_varname { - $$ = $2; - $$->nodeData.condition.flags |= TND_DEFINED; + | expression '+' expression { + $$ = newExpressionNode( "+", makeList2( $1, $3 ) ); } - | NOT_P condition { - $$ = $2; - if ( $$->nodeData.condition.flags & TND_NOT ) - $$->nodeData.condition.flags &= ~TND_NOT; - else - $$->nodeData.condition.flags |= TND_NOT; + | expression '-' expression { + $$ = newExpressionNode( "-", makeList2( $1, $3 ) ); } + | expression '*' expression { + $$ = newExpressionNode( "*", makeList2( $1, $3 ) ); + } + | expression '/' expression { + $$ = newExpressionNode( "/", makeList2( $1, $3 ) ); + } + | expression '%' expression { + $$ = newExpressionNode( "%", makeList2( $1, $3 ) ); + } + | '-' expression %prec NEG { + $$ = newExpressionNode( "-", GListPush( NULL, $2 ) ); + } + | expression AND_P expression { + $$ = newExpressionNode( "&&", makeList2( $1, $3 ) ); + } + | expression OR_P expression { + $$ = newExpressionNode( "||", makeList2( $1, $3 ) ); + } + | expression '?' expression ':' expression { + $$ = newExpressionNode( "?", GListPush( makeList2( $1, $3 ), $5 ) ); + } + | NOT_P expression { + $$ = newExpressionNode( "!", GListPush( NULL, $2 ) ); + } + | expression CMP_P expression { + $$ = newExpressionNode( $2, makeList2( $1, $3 ) ); + } + | varname '(' ')' { + $$ = newExpressionNode( $1, NULL ); + } + | varname '(' expression ')' { + $$ = newExpressionNode( $1, GListPush( NULL, $3 ) ); + } + | varname '(' list_expression ')' { + $$ = newExpressionNode( $1, $3 ); + } + | '(' expression ')' { $$=$2; } ; node: @@ -145,14 +212,13 @@ node: $$->nodeData.text.value = $1; $$->nodeData.text.valueLength = strlen($1); } - | VAR_OPEN opt_global varname opt_format opt_default opt_escape VAR_CLOSE { + | VAR_OPEN expression opt_format opt_default opt_escape VAR_CLOSE { $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); - $$->type = VariableNode; - $$->nodeData.variable.varName = $3; - $$->nodeData.variable.varNameLength = strlen($3); - $$->nodeData.variable.formatValue = $4; - $$->nodeData.variable.defaultValue = $5; - $$->nodeData.variable.flags = $2 | $6; + $$->type = PrintNode; + $$->nodeData.print.expressionNode = $2; + $$->nodeData.print.formatValue = $3; + $$->nodeData.print.defaultValue = $4; + $$->nodeData.print.flags = $5; } | INCLUDE_OPEN FILENAME INCLUDE_CLOSE { $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); @@ -166,12 +232,16 @@ node: $$->nodeData.loop.varNameLength = strlen($3); $$->nodeData.loop.bodyNode = $5; } - | EXPR_OPEN IF_P condition EXPR_CLOSE listnodes EXPR_OPEN ENDIF_P EXPR_CLOSE { - $$ = $3; + | EXPR_OPEN IF_P expression EXPR_CLOSE listnodes EXPR_OPEN ENDIF_P EXPR_CLOSE { + $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); + $$->type = ConditionNode; + $$->nodeData.condition.expressionNode = $3; $$->nodeData.condition.ifNode = $5; } - | EXPR_OPEN IF_P condition EXPR_CLOSE listnodes EXPR_OPEN ELSE_P EXPR_CLOSE listnodes EXPR_OPEN ENDIF_P EXPR_CLOSE { - $$ = $3; + | EXPR_OPEN IF_P expression EXPR_CLOSE listnodes EXPR_OPEN ELSE_P EXPR_CLOSE listnodes EXPR_OPEN ENDIF_P EXPR_CLOSE { + $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); + $$->type = ConditionNode; + $$->nodeData.condition.expressionNode = $3; $$->nodeData.condition.ifNode = $5; $$->nodeData.condition.elseNode = $9; } @@ -208,3 +278,19 @@ parseTemplateFile(Template tmpl, char* filename ) { return err; } +static TemplateNode +newExpressionNode(char *op, GList *args) { + TemplateNode res; + + res = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); + res->type = ExpressionNode; + res->nodeData.expression.functionName = op; + res->nodeData.expression.argsNode = args; + + return res; +} + +static GList * +makeList2(void *a, void *b) { + return GListPush( GListPush(NULL, a), b ); +} diff --git a/tmpl_scan.l b/tmpl_scan.l index 4379919..318736c 100644 --- a/tmpl_scan.l +++ b/tmpl_scan.l @@ -36,8 +36,10 @@ static MemoryContext *lexContext; %x xQUOTED %x xCOMMENT +LEXEMCHARSTART [a-zA-Z_] LEXEMCHAR [a-zA-Z0-9_] PATH [a-zA-Z0-9_/\.] +DIGIT [0-9] %% @@ -60,14 +62,7 @@ PATH [a-zA-Z0-9_/\.] BEGIN xCOMMENT; } -{LEXEMCHAR}+ { - yylval.str = strlower(mcnstrdup(lexContext, yytext, yyleng)); - return getIdent(yylval.str); - } - -\|\| { return OR_P; } - -[,\|\^] { +[,\|#] { yylval.punct = *yytext; return yylval.punct; } @@ -101,11 +96,47 @@ PATH [a-zA-Z0-9_/\.] addchar(*yytext); } -{LEXEMCHAR}+ { +{DIGIT}+ { + yylval.intval = atoi(yytext); + return INTEGER; + } + +{LEXEMCHARSTART}{LEXEMCHAR}* { yylval.str = strlower(mcnstrdup(lexContext, yytext, yyleng)); return getIdent(yylval.str); } +\|\| { return OR_P; } + +\&\& { return AND_P; } + +\! { return NOT_P; } + +[\^\%\+\*\/\-\(\)\?\:] { + yylval.punct = *yytext; + return yylval.punct; + } + +(\<|\<=|\>=|\>|==|\!=|\<\>) { + yylval.str = mcnstrdup(lexContext, yytext, yyleng); + return CMP_P; + } + +{DIGIT}+"."{DIGIT}+ { + yylval.floatval = atof(yytext); + return DOUBLE; + } + +{DIGIT}+[eE]["+""-"]?{DIGIT}+ { + yylval.floatval = atof(yytext); + return DOUBLE; + } + +{DIGIT}+"."{DIGIT}+[eE]["+""-"]?{DIGIT}+ { + yylval.floatval = atof(yytext); + return DOUBLE; + } + \@\> { BEGIN INITIAL; return EXPR_CLOSE; @@ -172,11 +203,9 @@ static KeyWord keywords[] = { { "H", 0, HTMLESCAPE }, { "U", 0, URLESCAPE }, { "IF", 0, IF_P }, - { "NOT", 0, NOT_P }, { "ELSE", 0, ELSE_P }, { "LOOP", 0, LOOP_P }, { "ENDIF", 0, ENDIF_P }, - { "DEFINED", 0, DEFINED_P }, { "ENDLOOP", 0, ENDLOOP_P } }; diff --git a/tmpltest.c b/tmpltest.c index 5c3986f..c5aef19 100644 --- a/tmpltest.c +++ b/tmpltest.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "tmalloc.h" @@ -42,7 +43,7 @@ static void usage() { puts( "Usage:\n" - "memtest [-t TEMPLATENAME]\n" + "tmpltest [-t TEMPLATENAME]\n" ); exit(1); } @@ -53,6 +54,19 @@ outfunc(char *str, int len) { fputs( str, stdout ); } +static int counter = 0; + +static VariableValue +localCounter(Template tmpl, int nargs, VariableValue *args) { + VariableValue out = tmalloc(sizeof(VariableValue)); + + out->type = valueInt; + out->flags = TND_DEFINED; + out->value.intValue = ++counter; + + return out; +} + extern char *optarg; extern int opterr; @@ -62,6 +76,12 @@ main(int argn, char *argv[]) { char *name = NULL; TemplateData template; int i; + executeFunctionDescData funcs[] = { + {"callcounter", 0, localCounter}, + {NULL,0,NULL} + }; + + opentlog(TL_OPEN_STDERR,TL_DEBUG, NULL); opterr=0; @@ -81,14 +101,16 @@ main(int argn, char *argv[]) { if (!name) usage(); + setlocale(LC_ALL,"ru_RU.UTF-8"); base = allocMemoryContext(NULL, MC_DEBUG); - printf("initTemplate: %d\n", initTemplate(&template, base, ".", name) ); - /* dumpTemplate(&template); */ + printf("initTemplate: %d\n", initTemplate(&template, base, funcs, ".", name) ); + /* dumpTemplate(&template); */ setTemplateValueInt(&template, "ID", 17); setTemplateValueUndefined(&template, "emptyID"); setTemplateValueInt(&template, "zeroid", 0); + setTemplateValueString(&template, "str", "QWERTY"); addTemplateRow(&template, "outerLoop"); setTemplateValueString(&template, "outerLoop.data1", "ha1");