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    }