Commit 5af2b7c0 authored by Dimitri van Heesch's avatar Dimitri van Heesch
Browse files

Added basic arithmetic operations to the template expressions, and made the expression lexer faster

parent 92d53a47
Loading
Loading
Loading
Loading
+357 −167
Original line number Original line Diff line number Diff line
@@ -504,6 +504,8 @@ class Operator
         not
         not
         in
         in
         ==, !=, <, >, <=, >=
         ==, !=, <, >, <=, >=
         +, -
         *, /, %
         |
         |
         :
         :
         ,
         ,
@@ -511,7 +513,9 @@ class Operator
    enum Type
    enum Type
    {
    {
      Or, And, Not, In, Equal, NotEqual, Less, Greater, LessEqual,
      Or, And, Not, In, Equal, NotEqual, Less, Greater, LessEqual,
      GreaterEqual, Filter, Colon, Comma, Last
      GreaterEqual, Plus, Minus, Multiply, Divide, Modulo, Filter, Colon, Comma,
      LeftParen, RightParen,
      Last
    };
    };


    static const char *toString(Type op)
    static const char *toString(Type op)
@@ -528,9 +532,16 @@ class Operator
        case Greater:      return ">";
        case Greater:      return ">";
        case LessEqual:    return "<=";
        case LessEqual:    return "<=";
        case GreaterEqual: return ">=";
        case GreaterEqual: return ">=";
        case Plus:         return "+";
        case Minus:        return "-";
        case Multiply:     return "*";
        case Divide:       return "/";
        case Modulo:       return "%";
        case Filter:       return "|";
        case Filter:       return "|";
        case Colon:        return ":";
        case Colon:        return ":";
        case Comma:        return ",";
        case Comma:        return ",";
        case LeftParen:    return "(";
        case RightParen:   return ")";
        case Last:         return "?";
        case Last:         return "?";
      }
      }
      return "?";
      return "?";
@@ -890,7 +901,7 @@ class ExprAstFunctionVariable : public ExprAst
  public:
  public:
    ExprAstFunctionVariable(ExprAst *var,const QList<ExprAst> &args)
    ExprAstFunctionVariable(ExprAst *var,const QList<ExprAst> &args)
      : m_var(var), m_args(args)
      : m_var(var), m_args(args)
    { TRACE(("ExprAstFunctionVariable(%s)\n",var->name().data()));
    { TRACE(("ExprAstFunctionVariable()\n"));
      m_args.setAutoDelete(TRUE);
      m_args.setAutoDelete(TRUE);
    }
    }
    virtual TemplateVariant resolve(TemplateContext *c)
    virtual TemplateVariant resolve(TemplateContext *c)
@@ -982,6 +993,28 @@ class ExprAstNegate : public ExprAst
    ExprAst *m_expr;
    ExprAst *m_expr;
};
};


class ExprAstUnary : public ExprAst
{
  public:
    ExprAstUnary(Operator::Type op,ExprAst *exp) : m_operator(op), m_exp(exp)
    { TRACE(("ExprAstUnary %s\n",Operator::toString(op))); }
   ~ExprAstUnary() { delete m_exp; }
    virtual TemplateVariant resolve(TemplateContext *c)
    {
      TemplateVariant exp = m_exp->resolve(c);
      switch (m_operator)
      {
        case Operator::Minus:
          return -exp.toInt();
        default:
          return TemplateVariant();
      }
    }
  private:
    Operator::Type m_operator;
    ExprAst *m_exp;
};

