Пример #1
0
        //---------------------------------------------------------------------------------------
        // ParseFunction
        //
        //  FunctionDeclaration :
        //    VisibilityModifier 'function' Identifier '('
        //                          FormalParameterList ')' '{' FunctionBody '}'
        //
        //  FormalParameterList :
        //    <empty> |
        //    IdentifierList Identifier
        //
        //  IdentifierList :
        //    <empty> |
        //    Identifier, IdentifierList
        //---------------------------------------------------------------------------------------
        private FunctionObject ParseFunction(FunctionType functionType, Context fncCtx)
        {
            Lookup name = null;
            List<ParameterDeclaration> formalParameters = null;
            Block body = null;
            bool inExpression = (functionType == FunctionType.Expression);

            GetNextToken();

            // get the function name or make an anonymous function if in expression "position"
            if (JSToken.Identifier == m_currentToken.Token)
            {
                name = new Lookup(m_scanner.GetIdentifier(), m_currentToken.Clone(), this);
                GetNextToken();
            }
            else
            {
                string identifier = JSKeyword.CanBeIdentifier(m_currentToken.Token);
                if (null != identifier)
                {
                    name = new Lookup(identifier, m_currentToken.Clone(), this);
                    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, 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 Lookup(identifier, CurrentPositionContext(), this);
                            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>();

            // create the function scope and stick it onto the scope stack
            FunctionScope functionScope = new FunctionScope(
              ScopeStack.Peek(),
              (functionType != FunctionType.Declaration),
              this
              );
            ScopeStack.Push(functionScope);

            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 indentifier. Otherwise we didn't expand the 
                    // name, so just report that we expected an open parent at this point.
                    if (expandedIndentifier)
                    {
                        name.Name = name.Context.Code;
                        name.Context.HandleError(JSError.FunctionNameMustBeIdentifier, true);
                    }
                    else
                    {
                        ReportError(JSError.NoLeftParenthesis, true);
                    }
                }

                if (m_currentToken.Token == JSToken.LeftParenthesis)
                {
                    // skip the open paren
                    GetNextToken();

                    Context paramArrayContext = null;
                    formalParameters = new List<ParameterDeclaration>();

                    // create the list of arguments and update the context
                    while (JSToken.RightParenthesis != m_currentToken.Token)
                    {
                        if (paramArrayContext != null)
                        {
                            ReportError(JSError.ParameterListNotLast, paramArrayContext, true);
                            paramArrayContext = null;
                        }
                        String id = null;
                        m_noSkipTokenSet.Add(NoSkipTokenSet.s_FunctionDeclNoSkipTokenSet);
                        try
                        {
                            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.GetIdentifier();
                                }
                                
                                Context paramCtx = m_currentToken.Clone();
                                GetNextToken();

                                formalParameters.Add(new ParameterDeclaration(paramCtx, this, id, formalParameters.Count));
                            }

                            // got an arg, it should be either a ',' or ')'
                            if (JSToken.RightParenthesis == m_currentToken.Token)
                                break;
                            else if (JSToken.Comma != m_currentToken.Token)
                            {
                                // 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 Block(m_currentToken.Clone(), this);
                    GetNextToken();

                    while (JSToken.RightCurly != m_currentToken.Token)
                    {
                        try
                        {
                            // function body's are SourceElements (Statements + FunctionDeclarations)
                            body.Append(ParseStatement(true));
                        }
                        catch (RecoveryTokenException exc)
                        {
                            if (exc._partiallyComputedNode != null)
                            {
                                body.Append(exc._partiallyComputedNode);
                            }
                            if (IndexOfToken(NoSkipTokenSet.s_StartStatementNoSkipTokenSet, exc) == -1)
                                throw;
                        }
                    }

                    body.Context.UpdateWith(m_currentToken);
                    fncCtx.UpdateWith(m_currentToken);
                }
                catch (RecoveryTokenException exc)
                {
                    if (IndexOfToken(NoSkipTokenSet.s_BlockNoSkipTokenSet, exc) == -1)
                    {
                        exc._partiallyComputedNode = new FunctionObject(
                          name,
                          this,
                          (inExpression ? FunctionType.Expression : FunctionType.Declaration),
                          formalParameters == null ? null : formalParameters.ToArray(),
                          body,
                          fncCtx,
                          functionScope
                          );
                        throw;
                    }
                }
                finally
                {
                    m_blockType.RemoveAt(m_blockType.Count - 1);
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_StartStatementNoSkipTokenSet);
                    m_noSkipTokenSet.Remove(NoSkipTokenSet.s_BlockNoSkipTokenSet);
                }

                GetNextToken();
            }
            finally
            {
                // pop the scope off the stack
                ScopeStack.Pop();

                // restore state
                m_blockType = blockType;
                m_labelTable = labelTable;
            }

            return new FunctionObject(
                name,
                this,
                functionType,
                formalParameters == null ? null : formalParameters.ToArray(),
                body,
                fncCtx,
                functionScope);
        }
