/*
* 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.build;
import net.sourceforge.chaperon.build.conflict.ConflictList;
import net.sourceforge.chaperon.build.conflict.ReduceReduceConflict;
import net.sourceforge.chaperon.build.conflict.ShiftReduceConflict;
import net.sourceforge.chaperon.common.IntegerSet;
import net.sourceforge.chaperon.model.Violations;
import net.sourceforge.chaperon.model.grammar.Associativity;
import net.sourceforge.chaperon.model.grammar.Error;
import net.sourceforge.chaperon.model.grammar.Grammar;
import net.sourceforge.chaperon.model.symbol.SymbolSet;
import net.sourceforge.chaperon.model.symbol.Terminal;
import net.sourceforge.chaperon.process.ParserAutomaton;
import org.apache.commons.logging.Log;
/**
* This class represents a builder for parser automata.
*
* @author <a href="mailto:stephan@apache.org">Stephan Michels</a>
* @version CVS $Id: ParserAutomatonBuilder.java,v 1.27 2003/12/14 09:48:33 benedikta Exp $
*/
public class ParserAutomatonBuilder
{
private static final EndOfFile EOF = new EndOfFile();
private Grammar grammar;
private SymbolSet tsymbols;
private SymbolSet ntsymbols;
private FirstSetCollection firstsets;
private ItemSetCollection itemsets;
private ParserAutomaton automaton;
private ConflictList conflicts = new ConflictList();
private Log log;
/**
* Create a builder for an parser automaton.
*
* @param grammar Grammar, which should be used to build the automaton.
*/
public ParserAutomatonBuilder(Grammar grammar)
{
this(grammar, null);
}
/**
* Create a builder for an parser automaton.
*
* @param grammar Grammar, which should be used to build the automaton.
* @param log Log, which should be used.
*/
public ParserAutomatonBuilder(Grammar grammar, Log log)
{
this.grammar = (Grammar)grammar.clone();
this.log = log;
Violations violations = grammar.validate();
if ((violations!=null) && (violations.getViolationCount()>0))
throw new IllegalArgumentException("Grammar is not valid: "+violations.getViolation(0));
SymbolSet symbols = grammar.getSymbols();
tsymbols = symbols.getTerminals();
ntsymbols = symbols.getNonterminals();
//long time = System.currentTimeMillis();
// generate all first sets
if ((log!=null) && (log.isDebugEnabled()))
log.debug("Generating first sets");
firstsets = new FirstSetCollection(grammar, log);
if ((log!=null) && (log.isDebugEnabled()))
log.debug(firstsets.toString());
// calculation of alle states and transitions
if ((log!=null) && (log.isDebugEnabled()))
log.debug("Building states and transitions");
itemsets = new ItemSetCollection(grammar, firstsets, log);
if ((log!=null) && (log.isDebugEnabled()))
log.debug(itemsets.toString());
if ((log!=null) && (log.isDebugEnabled()))
log.debug("Building parser automaton");
automaton =
new ParserAutomaton(tsymbols.getSymbolCount(), ntsymbols.getSymbolCount(),
grammar.getProductionCount(), 0, itemsets.getItemSetCount());
// for alle terminal symbols
for (int i = 0; i<tsymbols.getSymbolCount(); i++)
automaton.setTerminal(i, tsymbols.getSymbol(i).getName());
// for the non terminal symbols
for (int i = 0; i<ntsymbols.getSymbolCount(); i++)
automaton.setNonterminal(i, ntsymbols.getSymbol(i).getName());
// for all productions
for (int i = 0; i<grammar.getProductionCount(); i++)
{
automaton.setProductionSymbol(i, ntsymbols.indexOf(grammar.getProduction(i).getSymbol()));
automaton.setProductionLength(i, grammar.getProduction(i).getLength());
}
// for all itemsets I in itemsets C
for (int state = 0; state<itemsets.getItemSetCount(); state++)
{
ItemSet I = itemsets.getItemSet(state);
SymbolSet shiftsymbols = I.getShiftSymbols(); // Transition symbols for shift actions
SymbolSet reducesymbols = I.getReduceSymbols(); // Lookahead symbols for reduce actions
for (int symbol = 0; symbol<tsymbols.getSymbolCount(); symbol++)
{
IntegerSet reduceproductions = I.getReduceProductions(tsymbols.getSymbol(symbol));
int productionpriority = -1;
int highestproduction = -1;
for (int k = 0; k<reduceproductions.getCount(); k++)
{
ReduceReduceConflict reduceconflict = null;
if (k>0)
{
reduceconflict =
new ReduceReduceConflict(grammar, itemsets, state,
(Terminal)tsymbols.getSymbol(symbol),
reduceproductions.get(k-1), reduceproductions.get(k));
conflicts.addConflict(reduceconflict);
}
if (grammar.getPriority(grammar.getProduction(reduceproductions.get(k)))>productionpriority)
{
highestproduction = reduceproductions.get(k);
productionpriority = grammar.getPriority(grammar.getProduction(highestproduction));
if ((log!=null) && (reduceconflict!=null))
log.info(reduceconflict.toString());
}
else if (grammar.getPriority(grammar.getProduction(reduceproductions.get(k)))==productionpriority)
{
if (log!=null)
log.warn(reduceconflict.toString());
}
}
if (reduceproductions.getCount()>1)
if ((log!=null) && (log.isInfoEnabled()))
log.info("The parser will reduce the production "+
grammar.getProduction(highestproduction));
boolean errorreduce = reducesymbols.contains(Error.instance);
if (shiftsymbols.contains(tsymbols.getSymbol(symbol)))
{
if ((errorreduce) || (reducesymbols.contains(tsymbols.getSymbol(symbol))))
{
int tokenpriority = grammar.getPriority((Terminal)tsymbols.getSymbol(symbol));
ShiftReduceConflict shiftconflict =
new ShiftReduceConflict(grammar, itemsets, state,
(Terminal)tsymbols.getSymbol(symbol), highestproduction);
if (tokenpriority>productionpriority)
{
automaton.setShiftAction(state, symbol, I.getTransition(tsymbols.getSymbol(symbol)));
if (log!=null)
{
log.info(shiftconflict.toString());
log.info("The parser will shift");
}
}
else if (tokenpriority<productionpriority)
{
automaton.setReduceAction(state, symbol, highestproduction);
if (log!=null)
{
log.info(shiftconflict.toString());
log.info("The parser will reduce");
}
}
else
{
if (log!=null)
log.warn(shiftconflict.toString());
Associativity associativity =
grammar.getAssociativity((Terminal)tsymbols.getSymbol(symbol));
if (associativity.equals(Associativity.RIGHT))
{
// if the terminal has a right associativity
automaton.setShiftAction(state, symbol, I.getTransition(tsymbols.getSymbol(symbol)));
if (log!=null)
log.warn("The parser will shift");
}
else if (associativity.equals(Associativity.LEFT))
{
// if the terminal has a left associativity
automaton.setReduceAction(state, symbol, highestproduction);
if (log!=null)
log.warn("The parser will reduce");
}
else
{
// SHIFT should be always prefered
automaton.setShiftAction(state, symbol, I.getTransition(tsymbols.getSymbol(symbol)));
if (log!=null)
log.warn("The parser will shift");
}
}
}
else
automaton.setShiftAction(state, symbol, I.getTransition(tsymbols.getSymbol(symbol)));
}
else if ((errorreduce) || (reducesymbols.contains(tsymbols.getSymbol(symbol))))
automaton.setReduceAction(state, symbol, highestproduction);
else
for (int i = 0; i<shiftsymbols.getSymbolCount(); i++)
if (shiftsymbols.getSymbol(i) instanceof Error)
automaton.setErrorAction(state, symbol, I.getTransition(shiftsymbols.getSymbol(i)));
}
// create all actions for the end of file.
if (reducesymbols.contains(EOF))
{
IntegerSet reduceproductions = I.getReduceProductions(EOF);
int productionpriority = -1;
int highestproduction = -1;
for (int k = 0; k<reduceproductions.getCount(); k++)
{
if (grammar.getPriority(grammar.getProduction(reduceproductions.get(k)))>productionpriority)
{
highestproduction = reduceproductions.get(k);
productionpriority = grammar.getPriority(grammar.getProduction(highestproduction));
}
}
if ((grammar.getProduction(highestproduction).getSymbol().equals(grammar.getStartSymbol())))
automaton.setAcceptAction(state, highestproduction);
else
automaton.setReduceAction(state, highestproduction);
}
else
for (int i = 0; i<shiftsymbols.getSymbolCount(); i++)
if (shiftsymbols.getSymbol(i) instanceof Error)
automaton.setErrorAction(state, I.getTransition(shiftsymbols.getSymbol(i)));
// create all entries for the goto table
for (int symbol = 0; symbol<ntsymbols.getSymbolCount(); symbol++)
if (shiftsymbols.contains(ntsymbols.getSymbol(symbol)))
automaton.setTransition(state, symbol, I.getTransition(ntsymbols.getSymbol(symbol)));
}
if ((log!=null) && (log.isDebugEnabled()))
log.debug("Parser automaton:\n"+automaton.toString());
}
/**
* Returns the generated parser automaton. If a error occurs the method will return null.
*
* @return The parser automaton.
*/
public ParserAutomaton getParserAutomaton()
{
return automaton;
}
}