blob: 401bbb3d1c40308d40a81004ede466ce8e3f48fd [file] [log] [blame]
/*
* [The "BSD license"]
* Copyright (c) 2010 Terence Parr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.antlr.tool;
import org.antlr.analysis.*;
import org.antlr.misc.Utils;
import java.util.*;
/** An aspect of FA (finite automata) that knows how to dump them to serialized
* strings.
*/
public class FASerializer {
/** To prevent infinite recursion when walking state machines, record
* which states we've visited. Make a new set every time you start
* walking in case you reuse this object. Multiple threads will trash
* this shared variable. Use a different FASerializer per thread.
*/
protected Set markedStates;
/** Each state we walk will get a new state number for serialization
* purposes. This is the variable that tracks state numbers.
*/
protected int stateCounter = 0;
/** Rather than add a new instance variable to NFA and DFA just for
* serializing machines, map old state numbers to new state numbers
* by a State object -> Integer new state number HashMap.
*/
protected Map stateNumberTranslator;
protected Grammar grammar;
/** This aspect is associated with a grammar; used to get token names */
public FASerializer(Grammar grammar) {
this.grammar = grammar;
}
public String serialize(State s) {
if ( s==null ) {
return "<no automaton>";
}
return serialize(s, true);
}
/** Return a string representation of a state machine. Two identical
* NFAs or DFAs will have identical serialized representations. The
* state numbers inside the state are not used; instead, a new number
* is computed and because the serialization will walk the two
* machines using the same specific algorithm, then the state numbers
* will be identical. Accept states are distinguished from regular
* states.
*/
public String serialize(State s, boolean renumber) {
markedStates = new HashSet();
stateCounter = 0;
if ( renumber ) {
stateNumberTranslator = new HashMap();
walkFANormalizingStateNumbers(s);
}
List lines = new ArrayList();
if ( s.getNumberOfTransitions()>0 ) {
walkSerializingFA(lines, s);
}
else {
// special case: s0 is an accept
String s0 = getStateString(0, s);
lines.add(s0+"\n");
}
StringBuffer buf = new StringBuffer(0);
// sort lines to normalize; makes states come out ordered
// and then ordered by edge labels then by target state number :)
Collections.sort(lines);
for (int i = 0; i < lines.size(); i++) {
String line = (String) lines.get(i);
buf.append(line);
}
return buf.toString();
}
/** In stateNumberTranslator, get a map from State to new, normalized
* state number. Used by walkSerializingFA to make sure any two
* identical state machines will serialize the same way.
*/
protected void walkFANormalizingStateNumbers(State s) {
if ( s==null ) {
ErrorManager.internalError("null state s");
return;
}
if ( stateNumberTranslator.get(s)!=null ) {
return; // already did this state
}
// assign a new state number for this node if there isn't one
stateNumberTranslator.put(s, Utils.integer(stateCounter));
stateCounter++;
// visit nodes pointed to by each transition;
for (int i = 0; i < s.getNumberOfTransitions(); i++) {
Transition edge = (Transition) s.transition(i);
walkFANormalizingStateNumbers(edge.target); // keep walkin'
// if this transition is a rule reference, the node "following" this state
// will not be found and appear to be not in graph. Must explicitly jump
// to it, but don't "draw" an edge.
if ( edge instanceof RuleClosureTransition ) {
walkFANormalizingStateNumbers(((RuleClosureTransition) edge).followState);
}
}
}
protected void walkSerializingFA(List lines, State s) {
if ( markedStates.contains(s) ) {
return; // already visited this node
}
markedStates.add(s); // mark this node as completed.
int normalizedStateNumber = s.stateNumber;
if ( stateNumberTranslator!=null ) {
Integer normalizedStateNumberI = (Integer)stateNumberTranslator.get(s);
normalizedStateNumber = normalizedStateNumberI.intValue();
}
String stateStr = getStateString(normalizedStateNumber, s);
// depth first walk each transition, printing its edge first
for (int i = 0; i < s.getNumberOfTransitions(); i++) {
Transition edge = (Transition) s.transition(i);
StringBuffer buf = new StringBuffer();
buf.append(stateStr);
if ( edge.isAction() ) {
buf.append("-{}->");
}
else if ( edge.isEpsilon() ) {
buf.append("->");
}
else if ( edge.isSemanticPredicate() ) {
buf.append("-{"+edge.label.getSemanticContext()+"}?->");
}
else {
String predsStr = "";
if ( edge.target instanceof DFAState ) {
// look for gated predicates; don't add gated to simple sempred edges
SemanticContext preds =
((DFAState)edge.target).getGatedPredicatesInNFAConfigurations();
if ( preds!=null ) {
predsStr = "&&{"+
preds.genExpr(grammar.generator,
grammar.generator.getTemplates(), null).render()
+"}?";
}
}
buf.append("-"+edge.label.toString(grammar)+predsStr+"->");
}
int normalizedTargetStateNumber = edge.target.stateNumber;
if ( stateNumberTranslator!=null ) {
Integer normalizedTargetStateNumberI =
(Integer)stateNumberTranslator.get(edge.target);
normalizedTargetStateNumber = normalizedTargetStateNumberI.intValue();
}
buf.append(getStateString(normalizedTargetStateNumber, edge.target));
buf.append("\n");
lines.add(buf.toString());
// walk this transition
walkSerializingFA(lines, edge.target);
// if this transition is a rule reference, the node "following" this state
// will not be found and appear to be not in graph. Must explicitly jump
// to it, but don't "draw" an edge.
if ( edge instanceof RuleClosureTransition ) {
walkSerializingFA(lines, ((RuleClosureTransition) edge).followState);
}
}
}
private String getStateString(int n, State s) {
String stateStr = ".s"+n;
if ( s.isAcceptState() ) {
if ( s instanceof DFAState ) {
stateStr = ":s"+n+"=>"+((DFAState)s).getUniquelyPredictedAlt();
}
else {
stateStr = ":s"+n;
}
}
return stateStr;
}
}