/** @brief Class representing a binary operator in the AST */
/** @brief Class representing a binary operator in the AST */
class ExprAstBinary : public ExprAst
class ExprAstBinary : public ExprAst
{
{
@@ -992,6 +1025,7 @@ class ExprAstBinary : public ExprAst
   ~ExprAstBinary() { delete m_lhs; delete m_rhs; }
   ~ExprAstBinary() { delete m_lhs; delete m_rhs; }
    virtual TemplateVariant resolve(TemplateContext *c)
    virtual TemplateVariant resolve(TemplateContext *c)
    {
    {
      TemplateContextImpl *ci = dynamic_cast<TemplateContextImpl*>(c);
      TemplateVariant lhs = m_lhs->resolve(c);
      TemplateVariant lhs = m_lhs->resolve(c);
      TemplateVariant rhs = m_rhs ? m_rhs->resolve(c) : TemplateVariant();
      TemplateVariant rhs = m_rhs ? m_rhs->resolve(c) : TemplateVariant();
      switch(m_operator)
      switch(m_operator)
@@ -1040,6 +1074,44 @@ class ExprAstBinary : public ExprAst
          {
          {
            return lhs.toInt()>=rhs.toInt();
            return lhs.toInt()>=rhs.toInt();
          }
          }
        case Operator::Plus:
          {
            return TemplateVariant(lhs.toInt() + rhs.toInt());
          }
        case Operator::Minus:
          {
            return TemplateVariant(lhs.toInt() - rhs.toInt());
          }
        case Operator::Multiply:
          {
            return TemplateVariant(lhs.toInt() * rhs.toInt());
          }
        case Operator::Divide:
          {
            int denom = rhs.toInt();
            if (denom!=0)
            {
              return TemplateVariant(lhs.toInt() / denom);
            }
            else // divide by zero
            {
              ci->warn(ci->templateName(),ci->line(),"division by zero while evaluating expression is undefined");
              return 0;
            }
          }
        case Operator::Modulo:
          {
            int denom = rhs.toInt();
            if (denom!=0)
            {
              return TemplateVariant(lhs.toInt() % denom);
            }
            else // module zero
            {
              ci->warn(ci->templateName(),ci->line(),"modulo zero while evaluating expression is undefined");
              return 0;
            }
          }
        default:
        default:
          return TemplateVariant();
          return TemplateVariant();
      }
      }
@@ -1110,23 +1182,7 @@ class ExpressionParser
      if (expr==0) return 0;
      if (expr==0) return 0;
      m_tokenStream = expr;
      m_tokenStream = expr;
      getNextToken();
      getNextToken();
      return parseOrExpression();
      return parseExpression();
    }

    ExprAst *parsePrimary(const char *expr)
    {
      if (expr==0) return 0;
      m_tokenStream = expr;
      getNextToken();
      return parsePrimaryExpression();
    }

    ExprAst *parseVariable(const char *varExpr)
    {
      if (varExpr==0) return 0;
      m_tokenStream = varExpr;
      getNextToken();
      return parseFilteredVariable();
    }
    }


  private:
  private:
