/* * Parser.cs * * This work is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * This work is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA * * As a special exception, the copyright holders of this library give * you permission to link this library with independent modules to * produce an executable, regardless of the license terms of these * independent modules, and to copy and distribute the resulting * executable under terms of your choice, provided that you also meet, * for each linked independent module, the terms and conditions of the * license of that module. An independent module is a module which is * not derived from or based on this library. If you modify this * library, you may extend this exception to your version of the * library, but you are not obligated to do so. If you do not wish to * do so, delete this exception statement from your version. * * Copyright (c) 2003 Per Cederberg. All rights reserved. */ using System; using System.Collections; using System.Text; namespace PerCederberg.Grammatica.Parser { /** * A base parser class. This class provides the standard parser * interface, as well as token handling. * * @author Per Cederberg, <per at percederberg dot net> * @version 1.1 */ public abstract class Parser { /** * The parser initialization flag. */ private bool initialized = false; /** * The tokenizer to use. */ private Tokenizer tokenizer; /** * The analyzer to use for callbacks. */ private Analyzer analyzer; /** * The list of production patterns. */ private ArrayList patterns = new ArrayList(); /** * The map with production patterns and their id:s. This map * contains the production patterns indexed by their id:s. */ private Hashtable patternIds = new Hashtable(); /** * The list of buffered tokens. This list will contain tokens that * have been read from the tokenizer, but not yet consumed. */ private ArrayList tokens = new ArrayList(); /** * The error log. All parse errors will be added to this log as * the parser attempts to recover from the error. If the error * count is higher than zero (0), this log will be thrown as the * result from the parse() method. */ private ParserLogException errorLog = new ParserLogException(); /** * The error recovery counter. This counter is initially set to a * negative value to indicate that no error requiring recovery * has been encountered. When a parse error is found, the counter * is set to three (3), and is then decreased by one for each * correctly read token until it reaches zero (0). */ private int errorRecovery = -1; /** * Creates a new parser. * * @param tokenizer the tokenizer to use */ internal Parser(Tokenizer tokenizer) : this(tokenizer, new Analyzer()) { } /** * Creates a new parser. * * @param tokenizer the tokenizer to use * @param analyzer the analyzer callback to use */ internal Parser(Tokenizer tokenizer, Analyzer analyzer) { this.tokenizer = tokenizer; this.analyzer = analyzer; } /** * Sets the parser initialized flag. Normally this flag is set by * the prepare() method, but this method allows further * modifications to it. * * @param initialized the new initialized flag */ internal void SetInitialized(bool initialized) { this.initialized = initialized; } /** * Adds a new production pattern to the parser. The first pattern * added is assumed to be the starting point in the grammar. The * patterns added may be validated to some extent. * * @param pattern the pattern to add * * @throws ParserCreationException if the pattern couldn't be * added correctly to the parser */ public virtual void AddPattern(ProductionPattern pattern) { if (pattern.GetAlternativeCount() <= 0) { throw new ParserCreationException( ParserCreationException.ErrorType.INVALID_PRODUCTION, pattern.GetName(), "no production alternatives are present (must have at " + "least one)"); } if (patternIds.ContainsKey(pattern.GetId())) { throw new ParserCreationException( ParserCreationException.ErrorType.INVALID_PRODUCTION, pattern.GetName(), "another pattern with the same id (" + pattern.GetId() + ") has already been added"); } patterns.Add(pattern); patternIds.Add(pattern.GetId(), pattern); SetInitialized(false); } /** * Initializes the parser. All the added production patterns will * be analyzed for ambiguities and errors. This method also * initializes internal data structures used during the parsing. * * @throws ParserCreationException if the parser couldn't be * initialized correctly */ public virtual void Prepare() { if (patterns.Count <= 0) { throw new ParserCreationException( ParserCreationException.ErrorType.INVALID_PARSER, "no production patterns have been added"); } for (int i = 0; i < patterns.Count; i++) { CheckPattern((ProductionPattern) patterns[i]); } SetInitialized(true); } /** * Checks a production pattern for completeness. If some rule * in the pattern referenced an production pattern not added * to this parser, a parser creation exception will be thrown. * * @param pattern the production pattern to check * * @throws ParserCreationException if the pattern referenced a * pattern not added to this parser */ private void CheckPattern(ProductionPattern pattern) { for (int i = 0; i < pattern.GetAlternativeCount(); i++) { CheckRule(pattern.GetName(), pattern.GetAlternative(i)); } } /** * Checks a production pattern rule for completeness. If some * element in the rule referenced an production pattern not * added to this parser, a parser creation exception will be * thrown. * * @param name the name of the pattern being checked * @param rule the production pattern rule to check * * @throws ParserCreationException if the rule referenced a * pattern not added to this parser */ private void CheckRule(string name, ProductionPatternAlternative rule) { for (int i = 0; i < rule.GetElementCount(); i++) { CheckElement(name, rule.GetElement(i)); } } /** * Checks a production pattern element for completeness. If * the element references a production pattern not added to * this parser, a parser creation exception will be thrown. * * @param name the name of the pattern being checked * @param elem the production pattern element to check * * @throws ParserCreationException if the element referenced a * pattern not added to this parser */ private void CheckElement(string name, ProductionPatternElement elem) { if (elem.IsProduction() && GetPattern(elem.GetId()) == null) { throw new ParserCreationException( ParserCreationException.ErrorType.INVALID_PRODUCTION, name, "an undefined production pattern id (" + elem.GetId() + ") is referenced"); } } /** * Parses the token stream and returns a parse tree. This method * will call prepare() if not previously called. In case of a * parse error, the parser will attempt to recover and throw all * the errors found in a parser log exception in the end. * * @return the parse tree * * @throws ParserCreationException if the parser couldn't be * initialized correctly * @throws ParserLogException if the input couldn't be parsed * correctly * * @see #prepare */ public Node Parse() { Node root = null; // Initialize parser if (!initialized) { Prepare(); } // Parse input try { root = ParseStart(); } catch (ParseException e) { AddError(e, true); } // Check for errors if (errorLog.GetErrorCount() > 0) { throw errorLog; } return root; } /** * Parses the token stream and returns a parse tree. * * @return the parse tree * * @throws ParseException if the input couldn't be parsed * correctly */ protected abstract Node ParseStart(); /** * Adds an error to the error log. If the parser is in error * recovery mode, the error will not be added to the log. If the * recovery flag is set, this method will set the error recovery * counter thus enter error recovery mode. Only lexical or * syntactical errors require recovery, so this flag shouldn't be * set otherwise. * * @param e the error to add * @param recovery the recover flag */ internal void AddError(ParseException e, bool recovery) { if (errorRecovery <= 0) { errorLog.AddError(e); } if (recovery) { errorRecovery = 3; } } /** * Returns the production pattern with the specified id. * * @param id the production pattern id * * @return the production pattern found, or * null if non-existent */ internal ProductionPattern GetPattern(int id) { return (ProductionPattern) patternIds[id]; } /** * Returns the production pattern for the starting production. * * @return the start production pattern, or * null if no patterns have been added */ internal ProductionPattern GetStartPattern() { if (patterns.Count <= 0) { return null; } else { return (ProductionPattern) patterns[0]; } } /** * Returns the ordered set of production patterns. * * @return the ordered set of production patterns */ internal ICollection GetPatterns() { return patterns; } /** * Handles the parser entering a production. This method calls the * appropriate analyzer callback if the node is not hidden. Note * that this method will not call any callback if an error * requiring recovery has ocurred. * * @param node the parse tree node */ internal void EnterNode(Node node) { if (!node.IsHidden() && errorRecovery < 0) { try { analyzer.Enter(node); } catch (ParseException e) { AddError(e, false); } } } /** * Handles the parser leaving a production. This method calls the * appropriate analyzer callback if the node is not hidden, and * returns the result. Note that this method will not call any * callback if an error requiring recovery has ocurred. * * @param node the parse tree node * * @return the parse tree node, or * null if no parse tree should be created */ internal Node ExitNode(Node node) { if (!node.IsHidden() && errorRecovery < 0) { try { return analyzer.Exit(node); } catch (ParseException e) { AddError(e, false); } } return node; } /** * Handles the parser adding a child node to a production. This * method calls the appropriate analyzer callback. Note that this * method will not call any callback if an error requiring * recovery has ocurred. * * @param node the parent parse tree node * @param child the child parse tree node, or null */ internal void AddNode(Production node, Node child) { if (errorRecovery >= 0) { // Do nothing } else if (node.IsHidden()) { node.AddChild(child); } else if (child != null && child.IsHidden()) { for (int i = 0; i < child.GetChildCount(); i++) { AddNode(node, child.GetChildAt(i)); } } else { try { analyzer.Child(node, child); } catch (ParseException e) { AddError(e, false); } } } /** * Reads and consumes the next token in the queue. If no token * was available for consumation, a parse error will be * thrown. * * @return the token consumed * * @throws ParseException if the input stream couldn't be read or * parsed correctly */ internal Token NextToken() { Token token = PeekToken(0); if (token != null) { tokens.RemoveAt(0); return token; } else { throw new ParseException( ParseException.ErrorType.UNEXPECTED_EOF, null, tokenizer.GetCurrentLine(), tokenizer.GetCurrentColumn()); } } /** * Reads and consumes the next token in the queue. If no token was * available for consumation, a parse error will be thrown. A * parse error will also be thrown if the token id didn't match * the specified one. * * @param id the expected token id * * @return the token consumed * * @throws ParseException if the input stream couldn't be parsed * correctly, or if the token wasn't expected */ internal Token NextToken(int id) { Token token = NextToken(); ArrayList list; if (token.GetId() == id) { if (errorRecovery > 0) { errorRecovery--; } return token; } else { list = new ArrayList(1); list.Add(tokenizer.GetPatternDescription(id)); throw new ParseException( ParseException.ErrorType.UNEXPECTED_TOKEN, token.ToShortString(), list, token.GetStartLine(), token.GetStartColumn()); } } /** * Returns a token from the queue. This method is used to check * coming tokens before they have been consumed. Any number of * tokens forward can be checked. * * @param steps the token queue number, zero (0) for first * * @return the token in the queue, or * null if no more tokens in the queue */ internal Token PeekToken(int steps) { Token token; while (steps >= tokens.Count) { try { token = tokenizer.Next(); if (token == null) { return null; } else { tokens.Add(token); } } catch (ParseException e) { AddError(e, true); } } return (Token) tokens[steps]; } /** * Returns a string representation of this parser. The string will * contain all the production definitions and various additional * information. * * @return a detailed string representation of this parser */ public override string ToString() { StringBuilder buffer = new StringBuilder(); for (int i = 0; i < patterns.Count; i++) { buffer.Append(ToString((ProductionPattern) patterns[i])); buffer.Append("\n"); } return buffer.ToString(); } /** * Returns a string representation of a production pattern. * * @param prod the production pattern * * @return a detailed string representation of the pattern */ private string ToString(ProductionPattern prod) { StringBuilder buffer = new StringBuilder(); StringBuilder indent = new StringBuilder(); LookAheadSet set; int i; buffer.Append(prod.GetName()); buffer.Append(" ("); buffer.Append(prod.GetId()); buffer.Append(") "); for (i = 0; i < buffer.Length; i++) { indent.Append(" "); } buffer.Append("= "); indent.Append("| "); for (i = 0; i < prod.GetAlternativeCount(); i++) { if (i > 0) { buffer.Append(indent); } buffer.Append(ToString(prod.GetAlternative(i))); buffer.Append("\n"); } for (i = 0; i < prod.GetAlternativeCount(); i++) { set = prod.GetAlternative(i).GetLookAhead(); if (set.GetMaxLength() > 1) { buffer.Append("Using "); buffer.Append(set.GetMaxLength()); buffer.Append(" token look-ahead for alternative "); buffer.Append(i + 1); buffer.Append(": "); buffer.Append(set.ToString(tokenizer)); buffer.Append("\n"); } } return buffer.ToString(); } /** * Returns a string representation of a production pattern * alternative. * * @param alt the production pattern alternative * * @return a detailed string representation of the alternative */ private string ToString(ProductionPatternAlternative alt) { StringBuilder buffer = new StringBuilder(); for (int i = 0; i < alt.GetElementCount(); i++) { if (i > 0) { buffer.Append(" "); } buffer.Append(ToString(alt.GetElement(i))); } return buffer.ToString(); } /** * Returns a string representation of a production pattern * element. * * @param elem the production pattern element * * @return a detailed string representation of the element */ private string ToString(ProductionPatternElement elem) { StringBuilder buffer = new StringBuilder(); int min = elem.GetMinCount(); int max = elem.GetMaxCount(); if (min == 0 && max == 1) { buffer.Append("["); } if (elem.IsToken()) { buffer.Append(GetTokenDescription(elem.GetId())); } else { buffer.Append(GetPattern(elem.GetId()).GetName()); } if (min == 0 && max == 1) { buffer.Append("]"); } else if (min == 0 && max == Int32.MaxValue) { buffer.Append("*"); } else if (min == 1 && max == Int32.MaxValue) { buffer.Append("+"); } else if (min != 1 || max != 1) { buffer.Append("{"); buffer.Append(min); buffer.Append(","); buffer.Append(max); buffer.Append("}"); } return buffer.ToString(); } /** * Returns a token description for a specified token. * * @param token the token to describe * * @return the token description */ internal string GetTokenDescription(int token) { if (tokenizer == null) { return ""; } else { return tokenizer.GetPatternDescription(token); } } } }