示例#1
0
        /// <summary>
        /// Creates an instance of the JSParser class that can be used to parse the given source code.
        /// </summary>
        /// <param name="source">Source code to parse.</param>
        public JsParser(string source)
        {
            m_severity = 5;
            m_blockType = new List<BlockType>(16);
            m_labelTable = new Dictionary<string, LabelInfo>();
            m_noSkipTokenSet = new NoSkipTokenSet();
            m_importantComments = new List<JsContext>();

            m_document = new JsDocumentContext(this, source);
            m_scanner = new JsScanner(new JsContext(m_document));
            m_currentToken = new JsContext(m_document);

            // if the scanner encounters a special "globals" comment, it'll fire this event
            // at which point we will define a field with that name in the global scope.
            m_scanner.GlobalDefine += (sender, ea) =>
                {
                    var globalScope = GlobalScope;
                    if (globalScope[ea.Name] == null)
                    {
                        var field = globalScope.CreateField(ea.Name, null, FieldAttributes.SpecialName);
                        globalScope.AddField(field);
                    }
                };

            // this event is fired whenever a ///#SOURCE comment is encountered
            m_scanner.NewModule += (sender, ea) =>
                {
                    m_newModule = true;

                    // we also want to assume that we found a newline character after
                    // the comment
                    m_foundEndOfLine = true;
                };
        }
 public JsDirectivePrologue(string value, JsContext context, JsParser parser)
     : base(value, JsPrimitiveType.String, context, parser)
 {
     // this is a "use strict" directive if the source context is EXACTLY "use strict"
     // don't consider the quotes so it can be " or ' delimiters
     UseStrict = string.CompareOrdinal(Context.Code, 1, "use strict", 0, 10) == 0;
 }
 internal JsException(JsError errorNumber, JsContext context)
 {
     Value = null;
     m_context = (context == null ? null : context.Clone());
     FileContext = (context == null ? null : context.Document.FileContext);
     ErrorCode = errorNumber;
     CanRecover = true;
 }
        public JsConstantWrapper(Object value, JsPrimitiveType primitiveType, JsContext context, JsParser parser)
            : base(context, parser)
        {
            PrimitiveType = primitiveType;

            // force numerics to be of type double
            Value = (primitiveType == JsPrimitiveType.Number ? System.Convert.ToDouble(value, CultureInfo.InvariantCulture) : value);
        }
        private JsContext m_context; // = null;

        #endregion Fields

        #region Constructors

        public JsBlockScope(JsActivationObject parent, JsContext context, JsSettings settings)
            : base(parent, settings)
        {
            if (context == null)
            {
                throw new ArgumentNullException("context");
            }

            m_context = context.Clone();
        }
        public void OutputNumber(double numericValue, JsContext originalContext)
        {
            // numerics are doubles in JavaScript, so force it now as a shortcut
            if (double.IsNaN(numericValue) || double.IsInfinity(numericValue))
            {
                // weird number -- just return the original source code as-is.
                if (originalContext != null && !string.IsNullOrEmpty(originalContext.Code)
                    && !originalContext.Document.IsGenerated)
                {
                    m_writer.Write(originalContext.Code);
                    return;
                }

                // Hmmm... don't have an original source.
                // Must be generated. Just generate the proper JS literal.
                //
                // DANGER! If we just output NaN and Infinity and -Infinity blindly, that assumes
                // that there aren't any local variables in this scope chain with that
                // name, and we're pulling the GLOBAL properties. Might want to use properties
                // on the Number object -- which, of course, assumes that Number doesn't
                // resolve to a local variable...
                string objectName = double.IsNaN(numericValue) ? "NaN" : "Infinity";

                // we're good to go -- just return the name because it will resolve to the
                // global properties (make a special case for negative infinity)
                m_writer.Write(double.IsNegativeInfinity(numericValue) ? "-Infinity" : objectName);
            }
            else if (numericValue == 0)
            {
                // special case zero because we don't need to go through all those
                // gyrations to get a "0" -- and because negative zero is different
                // than a positive zero
                m_writer.Write(1 / numericValue < 0 ? "-0" : "0");
            }
            else
            {
                // normal string representations
                m_writer.Write(GetSmallestRep(numericValue.ToString("R", CultureInfo.InvariantCulture)));
            }
        }
 internal JsCatchScope(JsActivationObject parent, JsContext catchContext, JsSettings settings, JsParameterDeclaration catchParameter)
     : base(parent, catchContext, settings)
 {
     CatchParameter = catchParameter;
 }
 public JsObjectLiteralField(Object value, JsPrimitiveType primitiveType, JsContext context, JsParser parser)
     : base(value, primitiveType, context, parser)
 {
 }
 public JsLabeledStatement(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
 public JsCustomNode(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
 public JsSwitchCase(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
 public JsConstStatement(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
 protected JsExpression(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
 public JsVariableDeclaration(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
示例#15
0
        private JsFunctionObject ParseFunction(JsFunctionType functionType, JsContext fncCtx)
        {
            JsLookup name = null;
            JsAstNodeList formalParameters = null;
            JsBlock body = null;
            bool inExpression = (functionType == JsFunctionType.Expression);
            JsContext paramsContext = null;

            GetNextToken();

            // get the function name or make an anonymous function if in expression "position"
            if (JsToken.Identifier == m_currentToken.Token)
            {
                name = new JsLookup(m_currentToken.Clone(), this)
                    {
                        Name = m_scanner.Identifier
                    };
                GetNextToken();
            }
            else
            {
                string identifier = JsKeyword.CanBeIdentifier(m_currentToken.Token);
                if (null != identifier)
                {
                    name = new JsLookup(m_currentToken.Clone(), this)
                        {
                            Name = identifier
                        };
                    GetNextToken();
                }
                else
                {
                    if (!inExpression)
                    {
                        // if this isn't a function expression, then we need to throw an error because
                        // function DECLARATIONS always need a valid identifier name
                        ReportError(JsError.NoIdentifier, m_currentToken.Clone(), true);

                        // BUT if the current token is a left paren, we don't want to use it as the name.
                        // (fix for issue #14152)
                        if (m_currentToken.Token != JsToken.LeftParenthesis
                            && m_currentToken.Token != JsToken.LeftCurly)
                        {
                            identifier = m_currentToken.Code;
                            name = new JsLookup(CurrentPositionContext(), this)
                                {
                                    Name = identifier
                                };
                            GetNextToken();
                        }
                    }
                }
            }

            // make a new state and save the old one
            List<BlockType> blockType = m_blockType;
            m_blockType = new List<BlockType>(16);
            Dictionary<string, LabelInfo> labelTable = m_labelTable;
            m_labelTable = new Dictionary<string, LabelInfo>();

            try
            {
                // get the formal parameters
                if (JsToken.LeftParenthesis != m_currentToken.Token)
                {
                    // we expect a left paren at this point for standard cross-browser support.
                    // BUT -- some versions of IE allow an object property expression to be a function name, like window.onclick.
                    // we still want to throw the error, because it syntax errors on most browsers, but we still want to
                    // be able to parse it and return the intended results.
                    // Skip to the open paren and use whatever is in-between as the function name. Doesn't matter that it's
                    // an invalid identifier; it won't be accessible as a valid field anyway.
                    bool expandedIndentifier = false;
                    while (m_currentToken.Token != JsToken.LeftParenthesis
                        && m_currentToken.Token != JsToken.LeftCurly
                        && m_currentToken.Token != JsToken.Semicolon
                        && m_currentToken.Token != JsToken.EndOfFile)
                    {
                        name.Context.UpdateWith(m_currentToken);
                        GetNextToken();
                        expandedIndentifier = true;
                    }

                    // if we actually expanded the identifier context, then we want to report that
                    // the function name needs to be an identifier. Otherwise we didn't expand the
                    // name, so just report that we expected an open paren at this point.
                    if (expandedIndentifier)
                    {
                        name.Name = name.Context.Code;
                        name.Context.HandleError(JsError.FunctionNameMustBeIdentifier, false);
                    }
                    else
                    {
                        ReportError(JsError.NoLeftParenthesis, true);
                    }
                }

                if (m_currentToken.Token == JsToken.LeftParenthesis)
                {
                    // create the parameter list
                    formalParameters = new JsAstNodeList(m_currentToken.Clone(), this);
                    paramsContext = m_currentToken.Clone();

                    // skip the open paren
                    GetNextToken();

                    // create the list of arguments and update the context
                    while (JsToken.RightParenthesis != m_currentToken.Token)
                    {
                        String id = null;
                        m_noSkipTokenSet.Add(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet);
                        try
                        {
                            JsParameterDeclaration paramDecl = null;
                            if (JsToken.Identifier != m_currentToken.Token && (id = JsKeyword.CanBeIdentifier(m_currentToken.Token)) == null)
                            {
                                if (JsToken.LeftCurly == m_currentToken.Token)
                                {
                                    ReportError(JsError.NoRightParenthesis);
                                    break;
                                }
                                else if (JsToken.Comma == m_currentToken.Token)
                                {
                                    // We're missing an argument (or previous argument was malformed and
                                    // we skipped to the comma.)  Keep trying to parse the argument list --
                                    // we will skip the comma below.
                                    ReportError(JsError.SyntaxError, true);
                                }
                                else
                                {
                                    ReportError(JsError.SyntaxError, true);
                                    SkipTokensAndThrow();
                                }
                            }
                            else
                            {
                                if (null == id)
                                {
                                    id = m_scanner.Identifier;
                                }

                                paramDecl = new JsParameterDeclaration(m_currentToken.Clone(), this)
                                    {
                                        Name = id,
                                        Position = formalParameters.Count
                                    };
                                formalParameters.Append(paramDecl);
                                GetNextToken();
                            }

                            // got an arg, it should be either a ',' or ')'
                            if (JsToken.RightParenthesis == m_currentToken.Token)
                            {
                                break;
                            }
                            else if (JsToken.Comma == m_currentToken.Token)
                            {
                                // append the comma context as the terminator for the parameter
                                paramDecl.IfNotNull(p => p.TerminatingContext = m_currentToken.Clone());
                            }
                            else
                            {
                                // deal with error in some "intelligent" way
                                if (JsToken.LeftCurly == m_currentToken.Token)
                                {
                                    ReportError(JsError.NoRightParenthesis);
                                    break;
                                }
                                else
                                {
                                    if (JsToken.Identifier == m_currentToken.Token)
                                    {
                                        // it's possible that the guy was writing the type in C/C++ style (i.e. int x)
                                        ReportError(JsError.NoCommaOrTypeDefinitionError);
                                    }
                                    else
                                        ReportError(JsError.NoComma);
                                }
                            }

                            GetNextToken();
                        }
                        catch (RecoveryTokenException exc)
                        {
                            if (IndexOfToken(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet, exc) == -1)
                                throw;
                        }
                        finally
                        {
                            m_noSkipTokenSet.Remove(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet);
                        }
                    }

                    fncCtx.UpdateWith(m_currentToken);
                    GetNextToken();
                }

                // read the function body of non-abstract functions.
                if (JsToken.LeftCurly != m_currentToken.Token)
                    ReportError(JsError.NoLeftCurly, true);

                m_blockType.Add(BlockType.Block);
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_BlockNoSkipTokenSet);
                m_noSkipTokenSet.Add(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                try
                {
                    // parse the block locally to get the exact end of function
                    body = new JsBlock(m_currentToken.Clone(), this);
                    body.BraceOnNewLine = m_foundEndOfLine;
                    GetNextToken();

                    var possibleDirectivePrologue = true;
                    while (JsToken.RightCurly != m_currentToken.Token)
                    {
                        try
                        {
                            // function body's are SourceElements (Statements + FunctionDeclarations)
                            var statement = ParseStatement(true);
                            if (possibleDirectivePrologue)
                            {
                                var constantWrapper = statement as JsConstantWrapper;
                                if (constantWrapper != null && constantWrapper.PrimitiveType == JsPrimitiveType.String)
                                {
                                    // if it's already a directive prologues, we're good to go
                                    if (!(constantWrapper is JsDirectivePrologue))
                                    {
                                        // make the statement a directive prologue instead of a constant wrapper
                                        statement = new JsDirectivePrologue(constantWrapper.Value.ToString(), constantWrapper.Context, constantWrapper.Parser)
                                            {
                                                MayHaveIssues = constantWrapper.MayHaveIssues
                                            };
                                    }
                                }
                                else if (!m_newModule)
                                {
                                    // no longer considering constant wrappers
                                    possibleDirectivePrologue = false;
                                }
                            }
                            else if (m_newModule)
                            {
                                // we scanned into a new module -- we might find directive prologues again
                                possibleDirectivePrologue = true;
                            }

                            // add it to the body
                            body.Append(statement);
                        }
                        catch (RecoveryTokenException exc)
                        {
                            if (exc._partiallyComputedNode != null)
                            {
                                body.Append(exc._partiallyComputedNode);
                            }
                            if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1)
                                throw;
                        }
                    }

                    // make sure any important comments before the closing brace are kept
                    AppendImportantComments(body);

                    body.Context.UpdateWith(m_currentToken);
                    fncCtx.UpdateWith(m_currentToken);
                }
                catch (EndOfStreamException)
                {
                    // if we get an EOF here, we never had a chance to find the closing curly-brace
                    fncCtx.HandleError(JsError.UnclosedFunction, true);
                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1)
                    {
                        exc._partiallyComputedNode = new JsFunctionObject(fncCtx, this)
                            {
                                FunctionType = (inExpression ? JsFunctionType.Expression : JsFunctionType.Declaration),
                                IdContext = name.IfNotNull(n => n.Context),
                                Name = name.IfNotNull(n => n.Name),
                                ParameterDeclarations = formalParameters,
                                ParametersContext = paramsContext,
                                Body = body
                            };
                        throw;
                    }
                }
                finally
                {
                    m_blockType.RemoveAt(m_blockType.Count - 1);
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet);
                }

                GetNextToken();
            }
            finally
            {
                // restore state
                m_blockType = blockType;
                m_labelTable = labelTable;
            }

            return new JsFunctionObject(fncCtx, this)
                {
                    FunctionType = functionType,
                    IdContext = name.IfNotNull(n => n.Context),
                    Name = name.IfNotNull(n => n.Name),
                    ParameterDeclarations = formalParameters,
                    ParametersContext = paramsContext,
                    Body = body
                };
        }
示例#16
0
 public JsVar(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
示例#17
0
 public JsTryNode(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
示例#18
0
 public JsUnaryOperator(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
示例#19
0
 public JsLookup(JsContext context, JsParser parser)
     : base(context, parser)
 {
     RefType = JsReferenceType.Variable;
 }
 public JsCommaOperator(JsContext context, JsParser parser)
     : base(context, parser)
 {
     this.OperatorToken = JsToken.Comma;
 }
示例#21
0
 public JsVar(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
示例#22
0
        private void CCTooComplicated(JsContext context)
        {
            // we ONLY support /*@id@*/ or /*@cc_on@id@*/ or /*@!@*/ or /*@cc_on!@*/ in expressions right now.
            // throw an error, skip to the end of the comment, then ignore it and start
            // looking for the next token.
            (context ?? m_currentToken).HandleError(JsError.ConditionalCompilationTooComplex);

            // skip to end of conditional comment
            while (m_currentToken.Token != JsToken.EndOfFile && m_currentToken.Token != JsToken.ConditionalCommentEnd)
            {
                GetNextToken();
            }
            GetNextToken();
        }
示例#23
0
 public JsTryNode(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
示例#24
0
        //---------------------------------------------------------------------------------------
        // ReportError
        //
        //  Generate a parser error.
        //  The function is told whether or not next call to GetToken() should return the same
        //  token or not
        //---------------------------------------------------------------------------------------
        private void ReportError(JsError errorId, JsContext context, bool skipToken)
        {
            Debug.Assert(context != null);
            int previousSeverity = m_severity;
            m_severity = JsException.GetSeverity(errorId);
            // EOF error is special and it's the last error we can possibly get
            if (JsToken.EndOfFile == context.Token)
                EOFError(errorId); // EOF context is special
            else
            {
                // report the error if not in error condition and the
                // error for this token is not worse than the one for the
                // previous token
                if (m_goodTokensProcessed > 0 || m_severity < previousSeverity)
                    context.HandleError(errorId);

                // reset proper info
                if (skipToken)
                    m_goodTokensProcessed = -1;
                else
                {
                    m_useCurrentForNext = true;
                    m_goodTokensProcessed = 0;
                }
            }
        }
 protected JsIterationStatement(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
示例#26
0
        //---------------------------------------------------------------------------------------
        // CreateExpressionNode
        //
        //  Create the proper AST object according to operator
        //---------------------------------------------------------------------------------------
        private JsAstNode CreateExpressionNode(JsContext op, JsAstNode operand1, JsAstNode operand2)
        {
            JsContext context = operand1.Context.CombineWith(operand2.Context);
            switch (op.Token)
            {
                case JsToken.Assign:
                case JsToken.BitwiseAnd:
                case JsToken.BitwiseAndAssign:
                case JsToken.BitwiseOr:
                case JsToken.BitwiseOrAssign:
                case JsToken.BitwiseXor:
                case JsToken.BitwiseXorAssign:
                case JsToken.Divide:
                case JsToken.DivideAssign:
                case JsToken.Equal:
                case JsToken.GreaterThan:
                case JsToken.GreaterThanEqual:
                case JsToken.In:
                case JsToken.InstanceOf:
                case JsToken.LeftShift:
                case JsToken.LeftShiftAssign:
                case JsToken.LessThan:
                case JsToken.LessThanEqual:
                case JsToken.LogicalAnd:
                case JsToken.LogicalOr:
                case JsToken.Minus:
                case JsToken.MinusAssign:
                case JsToken.Modulo:
                case JsToken.ModuloAssign:
                case JsToken.Multiply:
                case JsToken.MultiplyAssign:
                case JsToken.NotEqual:
                case JsToken.Plus:
                case JsToken.PlusAssign:
                case JsToken.RightShift:
                case JsToken.RightShiftAssign:
                case JsToken.StrictEqual:
                case JsToken.StrictNotEqual:
                case JsToken.UnsignedRightShift:
                case JsToken.UnsignedRightShiftAssign:
                    // regular binary operator
                    return new JsBinaryOperator(context, this)
                        {
                            Operand1 = operand1,
                            Operand2 = operand2,
                            OperatorContext = op,
                            OperatorToken = op.Token
                        };

                case JsToken.Comma:
                    // use the special comma-operator class derived from binary operator.
                    // it has special logic to combine adjacent comma operators into a single
                    // node with an ast node list rather than nested binary operators
                    return JsCommaOperator.CombineWithComma(context, this, operand1, operand2);

                default:
                    // shouldn't get here!
                    Debug.Assert(false);
                    return null;
            }
        }
示例#27
0
 public JsMember(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
示例#28
0
 protected JsDeclaration(JsContext context, JsParser parser)
     : base(context, parser)
 {
     m_list = new List <JsVariableDeclaration>();
 }
示例#29
0
 public JsWhileNode(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
示例#30
0
        //---------------------------------------------------------------------------------------
        // GetNextToken
        //
        //  Return the next token or peeked token if this.errorToken is not null.
        //  Usually this.errorToken is set by AddError even though any code can look ahead
        //  by assigning this.errorToken.
        //  At this point the context is not saved so if position information is needed
        //  they have to be saved explicitely
        //---------------------------------------------------------------------------------------
        private void GetNextToken()
        {
            if (m_useCurrentForNext)
            {
                // we just want to keep using the current token.
                // but don't get into an infinite loop -- after a while,
                // give up and grab the next token from the scanner anyway
                m_useCurrentForNext = false;
                if (m_breakRecursion++ > 10)
                {
                    m_currentToken = ScanNextToken();
                }
            }
            else
            {
                m_goodTokensProcessed++;
                m_breakRecursion = 0;

                // the scanner reuses the same context object for performance,
                // so if we ever mean to hold onto it later, we need to clone it.
                m_currentToken = ScanNextToken();
            }
        }
示例#31
0
 public JsForIn(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
 public JsGroupingOperator(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
示例#33
0
 public JsWithScope(JsActivationObject parent, JsContext context, JsSettings settings)
     : base(parent, context, settings)
 {
     IsInWithScope = true;
 }
示例#34
0
 public JsWhileNode(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
 public JsUnaryOperator(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
示例#36
0
 protected JsExpression(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
 public JsConditionalCompilationIf(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
示例#38
0
        public void MarkSegment(JsAstNode node, int startLine, int startColumn, string name, JsContext context)
        {
            if (startLine == int.MaxValue)
            {
                throw new ArgumentOutOfRangeException("startLine");
            }

            // add the offsets
            startLine   += m_lineOffset;
            startColumn += m_columnOffset;

            // if we have a name, try adding it to the hash set of names. If it already exists, the call to Add
            // will return false. If it doesn't already exist, Add will return true and we'll append it to the list
            // of names. That way we have a nice list of names ordered by their first occurrence in the minified file.
            if (!string.IsNullOrEmpty(name) && m_names.Add(name))
            {
                m_nameList.Add(name);
            }

            // if this is a newline, the startline will be bigger than the largest line we've had so far
            m_maxMinifiedLine = Math.Max(m_maxMinifiedLine, startLine);

            // save the file context in our list of files
            if (context != null && context.Document != null && context.Document.FileContext != null)
            {
                // if this is the first instance of this file...
                if (m_sourceFiles.Add(context.Document.FileContext))
                {
                    // ...add it to the list, so we end up with a list of unique files
                    // sorted by their first occurence in the minified file.
                    m_sourceFileList.Add(MakeRelative(context.Document.FileContext, m_mapPath));
                }
            }

            // create the segment object and add it to the list.
            // the destination line/col numbers are zero-based. The format expects line to be 1-based and col 0-based.
            // the context line is one-based; col is zero-based. The format expects both to be zero-based.
            var segment = CreateSegment(
                startLine + 1,
                startColumn,
                context == null || context.StartLineNumber < 1 ? -1 : context.StartLineNumber - 1,
                context == null || context.StartColumn < 0 ? -1 : context.StartColumn,
                context.IfNotNull(c => MakeRelative(c.Document.FileContext, m_mapPath)),
                name);

            m_segments.Add(segment);
        }
示例#39
0
 public JsBlock(JsContext context, JsParser parser)
     : base(context, parser)
 {
     m_list = new List<JsAstNode>();
 }
 public JsVariableDeclaration(JsContext context, JsParser parser)
     : base(context, parser)
 {
 }
示例#41
0
 internal UndefinedReferenceException(JsLookup lookup, JsContext context)
 {
     m_lookup = lookup;
     m_name = lookup.Name;
     m_type = lookup.RefType;
     m_context = context;
 }
        public static JsAstNode CombineWithComma(JsContext context, JsParser parser, JsAstNode operand1, JsAstNode operand2)
        {
            var comma = new JsCommaOperator(context, parser);

            // if the left is a comma-operator already....
            var leftBinary  = operand1 as JsBinaryOperator;
            var rightBinary = operand2 as JsBinaryOperator;

            if (leftBinary != null && leftBinary.OperatorToken == JsToken.Comma)
            {
                // the left-hand side is already a comma operator. Instead of nesting these, we're
                // going to combine them
                // move the old list's left-hand side to our left-hand side
                comma.Operand1 = leftBinary.Operand1;

                JsAstNodeList list;
                if (rightBinary != null && rightBinary.OperatorToken == JsToken.Comma)
                {
                    // the right is ALSO a comma operator. Create a new list, append all the rest of the operands
                    // and set our right-hand side to be the list
                    list = new JsAstNodeList(null, parser);
                    list.Append(leftBinary.Operand2).Append(rightBinary.Operand1).Append(rightBinary.Operand2);
                }
                else
                {
                    // the right is not a comma operator.
                    // see if the left-hand side already has a list we can use
                    list = leftBinary.Operand2 as JsAstNodeList;
                    if (list == null)
                    {
                        // it's not a list already
                        // create a new list with the left's right and our right and set it to our right
                        list = new JsAstNodeList(null, parser);
                        list.Append(leftBinary.Operand2);
                    }

                    // and add our right-hand operand to the end of the list
                    list.Append(operand2);
                }

                // set the list on the right
                comma.Operand2 = list;
            }
            else if (rightBinary != null && rightBinary.OperatorToken == JsToken.Comma)
            {
                // the left hand side is NOT a comma operator.
                comma.Operand1 = operand1;

                // the right-hand side is already a comma-operator, but the left is not.
                // see if it already has a list we can reuse
                var rightList = rightBinary.Operand2 as JsAstNodeList;
                if (rightList != null)
                {
                    // it does. Prepend its right-hand operand and use the list
                    rightList.Insert(0, rightBinary.Operand1);
                }
                else
                {
                    // it's not -- create a new list containing the operands
                    rightList = new JsAstNodeList(rightBinary.Context, parser);
                    rightList.Append(rightBinary.Operand1);
                    rightList.Append(rightBinary.Operand2);
                }

                comma.Operand2 = rightList;
            }
            else
            {
                comma.Operand1 = operand1;
                comma.Operand2 = operand2;
            }

            return(comma);
        }