/*
* xtc - The eXTensible Compiler
* Copyright (C) 2004-2008 Robert Grimm
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*/
package xtc.parser;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import xtc.Constants;
import xtc.tree.Utility;
import xtc.tree.Visitor;
import xtc.type.AST;
import xtc.type.Type;
import xtc.type.Wildcard;
import xtc.type.VoidT;
import xtc.util.Utilities;
/**
* Utility for analyzing and modifying grammar modules. This class
* provides functionality that helps process either a {@link Grammar
* collection of modules} or a single {@link Module module}. In
* particular, it provides:</ul>
*
* <li>A mapping from module names to modules, which is initialized
* through {@link #init(Grammar)} and accessed through {@link
* #lookup(String)} and {@link #lookup(ModuleName)}. After
* initialization, the grammar can be accessed through {@link
* #grammar()}.<p /></li>
*
* <li>A mapping from nonterminals to productions, which is
* initialized either through {@link #init(Grammar)} or {@link
* #init(Module)} and accessed through {@link #lookup(NonTerminal)},
* {@link #lookup(NonTerminal,Module)}, and {@link
* #lookupGlobally(NonTerminal)}. When initializing this mapping
* through a grammar, {@link #lookup(NonTerminal) relative look ups}
* can only be performed within the context of {@link #process(Module)
* processing} a module. Note that the mapping contains both
* qualified and unqualified names.<p /></li>
*
* <li>Methods to {@link #isTopLevel(Module) test for} and {@link
* #topLevel() access} the top-level (or root) module of a grammar.<p
* /></li>
*
* <li>Methods to {@link #add(Module) add} and {@link #remove(Module)
* remove} modules. These methods update both the mapping from module
* names to modules and from nonterminals to productions, but they do
* not update the current grammar.<p /></li>
*
* <li>Methods to {@link #isImported(String,Module) test} whether a
* module is imported by another, to {@link #trace trace} a module's
* dependencies, and to {@link #hasAttribute(Module,String,Set) test}
* whether a module or any of its dependencies has a specified
* attribute.<p /></li>
*
* <li>Methods to {@link #isDefined(Production,Module) test} whether a
* production is defined by a module and to {@link
* #isImported(Production,Module) test} whether a production is
* imported by a module.<p /></li>
*
* <li>Methods to start {@link #process(Module) processing} a module
* and to {@link #currentModule() determine} the current module;
* additionally, methods to {@link #enter(Production) switch to} a
* different module and {@link #exit(Object) switch back} again when
* processing productions from several modules.<p /></li>
*
* <li>A method to uniquely {@link #uniquify()} the names of all
* productions across all modules.<p /></li>
*
* <li>Methods to start {@link #process(Production) processing} a
* production and to {@link #current() determine} the current
* production.<p /></li>
*
* <li>A working set, marked set, and processed set to determine
* properties of productions. The idea behind these three sets is
* that the working set keeps track of all productions during an
* analysis pass and is used to prevent infinite recursions, the
* marked set tracks the productions having the property, and the
* processed set (which typically is a superset of the marked set)
* tracks the analyzed productions. The working set is accessed
* through {@link #workingOn(NonTerminal)}, {@link
* #notWorkingOn(NonTerminal)}, {@link #notWorkingOnAny()}, {@link
* #isBeingWorkedOn(NonTerminal)}, and {@link #working()}. The marked
* set is accessed through {@link #mark(NonTerminal)}, {@link
* #mark(Collection)}, {@link #markAll()}, {@link
* #unmark(NonTerminal)}, {@link #unmarkAll()}, {@link #hasMarked()},
* {@link #isMarked(NonTerminal)}, and {@link #marked()}. Finally,
* the processed set is accessed through {@link #clearProcessed()},
* {@link #processed(NonTerminal)}, and {@link
* #isProcessed(NonTerminal)}. Note that all nonterminals used in
* these sets should be fully qualified.<p /></li>
*
* <li>Methods to add and remove productions from a grammar. New
* productions are prepared for addition through {@link
* #add(FullProduction)} and committed to the grammar through {@link
* #addNewProductionsAt(int)}. Existing productions are removed
* through {@link #remove(FullProduction)}.<p /></li>
*
* <li>A set of methods for creating synthetic variable names and
* nonterminals for new productions: {@link #variable()}, {@link
* #split()}, {@link #choice()}, {@link #star()}, {@link #plus()},
* {@link #option()}, {@link #tail()}, and {@link #shared()}. Also, a
* method to test whether a given {@link #isSynthetic(String)
* variable} or {@link #isSynthetic(NonTerminal) nonterminal} is
* synthetic. Besides shared variables, synthetic variables can only
* be created while {@link #process(Production) processing} a
* production.<p /></li>
*
* <li>A method to {@link #strip(Element) strip} unnecessary ordered
* choices and sequences from an element and a method to {@link
* #stripChoices(OrderedChoice) strip} only ordered choices.<p /></li>
*
* <li>A method to {@link #copy(Element) copy} an element.<p /></li>
*
* <li>A set of methods for optimizing sequences that start with
* terminals through character switches: {@link
* #hasTerminalPrefix(Sequence)}, {@link
* #normalizeTerminals(Sequence)}, and {@link
* #joinTerminals(Sequence,Element)}.<p /></li>
*
* <li>The corresponding set of methods for folding common prefixes:
* {@link #haveCommonPrefix(Sequence,Sequence)}, {@link
* #normalizePrefix(Sequence,Sequence)}, and {@link
* #joinPrefixes(Sequence,Element)}.<p /></li>
*
* <li>A method to {@link #matchingText(Element) get the text} of an
* element.<p /></li>
*
* <li>A method to determine whether an element {@link
* #restrictsInput(Element) restricts the input}.<p /></li>
*
* <li>A method to determine whether an element {@link
* #consumesInput(Element) consumes the input}.<p /></li>
*
* <li>A method to determine whether an element {@link
* #matchesEmpty(Element) matches the empty input}.<p /></li>
*
* <li>A method to determine whether an element {@link
* #isNotFollowedBy(Element) relies only on not-followed-by
* predicates}.<p /></li>
*
* <li>A set of methods to process bindings, notably a method to
* determine whether an element {@link #isBindable(Element) can be
* bound}, a method to {@link #bind(List) add a binding} to a list of
* elements, a method to {@link #getBinding(List) access the only
* binding} in a list of elements, and methods to {@link
* #unbind(Element) unbind} and {@link #stripAndUnbind(Element) strip
* and unbind} an element.<p /></li>
*
* <li>A method to {@link #getValue(List,boolean) get a list's
* value}.</li>
*
* <li>Two methods to determine whether {@link
* #setsValue(Element,boolean) an element} or {@link
* #setsValue(List,boolean) a list} sets the semantic value, either
* through a binding, semantic action, or value element. Another
* method to {@link #setsNullValue(List) determine} whether a list
* sets the semantic value to <code>null</code>.<p /></li>
*
* <li>A method to determine whether an element's list value {@link
* #mayBeNull(Element) may be null}.<p /></li>
*
* <li>A method to {@link #type(Element) type} an element.<p /></li>
*
* </ul>
*
* To utilize this analyzer, the utility must be initialized with
* {@link #init(Grammar) a grammar} or {@link #init(Module) a module}
* and the visitor must be {@link #register registered}. When
* processing modules in a grammar, this utility must be {@link
* #process(Module) notified}. When processing productions, this
* utility must also be {@link #process(Production) notified}.
*
* <p />The analyzer utility tracks the current grammar or module so
* that it need not recreate its internal state as long as the same
* analyzer utility is used across different visitors.
*
* @author Robert Grimm
* @version $Revision: 1.147 $
*/
public class Analyzer extends Utility {
/**
* The separator character for creating new nonterminals, which
* should be illegal in regular variable or nonterminal names.
*
*/
public static final String SEPARATOR = "$$";
/** The base name for nonterminals representing shared productions. */
public static final String SHARED = SEPARATOR + "Shared";
/** The base name for synthetic variables. */
public static final String VARIABLE = "v$";
/** The name for dummy variables (in case of errors). */
public static final String DUMMY = VARIABLE + "dummy";
/** The suffix for nonterminals representing split alternatives. */
public static final String SPLIT = SEPARATOR + "Split";
/** The suffix for nonterminals representing choices. */
public static final String CHOICE = SEPARATOR + "Choice";
/**
* The suffix for nonterminals representing zero or more
* repetitions.
*/
public static final String STAR = SEPARATOR + "Star";
/**
* The suffix for nonterminals representing one or more
* repetitions.
*/
public static final String PLUS = SEPARATOR + "Plus";
/** The suffix for nonterminals representing options. */
public static final String OPTION = SEPARATOR + "Option";
/** The suffix for nonterminals representing tail productions. */
public static final String TAIL = SEPARATOR + "Tail";
/**
* The maximum character count for turning character classes into
* character switches.
*/
public static final int MAX_COUNT = 22;
// =======================================================================
/** The element copier. */
protected final Copier xerox;
// =======================================================================
/** Flag for whether we are processing a grammar or a module. */
protected boolean isGrammarMode = false;
// =======================================================================
/** The grammar. */
protected Grammar grammar;
/** The map from module names (as strings) to module objects. */
protected Map<String, Module> moduleMap;
/**
* The grammar-wide map from nonterminals to productions. For each
* production, this map contains two entries, one mapping the fully
* qualified name to the production, the other mappiing the
* unqualified name to either a production (if the set of modules
* only contains a single production with the unqualified name) or a
* list of productions (if the set of resolved modules contains more
* than one production with the unqualified name). Note that this
* map does not contain partial productions.
*/
protected Map<NonTerminal, Object> grammarPMap;
/** The current grammar module. */
protected Module mCurrent;
// =======================================================================
/** The self-contained grammar module. */
protected Module module;
/** The map from nonterminals to productions for the current module. */
protected Map<NonTerminal, FullProduction> pMap;
/** The current production. */
protected Production pCurrent;
// =======================================================================
/**
* The set of nonterminals corresponding to productions currently
* being processed.
*/
protected Set<NonTerminal> pWorking;
/**
* The set of nonterminals corresponding to productions having
* been marked.
*/
protected Set<NonTerminal> pMarked;
/**
* The set of nonterminals corresponding to productions having
* been processed.
*/
protected Set<NonTerminal> pProcessed;
/** The list of newly added productions. */
protected List<Production> pNew;
/** The count of synthetic variables for the current production. */
protected int varCount;
/** The count of splits for the current production. */
protected int splitCount;
/** The count of lifted choices for the current production. */
protected int choiceCount;
/** The count of desugared star repetitions for the current production. */
protected int starCount;
/** The count of desugared plus repetitions for the current production. */
protected int plusCount;
/** The count of desugared options for the current production. */
protected int optionCount;
/** The count of tail productions for the current production. */
protected int tailCount;
/** The count of shared productions. */
protected int sharedCount;
// =======================================================================
/** Create a new analyzer utility. */
public Analyzer() {
xerox = new Copier();
moduleMap = new HashMap<String, Module>();
grammarPMap = new HashMap<NonTerminal, Object>();
pMap = new HashMap<NonTerminal, FullProduction>();
pWorking = new HashSet<NonTerminal>();
pMarked = new HashSet<NonTerminal>();
pProcessed = new HashSet<NonTerminal>();
pNew = new ArrayList<Production>();
sharedCount = 1;
}
/** Forcibly reset the analyzer utility. */
public void reset() {
isGrammarMode = false;
grammar = null;
moduleMap.clear();
grammarPMap.clear();
mCurrent = null;
module = null;
pCurrent = null;
pMap.clear();
pWorking.clear();
pMarked.clear();
pProcessed.clear();
pNew.clear();
varCount = 1;
splitCount = 1;
choiceCount = 1;
starCount = 1;
plusCount = 1;
optionCount = 1;
tailCount = 1;
sharedCount = 1;
}
// =======================================================================
/**
* Initialize this analyzer for the specified grammar. This method
* initializes the map from module names to modules and the
* grammar-wide map from nonterminals to productions. It also sets
* the {@link Production#qName qualified name} field for each
* production (if it has not been initialized). This method also
* clears the sets of marked and processed nonterminals. It should
* be called before iterating over the grammar's modules.
*
* @param g The grammar.
*/
public void init(Grammar g) {
// Initialize the map from module names to modules and the
// grammar-wide map from nonterminals to productions.
if (grammar != g) {
grammar = g;
moduleMap.clear();
grammarPMap.clear();
for (Module m : g.modules) {
// Record the mapping between module name and module.
moduleMap.put(m.name.name, m);
// Record the productions.
for (Production p : m.productions) {
// Fill in the qualified name.
if (null == p.qName) {
p.qName = p.name.qualify(m.name.name);
p.qName.setLocation(p.name);
}
// Record the mappings, but skip partial productions as well
// as duplicate definitions within the same grammar module.
if (p.isFull()) {
addToGrammarMap((FullProduction)p);
}
}
}
}
// Clear the sets of marked and processed productions.
pMarked.clear();
pProcessed.clear();
// Clear the current production.
pCurrent = null;
// Clear the current module.
mCurrent = null;
// Set the processing mode.
isGrammarMode = true;
}
/**
* Determine whether the module with the specified name is imported
* by the specified module.
*
* @param name The module name.
* @param m The depending module.
* @return <code>true</code> if the specified module (or any of the
* modules modified by that module) imports the module with the
* specified name.
*/
public boolean isImported(String name, Module m) {
// Check the specified module and all modules modified by that
// module.
while (null != m) {
if (null != m.dependencies) {
for (ModuleDependency dep : m.dependencies) {
if (dep.isImport() && dep.visibleName().name.equals(name)) {
return true;
}
}
if (null != m.modification) {
// Continue checking with the modified module.
m = lookup(m.modification.visibleName());
} else {
// No more dependencies to check.
return false;
}
} else {
// No more dependencies to check.
return false;
}
}
// No more modules to check.
return false;
}
/**
* Trace the specified module's dependencies. This method traces
* the transitive closure of the specified module's import and
* modification dependencies. It adds the names of all imported
* modules to the imports set (excluding the name of the specified
* module) and the names of all modified modules to the modified
* map. The corresponding value in this mapping is
* <code>Boolean.TRUE</code> if the module is modified more than
* once. Otherwise, it is false. This method assumes that each
* module modification's {@link Module#modification modification}
* field has been correctly initialized. Furthermore, the specified
* set and map should be empty, with the exception of the set of
* imported modules containing the name of the root module.
*
* @param m The module.
* @param imports The imported modules.
* @param modified The modified modules.
*/
public void trace(Module m, Set<ModuleName> imports,
Map<ModuleName, Boolean> modified) {
// Trace any modifications.
if (null != m.modification) {
if (modified.containsKey(m.modification.visibleName())) {
modified.put(m.modification.visibleName(), Boolean.TRUE);
} else {
modified.put(m.modification.visibleName(), Boolean.FALSE);
Module m2 = lookup(m.modification.visibleName());
if (null != m2) {
trace(m2, imports, modified);
}
}
}
// Trace any imports.
if (null != m.dependencies) {
for (ModuleDependency dep : m.dependencies) {
if (dep.isImport()) {
if (! imports.contains(dep.visibleName())) {
imports.add(dep.visibleName());
Module m2 = lookup(dep.visibleName());
if (null != m2) {
trace(m2, imports, modified);
}
}
}
}
}
}
/**
* Determine whether the specified module or any of its import or
* modification dependencies has an attribute with the specified
* name.
*
* @param m The module.
* @param name The attribute name.
* @param visited An empty helper set.
* @return <code>true</code> if the specified module or any of its
* dependencies has the specified attribute.
*/
public boolean hasAttribute(Module m, String name, Set<ModuleName> visited) {
if (! visited.contains(m.name)) {
// Add this module to set of visited modules.
visited.add(m.name);
// Check this module's attributes and then dependencies.
if (m.hasAttribute(name)) {
return true;
} else if (null != m.dependencies) {
for (ModuleDependency dep : m.dependencies) {
if (dep.isImport() || dep.isModification()) {
Module m2 = lookup(dep.visibleName());
if ((null != m2) && hasAttribute(m2, name, visited)) {
return true;
}
}
}
}
}
// Done.
return false;
}
/**
* Add the specified module to the current grammar. This method
* adds the specified module to the map from module names to modules
* and its productions to the grammar-wide map from nonterminals to
* productions. However, it does not add the specified module to
* the grammar's {@link Grammar#modules list of modules}.
*
* @param m The module.
* @throws IllegalStateException Signals that this analyzer has not
* been initialized with a grammar.
*/
public void add(Module m) {
// Make sure we are in the right state.
if (! isGrammarMode) {
throw new IllegalStateException("Not initialized with grammar");
}
// Only add the module if it is not already part of the grammar.
if (! moduleMap.containsKey(m.name.name)) {
// Add the module mapping.
moduleMap.put(m.name.name, m);
// Add the productions.
for (Production p : m.productions) {
if (p.isFull()) {
addToGrammarMap((FullProduction)p);
}
}
}
}
/**
* Remove the specified module from the current grammar. This
* method removes the specified module from the map from module
* names to modules and its productions from the grammar-wide map
* from nonterminals to productions. Note that this method does not
* remove the specified module from the grammar's {@link
* Grammar#modules list of modules}.
*
* @param m The module.
* @throws IllegalStateException Signals that this analyzer has not
* been initialized with a grammar.
*/
public void remove(Module m) {
// Make sure we are in the right state.
if (! isGrammarMode) {
throw new IllegalStateException("Not initialized with grammar");
}
// Remove the module mapping.
moduleMap.remove(m.name.name);
// Remove the productions.
for (Production p : m.productions) {
if (p.isFull()) {
removeFromGrammarMap((FullProduction)p);
}
}
}
/**
* Cast the specified object as a list of full productions.
*
* @param o The object.
* @return The object as a list of full productions.
*/
@SuppressWarnings("unchecked")
private List<FullProduction> toFullProductionList(Object o) {
return (List<FullProduction>)o;
}
/**
* Add the specified production to the grammar-wide production map.
*
* @param p The production.
*/
private void addToGrammarMap(FullProduction p) {
if (! grammarPMap.containsKey(p.qName)) {
grammarPMap.put(p.qName, p);
if (grammarPMap.containsKey(p.name)) {
Object o = grammarPMap.get(p.name);
// There is more than one production with the same unqualified
// name, so we keep a list of productions.
if (o instanceof FullProduction) {
List<FullProduction> l = new ArrayList<FullProduction>();
l.add((FullProduction)o);
l.add(p);
grammarPMap.put(p.name, l);
} else {
List<FullProduction> l = toFullProductionList(o);
l.add(p);
}
} else {
grammarPMap.put(p.name, p);
}
}
}
/**
* Remove the specified production from the grammar-wide production
* map.
*
* @param p The production.
*/
private void removeFromGrammarMap(FullProduction p) {
grammarPMap.remove(p.qName);
Object o = grammarPMap.get(p.name);
if (o instanceof FullProduction) {
// Make sure we only remove the unqualified mapping if it
// actually describes the production to be removed.
if (p.qName.equals(((FullProduction)o).qName)) {
grammarPMap.remove(p.name);
}
} else {
List<FullProduction> l = toFullProductionList(o);
for (Iterator<FullProduction> iter = l.iterator(); iter.hasNext(); ) {
if (p.qName.equals(iter.next().qName)) {
iter.remove();
break;
}
}
if (1 == l.size()) {
o = l.get(0);
grammarPMap.put(p.name, o);
}
}
}
/**
* Get the initializing grammar.
*
* @see #init(Grammar)
*
* @return The grammar.
* @throws IllegalStateException Signals that this analyzer has not
* been initialized with a grammar.
*/
public Grammar grammar() {
// Make sure we are in the right state.
if (! isGrammarMode) {
throw new IllegalStateException("Not initialized with grammar");
}
// Do the work.
return grammar;
}
/**
* Determine whether the specified module is the top-level module.
*
* @param m The module.
* @return <code>true</code> if the specified module is the
* top-level module.
*/
public boolean isTopLevel(Module m) {
return isGrammarMode? (m == grammar.modules.get(0)) : (m == module);
}
/**
* Get the top-level module.
*
* @return The top-level module.
*/
public Module topLevel() {
return isGrammarMode? grammar.modules.get(0) : module;
}
// =======================================================================
/**
* Initialize this analyzer for the specified module. This method
* initializes the map from nonterminals to productions. It also
* clears the sets of marked and processed nonterminals. It should
* be called before iterating over the module's productions.
*
* @param m The self-contained module.
*/
public void init(Module m) {
// Initialize the map from nonterminals to productions and the set
// of top-level nonterminals.
if (module != m) {
module = m;
pMap.clear();
for (Production p : m.productions) {
if (p.isFull()) {
pMap.put(p.name, (FullProduction)p);
pMap.put(p.qName, (FullProduction)p);
}
}
}
// Clear the sets of marked and processed productions.
pMarked.clear();
pProcessed.clear();
// Clear the current production.
pCurrent = null;
// Clear the current module.
mCurrent = null;
// Set the processing mode.
isGrammarMode = false;
}
/**
* Get the initializing module.
*
* @see #init(Module)
*
* @return The module.
* @throws IllegalStateException Signals that this analyzer has not
* been initialized with a module.
*/
public Module module() {
// Make sure we are in the right state.
if (isGrammarMode) {
throw new IllegalStateException("Not initialized with module");
}
// Do the work.
return module;
}
// =======================================================================
/**
* Look up the specified module.
*
* @param module The module name.
* @return The corresponding module or <code>null</code> if there is
* no such module.
* @throws IllegalStateException Signals that this analyzer has not
* been initialized with a grammar.
*/
public Module lookup(String module) {
// Make sure we are in the right state.
if (! isGrammarMode) {
throw new IllegalStateException("Not initialized with grammar");
}
// Do the work.
return moduleMap.get(module);
}
/**
* Look up the specified module.
*
* @param module The module name.
* @return The corresponding module or <code>null</code> if there is
* no such module.
* @throws IllegalStateException Signals that this analyzer has not
* been initialized with a grammar.
*/
public Module lookup(ModuleName module) {
// Make sure we are in the right state.
if (! isGrammarMode) {
throw new IllegalStateException("Not initialized with grammar");
}
// Do the work.
return moduleMap.get(module.name);
}
/**
* Look up the production for the specified qualified nonterminal.
*
* @param nt The nonterminal.
* @return The corresponding production or <code>null</code> if
* there is no such production.
*/
public FullProduction lookupGlobally(NonTerminal nt) {
if (isGrammarMode) {
return nt.isQualified()? (FullProduction)grammarPMap.get(nt) : null;
} else {
return pMap.get(nt);
}
}
/**
* Look up the production for the specified nonterminal, which is
* also defined by the specified module.
*
* @param nt The nonterminal
* @param m The module.
* @return The corresponding production or <code>null</code> if the
* specified module does not define a production with the
* specified nonterminal.
* @throws IllegalArgumentException
* Signals multiple definitions for the specified nonterminal.
* @throws IllegalStateException Signals that this analyzer has not
* been initialized with a grammar.
*/
public FullProduction lookup(NonTerminal nt, Module m) {
if (! isGrammarMode) {
throw new IllegalStateException("Not initialized with grammar");
}
if (nt.isQualified()) {
if (nt.getQualifier().equals(m.name.name)) {
if (grammarPMap.containsKey(nt)) {
return (FullProduction)grammarPMap.get(nt);
} else {
// Continue with the unqualified nonterminal.
nt = nt.unqualify();
}
} else {
// Wrong module name.
return null;
}
}
Object o = grammarPMap.get(nt);
if (null == o) {
return null;
} else if (o instanceof FullProduction) {
FullProduction p = (FullProduction)o;
return isDefined(p, m)? p : null;
} else {
FullProduction result = null;
for (FullProduction p : toFullProductionList(o)) {
if (isDefined(p, m)) {
if (null == result) {
result = p;
} else {
throw new IllegalArgumentException("Multiple definitions for " + nt);
}
}
}
return result;
}
}
/**
* Look up the production for the specified nonterminal. Note that,
* when processing a grammar, this method requires initializing
* calls to {@link #process(Module)} to correctly resolve
* nonterminals.
*
* @param nt The nonterminal.
* @return The corresponding production or <code>null</code>
* if there is no such production.
* @throws IllegalArgumentException
* Signals that there are multiple, ambiguous productions.
*/
public FullProduction lookup(NonTerminal nt) {
if (isGrammarMode) {
// If the nonterminal is qualified, the corresponding production
// can be defined (1) in the current module or one of the
// modules modified by the current module or (2) in one of the
// imported modules or one of the modules modified by the
// imported modules.
if (nt.isQualified()) {
String qualifier = nt.getQualifier();
if (qualifier.equals(mCurrent.name.name)) {
return lookup(nt, mCurrent);
} else if (isImported(qualifier, mCurrent)) {
Module m = lookup(qualifier);
return (null != m)? lookup(nt, m) : null;
} else {
return null;
}
}
// The nonterminal is unqualified.
Object o = grammarPMap.get(nt);
if (null == o) {
return null;
} else if (o instanceof FullProduction) {
FullProduction p = (FullProduction)o;
return (isDefined(p, mCurrent) || isImported(p, mCurrent))? p : null;
} else {
FullProduction result = null;
List<FullProduction> list = toFullProductionList(o);
// If the nonterminal is defined in the current module, it
// takes precedence over all other definitions.
for (FullProduction p : list) {
if (isDefined(p, mCurrent)) {
if (null == result) {
result = p;
} else {
throw new IllegalArgumentException("Multiple definitions for "+nt);
}
}
}
if (null != result) {
return result;
}
// Otherwise, we look for a single imported nonterminal.
for (FullProduction p : list) {
if (isImported(p, mCurrent)) {
if (null == result) {
result = p;
} else {
throw new IllegalArgumentException("Multiple imported " +
"definitions for " + nt);
}
}
}
return result;
}
} else {
return pMap.get(nt);
}
}
/**
* Determine whether the specified production is defined by the
* specified module. This method works correctly in the presence of
* not yet applied module modifications, but it requires that this
* analyzer has been initialized with the corresponding grammar.
*
* @param p The production.
* @param m The module.
* @return <code>true</code> if the production is defined by the
* specified module.
*/
public boolean isDefined(Production p, Module m) {
final String qualifier = p.qName.getQualifier();
// First, check the specified module.
if (m.name.name.equals(qualifier)) {
return true;
}
// Next, walk the chain of modification dependencies.
while (null != m.modification) {
m = moduleMap.get(m.modification.visibleName().name);
if (null == m) {
break;
} else if (m.name.name.equals(qualifier)) {
return true;
}
}
// Finally, give up.
return false;
}
/**
* Determine whether the specified production is defined by a module
* imported by the specified module. This method works correctly in
* the presence of not yet applied module modifications, but it
* requires that this analyzer has been initialized with the
* corresponding grammar.
*
* @param p The production.
* @param m The module.
* @return <code>true</code> if the specified production is imported
* by the specified module.
*/
public boolean isImported(Production p, Module m) {
// First, check the imports of the specified module.
if (isImported1(p, m)) {
return (! p.hasAttribute(Constants.ATT_PRIVATE));
}
// Next, walk the chain of modification dependencies.
while (null != m.modification) {
m = moduleMap.get(m.modification.visibleName().name);
if (null == m) {
break;
} else if (isImported1(p, m)) {
return (! p.hasAttribute(Constants.ATT_PRIVATE));
}
}
// Finally, give up.
return false;
}
/**
* Determine whether the specified production is defined by a module
* imported by the specified module. This method does not follow
* modification dependencies.
*
* @param p The production.
* @param m The module.
* @return <code>true</code> if the specified production is imported
* by the specified module.
*/
private boolean isImported1(Production p, Module m) {
if (null != m.dependencies) {
for (ModuleDependency dep : m.dependencies) {
if (dep.isImport()) {
m = moduleMap.get(dep.visibleName().name);
if ((null != m) && isDefined(p, m)) {
return true;
}
}
}
}
return false;
}
// =======================================================================
/**
* Rename all productions to have unique names. Note that this
* method only works, if the corresponding visitor has initialized
* the analyzer with a {@link #init(Grammar) grammar}. Further note
* that this method may change a production's {@link Production#name
* name} as well as the internal mapping from nonterminals to
* productions. This method assumes that all partial productions
* have been applied.
*/
public void uniquify() {
// This operation only makes sense when we are processing a
// collection of modules.
if (! isGrammarMode) {
throw new IllegalStateException("Not initialized with grammar");
}
// The set of processed productions and the set of mappings to
// remove.
Set<NonTerminal> renamed = new HashSet<NonTerminal>();
Set<NonTerminal> remove = new HashSet<NonTerminal>();
// Process all productions to determine the unique names.
for (Module m : grammar.modules) {
for (Iterator<Production> iter=m.productions.iterator();iter.hasNext();) {
FullProduction p = (FullProduction)iter.next();
// Make sure we don't process this production twice.
if (renamed.contains(p.qName)) {
continue;
}
// Look up the unqualified name and process all productions
// with that name.
Object o = grammarPMap.get(p.name);
if (o instanceof FullProduction) {
// There is no need for renaming.
renamed.add(p.qName);
} else {
List<FullProduction> l = toFullProductionList(o);
// Remember to remove this mapping later on.
remove.add(p.name);
// Create a list of names, only qualified by the last
// identifier of the qualifier.
List<NonTerminal> names = new ArrayList<NonTerminal>();
for (FullProduction p2 : l) {
String qual = p2.qName.getQualifier();
if (Utilities.isQualified(qual)) {
names.add(p2.name.qualify(Utilities.getName(qual)));
} else {
names.add(p2.qName);
}
}
// If the list has only unique names, we use these names.
// Otherwise, we use the fully qualified names.
if (names.size() == new HashSet<NonTerminal>(names).size()) {
Iterator<FullProduction> iter2 = l.iterator();
Iterator<NonTerminal> iter3 = names.iterator();
while (iter2.hasNext()) {
FullProduction p2 = iter2.next();
p2.name = iter3.next();
renamed.add(p2.qName);
}
} else {
for (FullProduction p2 : l) {
p2.name = p2.qName;
renamed.add(p2.qName);
}
}
}
}
}
// Now, change all nonterminals in the grammar.
new Renamer(null, this, new Renamer.Translation() {
public NonTerminal map(NonTerminal nt, Analyzer analyzer) {
NonTerminal result = analyzer.lookup(nt).name;
if (nt.equals(result)) {
// Be sure to return the original nonterminal, which has
// the right source location.
result = nt;
} else {
// Create a copy of the nonterminal and preserve the
// original's location.
result = new NonTerminal(result.name);
result.setLocation(nt);
}
return result;
}}).dispatch(grammar);
// Finally, fix the grammar-wide production map.
for (NonTerminal name : remove) {
grammarPMap.remove(name);
}
}
// =======================================================================
/**
* Process the specified module. This method sets the current
* module when processing an entire grammar.
*
* @param m The module.
*/
public void process(Module m) {
if (isGrammarMode) {
mCurrent = m;
} else if (m != module) {
throw new IllegalArgumentException("Invalid module " + m);
}
}
/**
* Get the module currently being processed.
*
* @return The current module.
*/
public Module currentModule() {
return isGrammarMode? mCurrent : module;
}
/**
* Enter the specified production. This method temporarily switches
* the current module to the production's module.
*
* @param p The production.
* @return A closure for {@link #exit(Object)}.
*/
public Object enter(Production p) {
if (isGrammarMode) {
Module m = mCurrent;
mCurrent = lookup(p.qName.getQualifier());
return m;
} else {
return null;
}
}
/**
* Exit a previously entered production. This method restores the
* current module to the state before the call to {@link
* #enter(Production)}.
*
* @param closure The closure returned from <code>enter()</code>.
*/
public void exit(Object closure) {
if (isGrammarMode) {
mCurrent = (Module)closure;
}
}
/**
* Process the specified production. This method clears the set of
* working nonterminals. It also resets the counters for creating
* new variables and nonterminals (besides the counter for shared
* productions). It then invokes this analyzer's visitor on the
* specified production. This method should be called within the
* loop iterating over a grammar module's productions, but not at
* other locations within a visitor.
*
* @param p The production.
*/
public void process(Production p) {
// Initialize the per-production state.
pWorking.clear();
varCount = 1;
splitCount = 1;
choiceCount = 1;
starCount = 1;
plusCount = 1;
optionCount = 1;
tailCount = 1;
// Remember the current production.
pCurrent = p;
// Now, actually process the production.
visitor().dispatch(p);
}
/**
* Get the production currently being processed.
*
* @return The current production.
*/
public Production current() {
return pCurrent;
}
// =======================================================================
/**
* Set the status of the specified nonterminal as being worked on.
*
* @param nt The nonterminal.
*/
public void workingOn(NonTerminal nt) {
pWorking.add(nt);
}
/**
* Set the status of the specified nonterminal as not being worked
* on.
*
* @param nt The nonterminal.
*/
public void notWorkingOn(NonTerminal nt) {
pWorking.remove(nt);
}
/** Set the status of all nonterminals as not being worked on. */
public void notWorkingOnAny() {
pWorking.clear();
}
/**
* Determine whether the specified nonterminal is being worked on.
*
* @param nt The nonterminal.
* @return <code>true</code> if the nonterminal is being worked
* on.
*/
public boolean isBeingWorkedOn(NonTerminal nt) {
return pWorking.contains(nt);
}
/**
* Get the set of nonterminals being worked on. Note that the
* caller must copy the set if it keeps the reference to the
* returned set after the next call to {@link #process}.
*
* @return The working set.
*/
public Set<NonTerminal> working() {
return pWorking;
}
// =======================================================================
/**
* Mark the specified nonterminal.
*
* @param nt The nonterminal.
*/
public void mark(NonTerminal nt) {
pMarked.add(nt);
}
/**
* Mark the specified nonterminals.
*
* @param nts The list of nonterminals.
*/
public void mark(Collection<NonTerminal> nts) {
pMarked.addAll(nts);
}
/** Mark all of a grammar module's nonterminals. */
public void markAll() {
for (Production p : module.productions) {
pMarked.add(p.qName);
}
}
/**
* Unmark the specified nonterminal.
*
* @param nt The nonterminal.
*/
public void unmark(NonTerminal nt) {
pMarked.remove(nt);
}
/** Unmark all nonterminals. */
public void unmarkAll() {
pMarked.clear();
}
/**
* Determine whether any nonterminals are marked.
*
* @return <code>true</code> if any nonterminal has been marked.
*/
public boolean hasMarked() {
return (! pMarked.isEmpty());
}
/**
* Determine whether the specified nonterminal has been marked.
*
* @param nt The nonterminal.
* @return <code>true</code> if the nonterminal has been
* marked.
*/
public boolean isMarked(NonTerminal nt) {
return pMarked.contains(nt);
}
/**
* Get the set of marked nonterminals. Note that the caller must
* copy the set if it keeps the reference to the returned set after
* the next use of this analyzer.
*
* @return The marked set.
*/
public Set<NonTerminal> marked() {
return pMarked;
}
// =======================================================================
/** Clear the processed status of all nonterminals. */
public void clearProcessed() {
pProcessed.clear();
}
/**
* Set the status of the specified nonterminal as processed.
*
* @param nt The nonterminal.
*/
public void processed(NonTerminal nt) {
pProcessed.add(nt);
}
/**
* Determine whether the specified nonterminal has been processed.
*
* @param nt The nonterminal.
* @return <code>true</code> if the nonterminal has been processed.
*/
public boolean isProcessed(NonTerminal nt) {
return pProcessed.contains(nt);
}
// =======================================================================
/**
* Clear the list of newly generated productions. This method needs
* to be called before a processing step that may add new
* productions through {@link #add(FullProduction)} and {@link
* #addNewProductionsAt(int)}.
*/
public void startAdding() {
pNew.clear();
}
/**
* Prepare the specified production for addition to the grammar
* module. This method adds the specified production to the list of
* newly generated productions. It also adds the production to the
* map from nonterminals to productions and marks it as {@link
* Constants#SYNTHETIC synthetic}. However, addition is not
* complete: the productions in the list of newly generated
* productions still need to be added into the grammar itself. This
* is typically done within the main loop iterating over a grammar's
* productions and thus through a separate {@link
* #addNewProductionsAt(int) method}.
*
* @param p The new production.
*/
public void add(FullProduction p) {
p.setProperty(Constants.SYNTHETIC, Boolean.TRUE);
pNew.add(p);
pMap.put(p.name, p);
pMap.put(p.qName, p);
}
/**
* Add the newly generated productions to the grammar itself. This
* method adds the productions collected through {@link
* #add(FullProduction) add()} into the current grammar at the
* specified index of the grammar's list of productions.
*
* @param idx The index into the grammar's list of productions.
* @return The number of productions added.
*/
public int addNewProductionsAt(int idx) {
final int size = pNew.size();
if (0 != size) {
module.productions.addAll(idx, pNew);
}
return size;
}
/**
* Prepare the specified production for removal from the grammar.
* This method removes the specified production from the mapping
* from nonterminals to productions and, if present, from the set of
* top-level nonterminals. However, removal is not complete: the
* production still needs to be removed from the grammar itself.
* This is typically done within the main loop iterating over a
* grammar's productions.
*
* @param p The production.
*/
public void remove(FullProduction p) {
pMap.remove(p.name);
pMap.remove(p.qName);
}
// =======================================================================
/**
* Reset the counter for synthetic variables. Note that {@link
* #process(Production)} already resets this counter; this method
* provides more fine-grained control.
*/
public void resetVarCount() {
varCount = 1;
}
/**
* Get the current counter for synthetic variables.
*
* @return The current counter.
*/
public int getVarCount() {
return varCount;
}
/**
* Set the counter for synthetic variables.
*
* @param count The new counter value.
*/
public void setVarCount(int count) {
varCount = count;
}
/**
* Create a new synthetic variable.
*
* @return The name of the synthetic variable.
*/
public String variable() {
return VARIABLE + Integer.toString(varCount++);
}
/**
* Create a new synthetic variable with the specified marker.
*
* @param marker The marker.
* @return The name of the synthetic variable.
*/
public String variable(String marker) {
return VARIABLE + marker + "$" + Integer.toString(varCount++);
}
/**
* Determine whether the specified variable is a synthetic variable.
*
* @param var The variable name.
* @return <code>true</code> if the specified variable name is
* a synthetic variable returned by {@link #variable()}.
*/
public static boolean isSynthetic(String var) {
return var.startsWith(VARIABLE);
}
/**
* Create a new nonterminal for a split.
*
* @return The new nonterminal.
*/
public NonTerminal split() {
return
new NonTerminal(pCurrent.name + SPLIT + Integer.toString(splitCount++));
}
/**
* Create a new nonterminal for a choice.
*
* @return The new nonterminal.
*/
public NonTerminal choice() {
return
new NonTerminal(pCurrent.name + CHOICE + Integer.toString(choiceCount++));
}
/**
* Create a new nonterminal for zero or more repetitions.
*
* @return The new nonterminal.
*/
public NonTerminal star() {
return new NonTerminal(pCurrent.name + STAR + Integer.toString(starCount++));
}
/**
* Create a new nonterminal for one or more repetitions.
*
* @return The new nonterminal.
*/
public NonTerminal plus() {
return new NonTerminal(pCurrent.name + PLUS + Integer.toString(plusCount++));
}
/**
* Create a new nonterminal for an option.
*
* @return The new nonterminal.
*/
public NonTerminal option() {
return
new NonTerminal(pCurrent.name + OPTION + Integer.toString(optionCount++));
}
/**
* Create a new nonterminal for a tail production.
*
* @return The new nonterminal.
*/
public NonTerminal tail() {
return new NonTerminal(pCurrent.name + TAIL + Integer.toString(tailCount++));
}
/**
* Create a new nonterminal for a shared production.
*
* @return The new nonterminal.
*/
public NonTerminal shared() {
return new NonTerminal(SHARED + Integer.toString(sharedCount++));
}
/**
* Determine whether the specified nonterminal is synthetic.
*
* @param nt The nonterminal.
* @return <code>true</code> if the nonterminal is synthetic.
*/
public static boolean isSynthetic(NonTerminal nt) {
return (-1 != nt.name.indexOf(SEPARATOR));
}
// =======================================================================
/**
* Strip unnecessary ordered choices and sequences from the specified
* element. A choice or sequence is unnecessary if it contains only
* a single element.
*
* @param e The element.
* @return The stripped element.
*/
public static Element strip(Element e) {
switch (e.tag()) {
case CHOICE:
OrderedChoice c = (OrderedChoice)e;
if (1 == c.alternatives.size()) {
e = strip(c.alternatives.get(0));
}
break;
case SEQUENCE:
Sequence s = (Sequence)e;
if (1 == s.size()) {
e = strip(s.get(0));
}
break;
}
return e;
}
/**
* Strip unnecessary ordered choices from the specified choice. A
* choice is unnecessary if it contains only a single choice.
*
* @param c The choice.
* @return The stripped choice.
*/
public static OrderedChoice stripChoices(OrderedChoice c) {
boolean changed = false;
do {
if (1 == c.alternatives.size()) {
Element e = c.alternatives.get(0);
switch (e.tag()) {
case CHOICE:
c = (OrderedChoice)e;
changed = true;
break;
case SEQUENCE:
Sequence s = (Sequence)e;
if ((1 == s.size()) && (s.get(0) instanceof OrderedChoice)) {
c = (OrderedChoice)s.get(0);
changed = true;
}
break;
}
}
} while (changed);
return c;
}
// =======================================================================
/**
* Make a deep copy of the specified module.
*
* @param m The module.
* @return A deep copy.
*/
public Module copy(Module m) {
return (Module)xerox.dispatch(m);
}
/**
* Make a deep copy of the specified element.
*
* @param e The element.
* @return A deep copy.
*/
public <T extends Element> T copy(T e) {
return xerox.copy(e);
}
// =======================================================================
/**
* Determine whether the specified sequence starts with terminals
* that can be optimized. This method returns <code>true</code> if
* the terminals can be optimized through character switches.
* Currently, this is only the case for character and string
* literals.
*
* @param s The sequence.
* @return <code>true</code> if the sequence starts with optimizable
* terminals.
*/
public boolean hasTerminalPrefix(Sequence s) {
if (1 <= s.size()) {
Element e = s.get(0);
if ((e instanceof CharLiteral) ||
(e instanceof StringLiteral) ||
((e instanceof CharClass) &&
(((CharClass)e).count() <= MAX_COUNT))) {
return true;
}
}
return false;
}
/**
* Determine the length of the specified sequence after
* normalization.
*
* @param s The sequence
* @return The size of the normalized sequence or -1 if the sequence
* already is in normal form.
*/
private int normalLength(Sequence s) {
final int size = s.size();
boolean normal = true;
int count = 0;
loop: for (int i=0; i<size; i++) {
Element e = s.get(i);
switch (e.tag()) {
case CHAR_LITERAL:
normal = false;
count++;
break;
case STRING_LITERAL:
normal = false;
count += ((StringLiteral)e).text.length();
break;
case CHAR_CLASS:
count++;
break;
default:
count += (size-i);
break loop;
}
}
return (normal)? -1 : count;
}
/**
* Normalize the specified sequence for {@link
* #joinTerminals(Sequence,Element) joining} with other elements
* during terminal optimization. Currently, this method converts
* string literals into equivalent subsequences of character
* literals.
*
* @param s The sequence.
* @return The normalized sequence.
*/
public Sequence normalizeTerminals(Sequence s) {
final int nl = normalLength(s);
if (-1 == nl) return s;
final int l = s.size();
Sequence s2 = new Sequence(new ArrayList<Element>(nl));
s2.setLocation(s);
loop: for (int i=0; i<l; i++) {
Element e = s.get(i);
switch (e.tag()) {
case CHAR_LITERAL:
s2.add(new CharClass(((CharLiteral)e).c));
break;
case STRING_LITERAL: {
StringLiteral sl = (StringLiteral)e;
for (int j=0; j<sl.text.length(); j++) {
s2.add(new CharClass(sl.text.charAt(j)));
}
} break;
case CHAR_CLASS:
s2.add(e);
break;
default:
s2.addAll(s.elements.subList(i, l));
break loop;
}
}
return s2;
}
/**
* Join the specified sequence with the specified element. Note
* that the specified sequence must have been {@link
* #normalizeTerminals(Sequence) normalized}. Further note that the
* combined element is guaranteed to either be a sequence or an
* ordered choice.
*
* @param source The source sequence.
* @param target The target element.
* @return The combined element.
*/
public Element joinTerminals(Sequence source, Element target) {
// Handle trivial case first. Otherwise, normalize target.
if (null == target) {
return source;
} else if (target instanceof Sequence) {
// Strip sequence containing only an ordered choice.
Sequence s = (Sequence)target;
if (1 == s.size()) {
Element e = s.get(0);
if (e instanceof OrderedChoice) {
target = e;
}
}
}
// Now, do the real joining.
if (target instanceof Sequence) {
final Sequence t = (Sequence)target;
final Element t1 = t.isEmpty() ? null : t.get(0);
final Element s1 = source.isEmpty() ? null : source.get(0);
if ((s1 instanceof CharClass) &&
(t1 instanceof CharClass) &&
s1.equals(t1)) {
// Both sequences start with the same character class.
// Combine them into a single sequence, independent of the
// class's count.
Sequence result = new Sequence(joinTerminals(source.subSequence(1),
t.subSequence(1)));
result.setLocation(source);
result.elements.add(0, s1);
return result;
} else if ((s1 instanceof CharClass) &&
(((CharClass)s1).count() <= MAX_COUNT)) {
CharClass sk = (CharClass)s1;
if (t1 instanceof CharClass) {
CharClass tk = (CharClass)t1;
if (tk.count() <= MAX_COUNT) {
// Both sequences start with different character classes.
// Try to combine them into a new character switch.
return joinTerminals(source,
new Sequence(new
CharSwitch(tk,
t.subSequence(1))));
}
// Fall through to creating an ordered choice.
} else if (t1 instanceof CharSwitch) {
CharSwitch sw = (CharSwitch)t1;
// Strip the exclusive flag and then look for an existing case.
CharClass klass = new CharClass(sk.ranges);
CharCase kase = sw.hasCase(klass);
if (sk.exclusive) {
// We can only join the source into the character switch
// if the switch covers exactly the non-exclusive version.
if ((null != kase) && (1 == sw.cases.size())) {
sw.base = joinTerminals(source.subSequence(1), sw.base);
return target;
}
// Fall through to creating an ordered choice.
} else {
if (null != kase) {
// Join the sequence into the existing character case.
kase.element = joinTerminals(source.subSequence(1), kase.element);
return target;
} else if ((! sw.overlaps(klass)) && (null == sw.base)) {
// If there is no overlap with an existing case and the
// switch does not contain an exclusive character class,
// add a new character case.
sw.cases.add(new CharCase(klass, source.subSequence(1)));
return target;
}
// Fall through to creating an ordered choice.
}
}
}
// Create a new choice with the target and source.
OrderedChoice c = new OrderedChoice();
c.alternatives.add(Sequence.ensure(target));
c.alternatives.add(source);
return c;
} else if (target instanceof OrderedChoice) {
// Join the source with the last alternative.
OrderedChoice c = (OrderedChoice)target;
final int l = c.alternatives.size();
Element e = joinTerminals(source, c.alternatives.get(l-1));
if (e instanceof OrderedChoice) {
c.alternatives.remove(l-1);
c.alternatives.addAll(((OrderedChoice)e).alternatives);
} else {
c.alternatives.set(l-1, Sequence.ensure(e));
}
return c;
} else {
// Join the source with a new sequence containing the target
// element.
return joinTerminals(source, new Sequence(target));
}
}
// =======================================================================
/**
* Determine whether the specified sequences start with prefixes
* that can be folded.
*
* @param s1 The first sequence.
* @param s2 The second sequence.
* @return <code>true</code> if the two sequences start with prefixes
* that can be folded.
*/
public boolean haveCommonPrefix(Sequence s1, Sequence s2) {
final Element e1 = s1.isEmpty() ? null : s1.get(0);
final Element e2 = s2.isEmpty() ? null : s2.get(0);
if (e1 instanceof Binding) {
// FIXME: Handle bindings to yyValue
return e1.equals(e2);
} else if (null != e1) {
return e1.equals(e2);
} else {
return false;
}
}
/**
* Normalize the specified sequences for {@link
* #joinPrefixes(Sequence,Element) joining} with other sequences
* during prefix folding. The first specified sequence should
* always be the first sequence that has a common prefix with
* sequences following in an ordered choice, while the second
* specified sequence should iterate over the following sequences.
*
* @param s1 The first sequence.
* @param s2 The second sequence.
* @return The normalized second sequence.
*/
public Sequence normalizePrefix(Sequence s1, Sequence s2) {
// FIXME: Handle bindings to yyValue
return s2;
}
/**
* Join the specified sequence with the specified element. Note
* that the specified sequence must have been {@link
* #normalizePrefix(Sequence,Sequence) normalized}. Further note
* that that the combined element is guaranteed to either be a
* sequence or an ordered choice.
*
* @param source The source sequence.
* @param target The target element.
* @return The combined element.
*/
public Element joinPrefixes(Sequence source, Element target) {
// Handle trivial case first. Otherwise, normalize target.
if (null == target) {
return source;
} else if (target instanceof Sequence) {
// Strip sequence containing only an ordered choice.
Sequence s = (Sequence)target;
if (1 == s.size()) {
Element e = s.get(0);
if (e instanceof OrderedChoice) {
target = e;
}
}
}
// Now, do the real joining.
if (target instanceof Sequence) {
final Sequence t = (Sequence)target;
if (source.equals(t)) {
// Both sequences are the same. Return just one of them.
return source;
}
final Element t1 = t.isEmpty() ? null : t.get(0);
final Element s1 = source.isEmpty() ? null : source.get(0);
if ((null != s1) && s1.equals(t1)) {
// Both sequences start with the same prefix. Combine them
// into a single sequence.
Sequence result = new Sequence(joinPrefixes(source.subSequence(1),
t.subSequence(1)));
// The new sequence has the same source location as the
// source.
result.setLocation(source);
result.elements.add(0, s1);
return result;
}
// Create a new choice with the target and source.
OrderedChoice c = new OrderedChoice();
c.alternatives.add(Sequence.ensure(target));
c.alternatives.add(source);
return c;
} else if (target instanceof OrderedChoice) {
// Join the source with the last alternative.
OrderedChoice c = (OrderedChoice)target;
final int l = c.alternatives.size();
Element e = joinPrefixes(source, c.alternatives.get(l-1));
if (e instanceof OrderedChoice) {
c.alternatives.remove(l-1);
c.alternatives.addAll(((OrderedChoice)e).alternatives);
} else {
c.alternatives.set(l-1, Sequence.ensure(e));
}
return c;
} else {
// Join the source with a new sequence containing the target
// element.
return joinPrefixes(source, new Sequence(target));
}
}
// =======================================================================
/**
* Get the text matched by the specified element. This method
* analyzes the specified element, and, if the element always
* matches the same text, this method returns the static text.
* Otherwise, this method returns <code>null</code>. Note that this
* method ignores predicates, actions, node markers, null literals,
* and value elements, as they do not change the text matched by an
* element. Further note that this method recursively analyzes
* referenced nonterminals.
*
* @param e The element.
* @return The constant text.
*/
public String matchingText(Element e) {
final StringBuilder buf = new StringBuilder();
return matchingText(e, buf) ? buf.toString() : null;
}
/**
* Determine the specified element's static text.
*
* @param e The element.
* @param buf The buffer for the static text.
* @return <code>true</code> if the element has a static text.
*/
private boolean matchingText(Element e, StringBuilder buf) {
switch (e.tag()) {
case CHOICE: {
OrderedChoice c = (OrderedChoice)e;
if (1 == c.alternatives.size()) {
return matchingText(c.alternatives.get(0), buf);
} else {
return false;
}
}
case SEQUENCE:
for (Element el : ((Sequence)e).elements) {
if (! matchingText(el, buf)) return false;
}
return true;
case FOLLOWED_BY:
case NOT_FOLLOWED_BY:
case SEMANTIC_PREDICATE:
case ACTION:
case NODE_MARKER:
case NULL:
return true;
case BINDING:
case STRING_MATCH:
return matchingText(((UnaryOperator)e).element, buf);
case NONTERMINAL:
return matchingText(lookup((NonTerminal)e).choice, buf);
case STRING_LITERAL:
buf.append(((StringLiteral)e).text);
return true;
case CHAR_LITERAL:
buf.append(((CharLiteral)e).c);
return true;
case CHAR_CLASS: {
CharClass c = (CharClass)e;
if (1 == c.ranges.size()) {
CharRange r = c.ranges.get(0);
if (r.first == r.last) {
buf.append(r.first);
return true;
}
}
return false;
}
default:
return e instanceof ValueElement;
}
}
// =======================================================================
/**
* Determine whether the specified element restricts the input.
* Note that this method requires that the production and module
* containing the element are currently being processed; i.e., the
* corresponding <code>process()</code> invocations must have been
* performed. Further note that this method internally uses this
* analyzer's working set. Finally, note that, after processing a
* production, this method sets the production's {@link
* Properties#RESTRICT} property.
*
* @param e The element.
* @return <code>true</code> if the element restricts the input.
*/
public boolean restrictsInput(Element e) {
return (Boolean)restrictsInputVisitor.dispatch(e);
}
/** The restricts input visitor. */
@SuppressWarnings("unused")
private final Visitor restrictsInputVisitor = new Visitor() {
public Boolean visit(FullProduction p) {
// Perform internal consistency checks.
assert ! isBeingWorkedOn(p.qName);
assert ! p.hasProperty(Properties.RESTRICT);
// Enter the production's module.
Object closure = enter(p);
// Mark the production as being processed.
workingOn(p.qName);
// Process the production.
Boolean result = (Boolean)dispatch(p.choice);
// Remember the result.
p.setProperty(Properties.RESTRICT, result);
// Unmark the production.
notWorkingOn(p.qName);
// Exit the production's module.
exit(closure);
// Done.
return result;
}
public Boolean visit(OrderedChoice c) {
for (Sequence alt : c.alternatives) {
if (! (Boolean)dispatch(alt)) return Boolean.FALSE;
}
return Boolean.TRUE;
}
public Boolean visit(Repetition r) {
return r.once;
}
public Boolean visit(Option o) {
return Boolean.FALSE;
}
public Boolean visit(Sequence s) {
for (Element e : s.elements) {
if ((Boolean)dispatch(e)) return Boolean.TRUE;
}
return Boolean.FALSE;
}
public Boolean visit(Predicate p) {
// We assume that semantic predicates restrict the input.
return Boolean.TRUE;
}
public Boolean visit(NonTerminal nt) {
FullProduction p;
try {
p = lookup(nt);
} catch (IllegalArgumentException x) {
return Boolean.TRUE;
}
if (null != p) {
if (p.hasProperty(Properties.RESTRICT)) {
return (Boolean)p.getProperty(Properties.RESTRICT);
} else if (isBeingWorkedOn(p.qName)) {
return Boolean.TRUE;
} else {
return (Boolean)dispatch(p);
}
} else {
return Boolean.TRUE;
}
}
public Boolean visit(Terminal t) {
return Boolean.TRUE;
}
public Boolean visit(UnaryOperator op) {
// The default for bindings, string matches, and voided elements.
return (Boolean)dispatch(op.element);
}
public Boolean visit(ParserAction pa) {
// Parser actions are assumed to consume some input.
return Boolean.TRUE;
}
public Boolean visit(Element e) {
// Actions, parse tree nodes, null literals, and value
// elements do not consume any input.
return Boolean.FALSE;
}
};
// =======================================================================
/**
* Determine whether the specified element may consume the input.
* Note that this method requires that the production and module
* containing the element are currently being processed; i.e., the
* corresponding <code>process()</code> invocations must have been
* performed. Further note that this method internally uses this
* analyzer's working set. Finally, note that, after processing a
* production, this method sets the production's {@link
* Properties#CONSUMER} property.
*
* @param e The element.
* @return <code>true</code> if the element consumes the input.
*/
public boolean consumesInput(Element e) {
return (Boolean)consumesInputVisitor.dispatch(e);
}
/** The consumes input visitor. */
@SuppressWarnings("unused")
private final Visitor consumesInputVisitor = new Visitor() {
public Boolean visit(FullProduction p) {
// Perform internal consistency checks.
assert ! isBeingWorkedOn(p.qName);
assert ! p.hasProperty(Properties.CONSUMER);
// Enter the production's module.
Object closure = enter(p);
// Mark the production as being processed.
workingOn(p.qName);
// Process the production.
Boolean result = (Boolean)dispatch(p.choice);
// Remember the result.
p.setProperty(Properties.CONSUMER, result);
// Unmark the production.
notWorkingOn(p.qName);
// Exit the production's module.
exit(closure);
// Done.
return result;
}
public Boolean visit(OrderedChoice c) {
for (Sequence alt : c.alternatives) {
if ((Boolean)dispatch(alt)) return Boolean.TRUE;
}
return Boolean.FALSE;
}
public Boolean visit(Sequence s) {
for (Element e : s.elements) {
if ((Boolean)dispatch(e)) return Boolean.TRUE;
}
return Boolean.FALSE;
}
public Boolean visit(Predicate p) {
// Predicates inspect the input but do not consume it.
return Boolean.FALSE;
}
public Boolean visit(NonTerminal nt) {
FullProduction p;
try {
p = lookup(nt);
} catch (IllegalArgumentException x) {
return Boolean.TRUE;
}
if (null != p) {
if (p.hasProperty(Properties.CONSUMER)) {
return (Boolean)p.getProperty(Properties.CONSUMER);
} else if (isBeingWorkedOn(p.qName)) {
return Boolean.TRUE;
} else {
return (Boolean)dispatch(p);
}
} else {
return Boolean.TRUE;
}
}
public Boolean visit(Terminal t) {
return Boolean.TRUE;
}
public Boolean visit(UnaryOperator op) {
return (Boolean)dispatch(op.element);
}
public Boolean visit(ParserAction pa) {
// Parser actions are assumed to consume some input.
return Boolean.TRUE;
}
public Boolean visit(Element e) {
// Actions, parse tree nodes, null literals, and value
// elements do not consume any input.
return Boolean.FALSE;
}
};
// =======================================================================
/**
* Determine whether the specified element matches the empty input.
* Note that this method requires that the production and module
* containing the element are currently being processed; i.e., the
* corresponding <code>process()</code> invocations must have been
* performed. Further note that this method internally uses this
* analyzer's working set. Finally, note that, after processing a
* production, this method sets the production's {@link
* Properties#EMPTY} property.
*
* @param e The element.
* @return <code>true</code> if the element matches the empty input.
*/
public boolean matchesEmpty(Element e) {
return (Boolean)matchesEmptyVisitor.dispatch(e);
}
/** The matches empty visitor. */
@SuppressWarnings("unused")
private final Visitor matchesEmptyVisitor = new Visitor() {
public Boolean visit(FullProduction p) {
// Perform internal consistency checks.
assert ! isBeingWorkedOn(p.qName);
assert ! p.hasProperty(Properties.EMPTY);
// Enter the production's module.
Object closure = enter(p);
// Mark the production as being processed.
workingOn(p.qName);
// Process the production.
Boolean result = (Boolean)dispatch(p.choice);
// Remember the result.
p.setProperty(Properties.EMPTY, result);
// Unmark the production.
notWorkingOn(p.qName);
// Exit the production's module.
exit(closure);
// Done.
return result;
}
public Boolean visit(OrderedChoice c) {
for (Sequence alt : c.alternatives) {
if ((Boolean)dispatch(alt)) return Boolean.TRUE;
}
return Boolean.FALSE;
}
public Boolean visit(Repetition r) {
return ! r.once;
}
public Boolean visit(Option o) {
return Boolean.TRUE;
}
public Boolean visit(Sequence s) {
for (Element e : s.elements) {
if (! (Boolean)dispatch(e)) return Boolean.FALSE;
}
return Boolean.TRUE;
}
public Boolean visit(FollowedBy p) {
return (Boolean)dispatch(p.element);
}
public Boolean visit(NotFollowedBy p) {
return ! (Boolean)dispatch(p.element);
}
public Boolean visit(SemanticPredicate p) {
// We assume that semantic predicates restrict input.
return Boolean.FALSE;
}
public Boolean visit(NonTerminal nt) {
FullProduction p;
try {
p = lookup(nt);
} catch (IllegalArgumentException x) {
return Boolean.FALSE;
}
if (null != p) {
if (p.hasProperty(Properties.EMPTY)) {
return (Boolean)p.getProperty(Properties.EMPTY);
} else if (isBeingWorkedOn(p.qName)) {
return Boolean.FALSE;
} else {
return (Boolean)dispatch(p);
}
} else {
return Boolean.FALSE;
}
}
public Boolean visit(Terminal t) {
return Boolean.FALSE;
}
public Boolean visit(UnaryOperator op) {
// The default for bindings, string matches, and voided elements.
return (Boolean)dispatch(op.element);
}
public Boolean visit(ParserAction pa) {
// Parser actions are assumed to consume some input.
return Boolean.FALSE;
}
public Boolean visit(Element e) {
// Actions, parse tree nodes, null literals, and value
// elements do not consume any input.
return Boolean.TRUE;
}
};
// =======================================================================
/**
* Determine whether the specified element is a not-followed-by
* predicate. Note that this method requires that the production
* and module containing the element are currently being processed;
* i.e., the corresponding <code>process()</code> invocations must
* have been performed.
*
* @param e The element.
* @return <code>true</code> if the specified element is a
* not-followed-by predicate.
*/
public boolean isNotFollowedBy(Element e) {
return (Boolean)isNotFollowedByVisitor.dispatch(strip(e));
}
/** The is not-followed-by visitor. */
@SuppressWarnings("unused")
private final Visitor isNotFollowedByVisitor = new Visitor() {
public Boolean visit(FullProduction p) {
Object closure = enter(p);
Boolean result = (Boolean)dispatch(strip(p.choice));
exit(closure);
return result;
}
public Boolean visit(OrderedChoice c) {
for (Sequence alt : c.alternatives) {
if (! (Boolean)dispatch(alt)) return Boolean.FALSE;
}
return Boolean.TRUE;
}
public Boolean visit(Sequence s) {
for (Element e : s.elements) {
if (! (Boolean)dispatch(e)) return Boolean.FALSE;
}
return Boolean.TRUE;
}
public Boolean visit(NonTerminal nt) {
FullProduction p;
try {
p = lookup(nt);
} catch (IllegalArgumentException x) {
return Boolean.FALSE;
}
return null != p ? (Boolean)dispatch(p) : Boolean.FALSE;
}
public Boolean visit(NotFollowedBy p) {
return Boolean.TRUE;
}
public Boolean visit(Element e) {
return Boolean.FALSE;
}
};
// =======================================================================
/**
* Determine whether the specified element can be bound. This
* method returns <code>true</code> if the specified element is
* recognized by {@link #bind(List)} and {@link #bind(List,String)}
* as capturing a list's semantic value.
*
* @param e The element.
* @return <code>true</code> if the element can be bound.
*/
public boolean isBindable(Element e) {
switch (e.tag()) {
case NONTERMINAL:
return ! AST.isVoid(lookup((NonTerminal)e).type);
case NULL:
case CHOICE:
case OPTION:
case REPETITION:
case ANY_CHAR:
case CHAR_CLASS:
case CHAR_LITERAL:
case STRING_LITERAL:
case STRING_MATCH:
case PARSE_TREE_NODE:
case BINDING:
return true;
default:
return false;
}
}
// =======================================================================
/**
* Bind the elements in the specified list. This method analyzes
* the specified list and, if possible, adds in a binding for the
* semantic value of the elements in the list. If the list does not
* have a unique binding to capture the semantic value, this method
* returns <code>null</code> to indicate the need for an explicitly
* specified semantic value.
*
* @param l The list to bind.
* @return The corresponding binding.
*/
public Binding bind(List<Element> l) {
return bind(l, null);
}
/**
* Bind the elements in the specified list. This method analyzes
* the specified list and, if possible, adds in a binding for the
* semantic value of the elements in the list. If the list does not
* have a unique binding to capture the semantic value, this method
* returns <code>null</code> to indicate the need for an explicitly
* specified semantic value.
*
* @param l The list to bind.
* @param marker The marker for the binding variable or
* <code>null</code> if no marker should be used.
* @return The corresponding binding.
*/
public Binding bind(List<Element> l, String marker) {
Binding binding = null;
Element bound = null;
int idx = -1;
final int length = l.size();
loop: for (int i=0; i<length; i++) {
Element e = l.get(i);
switch (e.tag()) {
case NONTERMINAL:
// Void productions have no meaningful value. Skip them.
if (AST.isVoid(lookup((NonTerminal)e).type)) break;
// Fall through.
case NULL:
case CHOICE:
case OPTION:
case REPETITION:
case ANY_CHAR:
case CHAR_CLASS:
case CHAR_LITERAL:
case STRING_LITERAL:
case STRING_MATCH:
case PARSE_TREE_NODE:
if (-1 == idx) {
bound = e;
idx = i;
break;
} else {
binding = null;
idx = -1;
break loop;
}
case BINDING:
if (-1 == idx) {
binding = (Binding)e;
idx = i;
break;
} else {
binding = null;
idx = -1;
break loop;
}
case FOLLOWED_BY:
case NOT_FOLLOWED_BY:
case SEMANTIC_PREDICATE:
case VOIDED:
case NODE_MARKER:
// Predicates, voided elements, and node markers have no
// meaningful value. Skip them.
break;
case ACTION:
// Skip actions that do not set the semantic value.
if (! ((Action)e).setsValue()) break;
// Fall through.
default:
// Embedded sequences, character switches, parser actions, and
// value elements cannot be bound. All bets are off.
binding = null;
idx = -1;
break loop;
}
}
if (null != binding) {
// There is a single element that already has a binding.
return binding;
} else if (-1 == idx) {
// We require an explicitly specified semantic value.
return null;
} else {
if (null == marker) {
binding = new Binding(variable(), bound);
} else {
binding = new Binding(variable(marker), bound);
}
l.set(idx, binding);
return binding;
}
}
/**
* Get the binding for the specified list's value. If the list has
* a single binding, this method returns it; otherwise (i.e., if the
* sequence has no binding or multiple bindings), this method
* returns <code>null</code>.
*
* @param l The list.
* @return The list's only binding (if it exists).
*/
public static Binding getBinding(List<Element> l) {
Binding binding = null;
for (Element e : l) {
if (e instanceof Binding) {
if (null == binding) {
binding = (Binding)e;
} else {
return null;
}
}
}
return binding;
}
/**
* Unbind the specified element. If the element is a binding, this
* method returns the bound element. Otherwise, it returns the
* element.
*
* @param e The element.
* @return The unbound element.
*/
public static Element unbind(Element e) {
return e instanceof Binding ? ((Binding)e).element : e;
}
/**
* Strip and unbind the specified element. Invoking this method on
* element <code>e</code> is semantically equivalent to:<pre>
* Analyzer.strip(Analyzer.unbind(Analyzer.strip(e)))
* </pre>
*
* @param e The element.
* @return The stripped and unbound element.
*/
public static Element stripAndUnbind(Element e) {
return strip(unbind(strip(e)));
}
// =======================================================================
/**
* Get the specified list's semantic value. If the specified list
* explicitly sets the list's semantic value through a binding, a
* semantic action, a parser action, or a value element, this method
* returns the last such element. Next, if the list has a single
* element with a semantic value, this method returns that element.
* Otherwise, this method returns <code>null</code>.
*
* @param list The list.
* @param ignoreActions The flag for whether to ignore (parser)
* actions.
* @return The list's value or <code>null</code> if the value cannot
* be determined.
*/
public Element getValue(List<Element> list, boolean ignoreActions) {
Element value = null;
// First, try to find any explicit assignments of yyValue.
for (Element e : list) {
switch (e.tag()) {
case BINDING: {
Binding b = (Binding)e;
if (CodeGenerator.VALUE.equals(b.name)) value = b;
} break;
case ACTION: {
Action a = (Action)e;
if ((! ignoreActions) && a.setsValue()) value = a;
} break;
case PARSER_ACTION:
if (! ignoreActions) value = e;
break;
case ACTION_BASE_VALUE:
case BINDING_VALUE:
case GENERIC_ACTION_VALUE:
case GENERIC_RECURSION_VALUE:
case GENERIC_NODE_VALUE:
case EMPTY_LIST_VALUE:
case PROPER_LIST_VALUE:
case NULL_VALUE:
case STRING_VALUE:
case TOKEN_VALUE:
value = e;
break;
}
}
if (null != value) return value;
// Second, try to find a single value.
boolean foundMany = false;
for (Element e : list) {
switch (e.tag()) {
case NONTERMINAL:
if (AST.isVoid(lookup((NonTerminal)e).type)) break;
// Fall through.
case NULL:
case CHOICE:
case OPTION:
case REPETITION:
case ANY_CHAR:
case CHAR_CLASS:
case CHAR_LITERAL:
case STRING_LITERAL:
case STRING_MATCH:
case PARSE_TREE_NODE:
case BINDING:
if (! foundMany) {
if (null == value) {
value = e;
} else {
foundMany = true;
value = null;
}
}
break;
default:
// No value.
}
}
return value;
}
// =======================================================================
/**
* Determine whether the specified list of elements sets the
* semantic value. This method determines whether any element
* either is a binding to <code>yyValue</code>, an action setting
* <code>yyValue</code>, or a value element.
*
* @param l The list.
* @param all The flag for whether all alternatives of nested
* choices need to set the semantic value.
* @return <code>true</code> if the list sets the semantic value.
*/
public static boolean setsValue(List<Element> l, boolean all) {
return setsValue(new Sequence(l), all);
}
/**
* Determine whether the specified element sets the semantic value.
* This method determines whether the specified element contains
* either an explicit binding to <code>yyValue</code>, an action
* setting <code>yyValue</code>, or a value element.
*
* @param e The element.
* @param all The flag for whether all alternatives of choices need
* to set the semantic value.
* @return <code>true</code> if the element sets the semantic value.
*/
@SuppressWarnings("unused")
public static boolean setsValue(Element e, final boolean all) {
return (Boolean)new Visitor() {
private boolean isLast = true;
public Boolean visit(OrderedChoice c) {
if (! isLast) return Boolean.FALSE;
for (Sequence alt : c.alternatives) {
if (all) {
if (! (Boolean)dispatch(alt)) return Boolean.FALSE;
} else {
if ((Boolean)dispatch(alt)) return Boolean.TRUE;
}
}
return all;
}
public Boolean visit(Sequence s) {
if (! isLast) return Boolean.FALSE;
for (Iterator<Element> iter=s.elements.iterator(); iter.hasNext();) {
isLast = ! iter.hasNext();
if ((Boolean)dispatch(iter.next())) {
isLast = true;
return Boolean.TRUE;
}
}
isLast = true;
return Boolean.FALSE;
}
public Boolean visit(VoidedElement v) {
return (Boolean)dispatch(v.element);
}
public Boolean visit(Binding b) {
return CodeGenerator.VALUE.equals(b.name);
}
public Boolean visit(StringMatch m) {
return (Boolean)dispatch(m.element);
}
public Boolean visit(Action a) {
return a.setsValue();
}
public Boolean visit(ParserAction a) {
return Boolean.TRUE;
}
public Boolean visit(ValueElement v) {
return Boolean.TRUE;
}
public Boolean visit(Element e) {
return Boolean.FALSE;
}
}.dispatch(e);
}
// =======================================================================
/**
* Determine whether the specified list of elements sets the
* semantic value to <code>null</code>. This method determines
* whether the only value-setting element is a null value. It
* ignores nested choices and sequences.
*
* @param l The list.
* @return <code>true</code> if the list sets the semantic value to
* <code>null</code>.
*/
public static boolean setsNullValue(List<Element> l) {
boolean hasNull = false;
for (Element e : l) {
switch (e.tag()) {
case BINDING:
if (CodeGenerator.VALUE.equals(((Binding)e).name)) return false;
break;
case ACTION:
if (((Action)e).setsValue()) return false;
break;
case NULL_VALUE:
hasNull = true;
break;
case ACTION_BASE_VALUE:
case BINDING_VALUE:
case GENERIC_ACTION_VALUE:
case GENERIC_RECURSION_VALUE:
case GENERIC_NODE_VALUE:
case EMPTY_LIST_VALUE:
case PROPER_LIST_VALUE:
case STRING_VALUE:
case TOKEN_VALUE:
return false;
}
}
return hasNull;
}
// =======================================================================
/**
* Determine whether the list value of the specified element may be
* <code>null</code>. This method returns <code>true</code> if the
* specified element is an option or a nonterminal representing a
* desguared option.
*
* @param element The element.
* @return <code>true</code> if the element's value may be
* <code>null</code>.
*/
public boolean mayBeNull(Element element) {
switch (element.tag()) {
case OPTION:
return true;
case NONTERMINAL:
NonTerminal nt = (NonTerminal)element;
Production p = lookup(nt);
if (p.getBooleanProperty(Properties.OPTION)) return true;
}
return false;
}
// =======================================================================
/**
* Type the specified element. This method requires that this
* analyzer has been initialized with a grammar contained in a
* single module. It utilizes the type representation of {@link
* AST} and may return a {@link Wildcard}.
*
* @param element The element.
* @return The type.
* @throws IllegalArgumentException Signals that the element cannot
* be typed.
*/
public Type type(Element element) {
switch (element.tag()) {
case CHOICE:
case SEQUENCE:
return AST.ANY;
case REPETITION: {
Binding b = Analyzer.
getBinding(Sequence.ensure(((Repetition)element).element).elements);
return AST.listOf(null == b ? AST.ANY : type(b.element));
}
case OPTION: {
Binding b = Analyzer.
getBinding(Sequence.ensure(((Option)element).element).elements);
return null == b ? AST.ANY : AST.markOptional(type(b.element));
}
case VOIDED:
return VoidT.TYPE;
case BINDING:
return type(((Binding)element).element);
case NONTERMINAL:
return lookup((NonTerminal)element).type.deannotate();
case ANY_CHAR:
case CHAR_CLASS:
case CHAR_LITERAL:
case CHAR_SWITCH:
return AST.CHAR;
case STRING_LITERAL:
return AST.STRING;
case STRING_MATCH:
return module.hasAttribute(Constants.ATT_PARSE_TREE) ?
AST.NODE : AST.STRING;
case PARSE_TREE_NODE:
return AST.NODE;
case NULL:
return Wildcard.TYPE;
default:
throw new IllegalArgumentException("Unable to type " + element);
}
}
}