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