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 org.jetbrains.kotlin.js.backend.ast.*;
019 import org.jetbrains.kotlin.js.backend.ast.JsLiteral.JsBooleanLiteral;
020 import org.jetbrains.kotlin.js.backend.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 toFn.setName(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(mapExpression(fromTestExpr), mapStatement(fromThenBlock));
634
635 // Map the "else" block.
636 //
637 if (fromElseBlock != null) {
638 toIf.setElseStatement(mapStatement(fromElseBlock));
639 }
640
641 return toIf;
642 }
643
644 private JsExpression mapIncDecFixity(JsUnaryOperator op, Node node)
645 throws JsParserException {
646 switch (node.getIntDatum()) {
647 case TokenStream.PRE:
648 return mapPrefixOperation(op, node);
649 case TokenStream.POST:
650 return mapPostfixOperation(op, node);
651 default:
652 throw createParserException(
653 "Unknown prefix/postfix variant: " + node.getIntDatum(), node);
654 }
655 }
656
657 private JsLabel mapLabel(Node labelNode) throws JsParserException {
658 String fromName = labelNode.getFirstChild().getString();
659
660 JsName toName = scopeContext.enterLabel(fromName);
661
662 Node fromStmt = labelNode.getFirstChild().getNext();
663 JsLabel toLabel = new JsLabel(toName);
664 toLabel.setStatement(mapStatement(fromStmt));
665
666 scopeContext.exitLabel();
667
668 return toLabel;
669 }
670
671 private JsNew mapNew(Node newNode) throws JsParserException {
672 // Map the constructor expression, which is often just the name of
673 // some lambda.
674 //
675 Node fromCtorExpr = newNode.getFirstChild();
676 JsNew newExpr = new JsNew(
677 mapExpression(fromCtorExpr));
678
679 // Iterate over and map the arguments.
680 //
681 List<JsExpression> args = newExpr.getArguments();
682 Node fromArg = fromCtorExpr.getNext();
683 while (fromArg != null) {
684 args.add(mapExpression(fromArg));
685 fromArg = fromArg.getNext();
686 }
687
688 return newExpr;
689 }
690
691 private JsExpression mapIntNumber(Node numberNode) {
692 return program.getNumberLiteral((int) numberNode.getDouble());
693 }
694
695 private JsExpression mapDoubleNumber(Node numberNode) {
696 return program.getNumberLiteral(numberNode.getDouble());
697 }
698
699 private JsExpression mapObjectLit(Node objLitNode) throws JsParserException {
700 JsObjectLiteral toLit = new JsObjectLiteral();
701 Node fromPropInit = objLitNode.getFirstChild();
702 while (fromPropInit != null) {
703
704 Node fromLabelExpr = fromPropInit;
705 JsExpression toLabelExpr = mapExpression(fromLabelExpr);
706
707 // Advance to the initializer expression.
708 //
709 fromPropInit = fromPropInit.getNext();
710 Node fromValueExpr = fromPropInit;
711 if (fromValueExpr == null) {
712 throw createParserException("Expected an init expression for: "
713 + toLabelExpr, objLitNode);
714 }
715 JsExpression toValueExpr = mapExpression(fromValueExpr);
716
717 JsPropertyInitializer toPropInit = new JsPropertyInitializer(
718 toLabelExpr, toValueExpr);
719 toLit.getPropertyInitializers().add(toPropInit);
720
721 // Begin the next property initializer, if there is one.
722 //
723 fromPropInit = fromPropInit.getNext();
724 }
725
726 return toLit;
727 }
728
729 private JsExpression mapOptionalExpression(Node exprNode)
730 throws JsParserException {
731 JsNode unknown = map(exprNode);
732 if (unknown != null) {
733 if (unknown instanceof JsExpression) {
734 return (JsExpression) unknown;
735 }
736 else {
737 throw createParserException("Expecting an expression or null", exprNode);
738 }
739 }
740 return null;
741 }
742
743 private JsExpression mapPostfixOperation(JsUnaryOperator op, Node node)
744 throws JsParserException {
745 Node from = node.getFirstChild();
746 JsExpression to = mapExpression(from);
747 return new JsPostfixOperation(op, to);
748 }
749
750 private JsExpression mapPrefixOperation(JsUnaryOperator op, Node node)
751 throws JsParserException {
752 Node from = node.getFirstChild();
753 JsExpression to = mapExpression(from);
754 return new JsPrefixOperation(op, to);
755 }
756
757 private JsExpression mapPrimary(Node node) throws JsParserException {
758 switch (node.getIntDatum()) {
759 case TokenStream.THIS:
760 return JsLiteral.THIS;
761
762 case TokenStream.TRUE:
763 return JsBooleanLiteral.TRUE;
764
765 case TokenStream.FALSE:
766 return JsBooleanLiteral.FALSE;
767
768 case TokenStream.NULL:
769 return JsNullLiteral.NULL;
770
771 case TokenStream.UNDEFINED:
772 return JsLiteral.UNDEFINED;
773
774 default:
775 throw createParserException("Unknown primary: " + node.getIntDatum(),
776 node);
777 }
778 }
779
780 private JsNode mapRegExp(Node regExpNode) {
781 JsRegExp toRegExp = new JsRegExp();
782
783 Node fromPattern = regExpNode.getFirstChild();
784 toRegExp.setPattern(fromPattern.getString());
785
786 Node fromFlags = fromPattern.getNext();
787 if (fromFlags != null) {
788 toRegExp.setFlags(fromFlags.getString());
789 }
790
791 return toRegExp;
792 }
793
794 private JsExpression mapRelationalVariant(Node relNode)
795 throws JsParserException {
796 switch (relNode.getIntDatum()) {
797 case TokenStream.LT:
798 return mapBinaryOperation(JsBinaryOperator.LT, relNode);
799
800 case TokenStream.LE:
801 return mapBinaryOperation(JsBinaryOperator.LTE, relNode);
802
803 case TokenStream.GT:
804 return mapBinaryOperation(JsBinaryOperator.GT, relNode);
805
806 case TokenStream.GE:
807 return mapBinaryOperation(JsBinaryOperator.GTE, relNode);
808
809 case TokenStream.INSTANCEOF:
810 return mapBinaryOperation(JsBinaryOperator.INSTANCEOF, relNode);
811
812 case TokenStream.IN:
813 return mapBinaryOperation(JsBinaryOperator.INOP, relNode);
814
815 default:
816 throw createParserException("Unknown relational operator variant: "
817 + relNode.getIntDatum(), relNode);
818 }
819 }
820
821 private JsReturn mapReturn(Node returnNode) throws JsParserException {
822 JsReturn toReturn = new JsReturn();
823 Node from = returnNode.getFirstChild();
824 if (from != null) {
825 JsExpression to = mapExpression(from);
826 toReturn.setExpression(to);
827 }
828
829 return toReturn;
830 }
831
832 private JsExpression mapSetElem(Node setElemNode) throws JsParserException {
833 // Reuse the get elem code.
834 //
835 JsArrayAccess lhs = mapGetElem(setElemNode);
836
837 // Map the RHS.
838 //
839 Node fromRhs = setElemNode.getFirstChild().getNext().getNext();
840 JsExpression toRhs = mapExpression(fromRhs);
841
842 return new JsBinaryOperation(
843 JsBinaryOperator.ASG, lhs, toRhs);
844 }
845
846 private JsExpression mapSetProp(Node getPropNode) throws JsParserException {
847 // Reuse the get prop code.
848 //
849 JsNameRef lhs = mapGetProp(getPropNode);
850
851 // Map the RHS.
852 //
853 Node fromRhs = getPropNode.getFirstChild().getNext().getNext();
854 JsExpression toRhs = mapExpression(fromRhs);
855
856 return new JsBinaryOperation(
857 JsBinaryOperator.ASG, lhs, toRhs);
858 }
859
860 private JsExpression mapShiftVariant(Node shiftNode) throws JsParserException {
861 switch (shiftNode.getIntDatum()) {
862 case TokenStream.LSH:
863 return mapBinaryOperation(JsBinaryOperator.SHL, shiftNode);
864
865 case TokenStream.RSH:
866 return mapBinaryOperation(JsBinaryOperator.SHR, shiftNode);
867
868 case TokenStream.URSH:
869 return mapBinaryOperation(JsBinaryOperator.SHRU, shiftNode);
870
871 default:
872 throw createParserException("Unknown equality operator variant: "
873 + shiftNode.getIntDatum(), shiftNode);
874 }
875 }
876
877 private JsStatement mapStatement(Node nodeStmt) throws JsParserException {
878 JsNode unknown = map(nodeStmt);
879
880 if (unknown instanceof HasMetadata) {
881 HasMetadata metadataContainer = (HasMetadata) unknown;
882 metadataContainer.setData("line", nodeStmt.getLineno());
883 }
884
885 if (unknown != null) {
886 if (unknown instanceof JsStatement) {
887 return (JsStatement) unknown;
888 }
889 else if (unknown instanceof JsExpression) {
890 return ((JsExpression) unknown).makeStmt();
891 }
892 else {
893 throw createParserException("Expecting a statement", nodeStmt);
894 }
895 }
896 else {
897 // When map() returns null, we return an empty statement.
898 //
899 return JsEmpty.INSTANCE;
900 }
901 }
902
903 private void mapStatements(List<JsStatement> stmts, Node nodeStmts)
904 throws JsParserException {
905 Node curr = nodeStmts.getFirstChild();
906 while (curr != null) {
907 JsStatement stmt = mapStatement(curr);
908 if (stmt != null) {
909 stmts.add(stmt);
910 }
911 else {
912 // When mapStatement() returns null, we just ignore it.
913 //
914 }
915 curr = curr.getNext();
916 }
917 }
918
919 public List<JsStatement> mapStatements(Node nodeStmts)
920 throws JsParserException {
921 List<JsStatement> stmts = new ArrayList<JsStatement>();
922 mapStatements(stmts, nodeStmts);
923 return stmts;
924 }
925
926 private JsSwitch mapSwitchStatement(Node switchNode) throws JsParserException {
927 JsSwitch toSwitch = new JsSwitch();
928
929 // The switch expression.
930 //
931 Node fromSwitchExpr = switchNode.getFirstChild();
932 toSwitch.setExpression(mapExpression(fromSwitchExpr));
933
934 // The members.
935 //
936 Node fromMember = fromSwitchExpr.getNext();
937 while (fromMember != null) {
938 if (fromMember.getType() == TokenStream.CASE) {
939 JsCase toCase = new JsCase();
940
941 // Set the case expression. In JS, this can be any expression.
942 //
943 Node fromCaseExpr = fromMember.getFirstChild();
944 toCase.setCaseExpression(mapExpression(fromCaseExpr));
945
946 // Set the case statements.
947 //
948 Node fromCaseBlock = fromCaseExpr.getNext();
949 mapStatements(toCase.getStatements(), fromCaseBlock);
950
951 // Attach the case to the switch.
952 //
953 toSwitch.getCases().add(toCase);
954 }
955 else {
956 // This should be the only default statement.
957 // If more than one is present, we keep the last one.
958 //
959 assert (fromMember.getType() == TokenStream.DEFAULT);
960 JsDefault toDefault = new JsDefault();
961
962 // Set the default statements.
963 //
964 Node fromDefaultBlock = fromMember.getFirstChild();
965 mapStatements(toDefault.getStatements(), fromDefaultBlock);
966
967 // Attach the default to the switch.
968 //
969 toSwitch.getCases().add(toDefault);
970 }
971 fromMember = fromMember.getNext();
972 }
973
974 return toSwitch;
975 }
976
977 private JsThrow mapThrowStatement(Node throwNode) throws JsParserException {
978 // Create, map, and attach.
979 //
980 Node fromExpr = throwNode.getFirstChild();
981 JsThrow toThrow = new JsThrow(mapExpression(fromExpr));
982
983 return toThrow;
984 }
985
986 private JsTry mapTryStatement(Node tryNode) throws JsParserException {
987 JsTry toTry = new JsTry();
988
989 // Map the "try" body.
990 //
991 Node fromTryBody = tryNode.getFirstChild();
992 toTry.setTryBlock(mapBlock(fromTryBody));
993
994 // Map zero or more catch blocks.
995 //
996 Node fromCatchNodes = fromTryBody.getNext();
997 Node fromCatchNode = fromCatchNodes.getFirstChild();
998 while (fromCatchNode != null) {
999 assert (fromCatchNode.getType() == TokenStream.CATCH);
1000 // Map the catch variable.
1001 //
1002 Node fromCatchVarName = fromCatchNode.getFirstChild();
1003 JsCatch catchBlock = scopeContext.enterCatch(fromCatchVarName.getString());
1004
1005 // Pre-advance to the next catch block, if any.
1006 // We do this here to decide whether or not this is the last one.
1007 //
1008 fromCatchNode = fromCatchNode.getNext();
1009
1010 // Map the condition, with a little fixup based on whether or not
1011 // this is the last catch block.
1012 //
1013 Node fromCondition = fromCatchVarName.getNext();
1014 JsExpression toCondition = mapExpression(fromCondition);
1015 catchBlock.setCondition(toCondition);
1016 if (fromCatchNode == null) {
1017 if (toCondition instanceof JsBooleanLiteral) {
1018 if (((JsBooleanLiteral) toCondition).getValue()) {
1019 // Actually, this is an unconditional catch block.
1020 // Indicate that by nulling the condition.
1021 //
1022 catchBlock.setCondition(null);
1023 }
1024 }
1025 }
1026
1027 // Map the catch body.
1028 //
1029 Node fromCatchBody = fromCondition.getNext();
1030 catchBlock.setBody(mapBlock(fromCatchBody));
1031
1032 // Attach it.
1033 //
1034 toTry.getCatches().add(catchBlock);
1035 scopeContext.exitCatch();
1036 }
1037
1038 Node fromFinallyNode = fromCatchNodes.getNext();
1039 if (fromFinallyNode != null) {
1040 toTry.setFinallyBlock(mapBlock(fromFinallyNode));
1041 }
1042
1043 return toTry;
1044 }
1045
1046 private JsExpression mapUnaryVariant(Node unOp) throws JsParserException {
1047 switch (unOp.getIntDatum()) {
1048 case TokenStream.SUB:
1049 return mapPrefixOperation(JsUnaryOperator.NEG, unOp);
1050
1051 case TokenStream.NOT:
1052 return mapPrefixOperation(JsUnaryOperator.NOT, unOp);
1053
1054 case TokenStream.BITNOT:
1055 return mapPrefixOperation(JsUnaryOperator.BIT_NOT, unOp);
1056
1057 case TokenStream.TYPEOF:
1058 return mapPrefixOperation(JsUnaryOperator.TYPEOF, unOp);
1059
1060 case TokenStream.ADD:
1061 if (!isJsNumber(unOp.getFirstChild())) {
1062 return mapPrefixOperation(JsUnaryOperator.POS, unOp);
1063 }
1064 else {
1065 // Pretend we didn't see it.
1066 return mapExpression(unOp.getFirstChild());
1067 }
1068
1069 case TokenStream.VOID:
1070 return mapPrefixOperation(JsUnaryOperator.VOID, unOp);
1071
1072 default:
1073 throw createParserException(
1074 "Unknown unary operator variant: " + unOp.getIntDatum(), unOp);
1075 }
1076 }
1077
1078 private JsVars mapVar(Node varNode) throws JsParserException {
1079 JsVars toVars = new JsVars();
1080 Node fromVar = varNode.getFirstChild();
1081 while (fromVar != null) {
1082 // Use a conservative name allocation strategy that allocates all names
1083 // from the function's scope, even the names of properties in field
1084 // literals.
1085 //
1086 String fromName = fromVar.getString();
1087 JsName toName = scopeContext.localNameFor(fromName);
1088 JsVars.JsVar toVar = new JsVars.JsVar(toName);
1089
1090 Node fromInit = fromVar.getFirstChild();
1091 if (fromInit != null) {
1092 JsExpression toInit = mapExpression(fromInit);
1093 toVar.setInitExpression(toInit);
1094 }
1095 toVars.add(toVar);
1096
1097 fromVar = fromVar.getNext();
1098 }
1099
1100 return toVars;
1101 }
1102
1103 private JsNode mapWithStatement(Node withNode) throws JsParserException {
1104 // The "with" statement is unsupported because it introduces ambiguity
1105 // related to whether or not a name is obfuscatable that we cannot resolve
1106 // statically. This is modified in our copy of the Rhino Parser to provide
1107 // detailed source & line info. So, this method should never actually be
1108 // called.
1109 //
1110 throw createParserException("Internal error: unexpected token 'with'",
1111 withNode);
1112 }
1113
1114 private boolean isJsNumber(Node jsNode) {
1115 int type = jsNode.getType();
1116 return type == TokenStream.NUMBER || type == TokenStream.NUMBER;
1117 }
1118 }