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