Add simple template library
authorteodor <teodor>
Tue, 30 Sep 2008 13:21:38 +0000 (13:21 +0000)
committerteodor <teodor>
Tue, 30 Sep 2008 13:21:38 +0000 (13:21 +0000)
Makefile
data/template.tmpl [new file with mode: 0644]
data/template_include.tmpl [new file with mode: 0644]
expected/tmpl [new file with mode: 0644]
template.c [new file with mode: 0644]
template.h [new file with mode: 0644]
tests/tmpl [new file with mode: 0644]
tmpl_gram.y [new file with mode: 0644]
tmpl_scan.l [new file with mode: 0644]

index a387934..ba4ced9 100644 (file)
--- 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 (file)
index 0000000..774a0b3
--- /dev/null
@@ -0,0 +1,57 @@
+id: <% ID %> - <i>simple</i>
+idhex: <% ID, "0x%08x" %> - <b>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 @><ol><@ ENDIF @>
+                       <li><% camenty %>
+               <@ IF __LAST @></ol><@ ENDIF @>
+       <@ ENDLOOP @>
+<@ ENDLOOP @>
diff --git a/data/template_include.tmpl b/data/template_include.tmpl
new file mode 100644 (file)
index 0000000..e7b52cf
--- /dev/null
@@ -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 (file)
index 0000000..7585c3b
--- /dev/null
@@ -0,0 +1,66 @@
+initTemplate: 0
+id: 17 - <i>simple</i>
+idhex: 0x00000011 - <b>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
+
+       
+               <ol>
+                       <li>Number 1
+               
+       
+               
+                       <li>Number 2
+               </ol>
+       
+
+       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 (file)
index 0000000..4f6fbdd
--- /dev/null
@@ -0,0 +1,830 @@
+/*
+ * cOpyright (c) 2004 Teodor Sigaev <teodor@sigaev.ru>
+ * 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 <sys/types.h>
+#include <string.h>
+
+#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; i<instance->nrow;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 (file)
index 0000000..9b45bea
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+ * Copyright (c) 2008 Teodor Sigaev <teodor@sigaev.ru>
+ * 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 <sys/types.h>
+#include <time.h>
+#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 (file)
index 0000000..b73ec1b
--- /dev/null
@@ -0,0 +1 @@
+./tmpltest -t data/template.tmpl
diff --git a/tmpl_gram.y b/tmpl_gram.y
new file mode 100644 (file)
index 0000000..09353fc
--- /dev/null
@@ -0,0 +1,206 @@
+%{
+#include <string.h>
+#include <tlog.h>
+#include <tmalloc.h>
+#include <template.h>
+#include <tmpl_gram.h>
+
+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>                   node
+%type <node>                   listnodes
+%type <node>                   template
+%type <node>                   condition
+%type <node>                   condition_varname
+%type <str>                            varname
+
+%type <flags>                  opt_escape
+%type <flags>                  opt_global
+%type <str>                            opt_default
+%type <str>                            opt_format
+
+%token <str>                   OR_P
+%token <str>                   STRING
+%token <str>                   FILENAME
+%token <str>                   TEXT_P
+%token <str>                   LEXEME
+%token <str>                   VAR_OPEN VAR_CLOSE EXPR_OPEN EXPR_CLOSE 
+                                               INCLUDE_OPEN INCLUDE_CLOSE
+%token <str>                   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 (file)
index 0000000..4379919
--- /dev/null
@@ -0,0 +1,243 @@
+%{
+#include <tlog.h>
+#include <tmalloc.h>
+#include <template.h>
+#include <tmpl_gram.h>
+
+#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_/\.]
+
+%%
+
+<INITIAL>\<\%          {
+                                               BEGIN xVAR;
+                                               return VAR_OPEN;
+                                       }
+
+<INITIAL>\<\@          {
+                                               BEGIN xEXPR;
+                                               return EXPR_OPEN;
+                                       }
+
+<INITIAL>\<\&          {
+                                               BEGIN xINCLUDE;
+                                               return INCLUDE_OPEN;
+                                       }
+
+<INITIAL>\<\#          {
+                                               BEGIN xCOMMENT;
+                                       }
+
+<xVAR>{LEXEMCHAR}+     {
+                                               yylval.str      = strlower(mcnstrdup(lexContext, yytext, yyleng));
+                                               return  getIdent(yylval.str);
+                                       }
+
+<xVAR>\|\|                     { return OR_P; }
+
+<xVAR>[,\|\^]          {
+                                               yylval.punct = *yytext;
+                                               return yylval.punct;
+                                       }
+
+<xVAR>\%\>                     {
+                                               BEGIN INITIAL;
+                                               return VAR_CLOSE;
+                                       }
+
+<xVAR>\"                       {
+                                               total = 1024;
+                                               strbuf = mcalloc(lexContext, total);
+                                               length=0;
+                                               BEGIN xQUOTED;
+                                       }
+
+
+<xQUOTED>\\(.|\n)      { addchar(yytext[1]); }
+
+<xQUOTED>\"                    {
+                                               yylval.str = strbuf;
+                                               BEGIN xVAR;
+                                               strbuf = NULL;
+                                               return STRING;
+                                       }
+                                               
+<xQUOTED>[^\"\\]+      { addstring(yytext, yyleng); }
+
+<xQUOTED>\\                    { 
+                                               /* This is only needed for \ just before EOF */
+                                               addchar(*yytext); 
+                                       }
+
+<xEXPR>{LEXEMCHAR}+ {
+                                               yylval.str  = strlower(mcnstrdup(lexContext, yytext, yyleng));
+                                               return  getIdent(yylval.str);
+                                       }
+
+<xEXPR>\@\>                    {
+                                               BEGIN INITIAL;
+                                               return  EXPR_CLOSE;
+                                       }
+
+<xINCLUDE>{PATH}+      {
+                                               yylval.str  = mcnstrdup(lexContext, yytext, yyleng);
+                                               return FILENAME;
+                                       }
+
+<xINCLUDE>\&\>         {
+                                               BEGIN INITIAL;
+                                               return INCLUDE_CLOSE;
+                                       }
+
+<xCOMMENT>\#\>         {
+                                               BEGIN INITIAL;
+                                       }
+
+<xVAR,xINCLUDE,xEXPR,xCOMMENT>\n+      ;
+
+<xVAR,xINCLUDE,xEXPR,xCOMMENT>[\r\t ]+         ;
+
+<xCOMMENT>.            ;
+
+<xVAR,xINCLUDE,xEXPR>. { 
+                                               return yyerror("Syntax error in template tag", yylineno); 
+                                       }
+
+<xVAR,xQUOTED,xINCLUDE,xEXPR,xCOMMENT><<EOF>> { 
+                                               return yyerror("unterminated template tag", yylineno); 
+                                       }
+
+<INITIAL>[^\<]+                {
+                                               yylval.str  = mcnstrdup(lexContext, yytext, yyleng);
+                                               return TEXT_P;
+                                       }
+
+<INITIAL>([^\<]*\<[^\&\#\@\%][^\<]*)+  {
+                                               yylval.str  = mcnstrdup(lexContext, yytext, yyleng);
+                                               return TEXT_P;
+                                       }
+                               
+
+<INITIAL>\<            {
+                                               /* This is only needed for < just before EOF */
+                                               yylval.str  = mcnstrdup(lexContext, yytext, yyleng);
+                                               return TEXT_P;
+                                       }
+
+<INITIAL><<EOF>>       {
+                                       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; i<sizeof(keywords)/sizeof(KeyWord);i++)
+                       keywords[i].len = strlen(keywords[i].word);
+       }
+
+       lexContext = tmpl->templateContext;
+
+       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<sizeof(keywords)/sizeof(KeyWord) && len >= 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';
+}
+
+