001 /*
002 * Copyright 2008 Google Inc.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005 * use this file except in compliance with the License. You may obtain a copy of
006 * the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013 * License for the specific language governing permissions and limitations under
014 * the License.
015 */
016 package com.google.gwt.dev.js;
017
018 import com.google.dart.compiler.backend.js.ast.*;
019 import com.google.dart.compiler.backend.js.ast.JsLiteral.JsBooleanLiteral;
020 import com.google.gwt.dev.js.parserExceptions.JsParserException;
021 import com.google.gwt.dev.js.rhino.*;
022 import com.intellij.util.SmartList;
023 import org.jetbrains.annotations.NotNull;
024 import org.jetbrains.annotations.Nullable;
025
026 import java.util.ArrayList;
027 import java.util.List;
028
029 public class JsAstMapper {
030
031 private final JsProgram program;
032 private final ScopeContext scopeContext;
033
034 public JsAstMapper(@NotNull JsScope scope) {
035 scopeContext = new ScopeContext(scope);
036 program = scope.getProgram();
037 }
038
039 private static JsParserException createParserException(String msg, Node offender) {
040 CodePosition position = new CodePosition(offender.getLineno(), 0);
041 return new JsParserException("Parser encountered internal error: " + msg, position);
042 }
043
044 private JsNode map(Node node) throws JsParserException {
045 switch (node.getType()) {
046 case TokenStream.SCRIPT: {
047 JsBlock block = new JsBlock();
048 mapStatements(block.getStatements(), node);
049 return block;
050 }
051
052 case TokenStream.DEBUGGER:
053 return mapDebuggerStatement(node);
054
055 case TokenStream.VOID:
056 // VOID = nothing was parsed for this node
057 return null;
058
059 case TokenStream.EXPRSTMT:
060 return mapExpressionStatement(node);
061
062 case TokenStream.REGEXP:
063 return mapRegExp(node);
064
065 case TokenStream.ADD:
066 return mapBinaryOperation(JsBinaryOperator.ADD, node);
067
068 case TokenStream.SUB:
069 return mapBinaryOperation(JsBinaryOperator.SUB, node);
070
071 case TokenStream.MUL:
072 return mapBinaryOperation(JsBinaryOperator.MUL, node);
073
074 case TokenStream.DIV:
075 return mapBinaryOperation(JsBinaryOperator.DIV, node);
076
077 case TokenStream.MOD:
078 return mapBinaryOperation(JsBinaryOperator.MOD, node);
079
080 case TokenStream.AND:
081 return mapBinaryOperation(JsBinaryOperator.AND, node);
082
083 case TokenStream.OR:
084 return mapBinaryOperation(JsBinaryOperator.OR, node);
085
086 case TokenStream.BITAND:
087 return mapBinaryOperation(JsBinaryOperator.BIT_AND, node);
088
089 case TokenStream.BITOR:
090 return mapBinaryOperation(JsBinaryOperator.BIT_OR, node);
091
092 case TokenStream.BITXOR:
093 return mapBinaryOperation(JsBinaryOperator.BIT_XOR, node);
094
095 case TokenStream.ASSIGN:
096 return mapAssignmentVariant(node);
097
098 case TokenStream.RELOP:
099 return mapRelationalVariant(node);
100
101 case TokenStream.EQOP:
102 return mapEqualityVariant(node);
103
104 case TokenStream.SHOP:
105 return mapShiftVariant(node);
106
107 case TokenStream.UNARYOP:
108 return mapUnaryVariant(node);
109
110 case TokenStream.INC:
111 return mapIncDecFixity(JsUnaryOperator.INC, node);
112
113 case TokenStream.DEC:
114 return mapIncDecFixity(JsUnaryOperator.DEC, node);
115
116 case TokenStream.HOOK:
117 return mapConditional(node);
118
119 case TokenStream.STRING:
120 return program.getStringLiteral(node.getString());
121
122 case TokenStream.NUMBER_INT:
123 return mapIntNumber(node);
124
125 case TokenStream.NUMBER:
126 return mapDoubleNumber(node);
127
128 case TokenStream.CALL:
129 return mapCall(node);
130
131 case TokenStream.GETPROP:
132 return mapGetProp(node);
133
134 case TokenStream.SETPROP:
135 return mapSetProp(node);
136
137 case TokenStream.DELPROP:
138 return mapDeleteProp(node);
139
140 case TokenStream.IF:
141 return mapIfStatement(node);
142
143 case TokenStream.WHILE:
144 return mapDoOrWhileStatement(true, node);
145
146 case TokenStream.DO:
147 return mapDoOrWhileStatement(false, node);
148
149 case TokenStream.FOR:
150 return mapForStatement(node);
151
152 case TokenStream.WITH:
153 return mapWithStatement(node);
154
155 case TokenStream.GETELEM:
156 return mapGetElem(node);
157
158 case TokenStream.SETELEM:
159 return mapSetElem(node);
160
161 case TokenStream.FUNCTION:
162 return mapFunction(node);
163
164 case TokenStream.BLOCK:
165 return mapBlock(node);
166
167 case TokenStream.SETNAME:
168 return mapBinaryOperation(JsBinaryOperator.ASG, node);
169
170 case TokenStream.NAME:
171 case TokenStream.BINDNAME:
172 return scopeContext.globalNameFor(node.getString()).makeRef();
173
174 case TokenStream.RETURN:
175 return mapReturn(node);
176
177 case TokenStream.BREAK:
178 return mapBreak(node);
179
180 case TokenStream.CONTINUE:
181 return mapContinue(node);
182
183 case TokenStream.OBJLIT:
184 return mapObjectLit(node);
185
186 case TokenStream.ARRAYLIT:
187 return mapArrayLit(node);
188
189 case TokenStream.VAR:
190 return mapVar(node);
191
192 case TokenStream.PRIMARY:
193 return mapPrimary(node);
194
195 case TokenStream.COMMA:
196 return mapBinaryOperation(JsBinaryOperator.COMMA, node);
197
198 case TokenStream.NEW:
199 return mapNew(node);
200
201 case TokenStream.THROW:
202 return mapThrowStatement(node);
203
204 case TokenStream.TRY:
205 return mapTryStatement(node);
206
207 case TokenStream.SWITCH:
208 return mapSwitchStatement(node);
209
210 case TokenStream.LABEL:
211 return mapLabel(node);
212
213 default:
214 int tokenType = node.getType();
215 throw createParserException("Unexpected top-level token type: "
216 + tokenType, node);
217 }
218 }
219
220 private JsArrayLiteral mapArrayLit(Node node) throws JsParserException {
221 JsArrayLiteral toLit = new JsArrayLiteral();
222 Node from = node.getFirstChild();
223 while (from != null) {
224 toLit.getExpressions().add(mapExpression(from));
225 from = from.getNext();
226 }
227 return toLit;
228 }
229
230 /**
231 * Produces a {@link JsNameRef}.
232 */
233 private JsNameRef mapAsPropertyNameRef(Node nameRefNode)
234 throws JsParserException {
235 JsNode unknown = map(nameRefNode);
236 // This is weird, but for "a.b", the rhino AST calls "b" a string literal.
237 // However, since we know it's for a PROPGET, we can unstringliteralize it.
238 //
239 if (unknown instanceof JsStringLiteral) {
240 JsStringLiteral lit = (JsStringLiteral) unknown;
241 return scopeContext.referenceFor(lit.getValue());
242 }
243 else {
244 throw createParserException("Expecting a name reference", nameRefNode);
245 }
246 }
247
248 private JsExpression mapAssignmentVariant(Node asgNode)
249 throws JsParserException {
250 switch (asgNode.getIntDatum()) {
251 case TokenStream.NOP:
252 return mapBinaryOperation(JsBinaryOperator.ASG, asgNode);
253
254 case TokenStream.ADD:
255 return mapBinaryOperation(JsBinaryOperator.ASG_ADD, asgNode);
256
257 case TokenStream.SUB:
258 return mapBinaryOperation(JsBinaryOperator.ASG_SUB, asgNode);
259
260 case TokenStream.MUL:
261 return mapBinaryOperation(JsBinaryOperator.ASG_MUL, asgNode);
262
263 case TokenStream.DIV:
264 return mapBinaryOperation(JsBinaryOperator.ASG_DIV, asgNode);
265
266 case TokenStream.MOD:
267 return mapBinaryOperation(JsBinaryOperator.ASG_MOD, asgNode);
268
269 case TokenStream.BITAND:
270 return mapBinaryOperation(JsBinaryOperator.ASG_BIT_AND, asgNode);
271
272 case TokenStream.BITOR:
273 return mapBinaryOperation(JsBinaryOperator.ASG_BIT_OR, asgNode);
274
275 case TokenStream.BITXOR:
276 return mapBinaryOperation(JsBinaryOperator.ASG_BIT_XOR, asgNode);
277
278 case TokenStream.LSH:
279 return mapBinaryOperation(JsBinaryOperator.ASG_SHL, asgNode);
280
281 case TokenStream.RSH:
282 return mapBinaryOperation(JsBinaryOperator.ASG_SHR, asgNode);
283
284 case TokenStream.URSH:
285 return mapBinaryOperation(JsBinaryOperator.ASG_SHRU, asgNode);
286
287 default:
288 throw createParserException("Unknown assignment operator variant: "
289 + asgNode.getIntDatum(), asgNode);
290 }
291 }
292
293 private JsExpression mapBinaryOperation(JsBinaryOperator op, Node node)
294 throws JsParserException {
295 Node from1 = node.getFirstChild();
296 Node from2 = from1.getNext();
297
298 JsExpression to1 = mapExpression(from1);
299 JsExpression to2 = mapExpression(from2);
300
301 return new JsBinaryOperation(op, to1, to2);
302 }
303
304 private JsBlock mapBlock(Node nodeStmts) throws JsParserException {
305 JsBlock block = new JsBlock();
306 mapStatements(block.getStatements(), nodeStmts);
307 return block;
308 }
309
310 private JsBreak mapBreak(Node breakNode) {
311 return new JsBreak(getTargetLabel(breakNode));
312 }
313
314 @Nullable
315 private JsNameRef getTargetLabel(@NotNull Node statementWithLabel) {
316 int type = statementWithLabel.getType();
317 if (type != TokenStream.BREAK && type != TokenStream.CONTINUE) {
318 String tokenTypeName = TokenStream.tokenToName(statementWithLabel.getType());
319 throw new AssertionError("Unexpected node type with label: " + tokenTypeName);
320 }
321
322 Node label = statementWithLabel.getFirstChild();
323 if (label == null) return null;
324
325 String identifier = label.getString();
326 assert identifier != null: "If label exists identifier should not be null";
327
328 JsName labelName = scopeContext.labelFor(identifier);
329 assert labelName != null: "Unknown label name: " + identifier;
330
331 return labelName.makeRef();
332 }
333
334 private JsInvocation mapCall(Node callNode) throws JsParserException {
335 // Map the target expression.
336 //
337 Node from = callNode.getFirstChild();
338 JsExpression qualifier = mapExpression(from);
339
340 // Iterate over and map the arguments.
341 //
342 List<JsExpression> arguments = new SmartList<JsExpression>();
343 from = from.getNext();
344 while (from != null) {
345 arguments.add(mapExpression(from));
346 from = from.getNext();
347 }
348
349 return new JsInvocation(qualifier, arguments);
350 }
351
352 private JsExpression mapConditional(Node condNode) throws JsParserException {
353 JsConditional toCond = new JsConditional();
354
355 Node fromTest = condNode.getFirstChild();
356 toCond.setTestExpression(mapExpression(fromTest));
357
358 Node fromThen = fromTest.getNext();
359 toCond.setThenExpression(mapExpression(fromThen));
360
361 Node fromElse = fromThen.getNext();
362 toCond.setElseExpression(mapExpression(fromElse));
363
364 return toCond;
365 }
366
367 private JsContinue mapContinue(Node contNode) {
368 return new JsContinue(getTargetLabel(contNode));
369 }
370
371 private JsStatement mapDebuggerStatement(Node node) {
372 // Calls an optional method to invoke the debugger.
373 //
374 return new JsDebugger();
375 }
376
377 private JsExpression mapDeleteProp(Node node) throws JsParserException {
378 Node from = node.getFirstChild();
379 JsExpression to = mapExpression(from);
380 if (to instanceof JsNameRef) {
381 return new JsPrefixOperation(
382 JsUnaryOperator.DELETE, to);
383 }
384 else if (to instanceof JsArrayAccess) {
385 return new JsPrefixOperation(
386 JsUnaryOperator.DELETE, to);
387 }
388 else {
389 throw createParserException(
390 "'delete' can only operate on property names and array elements",
391 from);
392 }
393 }
394
395 private JsStatement mapDoOrWhileStatement(boolean isWhile, Node ifNode)
396 throws JsParserException {
397
398 // Pull out the pieces we want to map.
399 //
400 Node fromTestExpr;
401 Node fromBody;
402 if (isWhile) {
403 fromTestExpr = ifNode.getFirstChild();
404 fromBody = ifNode.getFirstChild().getNext();
405 }
406 else {
407 fromBody = ifNode.getFirstChild();
408 fromTestExpr = ifNode.getFirstChild().getNext();
409 }
410
411 // Map the test expression.
412 //
413 JsExpression toTestExpr = mapExpression(fromTestExpr);
414
415 // Map the body block.
416 //
417 JsStatement toBody = mapStatement(fromBody);
418
419 // Create and attach the "while" or "do" statement we're mapping to.
420 //
421 if (isWhile) {
422 return new JsWhile(toTestExpr, toBody);
423 }
424 else {
425 return new JsDoWhile(toTestExpr, toBody);
426 }
427 }
428
429 private JsExpression mapEqualityVariant(Node eqNode) throws JsParserException {
430 switch (eqNode.getIntDatum()) {
431 case TokenStream.EQ:
432 return mapBinaryOperation(JsBinaryOperator.EQ, eqNode);
433
434 case TokenStream.NE:
435 return mapBinaryOperation(JsBinaryOperator.NEQ, eqNode);
436
437 case TokenStream.SHEQ:
438 return mapBinaryOperation(JsBinaryOperator.REF_EQ, eqNode);
439
440 case TokenStream.SHNE:
441 return mapBinaryOperation(JsBinaryOperator.REF_NEQ, eqNode);
442
443 case TokenStream.LT:
444 return mapBinaryOperation(JsBinaryOperator.LT, eqNode);
445
446 case TokenStream.LE:
447 return mapBinaryOperation(JsBinaryOperator.LTE, eqNode);
448
449 case TokenStream.GT:
450 return mapBinaryOperation(JsBinaryOperator.GT, eqNode);
451
452 case TokenStream.GE:
453 return mapBinaryOperation(JsBinaryOperator.GTE, eqNode);
454
455 default:
456 throw createParserException("Unknown equality operator variant: "
457 + eqNode.getIntDatum(), eqNode);
458 }
459 }
460
461 private JsExpression mapExpression(Node exprNode) throws JsParserException {
462 JsNode unknown = map(exprNode);
463 if (unknown instanceof JsExpression) {
464 return (JsExpression) unknown;
465 }
466 else {
467 throw createParserException("Expecting an expression", exprNode);
468 }
469 }
470
471 private JsStatement mapExpressionStatement(Node node) throws JsParserException {
472 JsExpression expr = mapExpression(node.getFirstChild());
473 return expr.makeStmt();
474 }
475
476 private JsStatement mapForStatement(Node forNode) throws JsParserException {
477 Node fromInit = forNode.getFirstChild();
478 Node fromTest = fromInit.getNext();
479 Node fromIncr = fromTest.getNext();
480 Node fromBody = fromIncr.getNext();
481
482 if (fromBody == null) {
483 // This could be a "for...in" structure.
484 // We could based on the different child layout.
485 //
486 Node fromIter = forNode.getFirstChild();
487 Node fromObjExpr = fromIter.getNext();
488 fromBody = fromObjExpr.getNext();
489
490 JsForIn toForIn;
491 if (fromIter.getType() == TokenStream.VAR) {
492 // A named iterator var.
493 //
494 Node fromIterVarName = fromIter.getFirstChild();
495 String fromName = fromIterVarName.getString();
496 JsName toName = scopeContext.localNameFor(fromName);
497 toForIn = new JsForIn(toName);
498 Node fromIterInit = fromIterVarName.getFirstChild();
499 if (fromIterInit != null) {
500 // That has an initializer expression (useful only for side effects).
501 //
502 toForIn.setIterExpression(mapOptionalExpression(fromIterInit));
503 }
504 }
505 else {
506 // An unnamed iterator var.
507 //
508 toForIn = new JsForIn();
509 toForIn.setIterExpression(mapExpression(fromIter));
510 }
511 toForIn.setObjectExpression(mapExpression(fromObjExpr));
512
513 // The body stmt.
514 //
515 JsStatement bodyStmt = mapStatement(fromBody);
516 if (bodyStmt != null) {
517 toForIn.setBody(bodyStmt);
518 }
519 else {
520 toForIn.setBody(JsEmpty.INSTANCE$);
521 }
522
523 return toForIn;
524 }
525 else {
526 // Regular ol' for loop.
527 //
528 JsFor toFor;
529
530 // The first item is either an expression or a JsVars.
531 JsNode init = map(fromInit);
532 JsExpression condition = mapOptionalExpression(fromTest);
533 JsExpression increment = mapOptionalExpression(fromIncr);
534 assert (init != null);
535 if (init instanceof JsVars) {
536 toFor = new JsFor((JsVars) init, condition, increment);
537 }
538 else {
539 assert (init instanceof JsExpression);
540 toFor = new JsFor((JsExpression) init, condition, increment);
541 }
542
543 JsStatement bodyStmt = mapStatement(fromBody);
544 if (bodyStmt != null) {
545 toFor.setBody(bodyStmt);
546 }
547 else {
548 toFor.setBody(JsEmpty.INSTANCE$);
549 }
550 return toFor;
551 }
552 }
553
554 public JsFunction mapFunction(Node fnNode) throws JsParserException {
555 int nodeType = fnNode.getType();
556 assert nodeType == TokenStream.FUNCTION: "Expected function node, got: " + TokenStream.tokenToName(nodeType);
557 Node fromFnNameNode = fnNode.getFirstChild();
558 Node fromParamNode = fnNode.getFirstChild().getNext().getFirstChild();
559 Node fromBodyNode = fnNode.getFirstChild().getNext().getNext();
560 JsFunction toFn = scopeContext.enterFunction();
561
562 // Decide the function's name, if any.
563 //
564 String fnNameIdent = fromFnNameNode.getString();
565 if (fnNameIdent != null && fnNameIdent.length() > 0) {
566 scopeContext.globalNameFor(fnNameIdent);
567 }
568
569 while (fromParamNode != null) {
570 String fromParamName = fromParamNode.getString();
571 JsName name = scopeContext.localNameFor(fromParamName);
572 toFn.getParameters().add(new JsParameter(name));
573 fromParamNode = fromParamNode.getNext();
574 }
575
576 // Map the function's body.
577 //
578 JsBlock toBody = mapBlock(fromBodyNode);
579 toFn.setBody(toBody);
580
581 scopeContext.exitFunction();
582 return toFn;
583 }
584
585 private JsArrayAccess mapGetElem(Node getElemNode) throws JsParserException {
586 Node from1 = getElemNode.getFirstChild();
587 Node from2 = from1.getNext();
588
589 JsExpression to1 = mapExpression(from1);
590 JsExpression to2 = mapExpression(from2);
591
592 return new JsArrayAccess(to1, to2);
593 }
594
595 private JsNameRef mapGetProp(Node getPropNode) throws JsParserException {
596 Node from1 = getPropNode.getFirstChild();
597 Node from2 = from1.getNext();
598
599 JsExpression toQualifier = mapExpression(from1);
600 JsNameRef toNameRef;
601 if (from2 != null) {
602 toNameRef = mapAsPropertyNameRef(from2);
603 }
604 else {
605 // Special properties don't have a second expression.
606 //
607 Object obj = getPropNode.getProp(Node.SPECIAL_PROP_PROP);
608 assert (obj instanceof String);
609 toNameRef = scopeContext.referenceFor((String) obj);
610 }
611 toNameRef.setQualifier(toQualifier);
612
613 return toNameRef;
614 }
615
616 private JsIf mapIfStatement(Node ifNode) throws JsParserException {
617
618 // Pull out the pieces we want to map.
619 //
620 Node fromTestExpr = ifNode.getFirstChild();
621 Node fromThenBlock = ifNode.getFirstChild().getNext();
622 Node fromElseBlock = ifNode.getFirstChild().getNext().getNext();
623
624 // Create the "if" statement we're mapping to.
625 //
626 JsIf toIf = new JsIf();
627
628 // Map the test expression.
629 //
630 JsExpression toTestExpr = mapExpression(fromTestExpr);
631 toIf.setIfExpression(toTestExpr);
632
633 // Map the "then" block.
634 //
635 toIf.setThenStatement(mapStatement(fromThenBlock));
636
637 // Map the "else" block.
638 //
639 if (fromElseBlock != null) {
640 toIf.setElseStatement(mapStatement(fromElseBlock));
641 }
642
643 return toIf;
644 }
645
646 private JsExpression mapIncDecFixity(JsUnaryOperator op, Node node)
647 throws JsParserException {
648 switch (node.getIntDatum()) {
649 case TokenStream.PRE:
650 return mapPrefixOperation(op, node);
651 case TokenStream.POST:
652 return mapPostfixOperation(op, node);
653 default:
654 throw createParserException(
655 "Unknown prefix/postfix variant: " + node.getIntDatum(), node);
656 }
657 }
658
659 private JsLabel mapLabel(Node labelNode) throws JsParserException {
660 String fromName = labelNode.getFirstChild().getString();
661
662 JsName toName = scopeContext.enterLabel(fromName);
663
664 Node fromStmt = labelNode.getFirstChild().getNext();
665 JsLabel toLabel = new JsLabel(toName);
666 toLabel.setStatement(mapStatement(fromStmt));
667
668 scopeContext.exitLabel();
669
670 return toLabel;
671 }
672
673 private JsNew mapNew(Node newNode) throws JsParserException {
674 // Map the constructor expression, which is often just the name of
675 // some lambda.
676 //
677 Node fromCtorExpr = newNode.getFirstChild();
678 JsNew newExpr = new JsNew(
679 mapExpression(fromCtorExpr));
680
681 // Iterate over and map the arguments.
682 //
683 List<JsExpression> args = newExpr.getArguments();
684 Node fromArg = fromCtorExpr.getNext();
685 while (fromArg != null) {
686 args.add(mapExpression(fromArg));
687 fromArg = fromArg.getNext();
688 }
689
690 return newExpr;
691 }
692
693 private JsExpression mapIntNumber(Node numberNode) {
694 return program.getNumberLiteral((int) numberNode.getDouble());
695 }
696
697 private JsExpression mapDoubleNumber(Node numberNode) {
698 return program.getNumberLiteral(numberNode.getDouble());
699 }
700
701 private JsExpression mapObjectLit(Node objLitNode) throws JsParserException {
702 JsObjectLiteral toLit = new JsObjectLiteral();
703 Node fromPropInit = objLitNode.getFirstChild();
704 while (fromPropInit != null) {
705
706 Node fromLabelExpr = fromPropInit;
707 JsExpression toLabelExpr = mapExpression(fromLabelExpr);
708
709 // Advance to the initializer expression.
710 //
711 fromPropInit = fromPropInit.getNext();
712 Node fromValueExpr = fromPropInit;
713 if (fromValueExpr == null) {
714 throw createParserException("Expected an init expression for: "
715 + toLabelExpr, objLitNode);
716 }
717 JsExpression toValueExpr = mapExpression(fromValueExpr);
718
719 JsPropertyInitializer toPropInit = new JsPropertyInitializer(
720 toLabelExpr, toValueExpr);
721 toLit.getPropertyInitializers().add(toPropInit);
722
723 // Begin the next property initializer, if there is one.
724 //
725 fromPropInit = fromPropInit.getNext();
726 }
727
728 return toLit;
729 }
730
731 private JsExpression mapOptionalExpression(Node exprNode)
732 throws JsParserException {
733 JsNode unknown = map(exprNode);
734 if (unknown != null) {
735 if (unknown instanceof JsExpression) {
736 return (JsExpression) unknown;
737 }
738 else {
739 throw createParserException("Expecting an expression or null", exprNode);
740 }
741 }
742 return null;
743 }
744
745 private JsExpression mapPostfixOperation(JsUnaryOperator op, Node node)
746 throws JsParserException {
747 Node from = node.getFirstChild();
748 JsExpression to = mapExpression(from);
749 return new JsPostfixOperation(op, to);
750 }
751
752 private JsExpression mapPrefixOperation(JsUnaryOperator op, Node node)
753 throws JsParserException {
754 Node from = node.getFirstChild();
755 JsExpression to = mapExpression(from);
756 return new JsPrefixOperation(op, to);
757 }
758
759 private JsExpression mapPrimary(Node node) throws JsParserException {
760 switch (node.getIntDatum()) {
761 case TokenStream.THIS:
762 return JsLiteral.THIS;
763
764 case TokenStream.TRUE:
765 return JsBooleanLiteral.TRUE;
766
767 case TokenStream.FALSE:
768 return JsBooleanLiteral.FALSE;
769
770 case TokenStream.NULL:
771 return JsNullLiteral.NULL;
772
773 case TokenStream.UNDEFINED:
774 return JsLiteral.UNDEFINED;
775
776 default:
777 throw createParserException("Unknown primary: " + node.getIntDatum(),
778 node);
779 }
780 }
781
782 private JsNode mapRegExp(Node regExpNode) {
783 JsRegExp toRegExp = new JsRegExp();
784
785 Node fromPattern = regExpNode.getFirstChild();
786 toRegExp.setPattern(fromPattern.getString());
787
788 Node fromFlags = fromPattern.getNext();
789 if (fromFlags != null) {
790 toRegExp.setFlags(fromFlags.getString());
791 }
792
793 return toRegExp;
794 }
795
796 private JsExpression mapRelationalVariant(Node relNode)
797 throws JsParserException {
798 switch (relNode.getIntDatum()) {
799 case TokenStream.LT:
800 return mapBinaryOperation(JsBinaryOperator.LT, relNode);
801
802 case TokenStream.LE:
803 return mapBinaryOperation(JsBinaryOperator.LTE, relNode);
804
805 case TokenStream.GT:
806 return mapBinaryOperation(JsBinaryOperator.GT, relNode);
807
808 case TokenStream.GE:
809 return mapBinaryOperation(JsBinaryOperator.GTE, relNode);
810
811 case TokenStream.INSTANCEOF:
812 return mapBinaryOperation(JsBinaryOperator.INSTANCEOF, relNode);
813
814 case TokenStream.IN:
815 return mapBinaryOperation(JsBinaryOperator.INOP, relNode);
816
817 default:
818 throw createParserException("Unknown relational operator variant: "
819 + relNode.getIntDatum(), relNode);
820 }
821 }
822
823 private JsReturn mapReturn(Node returnNode) throws JsParserException {
824 JsReturn toReturn = new JsReturn();
825 Node from = returnNode.getFirstChild();
826 if (from != null) {
827 JsExpression to = mapExpression(from);
828 toReturn.setExpression(to);
829 }
830
831 return toReturn;
832 }
833
834 private JsExpression mapSetElem(Node setElemNode) throws JsParserException {
835 // Reuse the get elem code.
836 //
837 JsArrayAccess lhs = mapGetElem(setElemNode);
838
839 // Map the RHS.
840 //
841 Node fromRhs = setElemNode.getFirstChild().getNext().getNext();
842 JsExpression toRhs = mapExpression(fromRhs);
843
844 return new JsBinaryOperation(
845 JsBinaryOperator.ASG, lhs, toRhs);
846 }
847
848 private JsExpression mapSetProp(Node getPropNode) throws JsParserException {
849 // Reuse the get prop code.
850 //
851 JsNameRef lhs = mapGetProp(getPropNode);
852
853 // Map the RHS.
854 //
855 Node fromRhs = getPropNode.getFirstChild().getNext().getNext();
856 JsExpression toRhs = mapExpression(fromRhs);
857
858 return new JsBinaryOperation(
859 JsBinaryOperator.ASG, lhs, toRhs);
860 }
861
862 private JsExpression mapShiftVariant(Node shiftNode) throws JsParserException {
863 switch (shiftNode.getIntDatum()) {
864 case TokenStream.LSH:
865 return mapBinaryOperation(JsBinaryOperator.SHL, shiftNode);
866
867 case TokenStream.RSH:
868 return mapBinaryOperation(JsBinaryOperator.SHR, shiftNode);
869
870 case TokenStream.URSH:
871 return mapBinaryOperation(JsBinaryOperator.SHRU, shiftNode);
872
873 default:
874 throw createParserException("Unknown equality operator variant: "
875 + shiftNode.getIntDatum(), shiftNode);
876 }
877 }
878
879 private JsStatement mapStatement(Node nodeStmt) throws JsParserException {
880 JsNode unknown = map(nodeStmt);
881 if (unknown != null) {
882 if (unknown instanceof JsStatement) {
883 return (JsStatement) unknown;
884 }
885 else if (unknown instanceof JsExpression) {
886 return ((JsExpression) unknown).makeStmt();
887 }
888 else {
889 throw createParserException("Expecting a statement", nodeStmt);
890 }
891 }
892 else {
893 // When map() returns null, we return an empty statement.
894 //
895 return JsEmpty.INSTANCE$;
896 }
897 }
898
899 private void mapStatements(List<JsStatement> stmts, Node nodeStmts)
900 throws JsParserException {
901 Node curr = nodeStmts.getFirstChild();
902 while (curr != null) {
903 JsStatement stmt = mapStatement(curr);
904 if (stmt != null) {
905 stmts.add(stmt);
906 }
907 else {
908 // When mapStatement() returns null, we just ignore it.
909 //
910 }
911 curr = curr.getNext();
912 }
913 }
914
915 public List<JsStatement> mapStatements(Node nodeStmts)
916 throws JsParserException {
917 List<JsStatement> stmts = new ArrayList<JsStatement>();
918 mapStatements(stmts, nodeStmts);
919 return stmts;
920 }
921
922 private JsSwitch mapSwitchStatement(Node switchNode) throws JsParserException {
923 JsSwitch toSwitch = new JsSwitch();
924
925 // The switch expression.
926 //
927 Node fromSwitchExpr = switchNode.getFirstChild();
928 toSwitch.setExpression(mapExpression(fromSwitchExpr));
929
930 // The members.
931 //
932 Node fromMember = fromSwitchExpr.getNext();
933 while (fromMember != null) {
934 if (fromMember.getType() == TokenStream.CASE) {
935 JsCase toCase = new JsCase();
936
937 // Set the case expression. In JS, this can be any expression.
938 //
939 Node fromCaseExpr = fromMember.getFirstChild();
940 toCase.setCaseExpression(mapExpression(fromCaseExpr));
941
942 // Set the case statements.
943 //
944 Node fromCaseBlock = fromCaseExpr.getNext();
945 mapStatements(toCase.getStatements(), fromCaseBlock);
946
947 // Attach the case to the switch.
948 //
949 toSwitch.getCases().add(toCase);
950 }
951 else {
952 // This should be the only default statement.
953 // If more than one is present, we keep the last one.
954 //
955 assert (fromMember.getType() == TokenStream.DEFAULT);
956 JsDefault toDefault = new JsDefault();
957
958 // Set the default statements.
959 //
960 Node fromDefaultBlock = fromMember.getFirstChild();
961 mapStatements(toDefault.getStatements(), fromDefaultBlock);
962
963 // Attach the default to the switch.
964 //
965 toSwitch.getCases().add(toDefault);
966 }
967 fromMember = fromMember.getNext();
968 }
969
970 return toSwitch;
971 }
972
973 private JsThrow mapThrowStatement(Node throwNode) throws JsParserException {
974 // Create, map, and attach.
975 //
976 Node fromExpr = throwNode.getFirstChild();
977 JsThrow toThrow = new JsThrow(mapExpression(fromExpr));
978
979 return toThrow;
980 }
981
982 private JsTry mapTryStatement(Node tryNode) throws JsParserException {
983 JsTry toTry = new JsTry();
984
985 // Map the "try" body.
986 //
987 Node fromTryBody = tryNode.getFirstChild();
988 toTry.setTryBlock(mapBlock(fromTryBody));
989
990 // Map zero or more catch blocks.
991 //
992 Node fromCatchNodes = fromTryBody.getNext();
993 Node fromCatchNode = fromCatchNodes.getFirstChild();
994 while (fromCatchNode != null) {
995 assert (fromCatchNode.getType() == TokenStream.CATCH);
996 // Map the catch variable.
997 //
998 Node fromCatchVarName = fromCatchNode.getFirstChild();
999 JsCatch catchBlock = scopeContext.enterCatch(fromCatchVarName.getString());
1000
1001 // Pre-advance to the next catch block, if any.
1002 // We do this here to decide whether or not this is the last one.
1003 //
1004 fromCatchNode = fromCatchNode.getNext();
1005
1006 // Map the condition, with a little fixup based on whether or not
1007 // this is the last catch block.
1008 //
1009 Node fromCondition = fromCatchVarName.getNext();
1010 JsExpression toCondition = mapExpression(fromCondition);
1011 catchBlock.setCondition(toCondition);
1012 if (fromCatchNode == null) {
1013 if (toCondition instanceof JsBooleanLiteral) {
1014 if (((JsBooleanLiteral) toCondition).getValue()) {
1015 // Actually, this is an unconditional catch block.
1016 // Indicate that by nulling the condition.
1017 //
1018 catchBlock.setCondition(null);
1019 }
1020 }
1021 }
1022
1023 // Map the catch body.
1024 //
1025 Node fromCatchBody = fromCondition.getNext();
1026 catchBlock.setBody(mapBlock(fromCatchBody));
1027
1028 // Attach it.
1029 //
1030 toTry.getCatches().add(catchBlock);
1031 scopeContext.exitCatch();
1032 }
1033
1034 Node fromFinallyNode = fromCatchNodes.getNext();
1035 if (fromFinallyNode != null) {
1036 toTry.setFinallyBlock(mapBlock(fromFinallyNode));
1037 }
1038
1039 return toTry;
1040 }
1041
1042 private JsExpression mapUnaryVariant(Node unOp) throws JsParserException {
1043 switch (unOp.getIntDatum()) {
1044 case TokenStream.SUB:
1045 return mapPrefixOperation(JsUnaryOperator.NEG, unOp);
1046
1047 case TokenStream.NOT:
1048 return mapPrefixOperation(JsUnaryOperator.NOT, unOp);
1049
1050 case TokenStream.BITNOT:
1051 return mapPrefixOperation(JsUnaryOperator.BIT_NOT, unOp);
1052
1053 case TokenStream.TYPEOF:
1054 return mapPrefixOperation(JsUnaryOperator.TYPEOF, unOp);
1055
1056 case TokenStream.ADD:
1057 if (!isJsNumber(unOp.getFirstChild())) {
1058 return mapPrefixOperation(JsUnaryOperator.POS, unOp);
1059 }
1060 else {
1061 // Pretend we didn't see it.
1062 return mapExpression(unOp.getFirstChild());
1063 }
1064
1065 case TokenStream.VOID:
1066 return mapPrefixOperation(JsUnaryOperator.VOID, unOp);
1067
1068 default:
1069 throw createParserException(
1070 "Unknown unary operator variant: " + unOp.getIntDatum(), unOp);
1071 }
1072 }
1073
1074 private JsVars mapVar(Node varNode) throws JsParserException {
1075 JsVars toVars = new JsVars();
1076 Node fromVar = varNode.getFirstChild();
1077 while (fromVar != null) {
1078 // Use a conservative name allocation strategy that allocates all names
1079 // from the function's scope, even the names of properties in field
1080 // literals.
1081 //
1082 String fromName = fromVar.getString();
1083 JsName toName = scopeContext.localNameFor(fromName);
1084 JsVars.JsVar toVar = new JsVars.JsVar(toName);
1085
1086 Node fromInit = fromVar.getFirstChild();
1087 if (fromInit != null) {
1088 JsExpression toInit = mapExpression(fromInit);
1089 toVar.setInitExpression(toInit);
1090 }
1091 toVars.add(toVar);
1092
1093 fromVar = fromVar.getNext();
1094 }
1095
1096 return toVars;
1097 }
1098
1099 private JsNode mapWithStatement(Node withNode) throws JsParserException {
1100 // The "with" statement is unsupported because it introduces ambiguity
1101 // related to whether or not a name is obfuscatable that we cannot resolve
1102 // statically. This is modified in our copy of the Rhino Parser to provide
1103 // detailed source & line info. So, this method should never actually be
1104 // called.
1105 //
1106 throw createParserException("Internal error: unexpected token 'with'",
1107 withNode);
1108 }
1109
1110 private boolean isJsNumber(Node jsNode) {
1111 int type = jsNode.getType();
1112 return type == TokenStream.NUMBER || type == TokenStream.NUMBER;
1113 }
1114 }