View Javadoc

1   /*
2    * ASTUtils.java
3    * 
4    * Copyright (c) 2006-2007 David Holroyd
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package uk.co.badgersinfoil.metaas.impl;
20  
21  import java.io.IOException;
22  import java.io.Reader;
23  import java.io.StringReader;
24  import org.antlr.runtime.ANTLRReaderStream;
25  import org.antlr.runtime.MismatchedTokenException;
26  import org.antlr.runtime.NoViableAltException;
27  import org.antlr.runtime.RecognitionException;
28  import org.asdt.core.internal.antlr.AS3Lexer;
29  import org.asdt.core.internal.antlr.AS3Parser;
30  import uk.co.badgersinfoil.metaas.ActionScriptFactory;
31  import uk.co.badgersinfoil.metaas.SyntaxException;
32  import uk.co.badgersinfoil.metaas.dom.Expression;
33  import uk.co.badgersinfoil.metaas.impl.antlr.BasicListUpdateDelegate;
34  import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListToken;
35  import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListTokenSource;
36  import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListTokenStream;
37  import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListTree;
38  import uk.co.badgersinfoil.metaas.impl.antlr.LinkedListTreeAdaptor;
39  import uk.co.badgersinfoil.metaas.impl.antlr.TreeTokenListUpdateDelegate;
40  
41  
42  /**
43   * A collection of helper methods for dealing with AST nodes.
44   */
45  public class ASTUtils {
46  	
47  	public static final LinkedListTreeAdaptor TREE_ADAPTOR = new LinkedListTreeAdaptor();
48  
49  	static {
50  		TREE_ADAPTOR.setFactory(new LinkedListTreeAdaptor.Factory() {
51  			private BasicListUpdateDelegate basicDelegate = new BasicListUpdateDelegate();
52  			private ParentheticListUpdateDelegate blockDelegate = new ParentheticListUpdateDelegate(AS3Parser.LCURLY, AS3Parser.RCURLY);
53  			private ParentheticListUpdateDelegate metadataTagDelegate = new ParentheticListUpdateDelegate(AS3Parser.LBRACK, AS3Parser.RBRACK);
54  			private ParentheticListUpdateDelegate paramsDelegate = new ParentheticListUpdateDelegate(AS3Parser.LPAREN, AS3Parser.RPAREN);
55  			public TreeTokenListUpdateDelegate create(LinkedListTree payload) {
56  				if (payload.isNil()) {
57  					return basicDelegate;
58  				}
59  				switch (payload.getType()) {
60  				    case AS3Parser.BLOCK:
61  				    case AS3Parser.TYPE_BLOCK:
62  				    case AS3Parser.OBJECT_LITERAL:
63  					return blockDelegate;
64  				    case AS3Parser.ANNOTATION:
65  				    case AS3Parser.ARRAY_LITERAL:
66  					return metadataTagDelegate;
67  				    case AS3Parser.PARAMS:
68  				    case AS3Parser.ANNOTATION_PARAMS:
69  				    case AS3Parser.ARGUMENTS:
70  				    case AS3Parser.ENCPS_EXPR:
71  				    case AS3Parser.CONDITION:
72  					return paramsDelegate;
73  				    default:
74  					return basicDelegate;
75  				}
76  			}
77  		});
78  	}
79  
80  	/**
81  	 * Stringifies the given IDENTIFIER node.
82  	 */
83  	public static String identText(LinkedListTree ast) {
84  		if (ast.getType() != AS3Parser.IDENTIFIER) {
85  			throw new IllegalArgumentException("expected IDENTIFIER, but token was a " + tokenName(ast));
86  		}
87  		return stringifyIdentAux((LinkedListTree)ast.getChild(0));
88  	}
89  	
90  	private static String stringifyIdentAux(LinkedListTree ast) {
91  		StringBuffer result = new StringBuffer();
92  		if (ast.getType()==AS3Parser.DBL_COLON) {
93  			result.append(ast.getFirstChild());
94  			result.append("::");
95  			result.append(ast.getLastChild());
96  		} else if (ast.getType()==AS3Parser.PROPERTY_OR_IDENTIFIER
97  		           || ast.getType()==AS3Parser.DOT)
98  		{
99  			result.append(stringifyIdentAux(ast.getFirstChild()));
100 			result.append(".");
101 			result.append(stringifyIdentAux(ast.getLastChild()));
102 		} else {
103 			result.append(ast.getText());
104 		}
105 		return result.toString();
106 	}
107 
108 	public static String qualifiedIdentText(LinkedListTree ast) {
109 		if (ast.getType()==AS3Parser.DBL_COLON) {
110 			return ast.getFirstChild() + "::" + ast.getLastChild();
111 		}
112 		return ast.getText();
113 	}
114 
115 	public static String identStarText(LinkedListTree ast) {
116 		if (ast.getType() != AS3Parser.IDENTIFIER_STAR) {
117 			throw new IllegalArgumentException("expected IDENTIFIER_STAR, but token was a " + tokenName(ast));
118 		}
119 		StringBuffer result = new StringBuffer();
120 		for (int i=0; i<ast.getChildCount(); i++) {
121 			LinkedListTree part = (LinkedListTree)ast.getChild(i);
122 			if (result.length() > 0) {
123 				result.append(".");
124 			}
125 			result.append(part.getText());
126 		}
127 		return result.toString();
128 	}
129 
130 	/**
131 	 * Stringifies the type name from the given TYPE_SPEC node.
132 	 */
133 	public static String typeSpecText(LinkedListTree ast) {
134 		if (ast.getType() != AS3Parser.TYPE_SPEC) {
135 			throw new IllegalArgumentException("expected TYPE_SPEC, but token was a " + tokenName(ast));
136 		}
137 		LinkedListTree type = ast.getFirstChild();
138 		if (type.getType() == AS3Parser.IDENTIFIER) {
139 			return identText(type);
140 		}
141 		// handle e.g. "void",
142 		return type.getText();
143 	}
144 
145 	/**
146 	 * Helper for constructing error messages
147 	 */
148 	public static String tokenName(LinkedListTree ast) {
149 		return tokenName(ast.getType());
150 	}
151 
152 	/**
153 	 * Helper for constructing error messages
154 	 */
155 	public static String tokenName(int type) {
156 		if (type == -1) {
157 			return "<EOF>";
158 		}
159 		return AS3Parser.tokenNames[type];
160 	}
161 
162 	/**
163 	 * Returns the first child of the given AST node which has the given
164 	 * type, or null, if no such node exists.
165 	 */
166 	public static LinkedListTree findChildByType(LinkedListTree ast, int type) {
167 		return (LinkedListTree)ast.getFirstChildWithType(type);
168 	}
169 
170 	/**
171 	 * Returns an ActionScript3 parser which will recognise input from the
172 	 * given string.
173 	 */
174 	public static AS3Parser parse(String text) {
175 		return parse(new StringReader(text));
176 	}
177 
178 	/**
179 	 * Returns an ActionScript3 parser which will recognise input from the
180 	 * given reader.
181 	 */
182 	public static AS3Parser parse(Reader in) {
183 		ANTLRReaderStream cs;
184 		try {
185 			cs = new ANTLRReaderStream(in);
186 		} catch (IOException e) {
187 			throw new SyntaxException(e);
188 		}
189 		AS3Lexer lexer = new AS3Lexer(cs);
190 		LinkedListTokenSource linker = new LinkedListTokenSource(lexer);
191 		LinkedListTokenStream tokenStream = new LinkedListTokenStream(linker);
192 		AS3Parser parser = new AS3Parser(tokenStream);
193 		parser.setInput(lexer, cs);
194 		parser.setTreeAdaptor(TREE_ADAPTOR);
195 		return parser;
196 	}
197 
198 
199 	public static SyntaxException buildSyntaxException(String statement,
200 	                                                   AS3Parser parser,
201 	                                                   RecognitionException e)
202 	{
203 		String msg;
204 		if (e instanceof NoViableAltException) {
205 			NoViableAltException ex = (NoViableAltException)e;
206 			msg = "Unexpected token "+tokenName(parser, ex.getUnexpectedType());
207 			if (statement != null) {
208 				msg += " in "+ActionScriptFactory.str(statement);
209 			}
210 		} else if (e instanceof MismatchedTokenException) {
211 			MismatchedTokenException ex = (MismatchedTokenException)e;
212 			msg = "Unexpected token "+tokenName(parser, ex.getUnexpectedType())+" (expecting "+tokenName(parser, ex.expecting)+")";
213 			if (statement != null) {
214 				msg += " in "+ActionScriptFactory.str(statement);
215 			}
216 		} else {
217 			if (statement == null) {
218 				msg = "";
219 			} else {
220 				msg = "Problem parsing "+ActionScriptFactory.str(statement);
221 			}
222 		}
223 		msg += " at line " + e.line;
224 		return new SyntaxException(msg, e);
225 	}
226 
227 	private static String tokenName(AS3Parser parser, int type) {
228 		if (type == -1) {
229 			return "<end-of-file>";
230 		}
231 		return parser.getTokenNames()[type];
232 	}
233 
234 	/**
235 	 * Constructs a new AST of the given type, initialized to contain
236 	 * text matching the token's name (use for non-literals only).
237 	 * @deprecated
238 	 */
239 	public static LinkedListTree newAST(int type) {
240 		return newImaginaryAST(type);
241 	}
242 
243 	/**
244 	 * Constructs a new AST of the given type, initialized to contain
245 	 * text matching the token's name (use for non-literals only).
246 	 */
247 	public static LinkedListTree newImaginaryAST(int type) {
248 		return (LinkedListTree)TREE_ADAPTOR.create(type, tokenName(type));
249 	}
250 
251 	public static LinkedListTree newPlaceholderAST(int type) {
252 		LinkedListTree node = newImaginaryAST(type);
253 		LinkedListToken placeholder = TokenBuilder.newPlaceholder(node);
254 		return node;
255 	}
256 
257 	/**
258 	 * Constructs a new AST of the given type, initialized to contain the
259 	 * given text.
260 	 */
261 	public static LinkedListTree newAST(int type, String text) {
262 		LinkedListToken tok = TokenBuilder.newToken(type, text);
263 		LinkedListTree ast = (LinkedListTree)TREE_ADAPTOR.create(tok);
264 		return ast;
265 	}
266 
267 	public static LinkedListTree newAST(LinkedListToken tok) {
268 		return (LinkedListTree)TREE_ADAPTOR.create(tok);
269 	}
270 
271 	public static LinkedListTree newParentheticAST(int type,
272 	                                                int startType,
273 	                                                String startText,
274 	                                                int endType,
275 	                                                String endText) {
276 		LinkedListTree ast = newImaginaryAST(type);
277 		LinkedListToken start = TokenBuilder.newToken(startType, startText);
278 		ast.setStartToken(start);
279 		LinkedListToken stop = TokenBuilder.newToken(endType, endText);
280 		ast.setStopToken(stop);
281 		start.setNext(stop);
282 		ast.setInitialInsertionAfter(start);
283 		return ast;
284 	}
285 
286 	public static void increaseIndent(LinkedListTree node, String indent) {
287 		LinkedListToken newStart = increaseIndentAt(node.getStartToken(), indent);
288 		node.setStartToken(newStart);
289 		increaseIndentAfterFirstLine(node, indent);
290 	}
291 
292 	public static void increaseIndentAfterFirstLine(LinkedListTree node, String indent) {
293 		for (LinkedListToken tok=node.getStartToken() ; tok!=node.getStopToken(); tok=tok.getNext()) {
294 			switch (tok.getType()) {
295 			case AS3Parser.NL:
296 				tok = increaseIndentAt(tok.getNext(), indent);
297 				break;
298 			case AS3Parser.ML_COMMENT:
299 				DocCommentUtils.increaseCommentIndent(tok, indent);
300 				break;
301 			}
302 		}
303 	}
304 
305 	private static LinkedListToken increaseIndentAt(LinkedListToken tok, String indentStr) {
306 		if (tok.getType() == AS3Parser.WS) {
307 			tok.setText(indentStr + tok.getText());
308 			return tok;
309 		}
310 		LinkedListToken indent = TokenBuilder.newWhiteSpace(indentStr);
311 		tok.beforeInsert(indent);
312 		return indent;
313 	}
314 
315 	/**
316 	 * Inspects the whitespace preceeding the given tree node to try and
317 	 * determine its level of indentation.  Scans backwards from the
318 	 * startToken and returns the contents of the first whitespace token
319 	 * following a newline token, or the empty string if no such pattern
320 	 * is found.
321 	 */
322 	public static String findIndent(LinkedListTree node) {
323 		LinkedListToken tok = node.getStartToken();
324 		if (tok == null) {
325 			return findIndent(node.getParent());
326 		}
327 		// the start-token of this AST node is actually whitespace, so
328 		// scan forward until we hit a non-WS token,
329 		while (tok.getType()==AS3Parser.NL || tok.getType()==AS3Parser.WS) {
330 			if (tok.getNext() == null) {
331 				break;
332 			}
333 			tok = tok.getNext();
334 		}
335 		// search backwards though the tokens, looking for the start of
336 		// the line,
337 		for (; tok.getPrev()!=null; tok=tok.getPrev()) {
338 			if (tok.getType() == AS3Parser.NL) {
339 				break;
340 			}
341 		}
342 		if (tok.getType() == AS3Parser.WS) {
343 			return tok.getText();
344 		}
345 		if (tok.getType()!=AS3Parser.NL) {
346 			return "";
347 		}
348 		LinkedListToken startOfLine = tok.getNext();
349 		if (startOfLine.getType() == AS3Parser.WS) {
350 			return startOfLine.getText();
351 		}
352 		return "";
353 	}
354 
355 	public static void addChildWithIndentation(LinkedListTree ast, LinkedListTree stmt) {
356 		LinkedListTree last = ast.getLastChild();
357 		String indent;
358 		if (last == null) {
359 			indent = "\t" + ASTUtils.findIndent(ast);
360 		} else {
361 			indent = ASTUtils.findIndent(last);
362 		}
363 		ASTUtils.increaseIndent(stmt, indent);
364 		stmt.addToken(0, TokenBuilder.newNewline());
365 		ast.addChildWithTokens(stmt);
366 	}
367 
368 	public static void addChildWithIndentation(LinkedListTree ast, int index, LinkedListTree stmt) {
369 		LinkedListTree last = (LinkedListTree)ast.getChild(index);
370 		String indent;
371 		if (last == null) {
372 			indent = "\t" + ASTUtils.findIndent(ast);
373 		} else {
374 			indent = ASTUtils.findIndent(last);
375 		}
376 		ASTUtils.increaseIndent(stmt, indent);
377 		stmt.addToken(0, TokenBuilder.newNewline());
378 		ast.addChildWithTokens(index, stmt);
379 	}
380 
381 	public static String decodeStringLiteral(String str) {
382 		StringBuffer result = new StringBuffer();
383 		char[] s = str.toCharArray();
384 		int end = s.length - 1;
385 		if (s[0] != '"' && s[0] != '\'') {
386 			throw new SyntaxException("Invalid delimiter at position 0: "+s[0]);
387 		}
388 		char delimiter = s[0];
389 		for (int i=1; i<end; i++) {
390 			char c = s[i];
391 			switch (c) {
392 			case '\\':
393 				c = s[++i];
394 				switch (c) {
395 				case 'n':
396 					result.append('\n');
397 					break;
398 				case 't':
399 					result.append('\t');
400 					break;
401 				case '\\':
402 					result.append('\\');
403 					break;
404 				default:
405 					result.append(c);
406 				}
407 				break;
408 			default:
409 				result.append(c);
410 			}
411 		}
412 		if (s[end] != delimiter) {
413 			throw new SyntaxException("End delimiter doesn't match "+delimiter+" at position "+end);
414 		}
415 		return result.toString();
416 	}
417 
418 	public static LinkedListToken newStringLiteral(String constant) {
419 		return new LinkedListToken(AS3Parser.STRING_LITERAL, ActionScriptFactory.str(constant));
420 	}
421 
422 	/**
423 	 * Deletes any whitespace tokens following (but not including) the given
424 	 * token up to a comma token, and then deletes the comma token too.
425 	 * 
426 	 * Used when removing elements from comma-seperated lists.
427 	 */
428 	public static void removeTrailingWhitespaceAndComma(LinkedListToken stopToken) {
429 		for (LinkedListToken tok = stopToken.getNext(); tok!=null; tok=tok.getNext()) {
430 			if (tok.getChannel() == AS3Parser.HIDDEN) {
431 				// TODO: this might be incorrect (but never called?) see code in removePreceeding...
432 				tok.delete();
433 			} else if (tok.getType() == AS3Parser.COMMA) {
434 				tok.delete();
435 				break;
436 			} else {
437 				throw new SyntaxException("Unexpected token: "+tok);
438 			}
439 		}
440 	}
441 
442 	public static void removePreceedingWhitespaceAndComma(LinkedListToken startToken) {
443 		for (LinkedListToken tok = startToken.getPrev(); tok!=null; tok=tok.getPrev()) {
444 			if (tok.getChannel() == AS3Parser.HIDDEN) {
445 				LinkedListToken del = tok;
446 				tok = tok.getNext();
447 				del.delete();
448 				continue;
449 			} else if (tok.getType() == AS3Parser.COMMA) {
450 				tok.delete();
451 				break;
452 			} else {
453 				throw new SyntaxException("Unexpected token: "+tok);
454 			}
455 		}
456 	}
457 	
458 	public static void assertAS3TokenTypeIs(int expected, int actual) {
459 		if (expected != actual) {
460 			throw new SyntaxException("Expected "+tokenName(expected)+", got "+tokenName(actual));
461 		}
462 	}
463 	
464 	public static void assertAS3TokenTypeIs(String msg, int expected, int actual) {
465 		if (expected != actual) {
466 			throw new SyntaxException(msg+" Expected "+tokenName(expected)+", got "+tokenName(actual));
467 		}
468 	}
469 
470 	public static String stringifyNode(LinkedListTree ast) {
471 		StringBuffer result = new StringBuffer();
472 		for (LinkedListToken tok=ast.getStartToken(); tok!=null&&tok.getType()!=-1; tok=tok.getNext()) {
473 			result.append(tok.getText());
474 			if (tok == ast.getStopToken()) {
475 				break;
476 			}
477 		}
478 		return result.toString();
479 	}
480 
481 	public static void deleteAllChildren(LinkedListTree ast) {
482 		while (ast.getChildCount() > 0) {
483 			ast.deleteChild(0);
484 		}
485 	}
486 
487 	public static LinkedListTree ast(Expression expr) {
488 		return ((ASTExpression)expr).getAST();
489 	}
490 }