//--------------------------------------------------------------------------------------- // 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); }
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; } } }