/* * [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 ""; } 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; } }