@@ -1149,6 +1205,14 @@ class ExpressionParser
        Operator::Type op;
        Operator::Type op;
    };
    };


    ExprAst *parseExpression()
    {
      TRACE(("{parseExpression(%s)\n",m_tokenStream));
      ExprAst *result = parseOrExpression();
      TRACE(("}parseExpression(%s)\n",m_tokenStream));
      return result;
    }

    ExprAst *parseOrExpression()
    ExprAst *parseOrExpression()
    {
    {
      TRACE(("{parseOrExpression(%s)\n",m_tokenStream));
      TRACE(("{parseOrExpression(%s)\n",m_tokenStream));
@@ -1212,7 +1276,7 @@ class ExpressionParser
    ExprAst *parseCompareExpression()
    ExprAst *parseCompareExpression()
    {
    {
      TRACE(("{parseCompareExpression(%s)\n",m_tokenStream));
      TRACE(("{parseCompareExpression(%s)\n",m_tokenStream));
      ExprAst *lhs = parsePrimaryExpression();
      ExprAst *lhs = parseAdditiveExpression();
      if (lhs)
      if (lhs)
      {
      {
        Operator::Type op = m_curToken.op;
        Operator::Type op = m_curToken.op;
@@ -1235,6 +1299,74 @@ class ExpressionParser
      return lhs;
      return lhs;
    }
    }


    ExprAst *parseAdditiveExpression()
    {
      TRACE(("{parseAdditiveExpression(%s)\n",m_tokenStream));
      ExprAst *lhs = parseMultiplicativeExpression();
      if (lhs)
      {
        while (m_curToken.type==ExprToken::Operator &&
               (m_curToken.op==Operator::Plus || m_curToken.op==Operator::Minus))
        {
          Operator::Type op = m_curToken.op;
          getNextToken();
          ExprAst *rhs = parseMultiplicativeExpression();
          lhs = new ExprAstBinary(op,lhs,rhs);
        }
      }
      TRACE(("}parseAdditiveExpression(%s)\n",m_tokenStream));
      return lhs;
    }

    ExprAst *parseMultiplicativeExpression()
    {
      TRACE(("{parseMultiplicativeExpression(%s)\n",m_tokenStream));
      ExprAst *lhs = parseUnaryExpression();
      if (lhs)
      {
        while (m_curToken.type==ExprToken::Operator &&
               (m_curToken.op==Operator::Multiply || m_curToken.op==Operator::Divide || m_curToken.op==Operator::Modulo))
        {
          Operator::Type op = m_curToken.op;
          getNextToken();
          ExprAst *rhs = parseUnaryExpression();
          lhs = new ExprAstBinary(op,lhs,rhs);
        }
      }
      TRACE(("}parseMultiplicativeExpression(%s)\n",m_tokenStream));
      return lhs;
    }

    ExprAst *parseUnaryExpression()
    {
      TRACE(("{parseUnaryExpression(%s)\n",m_tokenStream));
      ExprAst *result=0;
      if (m_curToken.type==ExprToken::Operator)
      {
        if (m_curToken.op==Operator::Plus)
        {
          getNextToken();
          result = parsePrimaryExpression();
        }
        else if (m_curToken.op==Operator::Minus)
        {
          getNextToken();
          ExprAst *rhs = parsePrimaryExpression();
          result = new ExprAstUnary(m_curToken.op,rhs);
        }
        else
        {
          result = parsePrimaryExpression();
        }
      }
      else
      {
        result = parsePrimaryExpression();
      }
      TRACE(("}parseUnaryExpression(%s)\n",m_tokenStream));
      return result;
    }

    ExprAst *parsePrimaryExpression()
    ExprAst *parsePrimaryExpression()
    {
    {
      TRACE(("{parsePrimary(%s)\n",m_tokenStream));
      TRACE(("{parsePrimary(%s)\n",m_tokenStream));
@@ -1250,17 +1382,30 @@ class ExpressionParser
        case ExprToken::Literal:
        case ExprToken::Literal:
          result = parseLiteral();
          result = parseLiteral();
          break;
          break;
        default:
        case ExprToken::Operator:
          if (m_curToken.type==ExprToken::Operator)
          if (m_curToken.op==Operator::LeftParen)
          {
          {
            warn(m_parser->templateName(),m_line,"unexpected operator '%s' in expression",
            getNextToken(); // skip over opening bracket
                Operator::toString(m_curToken.op));
            result = parseExpression();
            if (m_curToken.type!=ExprToken::Operator ||
                m_curToken.op!=Operator::RightParen)
            {
              warn(m_parser->templateName(),m_line,"missing closing parenthesis");
            }
            }
            else
            else
            {
            {
            warn(m_parser->templateName(),m_line,"unexpected token in expression");
              getNextToken(); // skip over closing bracket
            }
            }
          }
          }
          else
          {
            warn(m_parser->templateName(),m_line,"unexpected operator '%s' in expression",
                Operator::toString(m_curToken.op));
          }
          break;
        default:
          warn(m_parser->templateName(),m_line,"unexpected token in expression");
      }
      TRACE(("}parsePrimary(%s)\n",m_tokenStream));
      TRACE(("}parsePrimary(%s)\n",m_tokenStream));
      return result;
      return result;
    }
    }
@@ -1364,83 +1509,121 @@ class ExpressionParser
      if (p==0 || *p=='\0') return FALSE;
      if (p==0 || *p=='\0') return FALSE;
      while (*p==' ') p++; // skip over spaces
      while (*p==' ') p++; // skip over spaces
      char c=*p;
      char c=*p;
      if (strncmp(p,"not ",4)==0)
      const char *q = p;
      {
      switch (c)
        m_curToken.type = ExprToken::Operator;
        m_curToken.op = Operator::Not;
        p+=4;
      }
      else if (strncmp(p,"and ",4)==0)
      {
      {
        m_curToken.type = ExprToken::Operator;
        case '=':
        m_curToken.op = Operator::And;
          if (c=='=' && *(p+1)=='=') // equal
        p+=4;
      }
      else if (strncmp(p,"or ",3)==0)
          {
          {
        m_curToken.type = ExprToken::Operator;
        m_curToken.op = Operator::Or;
        p+=3;
      }
      else if (c=='=' && *(p+1)=='=')
      {
        m_curToken.type = ExprToken::Operator;
            m_curToken.op = Operator::Equal;
            m_curToken.op = Operator::Equal;
            p+=2;
            p+=2;
          }
          }
      else if (c=='!' && *(p+1)=='=')
          break;
        case '!':
          if (c=='!' && *(p+1)=='=') // not equal
          {
          {
        m_curToken.type = ExprToken::Operator;
            m_curToken.op = Operator::NotEqual;
            m_curToken.op = Operator::NotEqual;
            p+=2;
            p+=2;
          }
          }
      else if (c=='<' && *(p+1)=='=')
          break;
        case '<':
          if (c=='<' && *(p+1)=='=') // less or equal
          {
          {
        m_curToken.type = ExprToken::Operator;
            m_curToken.op = Operator::LessEqual;
            m_curToken.op = Operator::LessEqual;
            p+=2;
            p+=2;
          }
          }
      else if (c=='>' && *(p+1)=='=')
          else // less
      {
        m_curToken.type = ExprToken::Operator;
        m_curToken.op = Operator::GreaterEqual;
        p+=2;
      }
      else if (c=='<')
          {
          {
        m_curToken.type = ExprToken::Operator;
            m_curToken.op = Operator::Less;
            m_curToken.op = Operator::Less;
            p++;
            p++;
          }
          }
      else if (c=='>')
          break;
        case '>':
          if (c=='>' && *(p+1)=='=') // greater or equal
          {
            m_curToken.op = Operator::GreaterEqual;
            p+=2;
          }
          else // greater
          {
          {
        m_curToken.type = ExprToken::Operator;
            m_curToken.op = Operator::Greater;
            m_curToken.op = Operator::Greater;
            p++;
            p++;
          }
          }
      else if (c=='|')
          break;
      {
        case '(':
        m_curToken.type = ExprToken::Operator;
          m_curToken.op = Operator::LeftParen;
          p++;
          break;
        case ')':
          m_curToken.op = Operator::RightParen;
          p++;
          break;
        case '|':
          m_curToken.op = Operator::Filter;
          m_curToken.op = Operator::Filter;
          p++;
          p++;
      }
          break;
      else if (c==':')
        case '+':
      {
          m_curToken.op = Operator::Plus;
        m_curToken.type = ExprToken::Operator;
          p++;
          break;
        case '-':
          m_curToken.op = Operator::Minus;
          p++;
          break;
        case '*':
          m_curToken.op = Operator::Multiply;
          p++;
          break;
        case '/':
          m_curToken.op = Operator::Divide;
          p++;
          break;
        case '%':
          m_curToken.op = Operator::Modulo;
          p++;
          break;
        case ':':
          m_curToken.op = Operator::Colon;
          m_curToken.op = Operator::Colon;
          p++;
          p++;
          break;
        case ',':
          m_curToken.op = Operator::Comma;
          p++;
          break;
        case 'n':
          if (strncmp(p,"not ",4)==0)
          {
            m_curToken.op = Operator::Not;
            p+=4;
          }
          break;
        case 'a':
          if (strncmp(p,"and ",4)==0)
          {
            m_curToken.op = Operator::And;
            p+=4;
          }
          break;
        case 'o':
          if (strncmp(p,"or ",3)==0)
          {
            m_curToken.op = Operator::Or;
            p+=3;
          }
          break;
        default:
          break;
      }
      }
      else if (c==',')
      if (p!=q) // found an operator
      {
      {
        m_curToken.type = ExprToken::Operator;
        m_curToken.type = ExprToken::Operator;
        m_curToken.op = Operator::Comma;
        p++;
      }
      }
      else if ((c=='-' && *(p+1)>='0' && *(p+1)<='9') || (c>='0' && c<='9'))
      else // no token found yet
      {
        if (c>='0' && c<='9') // number?
        {
        {
          m_curToken.type = ExprToken::Number;
          m_curToken.type = ExprToken::Number;
          const char *np = p;
          const char *np = p;
        if (c=='-') np++;
          m_curToken.num = 0;
          m_curToken.num = 0;
          while (*np>='0' && *np<='9')
          while (*np>='0' && *np<='9')
          {
          {
@@ -1448,10 +1631,9 @@ class ExpressionParser
            m_curToken.num+=*np-'0';
            m_curToken.num+=*np-'0';
            np++;
            np++;
          }
          }
        if (c=='-') m_curToken.num=-m_curToken.num;
          p=np;
          p=np;
        }
        }
      else if (c=='_' || (c>='a' && c<='z') || (c>='A' && c<='Z'))
        else if (c=='_' || (c>='a' && c<='z') || (c>='A' && c<='Z')) // identifier?
        {
        {
          m_curToken.type = ExprToken::Identifier;
          m_curToken.type = ExprToken::Identifier;
          s[0]=c;
          s[0]=c;
@@ -1479,7 +1661,7 @@ class ExpressionParser
            m_curToken.num = 0;
            m_curToken.num = 0;
          }
          }
        }
        }
      else if (c=='"' || c=='\'')
        else if (c=='"' || c=='\'') // string literal
        {
        {
          m_curToken.type = ExprToken::Literal;
          m_curToken.type = ExprToken::Literal;
          m_curToken.id.resize(0);
          m_curToken.id.resize(0);
@@ -1498,7 +1680,8 @@ class ExpressionParser
          }
          }
          if (*p==tokenChar) p++;
          if (*p==tokenChar) p++;
        }
        }
      else
      }
      if (p==q) // still no valid token found -> error
      {
      {
        m_curToken.type = ExprToken::Unknown;
        m_curToken.type = ExprToken::Unknown;
        char s[2];
        char s[2];
@@ -1758,7 +1941,11 @@ class TemplateNodeVariable : public TemplateNode
    {
    {
      TRACE(("TemplateNodeVariable(%s)\n",var.data()));
      TRACE(("TemplateNodeVariable(%s)\n",var.data()));
      ExpressionParser expParser(parser,line);
      ExpressionParser expParser(parser,line);
      m_var = expParser.parseVariable(var);
      m_var = expParser.parse(var);
      if (m_var==0)
      {
        parser->warn(m_templateName,line,"invalid expression '%s' for variable",var.data());
      }
    }
    }
    ~TemplateNodeVariable()
    ~TemplateNodeVariable()
    {
    {
@@ -1769,6 +1956,8 @@ class TemplateNodeVariable : public TemplateNode
    {
    {
      TemplateContextImpl* ci = dynamic_cast<TemplateContextImpl*>(c);
      TemplateContextImpl* ci = dynamic_cast<TemplateContextImpl*>(c);
      ci->setLocation(m_templateName,m_line);
      ci->setLocation(m_templateName,m_line);
      if (m_var)
      {
        TemplateVariant v = m_var->resolve(c);
        TemplateVariant v = m_var->resolve(c);
        if (v.type()==TemplateVariant::Function)
        if (v.type()==TemplateVariant::Function)
        {
        {
@@ -1784,6 +1973,7 @@ class TemplateNodeVariable : public TemplateNode
          ts << v.toString();
          ts << v.toString();
        }
        }
      }
      }
    }


  private:
  private:
    QCString m_templateName;
    QCString m_templateName;
@@ -1893,7 +2083,7 @@ class TemplateNodeRepeat : public TemplateNodeCreator<TemplateNodeRepeat>
    {
    {
      TRACE(("{TemplateNodeRepeat(%s)\n",data.data()));
      TRACE(("{TemplateNodeRepeat(%s)\n",data.data()));
      ExpressionParser expParser(parser,line);
      ExpressionParser expParser(parser,line);
      m_expr = expParser.parseVariable(data);
      m_expr = expParser.parse(data);
      QStrList stopAt;
      QStrList stopAt;
      stopAt.append("endrepeat");
      stopAt.append("endrepeat");
      parser->parse(this,line,stopAt,m_repeatNodes);
      parser->parse(this,line,stopAt,m_repeatNodes);
@@ -1985,7 +2175,7 @@ class TemplateNodeFor : public TemplateNodeCreator<TemplateNodeFor>
        }
        }
      }
      }
      ExpressionParser expParser(parser,line);
      ExpressionParser expParser(parser,line);
      m_expr = expParser.parseVariable(exprStr);
      m_expr = expParser.parse(exprStr);


      QStrList stopAt;
      QStrList stopAt;
      stopAt.append("endfor");
      stopAt.append("endfor");
@@ -2215,7 +2405,7 @@ class TemplateNodeExtend : public TemplateNodeCreator<TemplateNodeExtend>
      {
      {
        parser->warn(m_templateName,line,"extend tag is missing template file argument");
        parser->warn(m_templateName,line,"extend tag is missing template file argument");
      }
      }
      m_extendExpr = ep.parsePrimary(data);
      m_extendExpr = ep.parse(data);
      QStrList stopAt;
      QStrList stopAt;
      parser->parse(this,line,stopAt,m_nodes);
      parser->parse(this,line,stopAt,m_nodes);
      TRACE(("}TemplateNodeExtend(%s)\n",data.data()));
      TRACE(("}TemplateNodeExtend(%s)\n",data.data()));
@@ -2298,7 +2488,7 @@ class TemplateNodeInclude : public TemplateNodeCreator<TemplateNodeInclude>
      {
      {
        parser->warn(m_templateName,line,"include tag is missing template file argument");
        parser->warn(m_templateName,line,"include tag is missing template file argument");
      }
      }
      m_includeExpr = ep.parsePrimary(data);
      m_includeExpr = ep.parse(data);
    }
    }
   ~TemplateNodeInclude()
   ~TemplateNodeInclude()
    {
    {
@@ -2374,8 +2564,8 @@ class TemplateNodeCreate : public TemplateNodeCreator<TemplateNodeCreate>
      else
      else
      {
      {
        ExpressionParser ep(parser,line);
        ExpressionParser ep(parser,line);
        m_fileExpr = ep.parsePrimary(data.left(i).stripWhiteSpace());
        m_fileExpr = ep.parse(data.left(i).stripWhiteSpace());
        m_templateExpr = ep.parsePrimary(data.mid(i+6).stripWhiteSpace());
        m_templateExpr = ep.parse(data.mid(i+6).stripWhiteSpace());
      }
      }
    }
    }
   ~TemplateNodeCreate()
   ~TemplateNodeCreate()
