/*
* 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 xtc.tree.Visitor;
import xtc.type.AST;
import xtc.util.Runtime;
/**
* Visitor to identify text-only productions. A production is
* text-only if it does not define a semantic value beyond declaring a
* string value and references only other text-only productions (if
* any). Notably, a text-only production may not contain parser
* actions, semantic actions that reference {@link
* CodeGenerator#VALUE}, or bindings to {@link CodeGenerator#VALUE}.
* Note that this visitor only detects such productions but does not
* add appropriate value elements.
*
* @see Transformer
*
* @author Robert Grimm
* @version $Revision: 1.42 $
*/
public class TextTester extends Visitor {
/** The runtime. */
protected final Runtime runtime;
/** The analyzer utility. */
protected final Analyzer analyzer;
/** Flag for whether the current production is text-only. */
protected boolean isTextOnly;
/**
* Create a new text tester.
*
* @param runtime The runtime.
* @param analyzer The analyzer utility.
*/
public TextTester(Runtime runtime, Analyzer analyzer) {
this.runtime = runtime;
this.analyzer = analyzer;
}
/** Visit the specified grammar. */
public void visit(Grammar g) {
// Initialize the per-grammar state.
analyzer.register(this);
analyzer.init(g);
// Process the modules.
for (Module m : g.modules) {
analyzer.process(m);
for (Production p : m.productions) {
// Make sure that the production is full, has not been
// processed already, and returns a string.
if ((! p.isFull()) || analyzer.isProcessed(p.qName)) {
continue;
} else if (! AST.isString(p.type)) {
analyzer.processed(p.qName);
continue;
}
// Clear the per-production state.
isTextOnly = true;
// Process the production.
analyzer.process(p);
// Tabulate the results.
if (isTextOnly) {
// All visited productions are guaranteed to be text-only.
for (NonTerminal nt : analyzer.working()) {
Production p2 = analyzer.lookupGlobally(nt);
markTextOnly(p2, runtime.test("optionVerbose"));
analyzer.processed(p2.qName);
}
} else {
// We only know that the current production is not text-only.
analyzer.processed(p.qName);
}
}
}
}
/** Visit the specified grammar. */
public void visit(Module m) {
// Initialize the per-grammar state.
analyzer.register(this);
analyzer.init(m);
// Process the productions.
for (Production p : m.productions) {
// Make sure that the production has not been processed
// already and that it returns a string.
if (analyzer.isProcessed(p.qName)) {
continue;
} else if (! AST.isString(p.type)) {
analyzer.processed(p.qName);
continue;
}
// Clear the per-production state.
isTextOnly = true;
// Process the production.
analyzer.process(p);
// Tabulate the results.
if (isTextOnly) {
// All visited productions are guaranteed to be text-only.
for (NonTerminal nt : analyzer.working()) {
// This lookup is guaranteed to work, as the production's
// fully qualified name was added by visit(Production).
Production p2 = analyzer.lookup(nt);
markTextOnly(p2, runtime.test("optionVerbose"));
analyzer.processed(p2.qName);
}
} else {
// We only know that the current production is not text-only.
analyzer.processed(p.qName);
}
}
}
/** Visit the specified production. */
public void visit(Production p) {
Object closure = analyzer.enter(p);
analyzer.workingOn(p.qName);
dispatch(p.choice);
analyzer.exit(closure);
}
/** Visit the specified ordered choice. */
public void visit(OrderedChoice c) {
for (Sequence alt : c.alternatives) {
dispatch(alt);
if (! isTextOnly) {
// We don't need to look any further.
return;
}
}
}
/** Visit the specified sequence. */
public void visit(Sequence s) {
for (Element e : s.elements) {
dispatch(e);
if (! isTextOnly) {
// We don't need to look any further.
return;
}
}
}
/** Visit the specified predicate. */
public void visit(Predicate p) {
// Ignore the predicate.
}
/** Visit the specified binding. */
public void visit(Binding b) {
// We allow bindings in text-only productions, so that they can
// contain semantic predicates. However, we disallow a binding
// to CodeGenerator.VALUE.
if (CodeGenerator.VALUE.equals(b.name)) {
isTextOnly = false;
} else {
dispatch(b.element);
}
}
/** Visit the specified nonterminal. */
public void visit(NonTerminal nt) {
Production p;
try {
p = analyzer.lookup(nt);
} catch (IllegalArgumentException x) {
// Too many productions. We assume the worst.
isTextOnly = false;
return;
}
if (null == p) {
// No such production. We assume the worst.
isTextOnly = false;
} else if (analyzer.isProcessed(p.qName)) {
// If the corresponding production has already been processed,
// make sure it is text-only.
if (! p.getBooleanProperty(Properties.TEXT_ONLY)) {
isTextOnly = false;
}
} else if (! analyzer.isBeingWorkedOn(p.qName)) {
// The production has not been processed and is not yet under
// consideration. If it returns a string, check it out.
if (AST.isString(p.type)) {
dispatch(p);
} else {
isTextOnly = false;
}
}
}
/** Visit the specified character case. */
public void visit(CharCase c) {
dispatch(c.element);
}
/** Visit the specified character switch. */
public void visit(CharSwitch s) {
for (CharCase kase : s.cases) {
dispatch(kase);
if (! isTextOnly) {
// We don't need to look any further.
return;
}
}
dispatch(s.base);
}
/** Visit the specified terminal. */
public void visit(Terminal t) {
// Nothing to do. Terminals are text-only.
}
/**
* Visit the specified unary operator. This method provides the
* default implementation for repetitions, options, syntactic
* predicates, voided elements, and string matches.
*/
public void visit(UnaryOperator op) {
dispatch(op.element);
}
/** Visit the specified null literal. */
public void visit(NullLiteral l) {
// Nothing to do.
}
/** Visit the specified node marker. */
public void visit(NodeMarker m) {
isTextOnly = false;
}
/**
* Visit the specified action.
*
*/
public void visit(Action a) {
if (a.setsValue()) isTextOnly = false;
}
/** Visit the specified parser action. */
public void visit(ParserAction pa) {
isTextOnly = false;
}
/**
* Visit the specified element. This method provides the default
* implementation for parse tree nodes and value elements.
*/
public void visit(Element e) {
isTextOnly = false;
}
/**
* Mark the specified production as text-only.
*
* @param p The production.
* @param verbose The flag for whether to be verbose.
*/
public static void markTextOnly(Production p, boolean verbose) {
if (verbose) {
System.err.println("[Recognizing " + p.qName + " as text-only]");
}
p.setProperty(Properties.TEXT_ONLY, Boolean.TRUE);
}
}