All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mitchellbosecke.pebble.parser.ParserImpl Maven / Gradle / Ivy

/*******************************************************************************
 * This file is part of Pebble.
 * 
 * Original work Copyright (c) 2009-2013 by the Twig Team
 * Modified work Copyright (c) 2013 by Mitchell Bösecke
 * 
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 ******************************************************************************/
package com.mitchellbosecke.pebble.parser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import com.mitchellbosecke.pebble.PebbleEngine;
import com.mitchellbosecke.pebble.error.ParserException;
import com.mitchellbosecke.pebble.lexer.Token;
import com.mitchellbosecke.pebble.lexer.TokenStream;
import com.mitchellbosecke.pebble.node.Node;
import com.mitchellbosecke.pebble.node.NodeBlock;
import com.mitchellbosecke.pebble.node.NodeBody;
import com.mitchellbosecke.pebble.node.NodeExpression;
import com.mitchellbosecke.pebble.node.NodeMacro;
import com.mitchellbosecke.pebble.node.NodePrint;
import com.mitchellbosecke.pebble.node.NodeRoot;
import com.mitchellbosecke.pebble.node.NodeText;
import com.mitchellbosecke.pebble.tokenParser.TokenParser;
import com.mitchellbosecke.pebble.tokenParser.TokenParserBroker;

public class ParserImpl implements Parser {

	/**
	 * A reference to the main engine.
	 */
	private final PebbleEngine engine;

	/**
	 * TokenParserBroker filled with token parsers that were originally provided
	 * by the extensions
	 */
	private TokenParserBroker tokenParserBroker;

	/**
	 * An expression parser.
	 */
	private ExpressionParser expressionParser;

	/**
	 * The TokenStream that we are converting into an Abstract Syntax Tree.
	 */
	private TokenStream stream;

	/**
	 * The parent template file name which must be known for a proper
	 * compilation.
	 */
	private String parentFileName;

	/**
	 * Blocks to be compiled.
	 */
	private Map blocks;

	/**
	 * Macros to be compiled. Macros can be overloaded by name which explains
	 * why it's a Map of Lists.
	 */
	private Map macros;

	/**
	 * blockStack stores the names of the nested blocks to ensure that we always
	 * have access to the name of the block that we are currently in. This can
	 * be useful when implementing functions such as parent().
	 */
	private Stack blockStack;

	/**
	 * Parser stack storing the stateful data. This is so that one Parser
	 * instance can be used to parse multiple different templates by storing and
	 * resuming it's state
	 */
	private Stack parserStack = new Stack<>();

	/**
	 * Constructor
	 * 
	 * @param engine
	 *            The main PebbleEngine that this parser is working for
	 */
	public ParserImpl(PebbleEngine engine) {
		this.engine = engine;
	}

	/**
	 * Private constructor that takes in all stateful data
	 */
	private ParserImpl(PebbleEngine engine, TokenStream stream, String parentFileName, Map blocks,
			Map macros) {
		this.engine = engine;
		this.stream = stream;
		this.parentFileName = parentFileName;
		this.setBlocks(blocks);
		this.setMacros(macros);
	}

	@Override
	public NodeRoot parse(TokenStream stream) throws ParserException {

		// token parsers which have come from the extensions
		this.tokenParserBroker = engine.getTokenParserBroker();

		// expression parser
		this.expressionParser = new ExpressionParser(this);

		/*
		 * Store the state of this current parser just in case this parser
		 * instance was already being used to parse a template and this
		 * particular occurrence is a "sub template"
		 */
		parserStack.push(new ParserImpl(engine, stream, parentFileName, getBlocks(), getMacros()));

		this.stream = stream;

		this.parentFileName = null;

		this.blocks = new HashMap<>();
		this.blockStack = new Stack<>();

		this.setMacros(new HashMap());

		NodeBody body = subparse();

		NodeRoot root = new NodeRoot(body, parentFileName, getBlocks(), getMacros(), stream.getFilename());

		/*
		 * Resume the parser state
		 */
		Parser oldState = parserStack.pop();
		this.stream = oldState.getStream();
		this.parentFileName = oldState.getParentFileName();
		this.setBlocks(oldState.getBlocks());
		this.setMacros(oldState.getMacros());

		return root;
	}

	@Override
	public NodeBody subparse() throws ParserException {
		return subparse(null);
	}

