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    }