websitepanel/WebsitePanel/Sources/WebsitePanel.Templates/Parser.cs

712 lines
25 KiB
C#

// Copyright (c) 2012, Outercurve Foundation.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
// - Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// - 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.
//
// - Neither the name of the Outercurve Foundation nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT HOLDER OR 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.
using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;
using System.Diagnostics;
using WebsitePanel.Templates.AST;
namespace WebsitePanel.Templates
{
internal class Parser
{
Lexer lexer;
Token current;
List<Statement> statements;
public Parser(Lexer lexer)
{
this.lexer = lexer;
this.statements = new List<Statement>();
}
Token Consume()
{
Token old = current;
current = lexer.Next();
return old;
}
Token Consume(TokenType type)
{
Token old = current;
current = lexer.Next();
if (old.TokenType != type)
throw new ParserException("Unexpected token: " + current.TokenType.ToString() + ". Was expecting: " + type,
current.Line, current.Column);
return old;
}
Token Current
{
get { return current; }
}
public List<Statement> Parse()
{
statements.Clear();
Consume();
while (true)
{
Statement stm = ParseStatement();
if (stm == null)
break;
else
statements.Add(stm);
}
foreach (Statement stm in statements)
Debug.Write(stm);
return statements;
}
private Statement ParseStatement()
{
switch (Current.TokenType)
{
case TokenType.EOF:
return null;
case TokenType.Print:
return ParsePrintStatement();
case TokenType.Set:
return ParseSetStatement();
case TokenType.If:
return ParseIfStatement();
case TokenType.Foreach:
return ParseForeachStatement();
case TokenType.For:
return ParseForStatement();
case TokenType.Template:
return ParseTemplateStatement();
case TokenType.OpenTag:
return ParseCallTemplateStatement();
case TokenType.Text:
TextStatement text = new TextStatement(Current.Data, Current.Line, Current.Column);
Consume();
return text;
default:
throw new ParserException("Invalid token: " + Current.TokenType.ToString(), Current.Line, Current.Column);
}
}
private Statement ParsePrintStatement()
{
Consume(TokenType.Print);
PrintStatement stm = new PrintStatement(current.Line, current.Column);
stm.PrintExpression = ParseExpression();
return stm;
}
private Statement ParseSetStatement()
{
// parse statement declaration
SetStatement stm = new SetStatement(current.Line, current.Column);
Consume(TokenType.Set);
// parse tag attributes
do
{
if (Current.TokenType != TokenType.Attribute)
throw new ParserException("Expected tag attribute", stm.Line, stm.Column);
string attrName = Current.Data;
Consume(TokenType.Attribute);
Consume(TokenType.Assign);
// look for "test" attribute
if (attrName == "name")
{
stm.Name = Current.Data;
Consume(TokenType.String);
}
else if (attrName == "value")
{
stm.ValueExpression = ParseExpression();
}
}
while (Current.TokenType == TokenType.Attribute);
// check statement
if (stm.Name == null)
throw new ParserException("\"name\" attribute was not specified", stm.Line, stm.Column);
if (stm.ValueExpression == null)
throw new ParserException("\"value\" attribute was not specified", stm.Line, stm.Column);
// it must be an "empty" tag
Consume(TokenType.EmptyTag);
return stm;
}
private Statement ParseIfStatement()
{
// parse statement declaration
IfStatement stm = new IfStatement(current.Line, current.Column);
Consume(TokenType.If);
if(Current.TokenType != TokenType.Attribute)
throw new ParserException("Expected tag attribute", stm.Line, stm.Column);
// look for "test" attribute
if (Current.Data == "test")
{
Consume(TokenType.Attribute);
Consume(TokenType.Assign);
stm.Condition = ParseExpression();
}
// check statement
if (stm.Condition == null)
throw new ParserException("\"test\" attribute was not specified", stm.Line, stm.Column);
bool falseBlock = false;
// parse body
while (true)
{
if (Current.TokenType == TokenType.EndIf)
{
break;
}
else if (Current.TokenType == TokenType.Else)
{
Consume(); // else
falseBlock = true;
continue;
}
else if (Current.TokenType == TokenType.ElseIf)
{
stm.ElseIfStatements.Add(ParseElseIfStatement());
}
else
{
// parse statement
Statement blockStm = ParseStatement();
if (blockStm == null)
throw new ParserException("Expected closing </ad:if> tag", stm.Line, stm.Column);
if (falseBlock)
stm.FalseStatements.Add(blockStm);
else
stm.TrueStatements.Add(blockStm);
}
}
Consume(TokenType.EndIf);
return stm;
}
private ElseIfStatement ParseElseIfStatement()
{
// parse statement declaration
ElseIfStatement stm = new ElseIfStatement(current.Line, current.Column);
Consume(TokenType.ElseIf);
if (Current.TokenType != TokenType.Attribute)
throw new ParserException("Expected tag attribute", stm.Line, stm.Column);
// look for "test" attribute
if (Current.Data == "test")
{
Consume(TokenType.Attribute);
Consume(TokenType.Assign);
stm.Condition = ParseExpression();
}
// check statement
if (stm.Condition == null)
throw new ParserException("\"test\" attribute was not specified", stm.Line, stm.Column);
// parse body
while (true)
{
if (Current.TokenType == TokenType.ElseIf
|| Current.TokenType == TokenType.Else
|| Current.TokenType == TokenType.EndIf)
{
break;
}
else
{
Statement blockStm = ParseStatement();
if (blockStm == null)
throw new ParserException("Expected <ad:elseif>, <ad:else> or </ad:if> tags", stm.Line, stm.Column);
// parse statement
stm.TrueStatements.Add(blockStm);
}
}
return stm;
}
private Statement ParseForeachStatement()
{
// parse statement declaration
ForeachStatement stm = new ForeachStatement(current.Line, current.Column);
Consume(TokenType.Foreach);
// parse tag attributes
do
{
if (Current.TokenType != TokenType.Attribute)
throw new ParserException("Expected tag attribute", stm.Line, stm.Column);
string attrName = Current.Data;
Consume(TokenType.Attribute);
Consume(TokenType.Assign);
// look for "test" attribute
if (attrName == "collection")
{
stm.Collection = ParseExpression();
}
else if (attrName == "var")
{
stm.ElementIdentifier = Current.Data;
Consume(TokenType.String);
}
else if (attrName == "index")
{
stm.IndexIdentifier = Current.Data;
Consume(TokenType.String);
}
}
while (Current.TokenType == TokenType.Attribute);
// check statement
if (stm.Collection == null)
throw new ParserException("\"collection\" attribute was not specified", stm.Line, stm.Column);
if (stm.ElementIdentifier == null)
throw new ParserException("\"var\" attribute was not specified", stm.Line, stm.Column);
// parse body
while (true)
{
if (Current.TokenType == TokenType.EndForeach)
{
break;
}
else
{
Statement blockStm = ParseStatement();
if (blockStm == null)
throw new ParserException("Expected {/foreach} statement", stm.Line, stm.Column);
// parse statement
stm.Statements.Add(blockStm);
}
}
Consume(TokenType.EndForeach);
return stm;
}
private Statement ParseForStatement()
{
// parse statement declaration
ForStatement stm = new ForStatement(current.Line, current.Column);
Consume(TokenType.For);
// parse tag attributes
do
{
if (Current.TokenType != TokenType.Attribute)
throw new ParserException("Expected tag attribute", stm.Line, stm.Column);
string attrName = Current.Data;
Consume(TokenType.Attribute);
Consume(TokenType.Assign);
// look for "test" attribute
if (attrName == "from")
{
stm.StartIndex = ParseExpression();
}
else if (attrName == "to")
{
stm.EndIndex = ParseExpression();
}
else if (attrName == "index")
{
stm.IndexIdentifier = Current.Data;
Consume(TokenType.String);
}
}
while (Current.TokenType == TokenType.Attribute);
// check statement
if (stm.StartIndex == null)
throw new ParserException("\"from\" attribute was not specified", stm.Line, stm.Column);
if (stm.EndIndex == null)
throw new ParserException("\"to\" attribute was not specified", stm.Line, stm.Column);
if (stm.IndexIdentifier == null)
throw new ParserException("\"index\" attribute was not specified", stm.Line, stm.Column);
// parse body
while (true)
{
if (Current.TokenType == TokenType.EndFor)
{
break;
}
else
{
Statement blockStm = ParseStatement();
if (blockStm == null)
throw new ParserException("Expected </ad:for> tag", stm.Line, stm.Column);
// parse statement
stm.Statements.Add(blockStm);
}
}
Consume(TokenType.EndFor);
return stm;
}
private Statement ParseTemplateStatement()
{
// parse statement declaration
TemplateStatement stm = new TemplateStatement(current.Line, current.Column);
Consume(TokenType.Template);
// parse tag attributes
do
{
if (Current.TokenType != TokenType.Attribute)
throw new ParserException("Expected tag attribute", stm.Line, stm.Column);
string attrName = Current.Data;
Consume(TokenType.Attribute);
Consume(TokenType.Assign);
// look for "test" attribute
if (attrName == "name")
{
stm.Name = Current.Data;
Consume(TokenType.String);
}
}
while (Current.TokenType == TokenType.Attribute);
// check statement
if (stm.Name == null)
throw new ParserException("\"name\" attribute was not specified", stm.Line, stm.Column);
// parse body
while (true)
{
if (Current.TokenType == TokenType.EndTemplate)
{
break;
}
else
{
Statement blockStm = ParseStatement();
if (blockStm == null)
throw new ParserException("Expected {/template} tag", stm.Line, stm.Column);
// parse statement
stm.Statements.Add(blockStm);
}
}
Consume(TokenType.EndTemplate);
return stm;
}
private Statement ParseCallTemplateStatement()
{
// parse statement declaration
CallTemplateStatement stm = new CallTemplateStatement(current.Line, current.Column);
stm.TemplateName = Current.Data;
Consume(TokenType.OpenTag);
// parse tag attributes
do
{
if (Current.TokenType != TokenType.Attribute)
throw new ParserException("Expected tag attribute", stm.Line, stm.Column);
string attrName = Current.Data;
Consume(TokenType.Attribute);
Consume(TokenType.Assign);
// save parameter
stm.Parameters.Add(attrName, ParseExpression());
}
while (Current.TokenType == TokenType.Attribute);
// parse body
while (true)
{
if (Current.TokenType == TokenType.EmptyTag)
{
// />
Consume(TokenType.EmptyTag);
break;
}
else if (Current.TokenType == TokenType.CloseTag
&& Current.Data == stm.TemplateName)
{
Consume(TokenType.CloseTag);
break;
}
else
{
Statement blockStm = ParseStatement();
if (blockStm == null)
throw new ParserException("Expected closing tag", stm.Line, stm.Column);
// parse statement
stm.Statements.Add(blockStm);
}
}
return stm;
}
private Expression ParseExpression()
{
return ParseLogicalExpression();
}
private Expression ParseLogicalExpression()
{
Expression lhs = ParseRelationalExpression();
return ParseLogicalExpressionRest(lhs);
}
private Expression ParseLogicalExpressionRest(Expression lhs)
{
if (Current.TokenType == TokenType.And || Current.TokenType == TokenType.Or)
{
Consume(); // &&, ||
Expression rhs = ParseRelationalExpression();
BinaryExpression lhsChild = new BinaryExpression(lhs.Line, lhs.Column, lhs, TokenType.And, rhs);
return ParseLogicalExpressionRest(lhsChild);
}
else
{
return lhs;
}
}
private Expression ParseRelationalExpression()
{
Expression lhs = ParseAdditiveExpression();
if (Current.TokenType == TokenType.Less
|| Current.TokenType == TokenType.LessOrEqual
|| Current.TokenType == TokenType.Greater
|| Current.TokenType == TokenType.GreaterOrEqual
|| Current.TokenType == TokenType.Equal
|| Current.TokenType == TokenType.NotEqual)
{
Token tok = Consume(); // <, >, <=, >=, ==, !=
Expression rhs = ParseRelationalExpression(); // recursion
return new BinaryExpression(lhs.Line, lhs.Column, lhs, tok.TokenType, rhs);
}
return lhs;
}
private Expression ParseAdditiveExpression()
{
Expression lhs = ParseMultiplyExpression();
return ParseAdditiveExpressionRest(lhs);
}
private Expression ParseAdditiveExpressionRest(Expression lhs)
{
if (Current.TokenType == TokenType.Plus
|| Current.TokenType == TokenType.Minus)
{
Token tok = Consume(); // +, -
Expression rhs = ParseMultiplyExpression();
BinaryExpression lhsChild = new BinaryExpression(lhs.Line, lhs.Column, lhs, tok.TokenType, rhs);
return ParseAdditiveExpressionRest(lhsChild);
}
else
{
return lhs;
}
}
private Expression ParseMultiplyExpression()
{
Expression lhs = ParseUnaryExpression();
return ParseMultiplyExpressionRest(lhs);
}
private Expression ParseMultiplyExpressionRest(Expression lhs)
{
if (Current.TokenType == TokenType.Mult
|| Current.TokenType == TokenType.Div
|| Current.TokenType == TokenType.Mod)
{
Token tok = Consume(); // *, /, %
Expression rhs = ParseUnaryExpression();
BinaryExpression lhsChild = new BinaryExpression(lhs.Line, lhs.Column, lhs, tok.TokenType, rhs);
return ParseMultiplyExpressionRest(lhsChild);
}
return lhs;
}
private Expression ParseUnaryExpression()
{
if (Current.TokenType == TokenType.Minus ||
Current.TokenType == TokenType.Not)
{
Token tok = Consume(); // -, !
Expression rhs = ParseUnaryExpression();
UnaryExpression exp = new UnaryExpression(Current.Line, Current.Column, tok.TokenType, rhs);
return exp;
}
return ParsePrimaryExpression();
}
private Expression ParsePrimaryExpression()
{
if (Current.TokenType == TokenType.String)
{
LiteralExpression literal = new LiteralExpression(Current.Line, Current.Column, Current.Data);
Consume();
return literal;
}
else if (Current.TokenType == TokenType.Null)
{
LiteralExpression literal = new LiteralExpression(Current.Line, Current.Column, null);
Consume();
return literal;
}
else if (Current.TokenType == TokenType.True)
{
LiteralExpression literal = new LiteralExpression(Current.Line, Current.Column, true);
Consume();
return literal;
}
else if (Current.TokenType == TokenType.False)
{
LiteralExpression literal = new LiteralExpression(Current.Line, Current.Column, false);
Consume();
return literal;
}
else if (Current.TokenType == TokenType.Integer)
{
LiteralExpression literal = new LiteralExpression(Current.Line, Current.Column, Int32.Parse(Current.Data));
Consume();
return literal;
}
else if (Current.TokenType == TokenType.Decimal)
{
LiteralExpression literal = new LiteralExpression(Current.Line, Current.Column,
Decimal.Parse(Current.Data, NumberFormatInfo.InvariantInfo));
Consume();
return literal;
}
else if (Current.TokenType == TokenType.Identifier)
{
IdentifierExpression id = new IdentifierExpression(Current.Line, Current.Column);
while (Current.TokenType == TokenType.Identifier)
{
id.Parts.Add(ParseIdentifierPart());
if (Current.TokenType != TokenType.Dot)
break;
else
{
Consume(); // .
if(Current.TokenType != TokenType.Identifier)
throw new ParserException("Wrong usage of period. Identifier was expected.", Current.Line, Current.Column);
}
}
return id;
}
else if (Current.TokenType == TokenType.LParen)
{
Consume(); // eat (
Expression exp = ParseExpression();
Consume(TokenType.RParen); // eat )
return exp;
}
else
throw new ParserException("Invalid token in expression: " + Current.TokenType + ". Was expecting identifier, string or number", Current.Line, Current.Column);
}
private IdentifierPart ParseIdentifierPart()
{
IdentifierPart part = new IdentifierPart(Current.Data, Current.Line, Current.Column);
Consume(); // consume identifier
// check for indexer
if (Current.TokenType == TokenType.LBracket)
{
Consume(); // [
part.Index = ParseExpression();
Consume(TokenType.RBracket); // ]
}
// check for method call
else if (Current.TokenType == TokenType.LParen)
{
Consume(); // (
part.IsMethod = true;
// parse parameters
while (Current.TokenType != TokenType.RParen)
{
part.MethodParameters.Add(ParseExpression());
if (Current.TokenType == TokenType.RParen)
break; // )
Consume(TokenType.Comma); // ,
}
Consume(TokenType.RParen); // )
}
return part;
}
}
}