From: teodor Date: Tue, 30 Sep 2008 13:21:38 +0000 (+0000) Subject: Add simple template library X-Git-Url: http://www.sigaev.ru/git/gitweb.cgi?a=commitdiff_plain;h=b99712a22de6fb3f45b36580e63596aac7e6061c;hp=654d594fd43a950db71f61ab770a82f148bf622b;p=tedtools.git Add simple template library --- diff --git a/Makefile b/Makefile index a387934..ba4ced9 100644 --- a/Makefile +++ b/Makefile @@ -1,16 +1,22 @@ topbuilddir=. PROGRAM=sfxtest hextest inftest kilter psortex flatdbtest \ - tbtreetest gendata memtest glisttest prstest + tbtreetest gendata memtest glisttest prstest \ + tmpltest LIBRARY=libtedtools.a LIBOBJ=tlog.o tmalloc.o tools.o prs_hmap.o sfxstr.o \ regis.o prs_inf.o shmem.o tcp.o udp.o connpool.o \ - psort.o flatdb.o tbtree.o glist.o + psort.o flatdb.o tbtree.o glist.o \ + tmpl_gram.o tmpl_scan.o template.o + + +BISON=bison -y -d +FLEX=flex -CF include $(topbuilddir)/Makefile.global -clean: clean-test +clean: clean-test clean-gram clean-test: rm -rf sfxtest.log sfxtest.dump BTREE @@ -20,7 +26,7 @@ test: all @[ -d results ] || mkdir results @[ -d diffs ] || mkdir diffs @[ -d temp ] || mkdir temp - @for FILE in btree flatdb hex inf mem psort sfxmem glist prsqs ; do \ + @for FILE in btree flatdb hex inf mem psort sfxmem glist prsqs tmpl ; do \ echo -n $$FILE " ........ " ; \ if sh tests/$$FILE > results/$$FILE 2>results/$$FILE.errout && diff -c expected/$$FILE results/$$FILE > diffs/$$FILE ; then \ echo ok ; \ @@ -28,3 +34,15 @@ test: all echo FAILED ; \ fi ; \ done + +clean-gram: + rm -f y.tab.c y.tab.h tmpl_gram.c tmpl_gram.h + rm -f tmpl_scan.c + +tmpl_gram.c: tmpl_gram.y + $(BISON) -p tmpl_yy tmpl_gram.y + mv -f y.tab.c tmpl_gram.c + mv -f y.tab.h tmpl_gram.h + +tmpl_scan.c: tmpl_scan.l tmpl_gram.c + $(FLEX) -P tmpl_yy -o'tmpl_scan.c' tmpl_scan.l diff --git a/data/template.tmpl b/data/template.tmpl new file mode 100644 index 0000000..774a0b3 --- /dev/null +++ b/data/template.tmpl @@ -0,0 +1,57 @@ +id: <% ID %> - simple +idhex: <% ID, "0x%08x" %> - HEX +idhexdef: <% ID, "HEX(0x%08x)" || "-1" %> -< +ndef: <% ndefID %> +ndef def: <% ndefID || "Wow" %> +empty <% EmptyId %> +empty def: <% EmptyId || "\"EmptyId\" - default" %> +zero <% zeroID %> +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 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 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 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 ID @> + <@ IF DEFINED zeroID @> + ID!=0 && defined(zeroID) - right + <@ ELSE @> + ID!=0 && !defined(zeroID) + <@endif@> +<@ELSE@> + <@ IF DEFINED zeroID @> + ID==0 && defined(zeroID) + <@ ELSE @> + ID==0 && && !defined(zeroID) + <@ENDIF@> +<@ENDIF@> + + +<@ LOOP outerLoop @> + <% __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 @> + <@ IF __FIRST @>
    <@ ENDIF @> +
  1. <% camenty %> + <@ IF __LAST @>
<@ ENDIF @> + <@ ENDLOOP @> +<@ ENDLOOP @> diff --git a/data/template_include.tmpl b/data/template_include.tmpl new file mode 100644 index 0000000..e7b52cf --- /dev/null +++ b/data/template_include.tmpl @@ -0,0 +1 @@ +<@if __FIRST @>FIRST<@endif@> <@if __LAST @>LAST<@endif@> id: <% ^ID %> <% DATA1 %>:<% DATA2 || "Data is absent" %>/<% DATA1 %> diff --git a/expected/tmpl b/expected/tmpl new file mode 100644 index 0000000..7585c3b --- /dev/null +++ b/expected/tmpl @@ -0,0 +1,66 @@ +initTemplate: 0 +id: 17 - simple +idhex: 0x00000011 - HEX +idhexdef: HEX(0x00000011) -< +ndef: +ndef def: Wow +empty +empty def: "EmptyId" - default +zero 0 +zero def: 0 + +ID-YES +DEFINED ID-YES +NOT ID-NO +NOT DEFINED ID-NO + +ndefID-NO +DEFINED ndefID-NO +DEFINED ndefID-YES +DEFINED ndefID-YES + + +EmptyId-NO +DEFINED EmptyId-NO +DEFINED EmptyId-YES +DEFINED EmptyId-YES + +zeroID-NO +DEFINED zeroID-YES +DEFINED zeroID-YES +NOT DEFINED zeroID-NO + + + + ID!=0 && defined(zeroID) - right + + + + + + 1/3. odd:true even:false + + FIRST id: 17 ha1:Data is absent/ha1 + + + + 2/3. odd:false even:true + + id: 17 10:WOW/10 + + +
    +
  1. Number 1 + + + +
  2. Number 2 +
