001: /* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
002: *
003: * ***** BEGIN LICENSE BLOCK *****
004: * Version: MPL 1.1/GPL 2.0
005: *
006: * The contents of this file are subject to the Mozilla Public License Version
007: * 1.1 (the "License"); you may not use this file except in compliance with
008: * the License. You may obtain a copy of the License at
009: * http://www.mozilla.org/MPL/
010: *
011: * Software distributed under the License is distributed on an "AS IS" basis,
012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
013: * for the specific language governing rights and limitations under the
014: * License.
015: *
016: * The Original Code is Rhino code, released
017: * May 6, 1999.
018: *
019: * The Initial Developer of the Original Code is
020: * Netscape Communications Corporation.
021: * Portions created by the Initial Developer are Copyright (C) 1997-1999
022: * the Initial Developer. All Rights Reserved.
023: *
024: * Contributor(s):
025: * Norris Boyd
026: * Igor Bukanov
027: * Bob Jervis
028: * Roger Lawrence
029: * Mike McCabe
030: *
031: * Alternatively, the contents of this file may be used under the terms of
032: * the GNU General Public License Version 2 or later (the "GPL"), in which
033: * case the provisions of the GPL are applicable instead of those above. If
034: * you wish to allow use of your version of this file only under the terms of
035: * the GPL and not to allow others to use your version of this file under the
036: * MPL, indicate your decision by deleting the provisions above and replacing
037: * them with the notice and other provisions required by the GPL. If you do
038: * not delete the provisions above, a recipient may use your version of this
039: * file under either the MPL or the GPL.
040: *
041: * ***** END LICENSE BLOCK ***** */
042:
043: package org.mozilla.javascript;
044:
045: import java.util.ArrayList;
046: import java.util.List;
047:
048: /**
049: * This class transforms a tree to a lower-level representation for codegen.
050: *
051: * @see Node
052: * @author Norris Boyd
053: */
054:
055: public class NodeTransformer {
056:
057: public NodeTransformer() {
058: }
059:
060: public final void transform(ScriptOrFnNode tree) {
061: transformCompilationUnit(tree);
062: for (int i = 0; i != tree.getFunctionCount(); ++i) {
063: FunctionNode fn = tree.getFunctionNode(i);
064: transform(fn);
065: }
066: }
067:
068: private void transformCompilationUnit(ScriptOrFnNode tree) {
069: loops = new ObjArray();
070: loopEnds = new ObjArray();
071:
072: // to save against upchecks if no finally blocks are used.
073: hasFinally = false;
074:
075: // Flatten all only if we are not using scope objects for block scope
076: boolean createScopeObjects = tree.getType() != Token.FUNCTION
077: || ((FunctionNode) tree).requiresActivation();
078: tree.flattenSymbolTable(!createScopeObjects);
079:
080: //uncomment to print tree before transformation
081: //if (Token.printTrees) System.out.println(tree.toStringTree(tree));
082: transformCompilationUnit_r(tree, tree, tree, createScopeObjects);
083: }
084:
085: private void transformCompilationUnit_r(final ScriptOrFnNode tree,
086: final Node parent, Node.Scope scope,
087: boolean createScopeObjects) {
088: Node node = null;
089: siblingLoop: for (;;) {
090: Node previous = null;
091: if (node == null) {
092: node = parent.getFirstChild();
093: } else {
094: previous = node;
095: node = node.getNext();
096: }
097: if (node == null) {
098: break;
099: }
100:
101: int type = node.getType();
102: if (createScopeObjects
103: && (type == Token.BLOCK || type == Token.LOOP || type == Token.ARRAYCOMP)
104: && (node instanceof Node.Scope)) {
105: Node.Scope newScope = (Node.Scope) node;
106: if (newScope.symbolTable != null) {
107: // transform to let statement so we get a with statement
108: // created to contain scoped let variables
109: Node let = new Node(
110: type == Token.ARRAYCOMP ? Token.LETEXPR
111: : Token.LET);
112: Node innerLet = new Node(Token.LET);
113: let.addChildToBack(innerLet);
114: for (String name : newScope.symbolTable.keySet()) {
115: innerLet.addChildToBack(Node.newString(
116: Token.NAME, name));
117: }
118: newScope.symbolTable = null; // so we don't transform again
119: Node oldNode = node;
120: node = replaceCurrent(parent, previous, node, let);
121: type = node.getType();
122: let.addChildToBack(oldNode);
123: }
124: }
125:
126: switch (type) {
127:
128: case Token.LABEL:
129: case Token.SWITCH:
130: case Token.LOOP:
131: loops.push(node);
132: loopEnds.push(((Node.Jump) node).target);
133: break;
134:
135: case Token.WITH: {
136: loops.push(node);
137: Node leave = node.getNext();
138: if (leave.getType() != Token.LEAVEWITH) {
139: Kit.codeBug();
140: }
141: loopEnds.push(leave);
142: break;
143: }
144:
145: case Token.TRY: {
146: Node.Jump jump = (Node.Jump) node;
147: Node finallytarget = jump.getFinally();
148: if (finallytarget != null) {
149: hasFinally = true;
150: loops.push(node);
151: loopEnds.push(finallytarget);
152: }
153: break;
154: }
155:
156: case Token.TARGET:
157: case Token.LEAVEWITH:
158: if (!loopEnds.isEmpty() && loopEnds.peek() == node) {
159: loopEnds.pop();
160: loops.pop();
161: }
162: break;
163:
164: case Token.YIELD:
165: ((FunctionNode) tree).addResumptionPoint(node);
166: break;
167:
168: case Token.RETURN: {
169: boolean isGenerator = tree.getType() == Token.FUNCTION
170: && ((FunctionNode) tree).isGenerator();
171: if (isGenerator) {
172: node.putIntProp(Node.GENERATOR_END_PROP, 1);
173: }
174: /* If we didn't support try/finally, it wouldn't be
175: * necessary to put LEAVEWITH nodes here... but as
176: * we do need a series of JSR FINALLY nodes before
177: * each RETURN, we need to ensure that each finally
178: * block gets the correct scope... which could mean
179: * that some LEAVEWITH nodes are necessary.
180: */
181: if (!hasFinally)
182: break; // skip the whole mess.
183: Node unwindBlock = null;
184: for (int i = loops.size() - 1; i >= 0; i--) {
185: Node n = (Node) loops.get(i);
186: int elemtype = n.getType();
187: if (elemtype == Token.TRY || elemtype == Token.WITH) {
188: Node unwind;
189: if (elemtype == Token.TRY) {
190: Node.Jump jsrnode = new Node.Jump(Token.JSR);
191: Node jsrtarget = ((Node.Jump) n)
192: .getFinally();
193: jsrnode.target = jsrtarget;
194: unwind = jsrnode;
195: } else {
196: unwind = new Node(Token.LEAVEWITH);
197: }
198: if (unwindBlock == null) {
199: unwindBlock = new Node(Token.BLOCK, node
200: .getLineno());
201: }
202: unwindBlock.addChildToBack(unwind);
203: }
204: }
205: if (unwindBlock != null) {
206: Node returnNode = node;
207: Node returnExpr = returnNode.getFirstChild();
208: node = replaceCurrent(parent, previous, node,
209: unwindBlock);
210: if (returnExpr == null || isGenerator) {
211: unwindBlock.addChildToBack(returnNode);
212: } else {
213: Node store = new Node(Token.EXPR_RESULT,
214: returnExpr);
215: unwindBlock.addChildToFront(store);
216: returnNode = new Node(Token.RETURN_RESULT);
217: unwindBlock.addChildToBack(returnNode);
218: // transform return expression
219: transformCompilationUnit_r(tree, store, scope,
220: createScopeObjects);
221: }
222: // skip transformCompilationUnit_r to avoid infinite loop
223: continue siblingLoop;
224: }
225: break;
226: }
227:
228: case Token.BREAK:
229: case Token.CONTINUE: {
230: Node.Jump jump = (Node.Jump) node;
231: Node.Jump jumpStatement = jump.getJumpStatement();
232: if (jumpStatement == null)
233: Kit.codeBug();
234:
235: for (int i = loops.size();;) {
236: if (i == 0) {
237: // Parser/IRFactory ensure that break/continue
238: // always has a jump statement associated with it
239: // which should be found
240: throw Kit.codeBug();
241: }
242: --i;
243: Node n = (Node) loops.get(i);
244: if (n == jumpStatement) {
245: break;
246: }
247:
248: int elemtype = n.getType();
249: if (elemtype == Token.WITH) {
250: Node leave = new Node(Token.LEAVEWITH);
251: previous = addBeforeCurrent(parent, previous,
252: node, leave);
253: } else if (elemtype == Token.TRY) {
254: Node.Jump tryNode = (Node.Jump) n;
255: Node.Jump jsrFinally = new Node.Jump(Token.JSR);
256: jsrFinally.target = tryNode.getFinally();
257: previous = addBeforeCurrent(parent, previous,
258: node, jsrFinally);
259: }
260: }
261:
262: if (type == Token.BREAK) {
263: jump.target = jumpStatement.target;
264: } else {
265: jump.target = jumpStatement.getContinue();
266: }
267: jump.setType(Token.GOTO);
268:
269: break;
270: }
271:
272: case Token.CALL:
273: visitCall(node, tree);
274: break;
275:
276: case Token.NEW:
277: visitNew(node, tree);
278: break;
279:
280: case Token.LETEXPR:
281: case Token.LET: {
282: Node child = node.getFirstChild();
283: if (child.getType() == Token.LET) {
284: // We have a let statement or expression rather than a
285: // let declaration
286: boolean createWith = tree.getType() != Token.FUNCTION
287: || ((FunctionNode) tree)
288: .requiresActivation();
289: node = visitLet(createWith, parent, previous, node);
290: break;
291: } else {
292: // fall through to process let declaration...
293: }
294: }
295: /* fall through */
296: case Token.CONST:
297: case Token.VAR: {
298: Node result = new Node(Token.BLOCK);
299: for (Node cursor = node.getFirstChild(); cursor != null;) {
300: // Move cursor to next before createAssignment gets chance
301: // to change n.next
302: Node n = cursor;
303: cursor = cursor.getNext();
304: if (n.getType() == Token.NAME) {
305: if (!n.hasChildren())
306: continue;
307: Node init = n.getFirstChild();
308: n.removeChild(init);
309: n.setType(Token.BINDNAME);
310: n = new Node(
311: type == Token.CONST ? Token.SETCONST
312: : Token.SETNAME, n, init);
313: } else {
314: // May be a destructuring assignment already transformed
315: // to a LETEXPR
316: if (n.getType() != Token.LETEXPR)
317: throw Kit.codeBug();
318: }
319: Node pop = new Node(Token.EXPR_VOID, n, node
320: .getLineno());
321: result.addChildToBack(pop);
322: }
323: node = replaceCurrent(parent, previous, node, result);
324: break;
325: }
326:
327: case Token.TYPEOFNAME: {
328: Node.Scope defining = scope.getDefiningScope(node
329: .getString());
330: if (defining != null) {
331: node.setScope(defining);
332: }
333: }
334: break;
335:
336: case Token.TYPEOF:
337: case Token.IFNE: {
338: /* We want to suppress warnings for undefined property o.p
339: * for the following constructs: typeof o.p, if (o.p),
340: * if (!o.p), if (o.p == undefined), if (undefined == o.p)
341: */
342: Node child = node.getFirstChild();
343: if (type == Token.IFNE) {
344: while (child.getType() == Token.NOT) {
345: child = child.getFirstChild();
346: }
347: if (child.getType() == Token.EQ
348: || child.getType() == Token.NE) {
349: Node first = child.getFirstChild();
350: Node last = child.getLastChild();
351: if (first.getType() == Token.NAME
352: && first.getString()
353: .equals("undefined"))
354: child = last;
355: else if (last.getType() == Token.NAME
356: && last.getString().equals("undefined"))
357: child = first;
358: }
359: }
360: if (child.getType() == Token.GETPROP)
361: child.setType(Token.GETPROPNOWARN);
362: break;
363: }
364:
365: case Token.NAME:
366: case Token.SETNAME:
367: case Token.SETCONST:
368: case Token.DELPROP: {
369: // Turn name to var for faster access if possible
370: if (createScopeObjects) {
371: break;
372: }
373: Node nameSource;
374: if (type == Token.NAME) {
375: nameSource = node;
376: } else {
377: nameSource = node.getFirstChild();
378: if (nameSource.getType() != Token.BINDNAME) {
379: if (type == Token.DELPROP) {
380: break;
381: }
382: throw Kit.codeBug();
383: }
384: }
385: if (nameSource.getScope() != null) {
386: break; // already have a scope set
387: }
388: String name = nameSource.getString();
389: Node.Scope defining = scope.getDefiningScope(name);
390: if (defining != null) {
391: nameSource.setScope(defining);
392: if (type == Token.NAME) {
393: node.setType(Token.GETVAR);
394: } else if (type == Token.SETNAME) {
395: node.setType(Token.SETVAR);
396: nameSource.setType(Token.STRING);
397: } else if (type == Token.SETCONST) {
398: node.setType(Token.SETCONSTVAR);
399: nameSource.setType(Token.STRING);
400: } else if (type == Token.DELPROP) {
401: // Local variables are by definition permanent
402: Node n = new Node(Token.FALSE);
403: node = replaceCurrent(parent, previous, node, n);
404: } else {
405: throw Kit.codeBug();
406: }
407: }
408: break;
409: }
410: }
411:
412: transformCompilationUnit_r(tree, node,
413: node instanceof Node.Scope ? (Node.Scope) node
414: : scope, createScopeObjects);
415: }
416: }
417:
418: protected void visitNew(Node node, ScriptOrFnNode tree) {
419: }
420:
421: protected void visitCall(Node node, ScriptOrFnNode tree) {
422: }
423:
424: protected Node visitLet(boolean createWith, Node parent,
425: Node previous, Node scopeNode) {
426: Node vars = scopeNode.getFirstChild();
427: Node body = vars.getNext();
428: scopeNode.removeChild(vars);
429: scopeNode.removeChild(body);
430: boolean isExpression = scopeNode.getType() == Token.LETEXPR;
431: Node result;
432: Node newVars;
433: if (createWith) {
434: result = new Node(isExpression ? Token.WITHEXPR
435: : Token.BLOCK);
436: result = replaceCurrent(parent, previous, scopeNode, result);
437: ArrayList<Object> list = new ArrayList<Object>();
438: Node objectLiteral = new Node(Token.OBJECTLIT);
439: for (Node v = vars.getFirstChild(); v != null; v = v
440: .getNext()) {
441: Node current = v;
442: if (current.getType() == Token.LETEXPR) {
443: // destructuring in let expr, e.g. let ([x, y] = [3, 4]) {}
444: List<?> destructuringNames = (List<?>) current
445: .getProp(Node.DESTRUCTURING_NAMES);
446: Node c = current.getFirstChild();
447: if (c.getType() != Token.LET)
448: throw Kit.codeBug();
449: // Add initialization code to front of body
450: if (isExpression) {
451: body = new Node(Token.COMMA, c.getNext(), body);
452: } else {
453: body = new Node(Token.BLOCK, new Node(
454: Token.EXPR_VOID, c.getNext()), body);
455: }
456: // Update "list" and "objectLiteral" for the variables
457: // defined in the destructuring assignment
458: if (destructuringNames != null) {
459: list.addAll(destructuringNames);
460: for (int i = 0; i < destructuringNames.size(); i++) {
461: objectLiteral.addChildToBack(new Node(
462: Token.VOID, Node.newNumber(0.0)));
463: }
464: }
465: current = c.getFirstChild(); // should be a NAME, checked below
466: }
467: if (current.getType() != Token.NAME)
468: throw Kit.codeBug();
469: list.add(ScriptRuntime.getIndexObject(current
470: .getString()));
471: Node init = current.getFirstChild();
472: if (init == null) {
473: init = new Node(Token.VOID, Node.newNumber(0.0));
474: }
475: objectLiteral.addChildToBack(init);
476: }
477: objectLiteral.putProp(Node.OBJECT_IDS_PROP, list.toArray());
478: newVars = new Node(Token.ENTERWITH, objectLiteral);
479: result.addChildToBack(newVars);
480: result.addChildToBack(new Node(Token.WITH, body));
481: result.addChildToBack(new Node(Token.LEAVEWITH));
482: } else {
483: result = new Node(isExpression ? Token.COMMA : Token.BLOCK);
484: result = replaceCurrent(parent, previous, scopeNode, result);
485: newVars = new Node(Token.COMMA);
486: for (Node v = vars.getFirstChild(); v != null; v = v
487: .getNext()) {
488: Node current = v;
489: if (current.getType() == Token.LETEXPR) {
490: // destructuring in let expr, e.g. let ([x, y] = [3, 4]) {}
491: Node c = current.getFirstChild();
492: if (c.getType() != Token.LET)
493: throw Kit.codeBug();
494: // Add initialization code to front of body
495: if (isExpression) {
496: body = new Node(Token.COMMA, c.getNext(), body);
497: } else {
498: body = new Node(Token.BLOCK, new Node(
499: Token.EXPR_VOID, c.getNext()), body);
500: }
501: // We're removing the LETEXPR, so move the symbols
502: Node.Scope.joinScopes((Node.Scope) current,
503: (Node.Scope) scopeNode);
504: current = c.getFirstChild(); // should be a NAME, checked below
505: }
506: if (current.getType() != Token.NAME)
507: throw Kit.codeBug();
508: Node stringNode = Node.newString(current.getString());
509: stringNode.setScope((Node.Scope) scopeNode);
510: Node init = current.getFirstChild();
511: if (init == null) {
512: init = new Node(Token.VOID, Node.newNumber(0.0));
513: }
514: newVars.addChildToBack(new Node(Token.SETVAR,
515: stringNode, init));
516: }
517: if (isExpression) {
518: result.addChildToBack(newVars);
519: scopeNode.setType(Token.COMMA);
520: result.addChildToBack(scopeNode);
521: scopeNode.addChildToBack(body);
522: } else {
523: result
524: .addChildToBack(new Node(Token.EXPR_VOID,
525: newVars));
526: scopeNode.setType(Token.BLOCK);
527: result.addChildToBack(scopeNode);
528: scopeNode.addChildrenToBack(body);
529: }
530: }
531: return result;
532: }
533:
534: private static Node addBeforeCurrent(Node parent, Node previous,
535: Node current, Node toAdd) {
536: if (previous == null) {
537: if (!(current == parent.getFirstChild()))
538: Kit.codeBug();
539: parent.addChildToFront(toAdd);
540: } else {
541: if (!(current == previous.getNext()))
542: Kit.codeBug();
543: parent.addChildAfter(toAdd, previous);
544: }
545: return toAdd;
546: }
547:
548: private static Node replaceCurrent(Node parent, Node previous,
549: Node current, Node replacement) {
550: if (previous == null) {
551: if (!(current == parent.getFirstChild()))
552: Kit.codeBug();
553: parent.replaceChild(current, replacement);
554: } else if (previous.next == current) {
555: // Check cachedPrev.next == current is necessary due to possible
556: // tree mutations
557: parent.replaceChildAfter(previous, replacement);
558: } else {
559: parent.replaceChild(current, replacement);
560: }
561: return replacement;
562: }
563:
564: private ObjArray loops;
565: private ObjArray loopEnds;
566: private boolean hasFinally;
567: }
|