001: /*
002: * SecondPassAnalyzer.java
003: *
004: * This work is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU General Public License as published
006: * by the Free Software Foundation; either version 2 of the License,
007: * or (at your option) any later version.
008: *
009: * This work is distributed in the hope that it will be useful, but
010: * WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * 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, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
017: * USA
018: *
019: * As a special exception, the copyright holders of this library give
020: * you permission to link this library with independent modules to
021: * produce an executable, regardless of the license terms of these
022: * independent modules, and to copy and distribute the resulting
023: * executable under terms of your choice, provided that you also meet,
024: * for each linked independent module, the terms and conditions of the
025: * license of that module. An independent module is a module which is
026: * not derived from or based on this library. If you modify this
027: * library, you may extend this exception to your version of the
028: * library, but you are not obligated to do so. If you do not wish to
029: * do so, delete this exception statement from your version.
030: *
031: * Copyright (c) 2003 Per Cederberg. All rights reserved.
032: */
033:
034: package net.percederberg.grammatica;
035:
036: import net.percederberg.grammatica.parser.Node;
037: import net.percederberg.grammatica.parser.ParseException;
038: import net.percederberg.grammatica.parser.ParserCreationException;
039: import net.percederberg.grammatica.parser.Production;
040: import net.percederberg.grammatica.parser.ProductionPattern;
041: import net.percederberg.grammatica.parser.ProductionPatternAlternative;
042: import net.percederberg.grammatica.parser.ProductionPatternElement;
043: import net.percederberg.grammatica.parser.Token;
044: import net.percederberg.grammatica.parser.TokenPattern;
045:
046: /**
047: * A second pass grammar analyzer. This class processes the grammar
048: * parse tree and adds all production pattern rules to the patterns.
049: * All required syntetic production patterns will also be added to
050: * the grammar.
051: *
052: * @author Per Cederberg, <per at percederberg dot net>
053: * @version 1.0
054: */
055: class SecondPassAnalyzer extends GrammarAnalyzer {
056:
057: /**
058: * The grammar where tokens and patterns are stored.
059: */
060: private Grammar grammar;
061:
062: /**
063: * The current production pattern. This is set when processing the
064: * production declaration, and is used when creating syntetic
065: * productions.
066: */
067: private ProductionPattern currentProduction = null;
068:
069: /**
070: * The next free syntetic production id.
071: */
072: private int nextSynteticId = 3001;
073:
074: /**
075: * Creates a new grammar analyser.
076: *
077: * @param grammar the grammar where objects are added
078: */
079: public SecondPassAnalyzer(Grammar grammar) {
080: this .grammar = grammar;
081: }
082:
083: /**
084: * Sets the node value to the token or production pattern. If no
085: * matching pattern was found, an exception is thrown.
086: *
087: * @param node the token node
088: *
089: * @return the token node
090: *
091: * @throws ParseException if the node analysis discovered errors
092: */
093: protected Node exitIdentifier(Token node) throws ParseException {
094: String name = node.getImage();
095: TokenPattern token = grammar.getTokenPatternByName(name);
096: ProductionPattern prod = grammar
097: .getProductionPatternByName(name);
098:
099: if (token != null) {
100: node.addValue(token);
101: } else if (prod != null) {
102: node.addValue(prod);
103: } else {
104: throw new ParseException(ParseException.ANALYSIS_ERROR,
105: "unrecognized identifier '" + name + "'", node
106: .getStartLine(), node.getStartColumn());
107: }
108: return node;
109: }
110:
111: /**
112: * Sets the node value to the token pattern. If no matching
113: * pattern was found, an exception is thrown.
114: *
115: * @param node the token node
116: *
117: * @return the token node
118: *
119: * @throws ParseException if the node analysis discovered errors
120: */
121: protected Node exitQuotedString(Token node) throws ParseException {
122: String str;
123: TokenPattern token;
124:
125: str = node.getImage();
126: str = str.substring(1, str.length() - 1);
127: token = grammar.getTokenPatternByImage(str);
128: if (token != null) {
129: node.addValue(token);
130: } else {
131: throw new ParseException(ParseException.ANALYSIS_ERROR,
132: "unrecognized token \"" + str + "\"", node
133: .getStartLine(), node.getStartColumn());
134: }
135: return node;
136: }
137:
138: /**
139: * Removes the parse tree by returning null.
140: *
141: * @param node the production node
142: *
143: * @return the new production node
144: */
145: protected Node exitGrammar(Production node) {
146: return null;
147: }
148:
149: /**
150: * Removes the production part from the parse tree by returning
151: * null.
152: *
153: * @param node the production node
154: *
155: * @return the new production node
156: */
157: protected Node exitProductionPart(Production node) {
158: return null;
159: }
160:
161: /**
162: * Sets the production name variable when encountering the
163: * identifier child. This variable is used when creating new
164: * subproductions.
165: *
166: * @param node the production node
167: * @param child the child to add
168: *
169: * @throws ParseException if the node analysis discovered errors
170: */
171: protected void childProductionDeclaration(Production node,
172: Node child) throws ParseException {
173:
174: super .childProductionDeclaration(node, child);
175: if (child.getId() == GrammarConstants.IDENTIFIER) {
176: currentProduction = (ProductionPattern) child.getValue(0);
177: }
178: }
179:
180: /**
181: * Adds all the pattern rules to the production pattern. This
182: * method also removes the production declaration from the parse
183: * tree by returning null.
184: *
185: * @param node the production node
186: *
187: * @return the new production node
188: *
189: * @throws ParseException if the node analysis discovered errors
190: */
191: protected Node exitProductionDeclaration(Production node)
192: throws ParseException {
193:
194: ProductionPattern pattern;
195: ProductionPatternAlternative alt;
196: Node child;
197:
198: pattern = (ProductionPattern) getValue(getChildAt(node, 0), 0);
199: child = getChildAt(node, 2);
200: for (int i = 0; i < child.getValueCount(); i++) {
201: alt = (ProductionPatternAlternative) getValue(child, i);
202: try {
203: pattern.addAlternative(alt);
204: } catch (ParserCreationException e) {
205: throw new ParseException(ParseException.ANALYSIS_ERROR,
206: e.getMessage(), node.getStartLine(), node
207: .getStartColumn());
208: }
209: }
210: return null;
211: }
212:
213: /**
214: * Sets the node values to the pattern rules.
215: *
216: * @param node the production node
217: *
218: * @return the new production node
219: *
220: * @throws ParseException if the node analysis discovered errors
221: */
222: protected Node exitProduction(Production node)
223: throws ParseException {
224: ProductionPatternAlternative alt;
225: ProductionPatternElement elem;
226: Node child;
227:
228: alt = new ProductionPatternAlternative();
229: node.addValue(alt);
230: for (int i = 0; i < node.getChildCount(); i++) {
231: child = getChildAt(node, i);
232: if (child.getId() == GrammarConstants.PRODUCTION_ATOM) {
233: for (int j = 0; j < child.getValueCount(); j++) {
234: elem = (ProductionPatternElement) getValue(child, j);
235: alt.addElement(elem);
236: }
237: } else if (child.getId() == GrammarConstants.PRODUCTION) {
238: node.addValues(child.getAllValues());
239: }
240: }
241:
242: return node;
243: }
244:
245: /**
246: * Sets the node value to the production pattern element.
247: *
248: * @param node the production node
249: *
250: * @return the new production node
251: *
252: * @throws ParseException if the node analysis discovered errors
253: */
254: protected Node exitProductionAtom(Production node)
255: throws ParseException {
256:
257: Node child;
258: boolean token = false;
259: int id = 0;
260: int min = 1;
261: int max = 1;
262: Object obj;
263:
264: // Handle the alternatives
265: child = getChildAt(node, 0);
266: switch (child.getId()) {
267: case GrammarConstants.IDENTIFIER:
268: obj = getValue(child, 0);
269: if (obj instanceof TokenPattern) {
270: token = true;
271: id = ((TokenPattern) obj).getId();
272: } else {
273: token = false;
274: id = ((ProductionPattern) obj).getId();
275: }
276: break;
277: case GrammarConstants.QUOTED_STRING:
278: token = true;
279: id = ((TokenPattern) getValue(child, 0)).getId();
280: break;
281: case GrammarConstants.LEFT_PAREN:
282: case GrammarConstants.LEFT_BRACE:
283: case GrammarConstants.LEFT_BRACKET:
284: ProductionPatternElement elem;
285:
286: if (child.getId() == GrammarConstants.LEFT_BRACE) {
287: min = 0;
288: max = -1;
289: } else if (child.getId() == GrammarConstants.LEFT_BRACKET) {
290: min = 0;
291: max = 1;
292: }
293: elem = getProductionElement(getChildAt(node, 1));
294: token = elem.isToken();
295: id = elem.getId();
296: break;
297: }
298:
299: // Handle optional '?', '*' or '+'
300: child = getChildAt(node, node.getChildCount() - 1);
301: if (child.getId() == GrammarConstants.QUESTION_MARK) {
302: min = 0;
303: max = 1;
304: } else if (child.getId() == GrammarConstants.ASTERISK) {
305: min = 0;
306: max = -1;
307: } else if (child.getId() == GrammarConstants.PLUS_SIGN) {
308: min = 1;
309: max = -1;
310: }
311:
312: // Create production pattern element
313: node
314: .addValue(new ProductionPatternElement(token, id, min,
315: max));
316: return node;
317: }
318:
319: /**
320: * Returns the production pattern element for a production node.
321: * The production node only contains a set of production rules, so
322: * this method normally creates a syntetic production and adds all
323: * the rules to it. If only a single rule was present in the rule
324: * set, and if it contained only an single mandatory element, that
325: * element will be returned instead.
326: *
327: * @param node the production parse tree node
328: *
329: * @return the production pattern element
330: *
331: * @throws ParseException if the node analysis discovered errors
332: */
333: private ProductionPatternElement getProductionElement(Node node)
334: throws ParseException {
335:
336: ProductionPattern prod;
337: ProductionPatternAlternative alt;
338: String str;
339:
340: alt = (ProductionPatternAlternative) getValue(node, 0);
341: if (node.getValueCount() == 1 && isSimple(alt)) {
342: return alt.getElement(0);
343: } else {
344: str = currentProduction.getName() + "("
345: + (nextSynteticId - 3000) + ")";
346: prod = new ProductionPattern(nextSynteticId, str);
347: prod.setSyntetic(true);
348: for (int i = 0; i < node.getValueCount(); i++) {
349: alt = (ProductionPatternAlternative) getValue(node, i);
350: try {
351: prod.addAlternative(alt);
352: } catch (ParserCreationException e) {
353: throw new ParseException(
354: ParseException.ANALYSIS_ERROR, e
355: .getMessage(), node.getStartLine(),
356: node.getStartColumn());
357: }
358: }
359: grammar.addProduction(prod, node.getStartLine(), node
360: .getEndLine());
361: return new ProductionPatternElement(false,
362: nextSynteticId++, 1, 1);
363: }
364: }
365:
366: /**
367: * Checks if a production pattern alternative contains a single
368: * mandatory element.
369: *
370: * @param alt the production pattern alternative
371: *
372: * @return true if the alternative is simple, or
373: * false otherwise
374: */
375: private boolean isSimple(ProductionPatternAlternative alt) {
376: return alt.getElementCount() == 1
377: && alt.getMinElementCount() == 1
378: && alt.getMaxElementCount() == 1;
379: }
380: }
|