+ + + 3/3. odd:true even:false + + LAST id: 17 ha3:Data is absent/ha3 + + + diff --git a/template.c b/template.c new file mode 100644 index 0000000..4f6fbdd --- /dev/null +++ b/template.c @@ -0,0 +1,830 @@ +/* + * cOpyright (c) 2004 Teodor Sigaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +#include "tmalloc.h" +#include "tlog.h" +#include "template.h" + +#define MAXDEPTH (128) + +typedef struct ParseState { + int depth; + char *basename; + Template tmpl; +} ParseState; + +static char * +getFilename(ParseState *state, char *filename) { + int len = 1 /* \0 */ + 1 /* / */ + strlen(filename); + char *res; + + if ( *filename == '/' ) + return filename; + + if ( state->basename && *state->basename ) + len += strlen(state->basename); + + res = mcalloc(state->tmpl->templateContext, sizeof(char) * len); + len = 0; + + if ( state->basename && *state->basename ) { + len = strlen(state->basename); + memcpy( res, state->basename, len ); + res[len++] = '/'; + } + + memcpy( res+len, filename, strlen(filename)); + res[ len + strlen(filename) ] = '\0'; + + return res; +} + +static int recursiveReadTemplate( ParseState *state, Template tmptmpl, char *filename ); + +static int +findIncludes(ParseState *state, TemplateNode *node) { + TemplateData tmp; + GListCell *cell; + + tmp.templateContext = state->tmpl->templateContext; + + if (node == NULL || *node == NULL) + return 0; + + switch((*node)->type) { + case IncludeNode: + tmp.tree = NULL; + if ( recursiveReadTemplate(state, &tmp, (*node)->nodeData.includeFile) != 0 ) + return 1; + *node = tmp.tree; + break; + case LoopNode: + if ( findIncludes(state, &( (*node)->nodeData.loop.bodyNode )) != 0 ) + return 1; + break; + case ConditionNode: + if ( findIncludes(state, &( (*node)->nodeData.condition.ifNode )) != 0 || + findIncludes(state, &( (*node)->nodeData.condition.elseNode )) != 0 ) + return 1; + break; + case CollectionNode: + GListForeach(cell, (*node)->nodeData.children) { + TemplateNode chld = (TemplateNode)GLCELL_DATA(cell); + + if ( findIncludes(state, &chld) != 0 ) + return 1; + + GLCELL_DATA(cell) = chld; + } + break; + case TextNode: + case VariableNode: + break; + default: + tlog(TL_CRIT|TL_EXIT, "unknown node type: %d", (*node)->type); + } + + return 0; +} + +static int +recursiveReadTemplate( ParseState *state, Template tmptmpl, char *filename ) { + int err; + char *fn = getFilename(state, filename); + + if ( state->depth > MAXDEPTH ) { + tlog(TL_ALARM, "too many depth of included templates"); + return 4; + } + + if ( tmptmpl == NULL ) + tmptmpl = state->tmpl; + + if ( (err=parseTemplateFile( tmptmpl, fn )) != 0 ) + return err; + + state->depth++; + + if ( (err=findIncludes(state, &(tmptmpl->tree))) != 0 ) + return err; + + state->depth--; + + return 0; +} + +static char* +qualifyVarname(Template tmpl, TemplateNode loopParentNode, char *varName, int *varNameLength) { + int len; + char *tmp; + + if ( ! loopParentNode ) + return varName; + + len = loopParentNode->nodeData.loop.varNameLength + *varNameLength + 2; + tmp = mcalloc(tmpl->templateContext, len); + len = loopParentNode->nodeData.loop.varNameLength; + memcpy( tmp, loopParentNode->nodeData.loop.varName, len); + tmp[ len++ ] = '.'; + memcpy( tmp + len, varName, *varNameLength); + len+=*varNameLength; + tmp[ len ] = '\0'; + + *varNameLength = len; + return tmp; +} + +static int +checkSpecialVariable(int flags, char *varName) { + if ( strcmp(varName, "__first") == 0 ) { + flags &= ~TND_GLOBAL; /* special vars cannot be global */ + flags |= TND___FIRST; + } else if ( strcmp(varName, "__last") == 0 ) { + flags &= ~TND_GLOBAL; /* special vars cannot be global */ + flags |= TND___LAST; + } else if ( strcmp(varName, "__counter") == 0 ) { + flags &= ~TND_GLOBAL; /* special vars cannot be global */ + flags |= TND___COUNTER; + } else if ( strcmp(varName, "__odd") == 0 ) { + flags &= ~TND_GLOBAL; /* special vars cannot be global */ + flags |= TND___ODD; + } else if ( strcmp(varName, "__even") == 0 ) { + flags &= ~TND_GLOBAL; /* special vars cannot be global */ + flags |= TND___EVEN; + } else if ( strcmp(varName, "__size") == 0 ) { + flags &= ~TND_GLOBAL; /* special vars cannot be global */ + flags |= TND___SIZE; + } + + return flags; +} + +static VariableValue +findVariable( Template tmpl, TemplateNode loopParentNode, int flags, char *varName, int varNameLength ) { + VariableValue *pvarval; + + if ( (pvarval = SFSFindData(&tmpl->variables, varName, varNameLength)) == NULL ) { + VariableValue pdata = mc0alloc(tmpl->templateContext, sizeof(VariableValueData)); + SFSDataIO in; + + in.key = varName; + in.keylen = varNameLength; + in.data = &pdata; + + SFSAdd(&tmpl->variables, &in); + + if ( loopParentNode && (flags & TND_GLOBAL) == 0 ) { + /* + * copy special flags to support new inform loopParentNode + */ + pdata->flags |= flags & TND__SPECIALMASK; + + if ( flags & ( TND___FIRST | TND___LAST | TND___ODD | TND___EVEN ) ) + pdata->type = valueBool; + else if ( flags & (TND___COUNTER | TND___SIZE) ) + pdata->type = valueInt; + else + pdata->type = valuePointer; + + loopParentNode->nodeData.loop.listVarValues = + GListPush( loopParentNode->nodeData.loop.listVarValues, pdata ); + } + + return pdata; + } + + return *pvarval; +} + +static int +addLoop(Template tmpl, TemplateNode node, TemplateNode loopParentNode) { + SFSDataIO in; + + if ( SFSFindData(&tmpl->variables, node->nodeData.loop.varName, node->nodeData.loop.varNameLength) != NULL ) { + tlog(TL_CRIT,"Loop marked '%s' is already defined", node->nodeData.loop.varName); + return 1; + } + + in.key = node->nodeData.loop.varName; + in.keylen = node->nodeData.loop.varNameLength; + in.data = &node; + + SFSAdd(&tmpl->variables, &in); + + if ( loopParentNode ) + loopParentNode->nodeData.loop.childrenLoop = + GListPush( loopParentNode->nodeData.loop.childrenLoop, node ); + + return 0; +} + +static int +compileTree( Template tmpl, TemplateNode node, TemplateNode loopParentNode ) { + GListCell *cell; + + if ( !node ) + return 0; + + switch(node->type) { + case LoopNode: + node->nodeData.loop.varName = qualifyVarname(tmpl, loopParentNode, + node->nodeData.loop.varName, + &node->nodeData.loop.varNameLength); + if ( compileTree(tmpl, node->nodeData.loop.bodyNode, node) ) + return 1; + + if ( addLoop( tmpl, node, 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.ifNode, loopParentNode) ) + return 1; + if ( compileTree(tmpl, node->nodeData.condition.elseNode, loopParentNode) ) + return 1; + break; + case CollectionNode: + GListForeach(cell, node->nodeData.children) { + TemplateNode chld = (TemplateNode)GLCELL_DATA(cell); + + if ( compileTree(tmpl, chld, loopParentNode) ) + return 1; + } + break; + case VariableNode: + node->nodeData.variable.flags = checkSpecialVariable( + node->nodeData.variable.flags, + node->nodeData.variable.varName ); + + if ( (node->nodeData.variable.flags & TND_GLOBAL) == 0 ) + node->nodeData.variable.varName = qualifyVarname(tmpl, loopParentNode, + node->nodeData.variable.varName, + &node->nodeData.variable.varNameLength); + + node->nodeData.variable.value = findVariable( tmpl, loopParentNode, + node->nodeData.variable.flags, + node->nodeData.variable.varName, + node->nodeData.variable.varNameLength ); + break; + case IncludeNode: + tlog(TL_CRIT|TL_EXIT, "unexpected IncludeNode"); + break; + case TextNode: + break; + default: + tlog(TL_CRIT|TL_EXIT, "unknown node type: %d", node->type); + } + + return 0; +} + +int +initTemplate( Template tmpl, MemoryContext *mc, char *basedir, char *filename ) { + ParseState state; + int err; + + state.depth = 0; + state.tmpl = tmpl; + state.basename = basedir; + + memset(tmpl, 0, sizeof(TemplateData)); + tmpl->templateContext = mc; + SFSInit_dp(&tmpl->variables, sizeof(void*), NULL); + + if ( (err=recursiveReadTemplate(&state, NULL, filename))!=0 ) + return err; + if ( (err=compileTree( tmpl, tmpl->tree, NULL ))!=0 ) + return err; + + return 0; +} + +void +resetTemplate( Template tmpl ) { + SFSDataIO out; + GListCell *cell; + + SFSIteratorStart( &tmpl->variables ); + + while( SFSIterate( &tmpl->variables, &out ) ) { + VariableValue varval = *(VariableValue*) out.data; + + if ( varval->type >= valueInt ) + varval->flags &= ~TND_DEFINED; + else if ( varval->type == LoopNode ) { + TemplateNode node = (TemplateNode) varval; + + GListForeach( cell, node->nodeData.loop.listVarValues ) { + varval = (VariableValue) GLCELL_DATA(cell); + + if ( varval->type == valuePointer ) + varval->value.ptrValue = NULL; + } + + GListForeach( cell, node->nodeData.loop.listInstance ) { + LoopInstance instance = GLCELL_DATA(cell); + + GListFree(instance->rowValues ); + } + GListTruncate( node->nodeData.loop.listInstance, 0 ); + } + } +} + +static void +freeNode( TemplateNode node ) { + GListCell *cell; + + if (!node) + return; + + switch (node->type) { + case LoopNode: + freeNode( node->nodeData.loop.bodyNode ); + GListFree( node->nodeData.loop.childrenLoop ); + GListFree( node->nodeData.loop.listVarValues ); + + GListForeach( cell, node->nodeData.loop.listInstance ) { + LoopInstance instance = GLCELL_DATA(cell); + + GListFree(instance->rowValues ); + } + GListFree( node->nodeData.loop.listInstance ); + break; + case CollectionNode: + GListForeach( cell, node->nodeData.children ) + freeNode( (TemplateNode)GLCELL_DATA(cell) ); + GListFree( node->nodeData.children ); + break; + case ConditionNode: + freeNode( node->nodeData.condition.ifNode ); + freeNode( node->nodeData.condition.elseNode ); + break; + case IncludeNode: + case VariableNode: + case TextNode: + default: + break; + } +} + +void +freeTemplate( Template tmpl ) { + SFSFree( &tmpl->variables, NULL ); + freeNode( tmpl->tree ); +} + +static void +newLoopInstance( Template tmpl, TemplateNode node ) { + node->nodeData.loop.listInstance = GListPush( + node->nodeData.loop.listInstance, + mc0alloc(tmpl->templateContext, sizeof(LoopInstanceData) ) ); +} + +int +addTemplateRow( Template tmpl, char * key ) { + TemplateNode *pnode, node; + char *lkey = strlower(mcstrdup(tmpl->templateContext, key)); + GListCell *cell; + VariableValue varvals; + int i=0, nvar; + LoopInstance instance; + + pnode = SFSFindData(&tmpl->variables, lkey, 0); + mcfree(lkey); + + if ( pnode == NULL ) + return TVAR_NOTFOUND; + + node = *pnode; + + if ( node->type != LoopNode ) + return TVAR_FORBIDDEN; + + nvar = GLIST_LENGTH( node->nodeData.loop.listVarValues ); + if ( nvar == 0 ) + /* loop without vars can not be looped */ + return TVAR_NOROW; + + if ( GLIST_LENGTH(node->nodeData.loop.listInstance) == 0 ) + newLoopInstance(tmpl, node); + + GListForeach( cell, node->nodeData.loop.childrenLoop ) + newLoopInstance( tmpl, GLCELL_DATA(cell) ); + + varvals = mcalloc( tmpl->templateContext, sizeof(VariableValueData) * nvar ); + GListForeach( cell, node->nodeData.loop.listVarValues ) { + VariableValue vv = GLCELL_DATA(cell); + + vv->value.ptrValue = varvals + i; + varvals[i].flags = 0; + i++; + } + + instance = GLCELL_DATA(GLIST_TAIL(node->nodeData.loop.listInstance)); + + instance->nrow++; + instance->rowValues = GListPush( instance->rowValues, varvals ); + + return TVAR_OK; +} + +static VariableValueData storage; + +static int +setTemplateValue( Template tmpl, char *key) { + VariableValue *pvarval, varval; + char *lkey = strlower(mcstrdup(tmpl->templateContext, key)); + + pvarval = SFSFindData(&tmpl->variables, lkey, 0); + mcfree(lkey); + + if ( pvarval == NULL ) + return TVAR_NOTFOUND; + + varval = *pvarval; + + if ( varval->type != 0 && varval->type < valueInt ) { + return TVAR_LOOPMARK; + } else if ( varval->type == valuePointer ) { + /* looped variable */ + varval = varval->value.ptrValue; + + if ( varval == NULL ) + return TVAR_NOROW; + + tassert( (varval->type & TND_GLOBAL) == 0 ); + } + + if ( varval->flags & TND__SPECIALMASK ) + return TVAR_FORBIDDEN; + + if ( storage.flags & TND_DEFINED ) { + varval->flags |= TND_DEFINED; + varval->type = storage.type; + varval->value = storage.value; + } else { + varval->flags &= ~TND_DEFINED; + } + + return TVAR_OK; +} + + +int +setTemplateValueInt( Template tmpl, char * key, int val ) { + storage.flags = TND_DEFINED; + storage.type = valueInt; + storage.value.intValue = val; + return setTemplateValue( tmpl, key ); +} + +int +setTemplateValueString( Template tmpl, char * key, char * val ) { + storage.flags = TND_DEFINED; + storage.type = valueString; + storage.value.stringValue = val; + return setTemplateValue( tmpl, key ); +} + +int +setTemplateValueTime( Template tmpl, char * key, time_t val ) { + storage.flags = TND_DEFINED; + storage.type = valueTime; + storage.value.timeValue = val; + return setTemplateValue( tmpl, key ); +} + +int +setTemplateValueBool( Template tmpl, char * key, int val ) { + storage.flags = TND_DEFINED; + storage.type = valueBool; + storage.value.boolValue = val; + return setTemplateValue( tmpl, key ); +} + +int +setTemplateValueDouble( Template tmpl, char * key, double val ) { + storage.flags = TND_DEFINED; + storage.type = valueDouble; + storage.value.boolValue = val; + return setTemplateValue( tmpl, key ); +} + +int +setTemplateValueUndefined( Template tmpl, char * key ) { + storage.flags = 0; + return setTemplateValue( tmpl, key ); +} + +static char * +printVal( Template tmpl, VariableValue value, int *len, char *format ) { + int printedlen; + char *res; + + *len = 0; + + if ( value->type == valueTime ) { + printedlen = 64; + res = mcalloc( tmpl->templateContext, printedlen+1 ); + + while ( (printedlen = strftime(NULL, 0, (format) ? format : "%Y-%m-%d %H:%M:%S", + localtime(&value->value.timeValue))) == 0 ) { + printedlen *= 2; + res=mcrealloc(res, printedlen); + } + + *len = printedlen; + return res; + } + + switch (value->type) { + case valueInt: + printedlen = snprintf(NULL, 0, (format) ? format : "%d", value->value.intValue); + break; + case valueString: + if ( value->value.stringValue == NULL || *value->value.stringValue == '\0' ) + return NULL; + printedlen = snprintf(NULL, 0, (format) ? format : "%s", value->value.stringValue); + break; + case valueBool: + printedlen = snprintf(NULL, 0, "%s", (value->value.boolValue) ? "true" : "false" ); + break; + case valueDouble: + printedlen = snprintf(NULL, 0, (format) ? format : "%f", value->value.doubleValue); + break; + case valuePointer: + case valueTime: + default: + return NULL; + } + + res = mcalloc( tmpl->templateContext, printedlen+1 ); + + switch (value->type) { + case valueInt: + printedlen = snprintf(res, printedlen+1, (format) ? format : "%d", value->value.intValue); + break; + case valueString: + printedlen = snprintf(res, printedlen+1, (format) ? format : "%s", value->value.stringValue); + break; + case valueBool: + printedlen = snprintf(res, printedlen+1, "%s", (value->value.boolValue) ? "true" : "false" ); + break; + case valueDouble: + printedlen = snprintf(res, printedlen+1, (format) ? format : "%f", value->value.doubleValue); + break; + case valuePointer: + case valueTime: + default: + return NULL; + } + + *len = printedlen; + 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; + } + } + + return 0; +} + +static void +printNode( Template tmpl, TemplateNode node ) { + GListCell *cell; + VariableValue value; + + if (!node) + return; + + switch (node->type) { + case LoopNode: + { + GListCell *cell = GListShift( node->nodeData.loop.listInstance ); + LoopInstance instance; + int i; + + if ( cell == NULL ) + return; + + instance = GLCELL_DATA(cell); + GListFreeCell( node->nodeData.loop.listInstance, cell ); + + for(i=0; inrow;i++) { + VariableValue varvals; + int j = 0; + + cell = GListShift( instance->rowValues ); + varvals = GLCELL_DATA(cell); + GListFreeCell( instance->rowValues, cell ); + + GListForeach( cell, node->nodeData.loop.listVarValues ) { + value = (VariableValue)GLCELL_DATA(cell); + + if ( value->flags & TND___FIRST ) { + if ( i==0 ) { + value->flags |= TND_DEFINED; + value->value.intValue = 1; + } else { + value->flags &= ~TND_DEFINED; + value->value.intValue = 0; + } + } else if ( value->flags & TND___LAST ) { + if ( i==instance->nrow - 1 ) { + value->flags |= TND_DEFINED; + value->value.intValue = 1; + } else { + value->flags &= ~TND_DEFINED; + value->value.intValue = 0; + } + } else if ( value->flags & TND___COUNTER ) { + value->flags |= TND_DEFINED; + value->value.intValue = i+1; + } else if ( value->flags & TND___SIZE ) { + value->flags |= TND_DEFINED; + value->value.intValue = instance->nrow; + } else if ( value->flags & TND___ODD ) { + value->flags |= TND_DEFINED; + value->value.boolValue = (i%2) ? 0 : 1 ; + } else if ( value->flags & TND___EVEN ) { + value->flags |= TND_DEFINED; + value->value.boolValue = (i%2) ? 1 : 0 ; + } else { + tassert( value->type == valuePointer ); + value->value.ptrValue = varvals+j; + } + + j++; + } + printNode( tmpl, node->nodeData.loop.bodyNode ); + } + } + 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 ); + } + 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; + + if ( value && (value->flags & TND_DEFINED) != 0 ) { + int len; + char *res; + + res = printVal(tmpl, value, &len, node->nodeData.variable.formatValue); + if ( res && len>0 ) { + tmpl->printString( res, len ); + mcfree(res); + } + } else if ( node->nodeData.variable.defaultValue ) { + tmpl->printString( node->nodeData.variable.defaultValue, + strlen( node->nodeData.variable.defaultValue ) ); + } + break; + case TextNode: + tmpl->printString( node->nodeData.text.value, node->nodeData.text.valueLength ); + break; + case IncludeNode: + default: + break; + } +} + +int +printTemplate( Template tmpl ) { + if (!tmpl->printString) + return 1; + + printNode(tmpl, tmpl->tree); + + return 0; +} + +static void +recursiveDump(Template tmpl, TemplateNode node, int level) { + GListCell *cell; + + if (node == NULL ) { + printf("%d void\n", level); + return; + } + + switch(node->type) { + case IncludeNode: + printf("%d IncludeNode\n", level); + break; + case LoopNode: + printf("%d LoopNode\n", level); + recursiveDump(tmpl, node->nodeData.loop.bodyNode, level+1); + break; + case ConditionNode: + printf("%d ConditionNode\n", level); + recursiveDump(tmpl, node->nodeData.condition.ifNode, level+1); + recursiveDump(tmpl, node->nodeData.condition.elseNode, level+1); + break; + case CollectionNode: + printf("%d CollectionNode\n", level); + 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); + break; + case VariableNode: + printf("%d VariableNode '%s'\n", level, node->nodeData.variable.varName); + break; + default: + tlog(TL_CRIT|TL_EXIT, "unknown node type: %d", node->type); + } +} + +void +dumpTemplate( Template tmpl ) { + recursiveDump(tmpl, tmpl->tree, 0); +} diff --git a/template.h b/template.h new file mode 100644 index 0000000..9b45bea --- /dev/null +++ b/template.h @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2008 Teodor Sigaev + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the author nor the names of any co-contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/****************************************************************************** + * SYNTAX * + ****************************************************************************** + * <% [^]VARNAME[, "FORMAT"] [||"DEFAULTVALUE"] [|(h|u)]%> + * - '^' mark means global variable. + * - 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@> + * <@ELSE@> + * <@ENDIF@> + * + * <@LOOP MARKNAME@> + * + * <@ENDLOOP@> + * + * <& FILENAME &> + * + * <# comment #> + * + ******************************************************************************/ + +#ifndef __TEMPLATE_H__ +#define __TEMPLATE_H__ + +#include +#include +#include "glist.h" +#include "sfxstr.h" + +typedef enum TemplateNodeType { + /* node tree types */ + TextNode = 100, + VariableNode, + IncludeNode, + LoopNode, + ConditionNode, + CollectionNode, + + /* value's types of variables */ + valueInt = 200, /* smallest of any values type */ + valueString, + valueTime, + valueBool, + valueDouble, + 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__SPECIALMASK (TND___FIRST | TND___LAST | TND___COUNTER | TND___SIZE | TND___ODD | TND___EVEN) +struct TemplateNodeData; +typedef struct TemplateNodeData *TemplateNode; + +typedef struct VariableValueData * VariableValue; +typedef struct VariableValueData { + TemplateNodeType type; /* should be first, see resetTemplate/freeTemplate */ + int flags; + union { + int intValue; + char *stringValue; + time_t timeValue; + int boolValue; + double doubleValue; + VariableValue ptrValue; /* special value to handle loop variables, + points to members of loopValues */ + } value; +} VariableValueData; + +typedef struct LoopInstanceData * LoopInstance; +typedef struct LoopInstanceData { + int nrow; + GList *rowValues; +} LoopInstanceData; + +typedef struct TemplateNodeData { + TemplateNodeType type; /* should be first, see resetTemplate/freeTemplate */ + + union { + /* TextNode */ + struct { + char *value; + int valueLength; + } text; + + /* VariableNode */ + struct { + char *varName; + int varNameLength; + VariableValue value; + int flags; + char *formatValue; + char *defaultValue; + } variable; + + /* IncludeNode */ + char *includeFile; + + /* LoopNode */ + struct { + char *varName; + int varNameLength; + TemplateNode bodyNode; + int counter; + GList *childrenLoop; /* to reset instance */ + GList *listVarValues; /* list of loop variables */ + GList *listInstance; + } loop; + + /* ConditionNode */ + struct { + int flags; + char *varName; + int varNameLength; + VariableValue value; + TemplateNode ifNode; + TemplateNode elseNode; + } condition; + + /* CollectionNode */ + GList *children; + } nodeData; + +} TemplateNodeData; + +/* prints result */ +typedef void (*outFn)(char *, int); + +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; +} 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 ); +void freeTemplate( Template tmpl ); +void resetTemplate( Template tmpl ); +int printTemplate( Template tmpl ); + +#define TVAR_OK (0) +#define TVAR_NOTFOUND (1) +#define TVAR_FORBIDDEN (2) +#define TVAR_NOROW (3) +#define TVAR_LOOPMARK (4) + +int setTemplateValueInt( Template tmpl, char * key, int val ); +int setTemplateValueString( Template tmpl, char * key, char * val ); +int setTemplateValueTime( Template tmpl, char * key, time_t val ); +int setTemplateValueBool( Template tmpl, char * key, int val ); +int setTemplateValueUndefined( Template tmpl, char * key ); +int setTemplateValueDouble( Template tmpl, char * key, double val ); +int addTemplateRow( Template tmpl, char * key ); + +void dumpTemplate( Template tmpl ); +#endif diff --git a/tests/tmpl b/tests/tmpl new file mode 100644 index 0000000..b73ec1b --- /dev/null +++ b/tests/tmpl @@ -0,0 +1 @@ +./tmpltest -t data/template.tmpl diff --git a/tmpl_gram.y b/tmpl_gram.y new file mode 100644 index 0000000..09353fc --- /dev/null +++ b/tmpl_gram.y @@ -0,0 +1,206 @@ +%{ +#include +#include +#include +#include +#include + +extern char *fileToParse; +char *fileToParse; + +int yylex(void); +int yyparse(void); +static int yyerror(char *s); +void startTemplateLex(Template tmpl, FILE *in); + +static Template curTmpl; +extern int tmpl_yylineno; + +%} + +%union { + char *str; + char punct; + int varname; + int flags; + TemplateNode node; +} + +%type node +%type listnodes +%type template +%type condition +%type condition_varname +%type varname + +%type opt_escape +%type opt_global +%type opt_default +%type opt_format + +%token OR_P +%token STRING +%token FILENAME +%token TEXT_P +%token LEXEME +%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 + +%% + +template: + listnodes { curTmpl->tree = $$ = $1; } + | { curTmpl->tree = $$ = NULL; } + ; + +listnodes: + node { $$ = $1; } + | listnodes node { + if ( $1->type == CollectionNode ) { + GListPush( $1->nodeData.children, $2 ); + $$ = $1; + } else { + $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); + $$->type = CollectionNode; + $$->nodeData.children = GListPush( $$->nodeData.children, $1 ); + $$->nodeData.children = GListPush( $$->nodeData.children, $2 ); + } + } + ; + +varname: + LEXEME + | HTMLESCAPE + | URLESCAPE + | IF_P + | ELSE_P + | LOOP_P + | ENDIF_P + | ENDLOOP_P + | NOT_P + | DEFINED_P + ; + +opt_escape: + '|' HTMLESCAPE { $$=TND_HTMLESCAPE; } + | '|' URLESCAPE { $$=TND_URLESCAPE; } + | { $$=0; } + ; + +opt_global: + '^' { $$=TND_GLOBAL; } + | { $$=0; } + ; + +opt_format: + ',' STRING { $$=$2; } + | { $$=NULL; } + ; + +opt_default: + OR_P 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); + } + ; + +condition: + condition_varname { + $$ = $1; + } + | DEFINED_P condition_varname { + $$ = $2; + $$->nodeData.condition.flags |= TND_DEFINED; + } + | NOT_P condition { + $$ = $2; + if ( $$->nodeData.condition.flags & TND_NOT ) + $$->nodeData.condition.flags &= ~TND_NOT; + else + $$->nodeData.condition.flags |= TND_NOT; + } + ; + +node: + TEXT_P { + $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); + $$->type = TextNode; + $$->nodeData.text.value = $1; + $$->nodeData.text.valueLength = strlen($1); + } + | VAR_OPEN opt_global varname 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; + } + | INCLUDE_OPEN FILENAME INCLUDE_CLOSE { + $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); + $$->type = IncludeNode; + $$->nodeData.includeFile = $2; + } + | EXPR_OPEN LOOP_P varname EXPR_CLOSE listnodes EXPR_OPEN ENDLOOP_P EXPR_CLOSE { + $$ = mc0alloc( curTmpl->templateContext, sizeof(TemplateNodeData) ); + $$->type = LoopNode; + $$->nodeData.loop.varName = $3; + $$->nodeData.loop.varNameLength = strlen($3); + $$->nodeData.loop.bodyNode = $5; + } + | EXPR_OPEN IF_P condition EXPR_CLOSE listnodes EXPR_OPEN ENDIF_P EXPR_CLOSE { + $$ = $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; + $$->nodeData.condition.ifNode = $5; + $$->nodeData.condition.elseNode = $9; + } + ; + +%% + +static int +yyerror(char *s) { + tlog(TL_CRIT,"template error at line %d in '%s': %s", tmpl_yylineno, fileToParse, s); + return 1; +} + +int +parseTemplateFile(Template tmpl, char* filename ) { + FILE *in = fopen(filename, "r"); + int err; + + if ( in == NULL ) + return 3; + + curTmpl = tmpl; + curTmpl->tree = NULL; + + fileToParse = filename; + startTemplateLex(tmpl, in); + err = yyparse(); + + fclose(in); + + return err; +} + diff --git a/tmpl_scan.l b/tmpl_scan.l new file mode 100644 index 0000000..4379919 --- /dev/null +++ b/tmpl_scan.l @@ -0,0 +1,243 @@ +%{ +#include +#include +#include +#include + +#ifndef yylval +#define yylval tmpl_yylval +#endif + +extern char* fileToParse; + +static int yyerror(char *s, int line); +static int getIdent(char *word); + +static char *strbuf = NULL; +static int length; +static int total; +static void addstring(char *s, int l); +static void addchar(char s); +static MemoryContext *lexContext; + +%} + +%option 8bit +%option never-interactive +%option nodefault +%option noyywrap +%option nounput +%option prefix="tmpl_yy" +%option yylineno + +%x xVAR +%x xEXPR +%x xINCLUDE +%x xQUOTED +%x xCOMMENT + +LEXEMCHAR [a-zA-Z0-9_] +PATH [a-zA-Z0-9_/\.] + +%% + +\<\% { + BEGIN xVAR; + return VAR_OPEN; + } + +\<\@ { + BEGIN xEXPR; + return EXPR_OPEN; + } + +\<\& { + BEGIN xINCLUDE; + return INCLUDE_OPEN; + } + +\<\# { + BEGIN xCOMMENT; + } + +{LEXEMCHAR}+ { + yylval.str = strlower(mcnstrdup(lexContext, yytext, yyleng)); + return getIdent(yylval.str); + } + +\|\| { return OR_P; } + +[,\|\^] { + yylval.punct = *yytext; + return yylval.punct; + } + +\%\> { + BEGIN INITIAL; + return VAR_CLOSE; + } + +\" { + total = 1024; + strbuf = mcalloc(lexContext, total); + length=0; + BEGIN xQUOTED; + } + + +\\(.|\n) { addchar(yytext[1]); } + +\" { + yylval.str = strbuf; + BEGIN xVAR; + strbuf = NULL; + return STRING; + } + +[^\"\\]+ { addstring(yytext, yyleng); } + +\\ { + /* This is only needed for \ just before EOF */ + addchar(*yytext); + } + +{LEXEMCHAR}+ { + yylval.str = strlower(mcnstrdup(lexContext, yytext, yyleng)); + return getIdent(yylval.str); + } + +\@\> { + BEGIN INITIAL; + return EXPR_CLOSE; + } + +{PATH}+ { + yylval.str = mcnstrdup(lexContext, yytext, yyleng); + return FILENAME; + } + +\&\> { + BEGIN INITIAL; + return INCLUDE_CLOSE; + } + +\#\> { + BEGIN INITIAL; + } + +\n+ ; + +[\r\t ]+ ; + +. ; + +. { + return yyerror("Syntax error in template tag", yylineno); + } + +<> { + return yyerror("unterminated template tag", yylineno); + } + +[^\<]+ { + yylval.str = mcnstrdup(lexContext, yytext, yyleng); + return TEXT_P; + } + +([^\<]*\<[^\&\#\@\%][^\<]*)+ { + yylval.str = mcnstrdup(lexContext, yytext, yyleng); + return TEXT_P; + } + + +\< { + /* This is only needed for < just before EOF */ + yylval.str = mcnstrdup(lexContext, yytext, yyleng); + return TEXT_P; + } + +<> { + yyterminate(); + } + +%% + +typedef struct KeyWord { + char *word; + int len; + int id; +} KeyWord; + +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 } +}; + +void +startTemplateLex(Template tmpl, FILE *in) { + if ( keywords->len == 0 ) { + int i; + for(i=0; itemplateContext; + + yyrestart(in); + BEGIN INITIAL; +} + +static int +getIdent(char *word) { + int len = strlen(word); + int i; + + if ( len > keywords[ sizeof(keywords)/sizeof(KeyWord)-1 ].len ) + return LEXEME; + + for(i=0; i= keywords[i].len; i++) + if ( len == keywords[i].len && strcasecmp(word, keywords[i].word) == 0 ) + return keywords[i].id; + + return LEXEME; +} + +static int +yyerror(char *s, int line) { + strbuf = NULL; + tlog(TL_CRIT,"template error at line %d in '%s': %s", line, fileToParse, s); + + return 0; +} + +static void +addstring(char *s, int l) { + while( length + l + 1 >= total ) { + total*=2; + strbuf=mcrealloc(strbuf, total); + } + + memcpy( strbuf+length, s, l); + length+=l; + strbuf[length] = '\0'; +} + +static void +addchar(char s) { + if( length + 2 >= total ) { + total*=2; + strbuf=mcrealloc(strbuf, total); + } + + strbuf[ length++ ] = s; + strbuf[length] = '\0'; +} + +