/*
* xtc - The eXTensible Compiler
* Copyright (C) 2004 Robert Grimm
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package xtc.parser;
import java.io.IOException;
import java.io.Reader;
import xtc.tree.Location;
/**
* The superclass of all packrat parsers. Note that packrat parsers
* also are {@link Result results}. However, this is purely a
* performance optimization and a packrat parser may only be treated
* as a result if it is returned from the {@link #character()} method.
*
* @author Robert Grimm
* @version $Revision: 1.1 $
*/
public abstract class PackratParser extends Result {
/**
* The dummy packrat parser. The dummy packrat parser must
* <i>not</i> be used for parsing anything. Its sole purpose is to
* enable the creation of a {@link ParseError#DUMMY dummy parse
* error}.
*/
public static final PackratParser DUMMY = new PackratParser() {
public PackratParser next() {
throw new IllegalStateException("Dummy packrat parser");
}
};
/** The reader for the character stream to be parsed. */
protected final Reader yyReader;
/** The current index into the character stream. */
protected final int yyCount;
/**
* The file name. This field is not final as to allow parsers, such
* as the C preprocessor, to adjust the file name while parsing.
*/
protected String yyFile;
/**
* The flag for whether the previous character was a carriage
* return. This flag is needed to correctly track line and column
* numbers.
*/
protected boolean yySeenCR;
/**
* The current line. This field is not final as to allow parsers,
* such as the C preprocessor, to adjust the line number while
* parsing.
*/
protected int yyLine;
/**
* The current column. This field is not final as to allow parsers,
* such as the C preprocessor, to adjust the column number while
* parsing.
*/
protected int yyColumn;
/**
* The field for this parser's character. Note that -2 indicates
* that the character has not yet been parsed and -1 that the
* end-of-file has been reached.
*/
private int yyChar;
/**
* Create a new, empty packrat parser. This constructor is used only
* for creating the {@link #DUMMY dummy packrat parser}.
*/
private PackratParser() {
yyReader = null;
yyCount = -1;
yyFile = "*** Not a file! ***";
yySeenCR = false;
yyLine = -1;
yyColumn = -1;
yyChar = -2;
}
/**
* Create a new packrat parser.
*
* @param reader The reader for the character stream to be parsed.
* @param file The name of the file backing the character stream.
*/
public PackratParser(Reader reader, String file) {
yyReader = reader;
yyCount = 0;
yyFile = file;
yySeenCR = false;
yyLine = 1;
yyColumn = 0;
yyChar = -2;
}
/**
* Create a new packrat parser, moving ahead one character. The
* specified packrat parser must represent a valid character; i.e.,
* its {@link #hasValue()} method must return <code>true</code>.
*
* @param previous The previous packrat parser.
*/
protected PackratParser(PackratParser previous) {
yyReader = previous.yyReader;
yyCount = previous.yyCount + 1;
yyChar = -2;
}
/**
* Get the parser for the next character. A concrete implementation
* of this method should simply return the parser object created by
* using the {@link #PackratParser(PackratParser)} constructor with
* <code>this</code> as the argument.
*
* @return The parser for the next character.
*/
protected abstract PackratParser next();
/**
* Update the location information for this parser. This method
* correctly sets this parser's {@link #yyFile}, {@link #yySeenCR},
* {@link #yyLine}, and {@link #yyColumn} fields. It must be
* invoked right after setting the previous parser's {@link #yyChar}
* field to its character value.
*
* @param previous The previous packrat parser.
*/
private void updateLocation(PackratParser previous) {
yyFile = previous.yyFile;
switch (previous.yyChar) {
case '\t':
yySeenCR = false;
yyLine = previous.yyLine;
yyColumn = ((previous.yyColumn >> 3) + 1) << 3;
return;
case '\n':
yySeenCR = false;
if (previous.yySeenCR) {
yyLine = previous.yyLine;
yyColumn = previous.yyColumn;
} else {
yyLine = previous.yyLine + 1;
yyColumn = 0;
}
return;
case '\r':
yySeenCR = true;
yyLine = previous.yyLine + 1;
yyColumn = 0;
return;
default:
yySeenCR = false;
yyLine = previous.yyLine;
yyColumn = previous.yyColumn + 1;
return;
}
}
/**
* Parse a character. This method returns the result of parsing the
* next character offered by this parser's character stream. If
* there is another character, the result is this parser; otherwise,
* it is a {@link ParseError}.
*
* @return The corresponding result.
* @throws IOException Signals an exceptional condition while
* accessing the character stream.
*/
protected final Result character() throws IOException {
switch (yyChar) {
case -2:
yyChar = yyReader.read();
if (0 <= yyChar) {
parser = next();
parser.updateLocation(this);
return this;
}
/* Fall through. */
case -1:
return new ParseError("End-of-file", this);
default:
return this;
}
}
public boolean hasValue() {
return (0 <= yyChar);
}
public char charValue() {
if (0 <= yyChar) {
return (char)yyChar;
} else {
throw new IllegalStateException("No character value available");
}
}
public Object semanticValue() {
throw new IllegalStateException("No semantic value available");
}
public ParseError parseError() {
return ParseError.DUMMY;
}
public SemanticValue createValue(Object value, ParseError error) {
return new SemanticValue(value, parser, error);
}
/**
* Get the difference between this parser and the specified parser.
* Both parsers must parse the same character stream.
*
* @param o The other parser.
* @return The difference as a string.
*/
protected final String getDifference(PackratParser o) {
PackratParser p;
int n;
if (yyCount < o.yyCount) {
p = this;
n = o.yyCount - yyCount;
} else if (yyCount > o.yyCount) {
p = o;
n = yyCount - o.yyCount;
} else {
return "";
}
StringBuffer buf = new StringBuffer(n);
int i = 0;
do {
buf.append((char)p.yyChar);
p = p.parser;
i++;
} while (i < n);
return buf.toString();
}
/**
* Get the current file name.
*
* @return The file name.
*/
public final String file() {
return yyFile;
}
/**
* Get the current line number.
*
* @return The line number.
*/
public final int line() {
return yyLine;
}
/**
* Get the current column number.
*
* @return The column number.
*/
public final int column() {
return yyColumn;
}
/**
* Get the current location.
*
* @return The location.
*/
public final Location location() {
return new Location(yyFile, yyLine, yyColumn);
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append(yyFile);
buf.append(':');
buf.append(Integer.toString(yyLine));
buf.append(',');
buf.append(Integer.toString(yyColumn));
buf.append(": ");
PackratParser p = this;
int i = 0;
do {
if (0 <= p.yyChar) {
buf.append((char)p.yyChar);
p = p.parser;
i++;
if ((20 <= i) && (0 <= p.yyChar)) {
buf.append(" ...");
}
} else {
break;
}
} while (i < 20);
return buf.toString();
}
}