/*
* xtc - The eXTensible Compiler
* Copyright (C) 2004-2007 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.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import xtc.Constants;
import xtc.Constants.FuzzyBoolean;
import xtc.tree.Visitor;
import xtc.type.AST;
import xtc.type.Type;
import xtc.type.Wildcard;
import xtc.util.Runtime;
import xtc.util.Utilities;
/**
* Visitor to fill in the production meta-data. Note that this
* visitor requires that a grammar's {@link Properties#GENERIC} and
* {@link Properties#RECURSIVE} have been appropriately set. Also
* note that this visitor does not create meta-data records; they must
* be created with the {@link MetaDataCreator meta-data creator}
* before applying this visitor. Further note that this visitor does
* not determine usage and self counts, as they need to be
* (repeatedly) determined during {@link DeadProductionEliminator dead
* production elimination}. Finally, note that this visitor assumes
* that the entire grammar is contained in a single module.
*
* @author Robert Grimm
* @version $Revision: 1.56 $
*/
public class MetaDataSetter extends Visitor {
/** The regular expression for matching import declarations. */
public static final Pattern IMPORT =
Pattern.compile("import\\s+(static\\s+)??(\\S+?)(\\.\\*)??;");
/** The runtime. */
protected final Runtime runtime;
/** The analyzer utility. */
protected final Analyzer analyzer;
/** The type operations. */
protected final AST ast;
/**
* Flag for whether the grammar has the {@link
* Constants#ATT_WITH_LOCATION withLocation} attribute.
*/
protected boolean withLocation;
/**
* Flag for whether the grammar has the {@link
* Constants#ATT_PARSE_TREE parseTree} attribute.
*/
protected boolean hasParseTree;
/** Flag for whether the grammar requires {@link xtc.tree.Locatable}. */
protected boolean requiresLocatable;
/** Flag for whether a production requires a character variable. */
protected boolean requiresChar;
/** Flag for whether a production requires an index variable. */
protected boolean requiresIndex;
/** Flag for whether a production requires a result variable. */
protected boolean requiresResult;
/** Flag for whether a production requires a predicate index variable. */
protected boolean requiresPredIndex;
/** Flag for whether a production requires a predicate result variable. */
protected boolean requiresPredResult;
/** Flag for whether a production requires a predicate matched variable. */
protected boolean requiresPredMatch;
/** Flag for whether a production requires a base index variable. */
protected boolean requiresBaseIndex;
/** The structure of repetitions. */
protected List<Boolean> repetitions;
/** The structure of bound repetitions. */
protected List<Type> boundRepetitions;
/** The structure of options. */
protected List<Type> options;
/** Flag for whether the current production may create a node value. */
protected boolean createsNodeValue;
/**
* Flag for whether the current element is the top-level element of
* a production.
*/
protected boolean isTopLevel;
/** Flag for whether the current sequence is repeated. */
protected boolean isRepeated;
/** Flag for whether the current sequence is optional. */
protected boolean isOptional;
/**
* Flag for whether the current element is the first element of a
* sequence.
*/
protected boolean isFirstElement;
/** Flag for whether the next element is bound. */
protected boolean isBound;
/** Flag for whether we are analyzing a predicate. */
protected boolean isPredicate;
/** Flag for whether we are analyzing a not-followed-by predicate. */
protected boolean isNotFollowedBy;
/**
* Flag for whether the current element is the last element in a
* predicate.
*/
protected boolean isLastInPredicate;
/** The current nesting level for repetitions. */
protected int repetitionLevel;
/** The current nesting level for options. */
protected int optionLevel;
/**
* Create a new meta-data setter.
*
* @param runtime The runtime.
* @param analyzer The analyzer utility.
* @param ast The type operations.
*/
public MetaDataSetter(Runtime runtime, Analyzer analyzer, AST ast) {
this.runtime = runtime;
this.analyzer = analyzer;
this.ast = ast;
}
/**
* Import the specified fully qualified type.
*
* @param name The type name.
*/
protected void importType(String name) {
ast.importType(name, Utilities.getName(name));
}
/** Analyze the specified grammar. */
public void visit(Module m) {
// Initialize the per-grammar state.
analyzer.register(this);
analyzer.init(m);
// Process the grammar's imports.
final String pkg = Utilities.getQualifier(m.getClassName());
if (null != pkg) ast.importModule(pkg + ".");
importType("java.io.Reader");
if (m.hasAttribute(Constants.NAME_MAIN)) {
importType("java.io.BufferedReader");
importType("java.io.BufferedWriter");
importType("java.io.File");
importType("java.io.FileReader");
importType("java.io.OutputStreamWriter");
}
importType("java.io.IOException");
if (m.hasAttribute(Constants.ATT_PROFILE)) {
importType("java.util.HashMap");
}
if (m.hasAttribute(Constants.NAME_STRING_SET)) {
importType("java.util.HashSet");
importType("java.util.Set");
}
if (m.getBooleanProperty(Properties.RECURSIVE)) {
importType("xtc.util.Action");
}
importType("xtc.util.Pair");
if (m.hasAttribute(Constants.ATT_WITH_LOCATION)) {
// Assume that we always import this interface.
importType("xtc.tree.Locatable");
}
if (m.getBooleanProperty(Properties.GENERIC) ||
m.hasAttribute(Constants.NAME_MAIN)) {
importType("xtc.tree.Node");
}
if (m.getBooleanProperty(Properties.GENERIC)) {
if (m.hasAttribute(Constants.NAME_FACTORY)) {
String factory = (String)m.getAttributeValue(Constants.NAME_FACTORY);
if (Utilities.isQualified(factory)) {
importType(factory);
}
} else {
importType("xtc.tree.GNode");
}
}
if (m.hasAttribute(Constants.ATT_PARSE_TREE)) {
importType("xtc.tree.Token");
importType("xtc.tree.Formatting");
}
if (m.hasAttribute(Constants.ATT_VERBOSE) ||
m.hasAttribute(Constants.NAME_MAIN) ||
m.hasAttribute(Constants.ATT_PROFILE) ||
m.hasAttribute(Constants.ATT_DUMP)) {
importType("xtc.tree.Printer");
}
if (m.hasAttribute(Constants.NAME_PRINTER)) {
importType("xtc.tree.Visitor");
}
importType("xtc.parser.ParserBase");
importType("xtc.parser.Column");
importType("xtc.parser.Result");
importType("xtc.parser.SemanticValue");
importType("xtc.parser.ParseError");
if (null != m.header) {
for (String line : m.header.code) {
Matcher matcher = IMPORT.matcher(line);
if (matcher.lookingAt()) {
if (null == matcher.group(3)) {
String name = matcher.group(2);
try {
ast.importType(name, Utilities.getName(name));
} catch (IllegalArgumentException x) {
runtime.error("inconsistent imports for '" +
Utilities.getName(name) + "'", m.header);
}
} else if (null == matcher.group(1)) {
ast.importModule(matcher.group(2) + ".");
} else {
ast.importModule(matcher.group(2) + "$");
}
}
}
}
// Initialize per-grammar flags.
withLocation = m.hasAttribute(Constants.ATT_WITH_LOCATION);
hasParseTree = m.hasAttribute(Constants.ATT_PARSE_TREE);
requiresLocatable = false;
// Visit all productions.
for (Production p : m.productions) analyzer.process(p);
// Record use of locatable interface.
if (requiresLocatable) {
m.setProperty(Properties.LOCATABLE, Boolean.TRUE);
}
}
/** Analyze the specified production. */
public void visit(Production p) {
MetaData md = (MetaData)p.getProperty(Properties.META_DATA);
// Initialize per-production flags.
requiresChar = false;
requiresIndex = false;
requiresResult = false;
requiresPredIndex = false;
requiresPredResult = false;
requiresPredMatch = false;
requiresBaseIndex = false;
repetitions = md.repetitions;
boundRepetitions = md.boundRepetitions;
options = md.options;
createsNodeValue = false;
isTopLevel = true;
isRepeated = false;
isOptional = false;
isFirstElement = false;
isBound = false;
isPredicate = false;
isNotFollowedBy = false;
isLastInPredicate = false;
repetitionLevel = 0;
optionLevel = 0;
// Visit the element.
dispatch(p.choice);
// Check the type.
if (withLocation && createsNodeValue) {
if (FuzzyBoolean.MAYBE == ast.hasLocation(p.type)) {
requiresLocatable = true;
}
}
// Copy flags into meta-data record.
md.requiresChar = requiresChar;
md.requiresIndex = requiresIndex;
md.requiresResult = requiresResult;
md.requiresPredIndex = requiresPredIndex;
md.requiresPredResult = requiresPredResult;
md.requiresPredMatch = requiresPredMatch;
md.requiresBaseIndex = requiresBaseIndex;
// Patch the types for bound repetitions.
int size = boundRepetitions.size();
for (int i=0; i<size; i++) {
Type t = boundRepetitions.get(i);
if (null != t) {
boundRepetitions.set(i, AST.listOf(ast.concretize(t, AST.ANY)));
}
}
// Patch the types for bound options.
size = options.size();
for (int i=0; i<size; i++) {
Type t = options.get(i);
if (null != t) {
options.set(i, ast.concretize(t, AST.ANY));
}
}
}
/** Analyze the specified ordered choice. */
public void visit(OrderedChoice c) {
final boolean top = isTopLevel;
isTopLevel = false;
for (Sequence alt : c.alternatives) {
if (top) isFirstElement = true;
dispatch(alt);
}
}
/** Analyze the specified repetition. */
public void visit(Repetition r) {
isTopLevel = false;
boolean repeated = isRepeated;
isRepeated = true;
boolean optional = isOptional;
isOptional = false;
isFirstElement = false;
boolean bound = isBound;
isBound = false;
repetitionLevel++;
if (repetitions.size() < repetitionLevel) {
repetitions.add(Boolean.FALSE);
boundRepetitions.add(null);
}
if (r.once) {
repetitions.set(repetitionLevel - 1, Boolean.TRUE);
}
if (bound) {
// Make sure the type that level is initialized.
if (null == boundRepetitions.get(repetitionLevel-1)) {
boundRepetitions.set(repetitionLevel-1, Wildcard.TYPE);
}
// Get the binding, determine the bound element's type, and then
// unify that type with any previously determined element type.
final Binding b1 = Analyzer.getBinding(((Sequence)r.element).elements);
final Type t1 = analyzer.type(b1.element);
final Type unity =
ast.unify(t1, boundRepetitions.get(repetitionLevel-1), false);
boundRepetitions.set(repetitionLevel-1, unity);
}
dispatch(r.element);
isRepeated = repeated;
isOptional = optional;
repetitionLevel--;
}
/** Analyze the specified option. */
public void visit(Option o) {
isTopLevel = false;
boolean repeated = isRepeated;
isRepeated = false;
boolean optional = isOptional;
isOptional = false;
isFirstElement = false;
boolean bound = isBound;
isBound = false;
optionLevel++;
if (options.size() < optionLevel) {
options.add(null);
}
if (bound) {
// Make sure the type at that level is initialized.
if (null == options.get(optionLevel-1)) {
options.set(optionLevel-1, Wildcard.TYPE);
}
// Get the binding, determine the bound element's type, and then
// unify that type with any previously determined type.
final Binding b1 = Analyzer.getBinding(((Sequence)o.element).elements);
final Type t1 = analyzer.type(b1.element);
final Type unity = ast.unify(t1, options.get(optionLevel-1), false);
options.set(optionLevel-1, unity);
}
dispatch(o.element);
isRepeated = repeated;
isOptional = optional;
optionLevel--;
}
/** Analyze the specified sequence. */
public void visit(Sequence s) {
isTopLevel = false;
boolean repeated = isRepeated;
isRepeated = false;
boolean optional = isOptional;
isOptional = false;
isBound = false;
final int size = s.size();
for (int i=0; i<size; i++) {
isLastInPredicate =
isPredicate && (! repeated) && (! optional) && (i == size-1);
dispatch(s.get(i));
}
isRepeated = repeated;
isOptional = optional;
}
/** Analyze the specified followed-by predicate. */
public void visit(FollowedBy p) {
isTopLevel = false;
isBound = false;
boolean first = isFirstElement;
isPredicate = true;
isNotFollowedBy = false;
dispatch(p.element);
isPredicate = false;
isFirstElement = first;
}
/**
* Determine whether we are processing a not-followed-by predicate.
*
* @return <code>true</code> if we are processing a not-followed-by
* predicate.
*/
protected boolean isNotFollowedBy() {
return (isPredicate && isNotFollowedBy);
}
/** Analyze the specified not-followed-by predicate. */
public void visit(NotFollowedBy p) {
isTopLevel = false;
isBound = false;
requiresPredMatch = true;
boolean first = isFirstElement;
isPredicate = true;
isNotFollowedBy = true;
dispatch(p.element);
isPredicate = false;
isFirstElement = first;
}
/** Analyze the specified semantic predicate. */
public void visit(SemanticPredicate p) {
isTopLevel = false;
// No change to parser, therefore no change to isFirstElement.
isBound = false;
dispatch(p.element);
}
/** Analyze the specified voided element. */
public void visit(VoidedElement v) {
isTopLevel = false;
// No change to parser, therefore no change to isFirstElement.
isBound = false;
dispatch(v.element);
}
/** Analyze the specified binding. */
public void visit(Binding b) {
isTopLevel = false;
// No change to parser, therefore no change to isFirstElement.
isBound = true;
dispatch(b.element);
}
/** Analyze the specified string match. */
public void visit(StringMatch m) {
isTopLevel = false;
isBound = false;
// Determine if we need a base index variable.
if ((! isNotFollowedBy()) &&
((! runtime.test("optimizeErrors1")) || (! isFirstElement))) {
requiresBaseIndex = true;
}
isFirstElement = false;
dispatch(m.element);
}
/** Analyze the specified nonterminal. */
public void visit(NonTerminal nt) {
isTopLevel = false;
isFirstElement = false;
isBound = false;
if (isPredicate) {
requiresPredResult = true;
} else {
requiresResult = true;
}
}
/** Analyze the specified any character element. */
public void visit(AnyChar a) {
isTopLevel = false;
isFirstElement = false;
isBound = false;
requiresChar = true;
if (isPredicate) {
if (! isLastInPredicate) {
requiresPredIndex = true;
}
} else {
requiresIndex = true;
}
}
/** Analyze the specified string literal. */
public void visit(StringLiteral l) {
isTopLevel = false;
isBound = false;
// Determine if we need a base index variable.
if ((! isNotFollowedBy()) &&
((! runtime.test("optimizeErrors1")) || (! isFirstElement))) {
requiresBaseIndex = true;
}
isFirstElement = false;
requiresChar = true;
if (isPredicate) {
if ((! isLastInPredicate) || (1 < l.text.length())) {
requiresPredIndex = true;
}
} else {
requiresIndex = true;
}
}
/** Analyze the specified character case. */
public void visit(CharCase c) {
if (null != c.element) {
dispatch(c.element);
}
}
/** Analyzer the specified character switch. */
public void visit(CharSwitch s) {
isTopLevel = false;
isFirstElement = false;
isBound = false;
requiresChar = true;
if (isPredicate) {
if (! isLastInPredicate) {
requiresPredIndex = true;
}
} else {
requiresIndex = true;
}
for (CharCase kase : s.cases) {
dispatch(kase);
}
dispatch(s.base);
}
/** Analyze the specified terminal. */
public void visit(Terminal t) {
isTopLevel = false;
isFirstElement = false;
isBound = false;
requiresChar = true;
if (isPredicate) {
if (! isLastInPredicate) {
requiresPredIndex = true;
}
} else {
requiresIndex = true;
}
}
/** Analyze the specified action. */
public void visit(Action a) {
if (a.setsValue()) createsNodeValue = true;
isTopLevel = false;
// No change to parser, therefore no change to isFirstElement.
isBound = false;
}
/** Analyze the specified parser action. */
public void visit(ParserAction pa) {
createsNodeValue = true;
isTopLevel = false;
isFirstElement = false;
isBound = false;
requiresBaseIndex = true;
}
/**
* Determine whether the current production can annotate a node with
* its location relative to {@link CodeGenerator#VALUE}.
*
* @return <code>true</code> if the location annotation can be
* optimized.
*/
protected boolean hasDirectLocation() {
return (withLocation &&
runtime.test("optimizeLocation") &&
AST.isNode(analyzer.current().type));
}
/** Analyze the specified token value. */
public void visit(TokenValue v) {
if (! hasDirectLocation()) createsNodeValue = true;
isTopLevel = false;
// No change to parser, therefore no change to isFirstElement.
isBound = false;
}
/** Analyze the specified action base value. */
public void visit(ActionBaseValue v) {
isTopLevel = false;
// No change to parser, therefore no change to isFirstElement.
isBound = false;
}
/** Analyze the specified generic value. */
public void visit(GenericValue v) {
isTopLevel = false;
// No change to parser, therefore no change to isFirstElement.
isBound = false;
}
/**
* Analyze the specified generic action value. Note that generic
* recursion values are also generic action values.
*/
public void visit(GenericActionValue v) {
if (! hasDirectLocation()) createsNodeValue = true;
isTopLevel = false;
// No change to parser, therefore no change to isFirstElement.
isBound = false;
}
/**
* Analyze the specified element. This method provides the default
* implementation for parse tree nodes, null literals, node markers,
* and value elements (besides token values, action base values, and
* generic values).
*/
public void visit(Element e) {
isTopLevel = false;
// No change to parser, therefore no change to isFirstElement.
isBound = false;
}
}