Пример #2
0
        public FunctionObject(Lookup identifier, JSParser parser, FunctionType functionType, ParameterDeclaration[] parameterDeclarations, Block bodyBlock, Context functionContext, FunctionScope functionScope)
            : base(functionContext, parser)
        {
            FunctionType = functionType;
            m_functionScope = functionScope;
            if (functionScope != null)
            {
                functionScope.FunctionObject = this;
            }

            m_name = string.Empty;
            Identifier = identifier;
            if (Identifier != null) { Identifier.Parent = this; }

            m_parameterDeclarations = parameterDeclarations;

            Body = bodyBlock;
            if (bodyBlock != null) { bodyBlock.Parent = this; }

            // now we need to make sure that the enclosing scope has the name of this function defined
            // so that any references get properly resolved once we start analyzing the parent scope
            // see if this is not anonymnous AND not a getter/setter
            bool isGetterSetter = (FunctionType == FunctionType.Getter || FunctionType == FunctionType.Setter);
            if (Identifier != null && !isGetterSetter)
            {
                // yes -- add the function name to the current enclosing
                // check whether the function name is in use already
                // shouldn't be any duplicate names
                ActivationObject enclosingScope = m_functionScope.Parent;
                // functions aren't owned by block scopes
                while (enclosingScope is BlockScope)
                {
                    enclosingScope = enclosingScope.Parent;
                }

                // if the enclosing scope already contains this name, then we know we have a dup
                string functionName = Identifier.Name;
                m_variableField = enclosingScope[functionName];
                if (m_variableField != null)
                {
                    // it's pointing to a function
                    m_variableField.IsFunction = true;

                    if (FunctionType == FunctionType.Expression)
                    {
                        // if the containing scope is itself a named function expression, then just
                        // continue on as if everything is fine. It will chain and be good.
                        if (!(m_variableField is JSNamedFunctionExpressionField))
                        {
                            if (m_variableField.NamedFunctionExpression != null)
                            {
                                // we have a second named function expression in the same scope
                                // with the same name. Not an error unless someone actually references
                                // it.

                                // we are now ambiguous.
                                m_variableField.IsAmbiguous = true;

                                // BUT because this field now points to multiple function object, we
                                // need to break the connection. We'll leave the inner NFEs pointing
                                // to this field as the outer field so the names all align, however.
                                DetachFromOuterField(true);

                                // create a new NFE pointing to the existing field as the outer so
                                // the names stay in sync, and with a value of our function object.
                                JSNamedFunctionExpressionField namedExpressionField =
                                    new JSNamedFunctionExpressionField(m_variableField);
                                namedExpressionField.FieldValue = this;
                                m_functionScope.AddField(namedExpressionField);

                                // hook our function object up to the named field
                                m_variableField = namedExpressionField;
                                Identifier.VariableField = namedExpressionField;

                                // we're done; quit.
                                return;
                            }
                            else if (m_variableField.IsAmbiguous)
                            {
                                // we're pointing to a field that is already marked as ambiguous.
                                // just create our own NFE pointing to this one, and hook us up.
                                JSNamedFunctionExpressionField namedExpressionField =
                                    new JSNamedFunctionExpressionField(m_variableField);
                                namedExpressionField.FieldValue = this;
                                m_functionScope.AddField(namedExpressionField);

                                // hook our function object up to the named field
                                m_variableField = namedExpressionField;
                                Identifier.VariableField = namedExpressionField;

                                // we're done; quit.
                                return;
                            }
                            else
                            {
                                // we are a named function expression in a scope that has already
                                // defined a local variable of the same name. Not good. Throw the
                                // error but keep them attached because the names have to be synced
                                // to keep the same meaning in all browsers.
                                Identifier.Context.HandleError(JSError.AmbiguousNamedFunctionExpression, false);

                                // if we are preserving function names, then we need to mark this field
                                // as not crunchable
                                if (Parser.Settings.PreserveFunctionNames)
                                {
                                    m_variableField.CanCrunch = false;
                                }
                            }
                        }
                        /*else
                        {
                            // it's okay; just chain the NFEs as normal and everything will work out
                            // and the names will be properly synced.
                        }*/
                    }
                    else
                    {
                        // function declaration -- duplicate name
                        Identifier.Context.HandleError(JSError.DuplicateName, false);
                    }
                }
                else
                {
                    // doesn't exist -- create it now
                    m_variableField = enclosingScope.DeclareField(functionName, this, 0);

                    // and it's a pointing to a function object
                    m_variableField.IsFunction = true;
                }

                // set the identifier variable field now. We *know* what the field is now, and during
                // Analyze mode we aren't going to recurse into the identifier because that would add
                // a reference to it.
                Identifier.VariableField = m_variableField;

                // if we're here, we have a name. if this is a function expression, then we have
                // a named function expression and we need to do a little more work to prepare for
                // the ambiguities of named function expressions in various browsers.
                if (FunctionType == FunctionType.Expression)
                {
                    // now add a field within the function scope that indicates that it's okay to reference
                    // this named function expression from WITHIN the function itself.
                    // the inner field points to the outer field since we're going to want to catch ambiguous
                    // references in the future
                    JSNamedFunctionExpressionField namedExpressionField = new JSNamedFunctionExpressionField(m_variableField);
                    m_functionScope.AddField(namedExpressionField);
                    m_variableField.NamedFunctionExpression = namedExpressionField;
                }
                else
                {
                    // function declarations are declared by definition
                    m_variableField.IsDeclared = true;
                }
            }
        }