/*
* xtc - The eXTensible Compiler
* Copyright (C) 2005-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.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import xtc.Constants;
import xtc.tree.Attribute;
import xtc.tree.Visitor;
import xtc.type.AST;
import xtc.type.ErrorT;
import xtc.type.Type;
import xtc.util.Runtime;
import xtc.util.Utilities;
/**
* Visitor to resolve grammar module dependencies.
*
* <p />Note that this visitor {@link TextTester marks} text-only
* productions as such. It also {@link LeftRecurser detects} left
* recursions and marks direct left recursions. Furthermore, it sets
* a grammar's {@link Properties#GENERIC} and {@link
* Properties#RECURSIVE} properties as appropriate.
*
* @author Robert Grimm
* @version $Revision: 1.128 $
*/
public class Resolver extends Visitor {
/** The runtime. */
protected final Runtime runtime;
/** The analyzer utility. */
protected final Analyzer analyzer;
/** The type operations. */
protected final AST ast;
/** The current checking phase. */
protected int phase;
/** The identity hash map of erroneous nonterminals. */
protected Map<NonTerminal, NonTerminal> badNTs;
/** The flag for whether the current module has a stateful attribute. */
protected boolean hasState;
/** The flag for whether the current module is a mofunctor. */
protected boolean isMofunctor;
/** Flag for whether the current expression is a predicate. */
protected boolean isPredicate;
/** The set of sequence names for the current production. */
protected Set<SequenceName> sequenceNames;
/**
* Create a new resolver.
*
* @param runtime The runtime.
* @param analyzer The analyzer utility.
* @param ast The type operations.
*/
public Resolver(Runtime runtime, Analyzer analyzer, AST ast) {
this.runtime = runtime;
this.analyzer = analyzer;
this.ast = ast;
badNTs = new IdentityHashMap<NonTerminal, NonTerminal>();
sequenceNames = new HashSet<SequenceName>();
}
/**
* Print the specified module's signature.
*
* @param m The module.
*/
protected void signature(Module m) {
// Print the globally visible module name.
System.out.print("module ");
System.out.print(m.name.name);
// If the module has been instantiated from another, print the
// original invocation.
if (m.name.hasProperty(Constants.ORIGINAL) ||
(null != m.parameters)) {
System.out.print(" = ");
ModuleName base =
m.name.hasProperty(Constants.ORIGINAL) ?
(ModuleName)m.name.getProperty(Constants.ORIGINAL) :
m.name;
System.out.print(base.name);
if (null == m.parameters) {
System.out.print("()");
} else {
System.out.print(m.parameters.toString());
}
}
if (null != m.dependencies) {
// Print the modification, if any.
for (ModuleDependency dep : m.dependencies) {
if (dep.isModification()) {
System.out.println();
System.out.print(" modifies ");
System.out.print(dep.visibleName().name);
break;
}
}
// Print the imports, if any.
boolean first = true;
for (ModuleDependency dep : m.dependencies) {
if (dep.isImport()) {
if (first) {
System.out.println();
System.out.print(" imports ");
first = false;
} else {
System.out.println(',');
System.out.print(" ");
}
System.out.print(dep.visibleName().name);
}
}
}
// Done.
System.out.println(';');
}
/**
* Load the module with the specified name.
*
* @param name The name.
* @return The corresponding grammar module.
* @throws IllegalArgmentException
* Signals that the file is too large.
* @throws FileNotFoundException
* Signals that the file does not exist.
* @throws IOException
* Signals an exceptional condition while accessing the file.
* @throws ParseException
* Signals a parse error.
*/
protected Module load(String name) throws IOException, ParseException {
File file = runtime.locate(Utilities.toPath(name, Constants.EXT_GRAMMAR));
if (! file.exists()) {
throw new FileNotFoundException(file + ": file not found");
} else if (! file.isFile()) {
throw new IllegalArgumentException(file + ": not a file");
} else if (Integer.MAX_VALUE < file.length()) {
throw new IllegalArgumentException(file + ": file too large");
}
Reader in = null;
try {
in = runtime.getReader(file);
PParser parser = new PParser(in, file.toString(), (int)file.length());
return (Module)parser.value(parser.pModule(0));
} finally {
if (null != in) {
try {
in.close();
} catch (IOException x) {
// Nothing to see here. Move on.
}
}
}
}
/**
* Rename the specified module. This method renames all module
* names in the specified module, including the module's {@link
* Module#name name}, the module's {@link Module#dependencies
* dependencies}, and each production's {@link Production#qName
* qualified name} (if it is not <code>null</code>).
*
* @param module The module.
* @param renaming The renaming.
*/
protected void rename(Module module, ModuleMap renaming) {
// Process the name.
module.name = module.name.rename(renaming);
// Process the parameters.
if (null != module.parameters) {
module.parameters.rename(renaming);
}
// Process the dependencies.
if (null != module.dependencies) {
for (ModuleDependency dep : module.dependencies) dep.rename(renaming);
}
// Process the productions.
Renamer renamer = new Renamer(runtime, analyzer, renaming);
for (Production p : module.productions) {
if (null != p.qName) {
p.qName = p.qName.rename(renaming);
}
renamer.dispatch(p);
}
}
/**
* Strip the specified production's ordered choice.
*
* @see Analyzer#stripChoices
*
* @param p The production to strip.
* @return The specified production.
*/
protected Production strip(Production p) {
if ((null != p) && (null != p.choice)) {
p.choice = Analyzer.stripChoices(p.choice);
}
return p;
}
/**
* Apply the specified alternative addition to the specified full
* production.
*
* @param p1 The alternative addition.
* @param p2 The full production.
*/
protected void apply(AlternativeAddition p1, FullProduction p2) {
final int length2 = p2.choice.alternatives.size();
int index = -1;
for (int i=0; i<length2; i++) {
if (p1.sequence.equals(p2.choice.alternatives.get(i).name)) {
index = i;
break;
}
}
if (-1 == index) {
StringBuilder buf = new StringBuilder();
buf.append("unable to add new alternative");
if (1 != p1.choice.alternatives.size()) {
buf.append('s');
}
if (p1.isBefore) {
buf.append(" before ");
} else {
buf.append(" after ");
}
buf.append("non-existent alternative '");
buf.append(p1.sequence.name);
buf.append("'");
runtime.error(buf.toString(), p1.sequence);
} else {
if (! p1.isBefore) {
index++;
}
p2.choice.alternatives.addAll(index, p1.choice.alternatives);
}
}
/**
* Apply the specified alternative removal to the specified full
* production.
*
* @param p1 The alternative removal.
* @param p2 The full production.
*/
protected void apply(AlternativeRemoval p1, FullProduction p2) {
for (SequenceName name : p1.sequences) {
boolean removed = false;
for (Iterator<Sequence> iter=p2.choice.alternatives.iterator();
iter.hasNext(); ) {
if (name.equals(iter.next().name)) {
iter.remove();
removed = true;
break;
}
}
if (! removed) {
runtime.error("unable to remove non-existent alternative '" + name +
"'", name);
}
}
}
/**
* Apply the specified production override to the specified full
* production.
*
* @param p1 The production override.
* @param p2 The full production.
*/
protected void apply(ProductionOverride p1, FullProduction p2) {
// Override the attributes.
if (null != p1.attributes) {
p2.attributes = p1.attributes;
}
// Override the element.
if (null != p1.choice) {
if (p1.isComplete) {
p2.choice = p1.choice;
} else {
for (Sequence s1 : p1.choice.alternatives) {
if (null == s1.name) {
runtime.error("overriding sequence without name", s1);
} else {
final int length2 = p2.choice.alternatives.size();
boolean changed = false;
for (int i=0; i<length2; i++) {
Sequence s2 = p2.choice.alternatives.get(i);
if (s1.name.equals(s2.name)) {
p2.choice.alternatives.set(i, s1);
changed = true;
break;
}
}
if (! changed) {
runtime.error("unable to override non-existent alternative '" +
s1.name + "'", s1);
}
}
}
}
}
}
/**
* Load all dependent modules. This method loads the transitive
* closure of all dependent modules for the specified top-level
* module and returns the closure as a list, with the specified
* module being the first list element. It checks that the
* specified top-level module is not parameterized, that the
* parameters of parameterized modules are well-formed, that
* arguments match parameters when instantiating parameterized
* modules, that different instantiations are consistent with each
* other, that a module does not have more than one modifies clause,
* and that module modifications do not result in circular
* dependencies. This method also reports any file access and
* parsing errors. Finally, this method fills in the auxiliary
* modification field for all loaded modules.
*
* @param m The module.
* @return The corresponding grammar or <code>null</code> in the
* case of errors.
*/
protected Grammar load(Module m) {
// Make sure the top-level module is not parameterized.
if ((null != m.parameters) && (0 < m.parameters.size())) {
runtime.error("parameterized top-level module '" + m.name.name + "'",
m.name);
return null;
}
// Print top-level module if so requested.
PrettyPrinter pp = null;
if (runtime.test("optionLoaded")) {
pp = new PrettyPrinter(runtime.console(), ast, true);
pp.dispatch(m);
pp.flush();
}
// Prepare for loading all dependent modules. The modules list
// contains all correctly loaded modules. The module map contains
// a mapping between visible module names (as strings) and
// modules. The loaded map contains a mapping between visible
// module names and the corresponding module dependencies (even if
// the module contained errors). The conflicts map is treated as
// a set that contains conflicting module dependencies that were
// already reported to the user.
List<Module> modules =
new ArrayList<Module>();
Map<String, Module> moduleMap =
new HashMap<String,Module>();
Map<ModuleName, ModuleDependency> loaded =
new HashMap<ModuleName, ModuleDependency>();
Map<ModuleDependency, Boolean> conflicts =
new IdentityHashMap<ModuleDependency, Boolean>();
modules.add(m);
moduleMap.put(m.name.name, m);
loaded.put(m.name, new ModuleImport(m.name));
// Process the top-level module's dependencies and fill in the
// auxiliary modification field.
List<ModuleDependency> workList = new ArrayList<ModuleDependency>();
if (null != m.dependencies) {
for (ModuleDependency dep : m.dependencies) {
if (dep.isModification()) {
// Only process one modification per module and do not allow
// self-mutilation.
boolean process = true;
if (null != m.modification) {
runtime.error("duplicate modifies declaration", dep);
process = false;
}
if (dep.visibleName().equals(m.name)) {
runtime.error("module '" + m.name + "' modifies itself", dep);
process = false;
}
if (process) {
m.modification = (ModuleModification)dep;
workList.add(dep);
}
} else {
workList.add(dep);
}
}
}
// Print the signature if so requested.
if (runtime.test("optionDependencies")) {
signature(m);
}
// Do the actual loading.
while (! workList.isEmpty()) {
ModuleDependency dep = workList.remove(0);
// Make sure that every module is processed only once.
if (loaded.containsKey(dep.visibleName())) {
ModuleDependency dep2 = loaded.get(dep.visibleName());
// Since modules live in a global name space, all dependencies
// need to be consistent with each other. In particular, two
// dependency declarations for the a module with the same name
// either need to have exactly the same structure or the later
// declaration must be a straight-forward declaration without
// any arguments or target name.
if (! dep.isConsistentWith(dep2)) {
if (! conflicts.containsKey(dep2)) {
conflicts.put(dep2, Boolean.TRUE);
runtime.error("inconsistent instantiation of module '" +
dep2.module + dep2.arguments + "' as '" +
dep2.visibleName() + "'", dep2);
}
runtime.error("inconsistent instantiation of module '" + dep.module +
dep.arguments + "' as '" + dep.visibleName() + "'", dep);
}
continue;
}
// Be verbose if so desired.
if (runtime.test("optionVerbose")) {
String path = Utilities.toPath(dep.module.name, Constants.EXT_GRAMMAR);
File file = null;
try {
file = runtime.locate(path);
} catch (Exception x) {
// Ignore.
}
if (null == file) {
System.err.println("[Loading module " + dep.module + "]");
} else {
System.err.println("[Loading module " + dep.module + " from " +
file + "]");
}
}
// Load module.
Module m2 = null;
try {
m2 = load(dep.module.name);
} catch (IllegalArgumentException x) {
runtime.error(x.getMessage(), dep);
} catch (FileNotFoundException x) {
runtime.error(x.getMessage(), dep);
} catch (IOException x) {
if (null == x.getMessage()) {
runtime.error("I/O error while accessing corresponding file", dep);
} else {
runtime.error(x.getMessage(), dep);
}
} catch (ParseException x) {
System.err.print(x.getMessage());
runtime.error();
}
// Remember that we touched this module, even if it didn't load
// as there is no need to generate the same error message more
// than once.
loaded.put(dep.visibleName(), dep);
// All the other work requires that we actually loaded the
// module.
if (null == m2) continue;
// Print the module if so requested.
if (runtime.test("optionLoaded")) {
pp.dispatch(m2);
pp.flush();
}
// Track errors in this module.
boolean moduleError = false;
// Make sure the module name matches the desired module name.
if (! dep.module.equals(m2.name)) {
String path = Utilities.toPath(dep.module.name, Constants.EXT_GRAMMAR);
File file = null;
try {
file = runtime.locate(path);
} catch (Exception x) {
// Ignore.
}
if (null == file) {
runtime.error("module name '" + m2.name +
"' inconsistent with file name", m2.name);
moduleError = true;
} else {
runtime.error("module name '" + m2.name +
"' inconsistent with file name " + file, m2.name);
moduleError = true;
}
}
// Check the module's parameters (if any).
if (null != m2.parameters) {
final int size = m2.parameters.size();
for (int i=0; i<size; i++) {
ModuleName name = m2.parameters.get(i);
if (m2.name.equals(name)) {
runtime.error("module parameter '" + name + "' same as module name",
name);
moduleError = true;
}
for (int j=0; j<i; j++) {
if (name.equals(m2.parameters.get(j))) {
runtime.error("duplicate module parameter '" + name + "'", name);
moduleError = true;
break;
}
}
}
}
// Check that argument and parameter numbers match.
final int argumentNumber = dep.arguments.size();
final int parameterNumber =
(null != m2.parameters)? m2.parameters.size() : 0;
if (argumentNumber != parameterNumber) {
StringBuilder buf = new StringBuilder();
buf.append(argumentNumber);
buf.append(" argument");
if (1 != argumentNumber) {
buf.append('s');
}
buf.append(" for module '");
buf.append(m2.name.name);
buf.append("' with ");
buf.append(parameterNumber);
buf.append(" parameter");
if (1 != parameterNumber) {
buf.append('s');
}
runtime.error(buf.toString(), dep);
moduleError = true;
}
// Only continue processing this module if there were no errors.
if (moduleError) continue;
// Perform any module renaming.
if ((0 != argumentNumber) || (! dep.module.equals(dep.visibleName()))) {
if (runtime.test("optionVerbose")) {
StringBuilder buf = new StringBuilder();
buf.append("[Instantiating module ");
buf.append(m2.name);
buf.append('(');
for (int i=0; i<argumentNumber; i++) {
buf.append(dep.arguments.get(i).name);
buf.append('/');
buf.append(m2.parameters.get(i).name);
if (i+1 < argumentNumber) {
buf.append(", ");
}
}
buf.append(") as ");
buf.append(dep.visibleName().name);
buf.append(']');
System.err.println(buf.toString());
}
// Construct a module map for renaming.
final ModuleMap renaming;
if (0 != argumentNumber) {
renaming = new ModuleMap(m2.parameters, dep.arguments);
} else {
renaming = new ModuleMap();
}
// If the module's declared name and the visible name differ,
// add the corresponding mapping to the module map.
if (! dep.module.equals(dep.visibleName())) {
renaming.put(dep.module, dep.visibleName());
}
// Rename the module.
rename(m2, renaming);
// The parameters are nulled out below to enable the printing
// of module dependencies.
}
// Add the module to the list of modules and put the module in
// the module map.
modules.add(m2);
moduleMap.put(m2.name.name, m2);
// Process any further dependencies and fill in the module's
// auxiliary modification field.
if (null != m2.dependencies) {
for (ModuleDependency dep2 : m2.dependencies) {
if (dep2.isModification()) {
// Only process one modification per module and do not
// allow self-mutilation.
boolean process = true;
if (null != m2.modification) {
runtime.error("duplicate modifies declaration", dep2);
process = false;
}
if (dep2.visibleName().equals(m2.name)) {
runtime.error("module '" + m2.name + "' modifies itself", dep2);
process = false;
}
if (process) {
m2.modification = (ModuleModification)dep2;
workList.add(dep2);
}
} else {
workList.add(dep2);
}
}
}
// Print the signature, if so desired.
if (runtime.test("optionDependencies")) {
signature(m2);
}
if (null != m2.parameters) {
m2.setProperty(Constants.ARGUMENTS, m2.parameters);
m2.parameters = null;
}
}
// Now, check for circular module modifications. The checked set
// tracks the names of modules that have been checked. The
// checking map contains a mapping from module names (as module
// names) to booleans. False indicates that the module has been
// checked once. True indicates that the module has been checked
// twice, meaning it is part of a circular chain of dependencies.
Map<ModuleName, Boolean> checking = new HashMap<ModuleName, Boolean>();
Set<ModuleName> checked = new HashSet<ModuleName>();
for (Module m2 : modules) {
// If the module has been checked already, move on.
if (checked.contains(m2.name)) continue;
// Reset the checking map.
checking.clear();
do {
if (checking.containsKey(m2.name)) {
boolean circular = checking.get(m2.name);
if (circular) {
// We are checking this module for the third time and can
// therefore stop walking the dependencies.
break;
} else {
// We are checking this module for the second time.
checking.put(m2.name, Boolean.TRUE);
}
} else {
// We are checking this module for the first time.
checking.put(m2.name, Boolean.FALSE);
}
// If this module has a modifies clause, we check the
// corresponding module next.
if (null != m2.modification) {
// Look up the corresponding module.
m2 = moduleMap.get(m2.modification.visibleName().name);
if (null == m2) {
// This is an unresolved or erroneous dependency. We need
// to stop here.
break;
}
} else {
// This module does not modify another.
break;
}
} while (true);
// Remember all visited modules and report errors for all
// circular dependencies. We do this in order of the resolved
// modules to print errors in a standardized order.
for (Module m3 : modules) {
if (checking.containsKey(m3.name)) {
// Remember as visited.
checked.add(m3.name);
// Report error.
if (checking.get(m3.name)) {
runtime.error("circular modifies dependency", m3.modification);
}
}
}
}
// Create the grammar.
Grammar g = new Grammar(modules);
// Print instantiated modules, if so requested.
if (runtime.test("optionInstantiated")) {
if (runtime.test("optionHtml")) {
new HtmlPrinter(runtime, analyzer, ast, false).dispatch(g);
} else {
pp = new PrettyPrinter(runtime.console(), ast, true);
pp.dispatch(g);
pp.flush();
}
}
// We are done.
return runtime.seenError()? null : g;
}
/**
* Perform basic checking for the specified grammar. This method
* does all correctness checking besides checking the contents of
* partial productions and checking for left-recursive definitions.
* Note that this method initializes the analyzer with the specified
* grammar.
*
* @param g The grammar.
* @return The list of global attributes, if any.
*/
protected List<Attribute> check(Grammar g) {
// Initialize the analyzer.
analyzer.register(this);
analyzer.init(g);
phase = 1;
// Initialize the top-level module and the list of global
// attributes.
Module root = g.modules.get(0);
List<Attribute> attributes = new ArrayList<Attribute>();
// Make sure that any stateful attributes agree in their class
// names. Additionally, collect any stateful, set, or flag
// attributes in the global list of attributes.
String stateName = null;
boolean stateError = false;
for (Module m2 : g.modules) {
// Process stateful attribute.
if (m2.hasAttribute(Constants.ATT_STATEFUL.getName())) {
Attribute state =
Attribute.get(Constants.ATT_STATEFUL.getName(), m2.attributes);
Object value = state.getValue();
if ((null != value) &&
(value instanceof String) &&
(! ((String)value).startsWith("\""))) {
if (null == stateName) {
attributes.add(state);
stateName = (String)state.getValue();
}
if (! stateName.equals(state.getValue())) {
stateError = true;
}
}
}
// Process set and flag attributes.
if (m2.hasAttribute(Constants.NAME_STRING_SET) ||
m2.hasAttribute(Constants.NAME_FLAG)) {
for (Attribute att : m2.attributes) {
String name = att.getName();
if ((Constants.NAME_STRING_SET.equals(name) ||
Constants.NAME_FLAG.equals(name)) &&
(! attributes.contains(att))) {
attributes.add(att);
}
}
}
}
// Now, do the actual well-formedness checking.
Set<ModuleName> visited = new HashSet<ModuleName>();
boolean hasTopLevel = false;
for (Module m2 : g.modules) {
analyzer.process(m2);
hasState = false;
isMofunctor = (null != m2.modification);
if (null != m2.attributes) {
final int length = m2.attributes.size();
for (int i=0; i<length; i++) {
Attribute att = m2.attributes.get(i);
String name = att.getName();
Object value = att.getValue();
if ((! Constants.ATT_WITH_LOCATION.equals(att)) &&
(! Constants.ATT_CONSTANT.equals(att)) &&
(! Constants.ATT_RAW_TYPES.equals(att)) &&
(! Constants.ATT_VERBOSE.equals(att)) &&
(! Constants.ATT_NO_WARNINGS.equals(att)) &&
(! Constants.ATT_IGNORING_CASE.equals(att)) &&
(! Constants.ATT_STATEFUL.getName().equals(name)) &&
(! Constants.NAME_PARSER.equals(name)) &&
(! Constants.NAME_MAIN.equals(name)) &&
(! Constants.NAME_PRINTER.equals(name)) &&
(! Constants.NAME_VISIBILITY.equals(name)) &&
(! Constants.NAME_STRING_SET.equals(name)) &&
(! Constants.NAME_FLAG.equals(name)) &&
(! Constants.NAME_FACTORY.equals(name)) &&
(! Constants.ATT_FLATTEN.equals(att)) &&
(! Constants.ATT_GENERIC_AS_VOID.equals(att)) &&
(! Constants.ATT_PARSE_TREE.equals(att)) &&
(! Constants.ATT_PROFILE.equals(att)) &&
(! Constants.ATT_DUMP.equals(att))) {
runtime.error("unrecognized grammar-wide attribute '"+att+"'", att);
} else {
for (int j=0; j<i; j++) {
Attribute att2 = m2.attributes.get(j);
if (name.equals(Constants.NAME_STRING_SET) ||
name.equals(Constants.NAME_FLAG)) {
if (att.equals(att2)) {
runtime.error("duplicate attribute '"+att+"'", att);
break;
} else if ((null != value) && value.equals(att2.getValue())) {
runtime.error("duplicate field name '" + att + "'", att);
}
} else if (name.equals(att2.getName())) {
runtime.error("duplicate attribute '" + name + "'", att);
break;
}
}
}
if (Constants.ATT_STATEFUL.getName().equals(name)) {
if (null == value) {
runtime.error("stateful attribute without class name", att);
} else if (! (value instanceof String)) {
runtime.error("stateful attribute with invalid value", att);
} else if (((String)value).startsWith("\"")) {
runtime.error("stateful attribute with invalid value", att);
} else if (stateError) {
runtime.error("inconsistent state class across modules", att);
}
hasState = true;
} else if (Constants.NAME_PARSER.equals(name)) {
if (null == value) {
runtime.error("parser attribute without class name", att);
} else if (! (value instanceof String)) {
runtime.error("parser attribute with invalid value", att);
} else if (((String)value).startsWith("\"")) {
runtime.error("parser attribute with invalid value", att);
}
} else if (Constants.NAME_MAIN.equals(name)) {
if (runtime.test("optionLGPL")) {
runtime.error("main attribute incompatible with LGPL", att);
} else if (null == value) {
runtime.error("main attribute without nonterminal value", att);
} else if (! (value instanceof String)) {
runtime.error("main attribute with invalid value", att);
} else if (((String)value).startsWith("\"")) {
runtime.error("main attribute with invalid value", att);
} else {
NonTerminal nt = new NonTerminal((String)value);
Production p = null;
boolean err = false;
try {
p = analyzer.lookup(nt);
} catch (IllegalArgumentException x) {
runtime.error("main attribute with ambiguous nonterminal '" +
nt + "'", att);
err = true;
}
if (! err) {
if (null == p) {
runtime.error("main attribute with undefined nonterminal '" +
nt + "'", att);
} else if (! analyzer.isDefined(p, m2)) {
runtime.error("main attribute with another module's " +
"nonterminal '" + nt + "'", att);
} else if (! p.hasAttribute(Constants.ATT_PUBLIC)) {
runtime.error("main attribute with non-public nonterminal '" +
nt + "'", att);
}
}
}
} else if (Constants.NAME_PRINTER.equals(name)) {
if (runtime.test("optionLGPL")) {
runtime.error("printer attribute incompatible with LGPL", att);
} else if (null == value) {
runtime.error("printer attribute without class name", att);
} else if (! (value instanceof String)) {
runtime.error("printer attribute with invalid value", att);
} else if (((String)value).startsWith("\"")) {
runtime.error("printer attribute with invalid value", att);
}
if (! m2.hasAttribute(Constants.NAME_MAIN)) {
runtime.error("printer attribute without main attribute", att);
}
} else if (Constants.NAME_VISIBILITY.equals(name)) {
if (null == value) {
runtime.error("visibility attribute without value", att);
} else if ((! Constants.ATT_PUBLIC.getValue().equals(value)) &&
(! Constants.ATT_PACKAGE_PRIVATE.getValue().
equals(value))) {
runtime.error("visibility attribute with invalid value", att);
}
} else if (Constants.NAME_STRING_SET.equals(name)) {
if (null == value) {
runtime.error("string set attribute without set value", att);
} else if (! (value instanceof String)) {
runtime.error("string set attribute with invalid value", att);
} else if (((String)value).startsWith("\"")) {
runtime.error("string set attribute with invalid value", att);
}
} else if (Constants.NAME_FLAG.equals(name)) {
if (null == value) {
runtime.error("flag attribute without flag value", att);
} else if (! (value instanceof String)) {
runtime.error("flag attribute with invalid value", att);
} else if (((String)value).startsWith("\"")) {
runtime.error("flag attribute with invalid value", att);
}
} else if (Constants.NAME_FACTORY.equals(name)) {
if (null == value) {
runtime.error("factory attribute without class name", att);
} else if (! (value instanceof String)) {
runtime.error("factory attribute with invalid value", att);
} else if (((String)value).startsWith("\"")) {
runtime.error("factory attribute with invalud value", att);
}
} else if (Constants.ATT_GENERIC_AS_VOID.equals(att)) {
if (m2.hasAttribute(Constants.ATT_PARSE_TREE)) {
runtime.error("genericAsVoid attribute incompatible with " +
"withParseTree attribute", att);
}
} else if (Constants.ATT_DUMP.equals(att)) {
if (runtime.test("optionLGPL")) {
runtime.error("dump attribute incompatible with LGPL", att);
}
} else if (Constants.ATT_RAW_TYPES.equals(att)) {
runtime.warning("the rawTypes attribute has been deprecated", att);
runtime.errConsole().loc(att).
pln(": warning: and will be removed in a future release").flush();
}
}
}
// If this module does not have a stateful attribute, check the
// the current module's direct imports as well as any modified
// modules and their direct imports.
if (! hasState) {
hasState =
analyzer.hasAttribute(m2, Constants.ATT_STATEFUL.getName(), visited);
visited.clear();
}
// Check the productions.
for (Production p : m2.productions) {
// Initialize the per-production state.
sequenceNames.clear();
// Process the production.
analyzer.process(p);
// Check for duplicate definitions.
try {
if ((! p.isPartial()) && (p != analyzer.lookup(p.name))) {
runtime.error("duplicate definition for nonterminal '" + p.name +
"'", p);
}
} catch (IllegalArgumentException x) {
runtime.error("duplicate definition for nonterminal '" + p.name +
"'", p);
}
// Process top-level productions. Only public productions
// from the top-level module are recognized as top-level for
// the entire grammar.
if (p.hasAttribute(Constants.ATT_PUBLIC)) {
if (analyzer.isDefined(p, root)) {
hasTopLevel = true;
} else {
p.attributes.remove(Constants.ATT_PUBLIC);
}
}
}
}
// Check for top-level nonterminals.
if (! hasTopLevel) {
runtime.error("no public nonterminal",
g.modules.get(0).productions.get(0));
}
// Done.
return attributes;
}
/**
* Apply any module modifications. This method applies all module
* modifications appearing in the specified grammar. It also
* removes unused modules from the grammar. This method assumes
* that all module dependencies have been successfully resolved,
* i.e., the specified grammar contains all module dependencies.
* This method also assumes that each module modification's {@link
* Module#modification modification} field has been correctly
* initialized.
*
* @param g The grammar.
*/
protected void applyModifications(Grammar g) {
// Initialize the analyzer.
analyzer.register(this);
analyzer.init(g);
phase = 2;
// Calculate the transitive closure of imported and modified
// modules.
Module root = g.modules.get(0);
Set<ModuleName> imports = new HashSet<ModuleName>();
Map<ModuleName, Boolean> modified = new HashMap<ModuleName, Boolean>();
imports.add(root.name);
analyzer.trace(root, imports, modified);
// Remove any modules that are not reachable from the top-level
// module (i.e., modules that have been instantiated but never
// imported or modified).
for (Iterator<Module> iter = g.modules.iterator(); iter.hasNext(); ) {
Module m = iter.next();
if ((! imports.contains(m.name)) && (! modified.containsKey(m.name))) {
if (runtime.test("optionVerbose")) {
System.err.println("[Unloading unused module " + m.name + "]");
}
analyzer.remove(m);
iter.remove();
}
}
// If there are any modifications, apply them.
if (0 != modified.size()) {
// Set up the list of module modifications (in dependency order)
// and the base module they are applied to. Also track the set
// of modules with errors. Furthermore, track the set of
// modules checked in phase 2. Finally, create the maps for the
// target module's productions and for both the target and
// source modules' productions.
List<Module> mofunctors = new ArrayList<Module>();
Module base;
Set<Module> erroneous = new HashSet<Module>();
Set<Module> checked = new HashSet<Module>();
Map<NonTerminal, Production> targetProductions =
new HashMap<NonTerminal, Production>();
Map<NonTerminal, Production> productions =
new HashMap<NonTerminal, Production>();
while (true) {
// Find a module modification to apply.
mofunctors.clear();
base = null;
for (Module m : g.modules) {
if ((null != m.modification) &&
(! erroneous.contains(m))) {
// We found a module modification without errors. Follow
// the the chain of dependencies until we reach the base
// module.
do {
mofunctors.add(m);
m = analyzer.lookup(m.modification.visibleName());
} while (null != m.modification);
base = m;
if (erroneous.contains(base)) {
// We have seen an error before.
erroneous.addAll(mofunctors);
base = null;
}
// We are done looking for a module modification.
break;
}
}
if (null == base) {
// Nothing left to do, either because all module
// modifications have been applied or there have been
// errors.
break;
}
// Set up the target module.
Module target = base;
// Iterate over the module modifications and apply them to the
// target.
for (int i=mofunctors.size()-1; i>=0; i--) {
Module source = mofunctors.get(i);
if (runtime.test("optionVerbose")) {
System.err.println("[Applying module " + source.name +
" to module " + target.name + "]");
}
// If the target module is modified in more than one way or
// both modified and imported, then we need to modify a
// copy.
if (modified.get(target.name) ||
(modified.containsKey(target.name) &&
imports.contains(target.name))) {
if (runtime.test("optionVerbose")) {
System.err.println("[Copying modified module " + target.name +
"]");
}
target = analyzer.copy(target);
} else {
// Remove the module from the analyzer's state, since we
// will update that state after applying the modification.
// Also, remove the module from the grammar, as it
// replaces the source module.
if (runtime.test("optionVerbose")) {
System.err.println("[Removing modified module " + target.name +
"]");
}
analyzer.remove(target);
g.remove(target);
}
// Since the source module is effectively replaced by the
// target module, we remove it from the analyzer state and
// replace it in the grammar. We need to preserve the
// position in the grammar as the top-level module may be a
// module modification.
if (runtime.test("optionVerbose")) {
System.err.println("[Removing modifying module " + source.name +
"]");
}
analyzer.remove(source);
g.replace(source, target);
// Rename the target module.
rename(target, new ModuleMap(target.name, source.name));
// Patch the target module's documentation comment.
target.documentation = source.documentation;
// Explicitly replace the target module's name with the
// source module's to preserve the original name, if any.
target.name = source.name;
// Similarly, patch the arguments property.
target.removeProperty(Constants.ARGUMENTS);
if (source.hasProperty(Constants.ARGUMENTS)) {
target.setProperty(Constants.ARGUMENTS,
source.getProperty(Constants.ARGUMENTS));
}
// Add the source module's dependencies, with exception of
// the modification, to the target module's dependencies.
// Note that we don't need to check whether the source
// module has dependencies, as it must have at least one
// modification dependency.
Iterator<ModuleDependency> iter2 = source.dependencies.iterator();
while (iter2.hasNext()) {
if (iter2.next().isModification()) {
iter2.remove();
// We continue iterating, just in case that there are
// several modifications.
}
}
if (null == target.dependencies) {
target.dependencies = source.dependencies;
} else {
target.dependencies.addAll(source.dependencies);
}
// Add the source module's header, body, and footer actions
// to the target module, if they exist.
if (null != source.header) {
if (null == target.header) {
target.header = source.header;
} else {
target.header.add(source.header);
}
}
if (null != source.body) {
if (null == target.body) {
target.body = source.body;
} else {
target.body.add(source.body);
}
}
if (null != source.footer) {
if (null == target.footer) {
target.footer = source.footer;
} else {
target.footer.add(source.footer);
}
}
// Replace the target module's options with the source
// module's options. However, if the target module has a
// stateful, set, or flag option, while the source module
// does not have that same option, add the corresponding
// option to the list of options first.
if (null != target.attributes) {
// Process stateful attribute.
if (target.hasAttribute(Constants.ATT_STATEFUL.getName())) {
if (null == source.attributes) {
source.attributes = new ArrayList<Attribute>();
}
if (! source.hasAttribute(Constants.ATT_STATEFUL.getName())) {
source.attributes.
add(Attribute.get(Constants.ATT_STATEFUL.getName(),
target.attributes));
}
}
// Process set and flag attributes.
if (target.hasAttribute(Constants.NAME_STRING_SET) ||
target.hasAttribute(Constants.NAME_FLAG)) {
if (null == source.attributes) {
source.attributes = new ArrayList<Attribute>();
}
for (Attribute att : target.attributes) {
String name = att.getName();
if ((Constants.NAME_STRING_SET.equals(name) ||
Constants.NAME_FLAG.equals(name)) &&
(! source.attributes.contains(att))) {
source.attributes.add(att);
}
}
}
}
target.attributes = source.attributes;
// Build a map of the target module's productions and a map
// of both the target and source modules' productions.
// While building the maps, also strip each production.
targetProductions.clear();
productions.clear();
for (Production p : target.productions) {
p = strip(p);
if (p.isFull() && (! productions.containsKey(p.name))) {
targetProductions.put(p.name, p);
productions.put(p.name, p);
}
}
for (Production p : source.productions) {
p = strip(p);
if (p.isFull() && (! productions.containsKey(p.name))) {
productions.put(p.name, p);
}
}
// Process the source module's productions.
int errorCount = runtime.errorCount();
for (Production p1 : source.productions) {
FullProduction p2 = (FullProduction)productions.get(p1.name);
if (p1.isFull()) {
// If the source module's production is a duplicate
// definition, that error has already been reported.
// Otherwise, we simply add it to the target module.
if (! targetProductions.containsKey(p1.name)) {
target.productions.add(p1);
targetProductions.put(p1.name, p1);
}
} else if (null != p2) {
if (p1.isAddition()) {
apply((AlternativeAddition)p1, p2);
} else if (p1.isRemoval()) {
apply((AlternativeRemoval)p1, p2);
} else if (p1.isOverride()) {
apply((ProductionOverride)p1, p2);
} else {
throw new AssertionError("Unrecognized production " + p1);
}
}
}
// Add the target module into the analyzer's state. Also,
// mark the target module as checked in phase 2.
if (runtime.test("optionVerbose")) {
System.err.println("[Adding resulting module " + target.name + "]");
}
analyzer.add(target);
checked.add(target);
// Re-check the target module for duplicate sequence names
// and for ambiguous or undefined nonterminals.
analyzer.process(target);
for (Production p : target.productions) {
sequenceNames.clear();
analyzer.process(p);
}
// If we have seen any errors, we mark the target module and
// any remaining module modifications as erroneous.
if (runtime.errorCount() != errorCount) {
erroneous.add(target);
List<Module> l = mofunctors.subList(0, i);
erroneous.addAll(l);
checked.addAll(l);
}
}
// Recalculate the transitive closure of imported and modified
// modules. We have to re-initialize the root, since that
// module may have changed.
root = g.modules.get(0);
imports.clear();
modified.clear();
imports.add(root.name);
analyzer.trace(root, imports, modified);
// We do not need to remove unreachable modules, because the
// initial removal has already removed all of them.
}
// Check all modules that have not been checked in phase 2 in
// order to detect amgibuous nonterminals.
phase = 3;
for (Module m : g.modules) {
if (! checked.contains(m)) {
analyzer.process(m);
for (Production p : m.productions) {
analyzer.process(p);
}
}
}
}
// Print the resulting modules if requested.
if (runtime.test("optionApplied")) {
if (runtime.test("optionHtml")) {
new HtmlPrinter(runtime, analyzer, ast, false).dispatch(g);
} else {
PrettyPrinter pp = new PrettyPrinter(runtime.console(), ast, true);
pp.dispatch(g);
pp.flush();
}
}
// Done.
}
/**
* Intern the grammar's types. This method assumes that all module
* modifications have been applied.
*
* @param g The grammar.
*/
protected void internTypes(Grammar g) {
// Determine the grammar's features.
boolean hasNode = false;
boolean hasToken = false;
boolean hasFormatting = false;
boolean hasAction = false;
// Check for parse trees.
if (g.modules.get(0).hasAttribute(Constants.ATT_PARSE_TREE)) {
hasToken = true;
hasFormatting = true;
}
// Check for generic productions, including generic productions
// that are directly left-recursive.
for (Module m : g.modules) {
for (Production p : m.productions) {
// Check for generic productions.
if (ast.isGenericNode(p.dType)) {
hasNode = true;
// Check for direct left recursions in generic productions.
for (Sequence s : p.choice.alternatives) {
final Element first = s.isEmpty() ? null :
Analyzer.stripAndUnbind(s.get(0));
if (p.name.equals(first) || p.qName.equals(first)) {
hasAction = true;
}
}
}
}
}
// Only annotate the grammar if the productions are not going to
// be voided out.
if (! g.modules.get(0).hasAttribute(Constants.ATT_GENERIC_AS_VOID)) {
if (hasNode) {
g.modules.get(0).setProperty(Properties.GENERIC, Boolean.TRUE);
}
if (hasAction) {
g.modules.get(0).setProperty(Properties.RECURSIVE, Boolean.TRUE);
}
}
// Initialize the grammar's type map.
ast.initialize(hasNode, hasToken, hasFormatting, hasAction);
// Intern the types.
for (Module m : g.modules) {
for (Production p : m.productions) {
Type t;
try {
t = ast.intern(p.dType);
} catch (IllegalArgumentException x) {
runtime.error(x.getMessage() + " for production '" + p.name + "'", p);
t = ErrorT.TYPE;
}
p.type = t;
}
}
// Check variant productions.
for (Module m : g.modules) {
for (Production p : m.productions) {
if (p.hasAttribute(Constants.ATT_VARIANT)) {
if (AST.isToken(p.type)) {
runtime.error("variant production for token type", p);
} else if (! AST.isNode(p.type)) {
runtime.error("variant production without node type", p);
}
}
}
}
}
/**
* Check for left-recursive productions. This method assumes that
* all module modifications have been applied.
*
* @param g The grammar.
*/
protected void checkRecursions(Grammar g) {
new TextTester(runtime, analyzer).dispatch(g);
LeftRecurser recurser = new LeftRecurser(runtime, analyzer);
recurser.dispatch(g);
Set<NonTerminal> recursive = recurser.recursive();
for (Module m : g.modules) {
for (Production p : m.productions) {
if (recursive.contains(p.qName)) {
// Report the production as malformed.
runtime.error("left-recursive definition for nonterminal '" +
p.name + "'", p);
}
}
}
}
/**
* Check repetitions for elements that accept the empty input. This
* method assumes that all module modifications have been applied.
*
* @param g The grammar.
*/
protected void checkRepetitions(Grammar g) {
analyzer.register(checkRepetitionsVisitor);
analyzer.init(g);
for (Module m : g.modules) {
analyzer.process(m);
for (Production p : m.productions) {
if (p.isFull()) {
analyzer.process(p);
}
}
}
}
/** The visitor for checking repetitions. */
@SuppressWarnings("unused")
private final Visitor checkRepetitionsVisitor = new Visitor() {
public void visit(Repetition r) {
dispatch(r.element);
if (analyzer.matchesEmpty(r.element) &&
! (Analyzer.strip(r.element) instanceof Action)) {
runtime.error("repeated element matches empty input", r);
}
}
public void visit(Option o) {
dispatch(o.element);
if (! analyzer.restrictsInput(o.element) &&
! (Analyzer.strip(o.element) instanceof Action) &&
! analyzer.grammar().modules.get(0).
hasAttribute(Constants.ATT_NO_WARNINGS) &&
! analyzer.current().hasAttribute(Constants.ATT_NO_WARNINGS)) {
runtime.warning("optional element already matches empty input", o);
}
}
public void visit(Production p) {
dispatch(p.choice);
}
public void visit(OrderedChoice c) {
for (Sequence alt : c.alternatives) dispatch(alt);
}
public void visit(Sequence s) {
for (Element e : s.elements) dispatch(e);
}
public void visit(UnaryOperator op) {
dispatch(op.element);
}
public void visit(Element e) {
// Nothing to do.
}
};
/**
* Check that explicit productions do not match the empty input.
*
* @param g The grammar.
*/
protected void checkExplicit(Grammar g) {
analyzer.register(checkExplicitVisitor);
analyzer.init(g);
for (Module m : g.modules) {
analyzer.process(m);
for (Production p : m.productions) {
if (p.isFull()) analyzer.process(p);
}
}
}
/** The visitor for checking explicit productions. */
@SuppressWarnings("unused")
private final Visitor checkExplicitVisitor = new Visitor() {
public void visit(Production p) {
if (p.hasAttribute(Constants.ATT_EXPLICIT) &&
analyzer.matchesEmpty(p.choice)) {
runtime.error("explicit production matches empty input", p);
}
}
};
/**
* Combine the modules in the specified grammar. This method
* combines the modules in the grammar by modifying the grammar's
* top-level module and then returns that module.
*
* @param g The grammar.
* @param attributes The list of global attributes.
* @return The grammar as a single module.
*/
protected Module combine(Grammar g, List<Attribute> attributes) {
// Get the top-level module.
Module m = g.modules.get(0);
// Null out the dependencies.
m.dependencies = null;
m.modification = null;
// Make sure that the single module has all global attributes.
if (0 < attributes.size()) {
if (null == m.attributes) {
m.attributes = attributes;
} else {
for (Attribute att : attributes) {
if (! m.attributes.contains(att)) {
m.attributes.add(att);
}
}
}
}
// Collect headers, bodies, and footers. We need to make sure
// that we do not add the same header, body, or footer more than
// once, since, in the presence of parameterized modules and
// module modifications, the same source module can be
// instantiated several times.
List<Action> headers = new ArrayList<Action>();
List<Action> bodies = new ArrayList<Action>();
List<Action> footers = new ArrayList<Action>();
for (Module m2 : g.modules) {
if ((null != m2.header) && (! headers.contains(m2.header))) {
headers.add(m2.header);
}
if ((null != m2.body) && (! bodies.contains(m2.body))) {
bodies.add(m2.body);
}
if ((null != m2.footer) && (! footers.contains(m2.footer))) {
footers.add(m2.footer);
}
}
// Combine all unique headers, bodies, and footers into a single
// header, body, and footer, respectively.
m.header = null;
for (Action a : headers) {
if (null == m.header) {
m.header = a;
} else {
m.header.add(a);
}
}
m.body = null;
for (Action a : bodies) {
if (null == m.body) {
m.body = a;
} else {
m.body.add(a);
}
}
m.footer = null;
for (Action a : footers) {
if (null == m.footer) {
m.footer = a;
} else {
m.footer.add(a);
}
}
// Now add in all productions.
for (Module m2 : g.modules) {
if (m != m2) {
m.productions.addAll(m2.productions);
}
}
return m;
}
/**
* Visit the specified module.
*
* @param m The module to resolve.
* @return A single, self-contained grammar module or
* <code>null</code> on an error condition.
*/
public Object visit(Module m) {
// Reset the resolver state.
badNTs.clear();
// ----------------------------------------------------------------------
// Load all dependent modules.
// ----------------------------------------------------------------------
Grammar g = load(m);
// Only continue if there were no errors.
if (runtime.seenError() ||
runtime.test("optionLoaded") ||
runtime.test("optionDependencies") ||
runtime.test("optionInstantiated")) {
return null;
}
// ----------------------------------------------------------------------
// Check as much as possible.
// ----------------------------------------------------------------------
List<Attribute> attributes = check(g);
// ----------------------------------------------------------------------
// Apply any module modifications.
// ----------------------------------------------------------------------
applyModifications(g);
if (runtime.test("optionApplied")) return null;
// ----------------------------------------------------------------------
// Intern types.
// ----------------------------------------------------------------------
internTypes(g);
// ----------------------------------------------------------------------
// Perform checks that require that modifications have been applied.
// ----------------------------------------------------------------------
checkRecursions(g);
checkRepetitions(g);
new ReachabilityChecker(runtime, analyzer).dispatch(g);
checkExplicit(g);
// Only continue if there were no errors.
if (runtime.seenError()) return null;
// ----------------------------------------------------------------------
// Rename nonterminals for consistency.
// ----------------------------------------------------------------------
analyzer.uniquify();
// ----------------------------------------------------------------------
// Combine all modules into a single module.
// ----------------------------------------------------------------------
return combine(g, attributes);
}
/** Analyze the specified production. */
public void visit(Production p) {
if (1 == phase) {
if (Constants.ATT_PACKAGE_PRIVATE.getValue().equals(p.dType) ||
Constants.ATT_INLINE.getName().equals(p.dType)) {
runtime.error("attribute '" + p.dType + "' as type for production '" +
p.name + "'", p);
} else if (Constants.ATT_WITH_LOCATION.getName().equals(p.dType) ||
Constants.ATT_CONSTANT.getName().equals(p.dType) ||
Constants.ATT_EXPLICIT.getName().equals(p.dType) ||
Constants.ATT_NO_INLINE.getName().equals(p.dType) ||
Constants.ATT_MEMOIZED.getName().equals(p.dType) ||
Constants.ATT_VERBOSE.getName().equals(p.dType) ||
Constants.ATT_VARIANT.getName().equals(p.dType) ||
Constants.ATT_IGNORING_CASE.getName().equals(p.dType) ||
Constants.ATT_STATEFUL.getName().equals(p.dType) ||
Constants.ATT_RESETTING.getName().equals(p.dType)) {
if (! p.isPartial() &&
! analyzer.grammar().modules.get(0).
hasAttribute(Constants.ATT_NO_WARNINGS) &&
! p.hasAttribute(Constants.ATT_NO_WARNINGS)) {
runtime.warning("attribute '" + p.dType + "' as type for production " +
p.name + "'", p);
}
}
if (p.isPartial()) {
if (! isMofunctor) {
runtime.error("production modification '" + p.name +
"' without modifies declaration", p);
} else {
// Make sure the corresponding full production is defined
// and the types match.
Production p2 = null;
try {
p2 = analyzer.lookup(p.name);
} catch (IllegalArgumentException x) {
// Nothing to do.
}
if ((null == p2) ||
(! analyzer.isDefined(p2, analyzer.currentModule()))) {
runtime.error("production modification '" + p.name +
"' without full production", p);
} else if (! p.dType.equals(p2.dType)) {
runtime.error("type '" + p.dType + "' of production modification '" +
p.name + "' does not match full production's type", p);
}
}
}
if (null != p.attributes) {
final int length = p.attributes.size();
for (int i=0; i<length; i++) {
Attribute att = p.attributes.get(i);
if ((! Constants.ATT_PUBLIC.equals(att)) &&
(! Constants.ATT_PROTECTED.equals(att)) &&
(! Constants.ATT_PRIVATE.equals(att)) &&
(! Constants.ATT_TRANSIENT.equals(att)) &&
(! Constants.ATT_INLINE.equals(att)) &&
(! Constants.ATT_NO_INLINE.equals(att)) &&
(! Constants.ATT_MEMOIZED.equals(att)) &&
(! Constants.ATT_WITH_LOCATION.equals(att)) &&
(! Constants.ATT_CONSTANT.equals(att)) &&
(! Constants.ATT_VARIANT.equals(att)) &&
(! Constants.ATT_EXPLICIT.equals(att)) &&
(! Constants.ATT_VERBOSE.equals(att)) &&
(! Constants.ATT_NO_WARNINGS.equals(att)) &&
(! Constants.ATT_IGNORING_CASE.equals(att)) &&
(! Constants.ATT_STATEFUL.equals(att)) &&
(! Constants.ATT_RESETTING.equals(att))) {
runtime.error("unrecognized per-production attribute '" + att +
"'", att);
} else if ((! hasState) && Constants.ATT_STATEFUL.equals(att)) {
runtime.error("stateful attribute without grammar-wide stateful " +
"attribute", att);
} else if ((! hasState) && Constants.ATT_RESETTING.equals(att)) {
runtime.error("resetting attribute without grammar-wide stateful " +
"attribute", att);
} else {
for (int j=0; j<i; j++) {
if (att.equals(p.attributes.get(j))) {
runtime.error("duplicate attribute '" + att.getName() + "'",
att);
break;
}
}
}
}
if (p.hasAttribute(Constants.ATT_MEMOIZED)) {
if (p.hasAttribute(Constants.ATT_TRANSIENT)) {
runtime.error("memozied attribute contradicts transient attribute",
Attribute.get(Constants.ATT_MEMOIZED.getName(),
p.attributes));
}
if (p.hasAttribute(Constants.ATT_INLINE)) {
runtime.error("memoized attribute contradicts inline attribute",
Attribute.get(Constants.ATT_MEMOIZED.getName(),
p.attributes));
}
}
if (p.hasAttribute(Constants.ATT_TRANSIENT) &&
p.hasAttribute(Constants.ATT_INLINE)) {
runtime.error("inline attribute subsumes transient attribute",
Attribute.get(Constants.ATT_INLINE.getName(),
p.attributes));
}
if (p.hasAttribute(Constants.ATT_INLINE) &&
p.hasAttribute(Constants.ATT_NO_INLINE)) {
runtime.error("inline attribute contradicts noinline attribute",
Attribute.get(Constants.ATT_NO_INLINE.getName(),
p.attributes));
}
}
}
dispatch(p.choice);
}
/** Analyze the specified ordered choice. */
public void visit(OrderedChoice c) {
for (Sequence alt : c.alternatives) dispatch(alt);
}
/** Analyze the specified repetition. */
public void visit(Repetition r) {
if (1 == phase) {
if (Analyzer.strip(r.element) instanceof Action) {
runtime.error("repeated action", r);
}
}
dispatch(r.element);
}
/** Analyze the specified option. */
public void visit(Option o) {
if (1 == phase) {
if (Analyzer.strip(o.element) instanceof Action) {
runtime.error("optional action", o);
}
}
dispatch(o.element);
}
/** Analyze the specified sequence. */
public void visit(Sequence s) {
if ((1 == phase) || (2 == phase)) {
if (null != s.name) {
if (sequenceNames.contains(s.name)) {
runtime.error("duplicate sequence name '" + s.name + "'", s);
} else {
sequenceNames.add(s.name);
}
}
}
for (Element e : s.elements) dispatch(e);
}
/** Analyze the specified predicate. */
public void visit(Predicate p) {
if (1 == phase) {
if (isPredicate) {
runtime.error("syntactic predicate within syntactic predicate", p);
}
}
boolean pred = isPredicate;
isPredicate = true;
dispatch(p.element);
isPredicate = pred;
}
/** Analyze the specified semantic predicate. */
public void visit(SemanticPredicate p ) {
if (1 == phase) {
if (! (p.element instanceof Action)) {
runtime.error("malformed semantic predicate", p);
} else {
Action a = (Action)p.element;
if ((null == a.code) || (0 >= a.code.size())) {
runtime.error("empty test for semantic predicate", p);
}
}
}
dispatch(p.element);
}
/** Analyze the specified voided element. */
public void visit(VoidedElement v) {
if (1 == phase) {
// We allow void:void:e and void:nt for void nonterminals, since
// the simplifier removes the unnecessary voided elements.
Element voided = Analyzer.strip(v.element);
switch (voided.tag()) {
case FOLLOWED_BY:
case NOT_FOLLOWED_BY:
case SEMANTIC_PREDICATE:
runtime.error("voided predicate", v);
break;
case BINDING:
runtime.error("voided binding", v);
break;
case ACTION:
runtime.error("voided action", v);
break;
case PARSER_ACTION:
runtime.error("voided parser action", v);
break;
case NULL:
runtime.error("voided null literal", v);
break;
case NODE_MARKER:
runtime.error("voided node marker", v);
break;
}
}
dispatch(v.element);
}
/** Analyze the specified binding. */
public void visit(Binding b) {
if (1 == phase) {
Element bound = Analyzer.strip(b.element);
switch (bound.tag()) {
case FOLLOWED_BY:
case NOT_FOLLOWED_BY:
case SEMANTIC_PREDICATE:
runtime.error("binding for predicate", b);
break;
case VOIDED:
runtime.error("binding for voided element", b);
break;
case BINDING:
runtime.error("binding for binding", b);
break;
case NONTERMINAL: {
NonTerminal nt = (NonTerminal)bound;
Production p = null;
try {
p = analyzer.lookup(nt);
} catch (IllegalArgumentException x) {
// Nothing to do.
}
if ((null != p) && ast.isVoid(p.dType)) {
runtime.error("binding for void nonterminal '" + nt + "'", b);
}
} break;
case ACTION:
runtime.error("binding for action", b);
break;
case PARSER_ACTION:
runtime.error("binding for parser action", b);
break;
case NODE_MARKER:
runtime.error("binding for node marker", b);
break;
}
}
dispatch(b.element);
}
/** Analyze the specified string match. */
public void visit(StringMatch m) {
if (1 == phase) {
Element matched = Analyzer.strip(m.element);
switch (matched.tag()) {
case FOLLOWED_BY:
case NOT_FOLLOWED_BY:
case SEMANTIC_PREDICATE:
runtime.error("string match on predicate", m);
break;
case VOIDED:
runtime.error("string match on voided element", m);
break;
case BINDING:
runtime.error("string match on binding " +
"(try binding the match instead)", m);
break;
case STRING_MATCH:
runtime.error("string match on another string match", m);
break;
case NONTERMINAL: {
NonTerminal nt = (NonTerminal)matched;
Production p = null;
try {
p = analyzer.lookup(nt);
} catch (IllegalArgumentException x) {
// Nothing to do.
}
if ((null != p) && ast.isVoid(p.dType)) {
runtime.error("string match on void nonterminal '" + nt + "'", m);
}
} break;
case ANY_CHAR:
case CHAR_CLASS:
case CHAR_LITERAL:
case CHAR_SWITCH:
case STRING_LITERAL:
runtime.error("match for terminal", m);
break;
case ACTION:
runtime.error("match for action", m);
break;
case PARSER_ACTION:
runtime.error("match for parser action", m);
break;
case NODE_MARKER:
runtime.error("match for node marker", m);
break;
}
}
dispatch(m.element);
}
/** Analyze the specified nonterminal. */
public void visit(NonTerminal nt) {
Production p = null;
try {
p = analyzer.lookup(nt);
} catch (IllegalArgumentException x) {
if (nt.hasProperty(Constants.ORIGINAL)) {
if (! badNTs.containsKey(nt)) {
runtime.error("ambiguous renamed nonterminal '" + nt + "'", nt);
badNTs.put(nt, nt);
}
} else {
if (! badNTs.containsKey(nt)) {
runtime.error("ambiguous nonterminal '" + nt + "'", nt);
badNTs.put(nt, nt);
}
}
return;
}
if (null == p) {
if (nt.hasProperty(Constants.ORIGINAL)) {
if (! badNTs.containsKey(nt)) {
runtime.error("undefined renamed nonterminal '" + nt + "'", nt);
badNTs.put(nt, nt);
}
} else {
if (! badNTs.containsKey(nt)) {
runtime.error("undefined nonterminal '" + nt + "'", nt);
badNTs.put(nt, nt);
}
}
}
}
/** Analyze the specified terminal. */
public void visit(Terminal t) {
// Nothing to do.
}
/** Analyze the specified string literal. */
public void visit(StringLiteral l) {
if (1 == phase) {
if (0 == l.text.length()) {
runtime.error("empty string literal", l);
}
}
}
/** Analyze the specified character class. */
public void visit(CharClass c) {
if (1 != phase) return;
final int length = c.ranges.size();
if (0 >= length) {
runtime.error("empty character class", c);
} else {
ArrayList<CharRange> list = new ArrayList<CharRange>(c.ranges);
Collections.sort(list);
for (int i=0; i<length-1; i++) {
CharRange r1 = list.get(i);
CharRange r2 = list.get(i+1);
if (r1.last >= r2.first) {
boolean single1 = (r1.first == r1.last);
boolean single2 = (r2.first == r2.last);
if (single1) {
if (single2) {
runtime.error("duplicate character '" +
Utilities.escape(r1.last, Utilities.FULL_ESCAPES) +
"' in character class", c);
} else {
runtime.error("character '" +
Utilities.escape(r1.last, Utilities.FULL_ESCAPES) +
"' already contained in range " +
Utilities.escape(r2.first, Utilities.FULL_ESCAPES) +
"-" + Utilities.escape(r2.last,
Utilities.FULL_ESCAPES), c);
}
} else {
if (single2) {
runtime.error("character '" +
Utilities.escape(r2.first, Utilities.FULL_ESCAPES) +
"' already contained in range " +
Utilities.escape(r1.first, Utilities.FULL_ESCAPES) +
"-" + Utilities.escape(r1.last,
Utilities.FULL_ESCAPES), c);
} else {
runtime.error("ranges " +
Utilities.escape(r1.first, Utilities.FULL_ESCAPES) +
"-" + Utilities.escape(r1.last,
Utilities.FULL_ESCAPES) +
" and " +
Utilities.escape(r2.first, Utilities.FULL_ESCAPES) +
"-" + Utilities.escape(r2.last,
Utilities.FULL_ESCAPES) +
" overlap", c);
}
}
}
}
}
}
/** Analyze the specified null literal. */
public void visit(NullLiteral l) {
// Nothing to do.
}
/** Analyze the specified node marker. */
public void visit(NodeMarker m) {
if (! ast.isGenericNode(analyzer.current().dType)) {
runtime.error("node marker in non-generic production", m);
} else if (isPredicate) {
runtime.error("node marker in predicate", m);
}
}
/** Analyze the specified action. */
public void visit(Action a) {
// Nothing to do.
}
/** Analyze the specified parser action. */
public void visit(ParserAction pa) {
if (1 == phase) {
if (! (pa.element instanceof Action)) {
runtime.error("malformed parser action", pa);
}
if (isPredicate) {
runtime.error("parser action within syntactic predicate", pa);
}
}
dispatch(pa.element);
}
/** Analyze the specified internal element. */
public void visit(InternalElement e) {
if (1 == phase) {
runtime.error("internal element", (Element)e);
}
}
}