001: /*
002: * xtc - The eXTensible Compiler
003: * Copyright (C) 2004-2007 Robert Grimm
004: *
005: * This program is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU General Public License
007: * version 2 as published by the Free Software Foundation.
008: *
009: * This program is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with this program; if not, write to the Free Software
016: * Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
017: * USA.
018: */
019: package xtc.parser;
020:
021: import xtc.tree.Visitor;
022:
023: import xtc.type.AST;
024:
025: import xtc.util.Runtime;
026:
027: /**
028: * Visitor to identify text-only productions. A production is
029: * text-only if it does not define a semantic value beyond declaring a
030: * string value and references only other text-only productions (if
031: * any). Notably, a text-only production may not contain parser
032: * actions, semantic actions that reference {@link
033: * CodeGenerator#VALUE}, or bindings to {@link CodeGenerator#VALUE}.
034: * Note that this visitor only detects such productions but does not
035: * add appropriate value elements.
036: *
037: * @see Transformer
038: *
039: * @author Robert Grimm
040: * @version $Revision: 1.41 $
041: */
042: public class TextTester extends Visitor {
043:
044: /** The runtime. */
045: protected final Runtime runtime;
046:
047: /** The analyzer utility. */
048: protected final Analyzer analyzer;
049:
050: /** Flag for whether the current production is text-only. */
051: protected boolean isTextOnly;
052:
053: /**
054: * Create a new text tester.
055: *
056: * @param runtime The runtime.
057: * @param analyzer The analyzer utility.
058: */
059: public TextTester(Runtime runtime, Analyzer analyzer) {
060: this .runtime = runtime;
061: this .analyzer = analyzer;
062: }
063:
064: /** Visit the specified grammar. */
065: public void visit(Grammar g) {
066: // Initialize the per-grammar state.
067: analyzer.register(this );
068: analyzer.init(g);
069:
070: // Process the modules.
071: for (Module m : g.modules) {
072: analyzer.process(m);
073:
074: for (Production p : m.productions) {
075: // Make sure that the production is full, has not been
076: // processed already, and returns a string.
077: if ((!p.isFull()) || analyzer.isProcessed(p.qName)) {
078: continue;
079: } else if (!AST.isString(p.type)) {
080: analyzer.processed(p.qName);
081: continue;
082: }
083:
084: // Clear the per-production state.
085: isTextOnly = true;
086:
087: // Process the production.
088: analyzer.process(p);
089:
090: // Tabulate the results.
091: if (isTextOnly) {
092: // All visited productions are guaranteed to be text-only.
093: for (NonTerminal nt : analyzer.working()) {
094: Production p2 = analyzer.lookupGlobally(nt);
095: markTextOnly(p2, runtime.test("optionVerbose"));
096: analyzer.processed(p2.qName);
097: }
098:
099: } else {
100: // We only know that the current production is not text-only.
101: analyzer.processed(p.qName);
102: }
103: }
104: }
105: }
106:
107: /** Visit the specified grammar. */
108: public void visit(Module m) {
109: // Initialize the per-grammar state.
110: analyzer.register(this );
111: analyzer.init(m);
112:
113: // Process the productions.
114: for (Production p : m.productions) {
115: // Make sure that the production has not been processed
116: // already and that it returns a string.
117: if (analyzer.isProcessed(p.qName)) {
118: continue;
119: } else if (!AST.isString(p.type)) {
120: analyzer.processed(p.qName);
121: continue;
122: }
123:
124: // Clear the per-production state.
125: isTextOnly = true;
126:
127: // Process the production.
128: analyzer.process(p);
129:
130: // Tabulate the results.
131: if (isTextOnly) {
132: // All visited productions are guaranteed to be text-only.
133: for (NonTerminal nt : analyzer.working()) {
134: // This lookup is guaranteed to work, as the production's
135: // fully qualified name was added by visit(Production).
136: Production p2 = analyzer.lookup(nt);
137:
138: markTextOnly(p2, runtime.test("optionVerbose"));
139: analyzer.processed(p2.qName);
140: }
141:
142: } else {
143: // We only know that the current production is not text-only.
144: analyzer.processed(p.qName);
145: }
146: }
147: }
148:
149: /** Visit the specified production. */
150: public void visit(Production p) {
151: Object closure = analyzer.enter(p);
152: analyzer.workingOn(p.qName);
153: dispatch(p.choice);
154: analyzer.exit(closure);
155: }
156:
157: /** Visit the specified ordered choice. */
158: public void visit(OrderedChoice c) {
159: for (Sequence alt : c.alternatives) {
160: dispatch(alt);
161: if (!isTextOnly) {
162: // We don't need to look any further.
163: return;
164: }
165: }
166: }
167:
168: /** Visit the specified sequence. */
169: public void visit(Sequence s) {
170: for (Element e : s.elements) {
171: dispatch(e);
172: if (!isTextOnly) {
173: // We don't need to look any further.
174: return;
175: }
176: }
177: }
178:
179: /** Visit the specified semantic predicate. */
180: public void visit(SemanticPredicate p) {
181: // Ignore the semantic action.
182: }
183:
184: /** Visit the specified binding. */
185: public void visit(Binding b) {
186: // We allow bindings in text-only productions, so that they can
187: // contain semantic predicates. However, we disallow a binding
188: // to CodeGenerator.VALUE.
189: if (CodeGenerator.VALUE.equals(b.name)) {
190: isTextOnly = false;
191: } else {
192: dispatch(b.element);
193: }
194: }
195:
196: /** Visit the specified nonterminal. */
197: public void visit(NonTerminal nt) {
198: Production p;
199:
200: try {
201: p = analyzer.lookup(nt);
202: } catch (IllegalArgumentException x) {
203: // Too many productions. We assume the worst.
204: isTextOnly = false;
205: return;
206: }
207:
208: if (null == p) {
209: // No such production. We assume the worst.
210: isTextOnly = false;
211:
212: } else if (analyzer.isProcessed(p.qName)) {
213: // If the corresponding production has already been processed,
214: // make sure it is text-only.
215: if (!p.getBooleanProperty(Properties.TEXT_ONLY)) {
216: isTextOnly = false;
217: }
218:
219: } else if (!analyzer.isBeingWorkedOn(p.qName)) {
220: // The production has not been processed and is not yet under
221: // consideration. If it returns a string, check it out.
222: if (AST.isString(p.type)) {
223: dispatch(p);
224: } else {
225: isTextOnly = false;
226: }
227: }
228: }
229:
230: /** Visit the specified character case. */
231: public void visit(CharCase c) {
232: dispatch(c.element);
233: }
234:
235: /** Visit the specified character switch. */
236: public void visit(CharSwitch s) {
237: for (CharCase kase : s.cases) {
238: dispatch(kase);
239: if (!isTextOnly) {
240: // We don't need to look any further.
241: return;
242: }
243: }
244: dispatch(s.base);
245: }
246:
247: /** Visit the specified terminal. */
248: public void visit(Terminal t) {
249: // Nothing to do. Terminals are text-only.
250: }
251:
252: /**
253: * Visit the specified unary operator. This method provides the
254: * default implementation for repetitions, options, syntactic
255: * predicates, voided elements, and string matches.
256: */
257: public void visit(UnaryOperator op) {
258: dispatch(op.element);
259: }
260:
261: /** Visit the specified null literal. */
262: public void visit(NullLiteral l) {
263: // Nothing to do.
264: }
265:
266: /** Visit the specified node marker. */
267: public void visit(NodeMarker m) {
268: isTextOnly = false;
269: }
270:
271: /**
272: * Visit the specified action.
273: *
274: */
275: public void visit(Action a) {
276: if (a.setsValue())
277: isTextOnly = false;
278: }
279:
280: /** Visit the specified parser action. */
281: public void visit(ParserAction pa) {
282: isTextOnly = false;
283: }
284:
285: /**
286: * Visit the specified element. This method provides the default
287: * implementation for parse tree nodes and value elements.
288: */
289: public void visit(Element e) {
290: isTextOnly = false;
291: }
292:
293: /**
294: * Mark the specified production as text-only.
295: *
296: * @param p The production.
297: * @param verbose The flag for whether to be verbose.
298: */
299: public static void markTextOnly(Production p, boolean verbose) {
300: if (verbose) {
301: System.err.println("[Recognizing " + p.qName
302: + " as text-only]");
303: }
304: p.setProperty(Properties.TEXT_ONLY, Boolean.TRUE);
305: }
306:
307: }
|