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