/*
* Copyright (C) Chaperon. All rights reserved.
* -------------------------------------------------------------------------
* This software is published under the terms of the Apache Software License
* version 1.1, a copy of which has been included with this distribution in
* the LICENSE file.
*/
package net.sourceforge.chaperon.parser;
import java.io.InputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Stack;
import java.util.Vector;
import org.apache.avalon.framework.logger.LogEnabled;
import org.apache.avalon.framework.logger.Logger;
import net.sourceforge.chaperon.helpers.Decoder;
import net.sourceforge.chaperon.helpers.IntegerList;
import net.sourceforge.chaperon.parser.output.EventListener;
import net.sourceforge.chaperon.parser.output.EventQueue;
/**
* A LR Parser, which implements a stack maschine
*
* @author Stephan Michels
* @version CVS $Id: Parser.java,v 1.11 2002/05/07 11:01:49 benedikta Exp $
*/
public class Parser implements LogEnabled
{
private Logger _logger;
/**
* Provide component with a logger.
*
* @param logger the logger
*/
public void enableLogging(Logger logger)
{
_logger = logger;
}
/*
* Parse the document given by an inputstream
*
* @param table Parser table
* @param input InputStream
*
* @return Structured document
*
* @throws ParserException
*/
public EventQueue parse(ParserTable table, InputStream input) throws ParserException
{
try
{
return parse(table, input, null);
} catch (UnsupportedEncodingException uee)
{
System.err.println("Encoding is not supported:"+uee.getMessage());
}
return null;
}
/*
* Parse the document given by an inputstream
*
* @param table Parser table
* @param input InputStream
* @param encoding Encoding the document
*
* @return Structured document
*
* @throws ParserException
*/
public EventQueue parse(ParserTable table, InputStream input, String encoding)
throws ParserException, UnsupportedEncodingException
{
if (input== null)
throw new NullPointerException("Inputstream is null");
if (table == null)
throw new NullPointerException("ParserTable is null");
Scanner scanner = new Scanner(table);
if ((encoding!=null) && (encoding.length()>0))
scanner.parse(input, encoding);
else
scanner.parse(input);
char[] text = scanner.getTextAsChars();
long time = System.currentTimeMillis();
int state = 0; // Current state
int i, j, oldstate;
IntegerList stack = new IntegerList();
stack.push(0); // First state is zero
Stack treestack = new Stack();
int tokenindex = -1; // Index of the token from the input
while (true)
{
state = stack.peek();
/* ============================= Reading token ================================ */
if (tokenindex < 0)
{
do
{
scanner.readNextToken(state);
tokenindex = scanner.getTokenIndex();
//System.out.println("tokenindex="+tokenindex+" ignorable="+scanner.isIgnorable());
if (scanner.isIgnorable())
{
TokenContainer tokencontainer = new TokenContainer();
tokencontainer.tokenindex = tokenindex;
tokencontainer.textstart = scanner.getTextStart();
tokencontainer.textlength = scanner.getTextLength();
tokencontainer.ignorable = true;
treestack.push(tokencontainer);
}
} while ((tokenindex>=0) && (scanner.isIgnorable()));
if (tokenindex < 0)
{
if (_logger!=null)
_logger.debug("State " + state + " no token could get recognized");
Vector acceptedsymbols = new Vector();
for (tokenindex = 0; tokenindex < table.getTerminalSymbolCount(); tokenindex++)
if (!table.isErrorAction(state, tokenindex))
acceptedsymbols.addElement(table.getTerminalSymbol(tokenindex));
String[] symbols = new String[acceptedsymbols.size()];
for (i = 0; i < acceptedsymbols.size(); i++)
symbols[i] = (String) acceptedsymbols.elementAt(i);
throw new ParserException("Token was not recognized",
scanner.getLineNumber(),
scanner.getColumnNumber(), symbols);
}
}
switch (table.getAction(state, tokenindex))
{
/* ================================== Error =================================== */
case ParserTable.ERROR :
if (_logger!=null)
_logger.debug("State " + state + " error token \"" +
Decoder.decode(new String(text, scanner.getTextStart(), scanner.getTextLength())) +
"\"("+table.getTerminalSymbol(tokenindex)+")");
Vector acceptedsymbols = new Vector();
for (tokenindex = 0; tokenindex < table.getTerminalSymbolCount(); tokenindex++)
if (!table.isErrorAction(state, tokenindex))
acceptedsymbols.addElement(table.getTerminalSymbol(tokenindex));
String[] symbols = new String[acceptedsymbols.size()];
for (i = 0; i < acceptedsymbols.size(); i++)
symbols[i] = (String) acceptedsymbols.elementAt(i);
throw new ParserException("Token was not recognized",
scanner.getLineNumber(),
scanner.getColumnNumber(), symbols);
/* ==================================== Shift =================================== */
case ParserTable.SHIFT :
if (_logger!=null)
_logger.debug("State " + state + " shift token \"" +
Decoder.decode(new String(text, scanner.getTextStart(), scanner.getTextLength())) +
"\"("+table.getTerminalSymbol(tokenindex)+")");
TokenContainer tokencontainer = new TokenContainer();
tokencontainer.tokenindex = tokenindex;
tokencontainer.textstart = scanner.getTextStart();
tokencontainer.textlength = scanner.getTextLength();
treestack.push(tokencontainer);
stack.push(table.getActionArgument(state, tokenindex));
tokenindex = -1;
break;
/* ============================ Reduce & Accept =================================== */
case ParserTable.REDUCE :
case ParserTable.ACCEPT :
if (_logger!=null)
_logger.debug("State " + state + " reduce by " +
table.getNonTerminalSymbol(table.getProductionSymbol(
table.getActionArgument(state, tokenindex))) +
" (" + (table.getActionArgument(state, tokenindex)) + ")");
ProductionContainer newcontainer = new ProductionContainer();
newcontainer.productionindex = table.getActionArgument(state, tokenindex);
for (i = 0; i < table.getProductionLength(table.getActionArgument(state, tokenindex)); i++)
{
if (treestack.peek() instanceof ProductionContainer)
{
stack.pop();
ProductionContainer container = (ProductionContainer) treestack.pop();
switch (table.getProductionReduceType(container.productionindex))
{
case ParserTable.APPEND : // APPEND
if (table.getProductionSymbol(newcontainer.productionindex)==
table.getProductionSymbol(container.productionindex))
newcontainer.insertChilds(container);
else
newcontainer.insert(container);
break;
case ParserTable.RESOLVE : // RESOLVE
newcontainer.insertChilds(container);
break;
case ParserTable.NORMAL : // NORMAL
newcontainer.insert(container);
break;
case ParserTable.NEGLECT : // NEGLECT
break;
}
}
else
{
TokenContainer container = (TokenContainer) treestack.pop();
newcontainer.insert(container);
if (container.ignorable)
i--;
else
stack.pop();
}
}
oldstate = stack.peek();
treestack.push(newcontainer);
//stack.push(table.getTransition(oldstate, table.getProductionSymbol(table.getActionArgument(state,
// tokenindex))));
/* ================================== Accept =================================== */
if ((table.isAcceptAction(state, tokenindex)) && (stack.getSize() == 1))
{
if (_logger!=null)
_logger.debug("State " + state + " accept & reduce production " +
table.getActionArgument(state, tokenindex));
stack.pop();
DocumentContainer documentcontainer = new DocumentContainer(table, text,
(ProductionContainer)treestack.pop());
if (_logger!=null)
_logger.debug("Parsing time " + (System.currentTimeMillis() - time) + " ms");
return documentcontainer;
}
else
stack.push(table.getTransition(oldstate, table.getProductionSymbol(
table.getActionArgument(state, tokenindex))));
}
}
}
private class Container
{
public Container previoussibling = null;
public Container nextsibling = null;
}
private class TokenContainer extends Container
{
public int tokenindex = -1;
public int textstart = -1;
public int textlength = -1;
public boolean ignorable = false;
}
private class ProductionContainer extends Container
{
public int productionindex = -1;
public Container firstchild = null;
public Container lastchild = null;
public void insert(Container container)
{
if (firstchild==null)
{
firstchild = container;
lastchild = container;
}
else
{
firstchild.previoussibling = container;
container.nextsibling = firstchild;
firstchild = container;
}
}
public void insertChilds(ProductionContainer container)
{
if (firstchild==null)
{
firstchild = container.firstchild;
lastchild = container.lastchild;
}
else
{
firstchild.previoussibling = container.lastchild;
container.lastchild.nextsibling = firstchild;
firstchild = container.firstchild;
}
}
}
private class DocumentContainer implements EventQueue
{
private ParserTable _table = null;
private char[] _text = null;
private ProductionContainer _root = null;
private DocumentContainer(ParserTable table, char[] text, ProductionContainer root)
{
_table = table;
_text = text;
_root = root;
}
public void fireEvents(EventListener listener)
{
listener.startDocument(_table);
fireEvents(listener, _root);
listener.endDocument(_table);
}
private void fireEvents(EventListener listener, Container container)
{
if (container instanceof ProductionContainer)
{
ProductionContainer productioncontainer = (ProductionContainer) container;
listener.startProduction(_table, productioncontainer.productionindex);
Container next = productioncontainer.firstchild;
while (next!=null)
{
fireEvents(listener, next);
next = next.nextsibling;
}
listener.endProduction(_table, productioncontainer.productionindex);
}
else
{
TokenContainer tokencontainer = (TokenContainer)container;
if (tokencontainer.ignorable)
{
listener.startIgnorableToken(_table, tokencontainer.tokenindex,
_text,
tokencontainer.textstart,
tokencontainer.textlength);
listener.endIgnorableToken(_table, tokencontainer.tokenindex);
}
else
{
listener.startToken(_table, tokencontainer.tokenindex,
_text,
tokencontainer.textstart,
tokencontainer.textlength);
listener.endToken(_table, tokencontainer.tokenindex);
}
}
}
}
}