001: /*
002: * Analyzer.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.parser;
035:
036: import java.util.ArrayList;
037:
038: /**
039: * A parse tree analyzer. This class provides callback methods that
040: * may be used either during parsing, or for a parse tree traversal.
041: * This class should be subclassed to provide adequate handling of the
042: * parse tree nodes.
043: *
044: * The general contract for the analyzer class does not guarantee a
045: * strict call order for the callback methods. Depending on the type
046: * of parser, the enter() and exit() methods for production nodes can
047: * be called either in a top-down or a bottom-up fashion. The only
048: * guarantee provided by this API, is that the calls for any given
049: * node will always be in the order enter(), child(), and exit(). If
050: * various child() calls are made, they will be made from left to
051: * right as child nodes are added (to the right).
052: *
053: * @author Per Cederberg, <per at percederberg dot net>
054: * @version 1.1
055: */
056: public class Analyzer {
057:
058: /**
059: * Creates a new parse tree analyzer.
060: */
061: public Analyzer() {
062: }
063:
064: /**
065: * Analyzes a parse tree node by traversing all it's child nodes.
066: * The tree traversal is depth-first, and the appropriate
067: * callback methods will be called. If the node is a production
068: * node, a new production node will be created and children will
069: * be added by recursively processing the children of the
070: * specified production node. This method is used to process a
071: * parse tree after creation.
072: *
073: * @param node the parse tree node to process
074: *
075: * @return the resulting parse tree node
076: *
077: * @throws ParserLogException if the node analysis discovered
078: * errors
079: */
080: public Node analyze(Node node) throws ParserLogException {
081: ParserLogException log = new ParserLogException();
082:
083: node = analyze(node, log);
084: if (log.getErrorCount() > 0) {
085: throw log;
086: }
087: return node;
088: }
089:
090: /**
091: * Analyzes a parse tree node by traversing all it's child nodes.
092: * The tree traversal is depth-first, and the appropriate
093: * callback methods will be called. If the node is a production
094: * node, a new production node will be created and children will
095: * be added by recursively processing the children of the
096: * specified production node. This method is used to process a
097: * parse tree after creation.
098: *
099: * @param node the parse tree node to process
100: * @param log the parser error log
101: *
102: * @return the resulting parse tree node
103: */
104: private Node analyze(Node node, ParserLogException log) {
105: Production prod;
106: int errorCount;
107:
108: errorCount = log.getErrorCount();
109: if (node instanceof Production) {
110: prod = (Production) node;
111: prod = new Production(prod.getPattern());
112: try {
113: enter(prod);
114: } catch (ParseException e) {
115: log.addError(e);
116: }
117: for (int i = 0; i < node.getChildCount(); i++) {
118: try {
119: child(prod, analyze(node.getChildAt(i), log));
120: } catch (ParseException e) {
121: log.addError(e);
122: }
123: }
124: try {
125: return exit(prod);
126: } catch (ParseException e) {
127: if (errorCount == log.getErrorCount()) {
128: log.addError(e);
129: }
130: }
131: } else {
132: node.removeAllValues();
133: try {
134: enter(node);
135: } catch (ParseException e) {
136: log.addError(e);
137: }
138: try {
139: return exit(node);
140: } catch (ParseException e) {
141: if (errorCount == log.getErrorCount()) {
142: log.addError(e);
143: }
144: }
145: }
146: return null;
147: }
148:
149: /**
150: * Called when entering a parse tree node. By default this method
151: * does nothing. A subclass can override this method to handle
152: * each node separately.
153: *
154: * @param node the node being entered
155: *
156: * @throws ParseException if the node analysis discovered errors
157: */
158: protected void enter(Node node) throws ParseException {
159: }
160:
161: /**
162: * Called when exiting a parse tree node. By default this method
163: * returns the node. A subclass can override this method to handle
164: * each node separately. If no parse tree should be created, this
165: * method should return null.
166: *
167: * @param node the node being exited
168: *
169: * @return the node to add to the parse tree, or
170: * null if no parse tree should be created
171: *
172: * @throws ParseException if the node analysis discovered errors
173: */
174: protected Node exit(Node node) throws ParseException {
175: return node;
176: }
177:
178: /**
179: * Called when adding a child to a parse tree node. By default
180: * this method adds the child to the production node. A subclass
181: * can override this method to handle each node separately. Note
182: * that the child node may be null if the corresponding exit()
183: * method returned null.
184: *
185: * @param node the parent node
186: * @param child the child node, or null
187: *
188: * @throws ParseException if the node analysis discovered errors
189: */
190: protected void child(Production node, Node child)
191: throws ParseException {
192:
193: node.addChild(child);
194: }
195:
196: /**
197: * Returns a child at the specified position. If either the node
198: * or the child node is null, this method will throw a parse
199: * exception with the internal error type.
200: *
201: * @param node the parent node
202: * @param pos the child position
203: *
204: * @return the child node
205: *
206: * @throws ParseException if either the node or the child node
207: * was null
208: */
209: protected Node getChildAt(Node node, int pos) throws ParseException {
210: Node child;
211:
212: if (node == null) {
213: throw new ParseException(ParseException.INTERNAL_ERROR,
214: "attempt to read 'null' parse tree node", -1, -1);
215: }
216: child = node.getChildAt(pos);
217: if (child == null) {
218: throw new ParseException(ParseException.INTERNAL_ERROR,
219: "node '" + node.getName() + "' has no child at "
220: + "position " + pos, node.getStartLine(),
221: node.getStartColumn());
222: }
223: return child;
224: }
225:
226: /**
227: * Returns the first child with the specified id. If the node is
228: * null, or no child with the specified id could be found, this
229: * method will throw a parse exception with the internal error
230: * type.
231: *
232: * @param node the parent node
233: * @param id the child node id
234: *
235: * @return the child node
236: *
237: * @throws ParseException if the node was null, or a child node
238: * couldn't be found
239: */
240: protected Node getChildWithId(Node node, int id)
241: throws ParseException {
242:
243: Node child;
244:
245: if (node == null) {
246: throw new ParseException(ParseException.INTERNAL_ERROR,
247: "attempt to read 'null' parse tree node", -1, -1);
248: }
249: for (int i = 0; i < node.getChildCount(); i++) {
250: child = node.getChildAt(i);
251: if (child != null && child.getId() == id) {
252: return child;
253: }
254: }
255: throw new ParseException(ParseException.INTERNAL_ERROR,
256: "node '" + node.getName() + "' has no child with id "
257: + id, node.getStartLine(), node
258: .getStartColumn());
259: }
260:
261: /**
262: * Returns the node value at the specified position. If either
263: * the node or the value is null, this method will throw a parse
264: * exception with the internal error type.
265: *
266: * @param node the parse tree node
267: * @param pos the child position
268: *
269: * @return the value object
270: *
271: * @throws ParseException if either the node or the value was null
272: */
273: protected Object getValue(Node node, int pos) throws ParseException {
274: Object value;
275:
276: if (node == null) {
277: throw new ParseException(ParseException.INTERNAL_ERROR,
278: "attempt to read 'null' parse tree node", -1, -1);
279: }
280: value = node.getValue(pos);
281: if (value == null) {
282: throw new ParseException(ParseException.INTERNAL_ERROR,
283: "node '" + node.getName() + "' has no value at "
284: + "position " + pos, node.getStartLine(),
285: node.getStartColumn());
286: }
287: return value;
288: }
289:
290: /**
291: * Returns the node integer value at the specified position. If
292: * either the node is null, or the value is not an instance of
293: * the Integer class, this method will throw a parse exception
294: * with the internal error type.
295: *
296: * @param node the parse tree node
297: * @param pos the child position
298: *
299: * @return the value object
300: *
301: * @throws ParseException if either the node was null, or the
302: * value wasn't an integer
303: */
304: protected int getIntValue(Node node, int pos) throws ParseException {
305: Object value;
306:
307: value = getValue(node, pos);
308: if (value instanceof Integer) {
309: return ((Integer) value).intValue();
310: } else {
311: throw new ParseException(ParseException.INTERNAL_ERROR,
312: "node '" + node.getName()
313: + "' has no integer value "
314: + "at position " + pos,
315: node.getStartLine(), node.getStartColumn());
316: }
317: }
318:
319: /**
320: * Returns the node string value at the specified position. If
321: * either the node is null, or the value is not an instance of
322: * the String class, this method will throw a parse exception
323: * with the internal error type.
324: *
325: * @param node the parse tree node
326: * @param pos the child position
327: *
328: * @return the value object
329: *
330: * @throws ParseException if either the node was null, or the
331: * value wasn't a string
332: */
333: protected String getStringValue(Node node, int pos)
334: throws ParseException {
335:
336: Object value;
337:
338: value = getValue(node, pos);
339: if (value instanceof String) {
340: return (String) value;
341: } else {
342: throw new ParseException(ParseException.INTERNAL_ERROR,
343: "node '" + node.getName()
344: + "' has no string value " + "at position "
345: + pos, node.getStartLine(), node
346: .getStartColumn());
347: }
348: }
349:
350: /**
351: * Returns all the node values for all child nodes.
352: *
353: * @param node the parse tree node
354: *
355: * @return a list with all the child node values
356: *
357: * @since 1.3
358: */
359: protected ArrayList getChildValues(Node node) {
360: ArrayList result = new ArrayList();
361: Node child;
362: ArrayList values;
363:
364: for (int i = 0; i < node.getChildCount(); i++) {
365: child = node.getChildAt(i);
366: values = child.getAllValues();
367: if (values != null) {
368: result.addAll(values);
369: }
370: }
371: return result;
372: }
373: }
|