@@ -2461,7 +2651,7 @@ class TemplateNodeTree : public TemplateNodeCreator<TemplateNodeTree>
      {
      {
        parser->warn(m_templateName,line,"recursetree tag is missing data argument");
        parser->warn(m_templateName,line,"recursetree tag is missing data argument");
      }
      }
      m_treeExpr = ep.parsePrimary(data);
      m_treeExpr = ep.parse(data);
      QStrList stopAt;
      QStrList stopAt;
      stopAt.append("endrecursetree");
      stopAt.append("endrecursetree");
      parser->parse(this,line,stopAt,m_treeNodes);
      parser->parse(this,line,stopAt,m_treeNodes);
@@ -2569,7 +2759,7 @@ class TemplateNodeWith : public TemplateNodeCreator<TemplateNodeWith>
        int j=arg.find('=');
        int j=arg.find('=');
        if (j>0)
        if (j>0)
        {
        {
          ExprAst *expr = expParser.parsePrimary(arg.mid(j+1));
          ExprAst *expr = expParser.parse(arg.mid(j+1));
          if (expr)
          if (expr)
          {
          {
            m_args.append(new Mapping(arg.left(j),expr));
            m_args.append(new Mapping(arg.left(j),expr));
@@ -2627,7 +2817,7 @@ class TemplateNodeCycle : public TemplateNodeCreator<TemplateNodeCycle>
      QValueListIterator<QCString> it = args.begin();
      QValueListIterator<QCString> it = args.begin();
      while (it!=args.end())
      while (it!=args.end())
      {
      {
        ExprAst *expr = expParser.parsePrimary(*it);
        ExprAst *expr = expParser.parse(*it);
        if (expr)
        if (expr)
        {
        {
          m_args.append(expr);
          m_args.append(expr);
@@ -2697,7 +2887,7 @@ class TemplateNodeSet : public TemplateNodeCreator<TemplateNodeSet>
        int j=arg.find('=');
        int j=arg.find('=');
        if (j>0)
        if (j>0)
        {
        {
          ExprAst *expr = expParser.parsePrimary(arg.mid(j+1));
          ExprAst *expr = expParser.parse(arg.mid(j+1));
          if (expr)
          if (expr)
          {
          {
            m_args.append(new Mapping(arg.left(j),expr));
            m_args.append(new Mapping(arg.left(j),expr));
@@ -2776,8 +2966,8 @@ class TemplateNodeMarkers : public TemplateNodeCreator<TemplateNodeMarkers>
      {
      {
        ExpressionParser expParser(parser,line);
        ExpressionParser expParser(parser,line);
        m_var = data.left(i);
        m_var = data.left(i);
        m_listExpr = expParser.parseVariable(data.mid(i+4,w-i-4));
        m_listExpr = expParser.parse(data.mid(i+4,w-i-4));
        m_patternExpr = expParser.parseVariable(data.right(data.length()-w-6));
        m_patternExpr = expParser.parse(data.right(data.length()-w-6));
      }
      }
      QStrList stopAt;
      QStrList stopAt;
      stopAt.append("endmarkers");
      stopAt.append("endmarkers");