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