	@Override
	/**
	 * The main method for the parser. This method does the work of converting
	 * a TokenStream into a Node
	 * 
	 * @param stopCondition	A stopping condition provided by a token parser
	 * @return Node		The root node of the generated Abstract Syntax Tree
	 */
	public NodeBody subparse(StoppingCondition stopCondition) throws ParserException {

		// these nodes will be the children of the root node
		List nodes = new ArrayList<>();

		Token token;
		while (!stream.isEOF()) {

			switch (stream.current().getType()) {
				case TEXT:

					/*
					 * The current token is a text token. Not much to do here
					 * other than convert it to a text Node.
					 */
					token = stream.current();
					nodes.add(new NodeText(token.getValue(), token.getLineNumber()));
					stream.next();
					break;

				case PRINT_START:

					/*
					 * We are entering a print delimited region at this point.
					 * These regions will contain some sort of expression so
					 * let's pass control to our expression parser.
					 */

					// go to the next token because the current one is just the
					// opening delimiter
					token = stream.next();

					NodeExpression expression = this.expressionParser.parseExpression();
					nodes.add(new NodePrint(expression, token.getLineNumber()));

					// we expect to see a print closing delimiter
					stream.expect(Token.Type.PRINT_END, engine.getLexer().getPrintCloseDelimiter());

					break;

				case EXECUTE_START:

					// go to the next token because the current one is just the
					// opening delimiter
					stream.next();

					token = stream.current();

					/*
					 * We expect a name token at the beginning of every block.
					 * 
					 * We do not use stream.expect() because it consumes the
					 * current token. The current token may be needed by a token
					 * parser which has provided a stopping condition. Ex. the
					 * 'if' token parser may need to check if the current token
					 * is either 'endif' or 'else' and act accordingly, thus we
					 * should not consume it.
					 */
					if (!Token.Type.NAME.equals(token.getType())) {
						throw new ParserException(null, "A block must start with a tag name.", token.getLineNumber(),
								stream.getFilename());
					}

					// If this method was executed using a TokenParser and
					// that parser provided a stopping condition (ex. checking
					// for the 'endif' token) let's check for that condition
					// now.
					if (stopCondition != null && stopCondition.evaluate(token)) {
						return new NodeBody(token.getLineNumber(), nodes);
					}

					// find an appropriate parser for this name
					TokenParser subparser = tokenParserBroker.getTokenParser(token.getValue());

					if (subparser == null) {
						throw new ParserException(null, String.format("Unexpected tag name \"%s\"", token.getValue()),
								token.getLineNumber(), stream.getFilename());
					}

					subparser.setParser(this);
					Node node = subparser.parse(token);

					// node might be null (ex. token is "extend" and the parser
					// simply sets the parent node)
					if (node != null) {
						nodes.add(node);
					}

					break;

				default:
					throw new ParserException(null, "Parser ended in undefined state.", stream.current()
							.getLineNumber(), stream.getFilename());
			}
		}

		// create the root node with the children that we have found
		return new NodeBody(stream.current().getLineNumber(), nodes);
	}

	@Override
	public TokenStream getStream() {
		return stream;
	}

	public void setStream(TokenStream stream) {
		this.stream = stream;
	}

	@Override
	public String getParentFileName() {
		return this.parentFileName;
	}

	public void setParentFileName(String parent) {
		this.parentFileName = parent;
	}

	@Override
	public void setBlock(String name, NodeBlock block) {
		getBlocks().put(name, block);
	}

	@Override
	public void pushBlockStack(String name) {
		blockStack.push(name);
	}

	@Override
	public void popBlockStack() {
		blockStack.pop();
	}

	public String peekBlockStack() {
		return blockStack.lastElement();
	}

	@Override
	public ExpressionParser getExpressionParser() {
		return this.expressionParser;
	}

	@Override
	public PebbleEngine getEngine() {
		return engine;
	}

	@Override
	public Map getBlocks() {
		return blocks;
	}

	@Override
	public void setBlocks(Map blocks) {
		this.blocks = blocks;
	}

	@Override
	public Map getMacros() {
		return macros;
	}

	@Override
	public void addMacro(String name, NodeMacro macro) {
		macros.put(name, macro);
	}

	public void setMacros(Map macros) {
		this.macros = macros;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy