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