/// <summary> /// Indicates that the next token is identical to the given one. Throws an exception if /// this is not the case. Consumes the token. /// </summary> /// <param name="token"> The expected token. </param> private void Expect(Token token) { if (this.nextToken == token) Consume(); else throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Expected '{0}'", token.Text)); }
// TOKEN HELPERS //_________________________________________________________________________________________ /// <summary> /// Discards the current token and reads the next one. /// </summary> private void Consume() { do { this.nextToken = this.lexer.NextToken(); } while ((this.nextToken is WhiteSpaceToken) == true); }
/// <summary> /// Converts the token to the string suitable for embedding in an error message. /// </summary> /// <param name="token"> The token to convert. Can be <c>null</c>. </param> /// <returns> A string suitable for embedding in an error message. </returns> public static string ToText(Token token) { if (token == null) return "end of input"; return string.Format("'{0}'", token.Text); }
/// <summary> /// Indicates that the next token is identical to the given one. Throws an exception if /// this is not the case. Consumes the token. /// </summary> /// <param name="token"> The expected token. </param> private void Expect(Token token) { if (this.nextToken == token) Consume(); else throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Expected '{0}' but found {1}", token.Text, Token.ToText(this.nextToken)), this.LineNumber, this.SourcePath); }
/// <summary> /// Reads the next token from the reader. /// </summary> /// <returns> A token, or <c>null</c> if there are no more tokens. </returns> public Token NextToken() { int c1 = ReadNextChar(); if (IsPunctuatorStartChar(c1) == true) { // Punctuator (puntcuation + operators). this.lastSignificantToken = ReadPunctuator(c1); return this.lastSignificantToken; } else if (IsWhiteSpace(c1) == true) { // White space. return ReadWhiteSpace(); } else if (IsIdentifierStartChar(c1) == true) { // Identifier or reserved word. this.lastSignificantToken = ReadIdentifier(c1); return this.lastSignificantToken; } else if (IsStringLiteralStartChar(c1) == true) { // String literal. this.lastSignificantToken = ReadStringLiteral(c1); return this.lastSignificantToken; } else if (IsNumericLiteralStartChar(c1) == true) { // Number literal. this.lastSignificantToken = ReadNumericLiteral(c1); return this.lastSignificantToken; } else if (IsLineTerminator(c1) == true) { // Line Terminator. this.lastSignificantToken = ReadLineTerminator(c1); return this.lastSignificantToken; } else if (c1 == '/') { // Comment or divide or regular expression. this.lastSignificantToken = ReadDivideCommentOrRegularExpression(); return this.lastSignificantToken; } else if (c1 == -1) { // End of input. this.lastSignificantToken = null; return null; } else throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Unexpected character '{0}'.", (char)c1), this.lineNumber, this.Source.Path); }
/// <summary> /// Parses a javascript expression. /// </summary> /// <param name="endToken"> A token that indicates the end of the expression. </param> /// <returns> An expression tree that represents the expression. </returns> private Expression ParseExpression(params Token[] endTokens) { // The root of the expression tree. Expression root = null; // The active operator, i.e. the one last encountered. OperatorExpression unboundOperator = null; while (this.nextToken != null) { if (this.nextToken is LiteralToken || this.nextToken is IdentifierToken || this.nextToken == KeywordToken.Function || this.nextToken == KeywordToken.This || this.nextToken == PunctuatorToken.LeftBrace || (this.nextToken == PunctuatorToken.LeftBracket && this.expressionState == ParserExpressionState.Literal) || (this.nextToken is KeywordToken && unboundOperator != null && unboundOperator.OperatorType == OperatorType.MemberAccess && this.expressionState == ParserExpressionState.Literal)) { // If a literal was found where an operator was expected, insert a semi-colon // automatically (if this would fix the error and a line terminator was // encountered) or throw an error. if (this.expressionState != ParserExpressionState.Literal) { // Check for automatic semi-colon insertion. if (Array.IndexOf(endTokens, PunctuatorToken.Semicolon) >= 0 && this.consumedLineTerminator == true) break; throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Expected operator but found {0}", Token.ToText(this.nextToken)), this.LineNumber, this.SourcePath); } // New in ECMAScript 5 is the ability to use keywords as property names. if ((this.nextToken is KeywordToken || (this.nextToken is LiteralToken && ((LiteralToken)this.nextToken).IsKeyword == true)) && unboundOperator != null && unboundOperator.OperatorType == OperatorType.MemberAccess && this.expressionState == ParserExpressionState.Literal) { this.nextToken = new IdentifierToken(this.nextToken.Text); } Expression terminal; if (this.nextToken is LiteralToken) // If the token is a literal, convert it to a literal expression. terminal = new LiteralExpression(((LiteralToken)this.nextToken).Value); else if (this.nextToken is IdentifierToken) { // If the token is an identifier, convert it to a NameExpression. var identifierName = ((IdentifierToken)this.nextToken).Name; terminal = new NameExpression(this.currentScope, identifierName); // Record each occurance of a variable name. if (unboundOperator == null || unboundOperator.OperatorType != OperatorType.MemberAccess) this.methodOptimizationHints.EncounteredVariable(identifierName); } else if (this.nextToken == KeywordToken.This) { // Convert "this" to an expression. terminal = new ThisExpression(); // Add method optimization info. this.methodOptimizationHints.HasThis = true; } else if (this.nextToken == PunctuatorToken.LeftBracket) // Array literal. terminal = ParseArrayLiteral(); else if (this.nextToken == PunctuatorToken.LeftBrace) // Object literal. terminal = ParseObjectLiteral(); else if (this.nextToken == KeywordToken.Function) terminal = ParseFunctionExpression(); else throw new InvalidOperationException("Unsupported literal type."); // Push the literal to the most recent unbound operator, or, if there is none, to // the root of the tree. if (root == null) { // This is the first term in an expression. root = terminal; } else { Debug.Assert(unboundOperator != null && unboundOperator.AcceptingOperands == true); unboundOperator.Push(terminal); } } else if (this.nextToken is PunctuatorToken || this.nextToken is KeywordToken) { // The token is an operator (o1). Operator newOperator = OperatorFromToken(this.nextToken, postfixOrInfix: this.expressionState == ParserExpressionState.Operator); // Make sure the token is actually an operator and not just a random keyword. if (newOperator == null) { // Check if the token is an end token, for example a semi-colon. if (Array.IndexOf(endTokens, this.nextToken) >= 0) break; // Check for automatic semi-colon insertion. if (Array.IndexOf(endTokens, PunctuatorToken.Semicolon) >= 0 && (this.consumedLineTerminator == true || this.nextToken == PunctuatorToken.RightBrace)) break; throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Unexpected token {0} in expression.", Token.ToText(this.nextToken)), this.LineNumber, this.SourcePath); } // Post-fix increment and decrement cannot have a line terminator in between // the operator and the operand. if (this.consumedLineTerminator == true && (newOperator == Operator.PostIncrement || newOperator == Operator.PostDecrement)) break; // There are four possibilities: // 1. The token is the second of a two-part operator (for example, the ':' in a // conditional operator. In this case, we need to go up the tree until we find // an instance of the operator and make that the active unbound operator. if (this.nextToken == newOperator.SecondaryToken) { // Traverse down the tree looking for the parent operator that corresponds to // this token. OperatorExpression parentExpression = null; var node = root as OperatorExpression; while (node != null) { if (node.Operator.Token == newOperator.Token && node.SecondTokenEncountered == false) parentExpression = node; if (node == unboundOperator) break; node = node.RightBranch; } // If the operator was not found, then this is a mismatched token, unless // it is the end token. For example, if an unbalanced right parenthesis is // found in an if statement then it is merely the end of the test expression. if (parentExpression == null) { // Check if the token is an end token, for example a right parenthesis. if (Array.IndexOf(endTokens, this.nextToken) >= 0) break; // Check for automatic semi-colon insertion. if (Array.IndexOf(endTokens, PunctuatorToken.Semicolon) >= 0 && this.consumedLineTerminator == true) break; throw new JavaScriptException(this.engine, "SyntaxError", "Mismatched closing token in expression.", this.LineNumber, this.SourcePath); } // Mark that we have seen the closing token. unboundOperator = parentExpression; unboundOperator.SecondTokenEncountered = true; } else { // Check if the token is an end token, for example the comma in a variable // declaration. if (Array.IndexOf(endTokens, this.nextToken) >= 0) { // But make sure the token isn't inside an operator. // For example, in the expression "var x = f(a, b)" the comma does not // indicate the start of a new variable clause because it is inside the // function call operator. bool insideOperator = false; var node = root as OperatorExpression; while (node != null) { if (node.Operator.SecondaryToken != null && node.SecondTokenEncountered == false) insideOperator = true; if (node == unboundOperator) break; node = node.RightBranch; } if (insideOperator == false) break; } // All the other situations involve the creation of a new operator. var newExpression = OperatorExpression.FromOperator(newOperator); // 2. The new operator is a prefix operator. The new operator becomes an operand // of the previous operator. if (newOperator.HasLHSOperand == false) { if (root == null) // "!" root = newExpression; else if (unboundOperator != null && unboundOperator.AcceptingOperands == true) { // "5 + !" unboundOperator.Push(newExpression); } else { // "5 !" or "5 + 5 !" // Check for automatic semi-colon insertion. if (Array.IndexOf(endTokens, PunctuatorToken.Semicolon) >= 0 && this.consumedLineTerminator == true) break; throw new JavaScriptException(this.engine, "SyntaxError", "Invalid use of prefix operator.", this.LineNumber, this.SourcePath); } } else { // Search up the tree for an operator that has a lower precedence. // Because we don't store the parent link, we have to traverse down the // tree and take the last one we find instead. OperatorExpression lowPrecedenceOperator = null; if (unboundOperator == null || (newOperator.Associativity == OperatorAssociativity.LeftToRight && unboundOperator.Precedence < newOperator.Precedence) || (newOperator.Associativity == OperatorAssociativity.RightToLeft && unboundOperator.Precedence <= newOperator.Precedence)) { // Performance optimization: look at the previous operator first. lowPrecedenceOperator = unboundOperator; } else { // Search for a lower precedence operator by traversing the tree. var node = root as OperatorExpression; while (node != null && node != unboundOperator) { if ((newOperator.Associativity == OperatorAssociativity.LeftToRight && node.Precedence < newOperator.Precedence) || (newOperator.Associativity == OperatorAssociativity.RightToLeft && node.Precedence <= newOperator.Precedence)) lowPrecedenceOperator = node; node = node.RightBranch; } } if (lowPrecedenceOperator == null) { // 3. The new operator has a lower precedence (or if the associativity is left to // right, a lower or equal precedence) than all the parent operators. The new // operator goes to the root of the tree and the previous operator becomes the // first operand for the new operator. if (root != null) newExpression.Push(root); root = newExpression; } else { // 4. Otherwise, the new operator can steal the last operand from the previous // operator and then put itself in the place of that last operand. if (lowPrecedenceOperator.OperandCount == 0) { // "! ++" // Check for automatic semi-colon insertion. if (Array.IndexOf(endTokens, PunctuatorToken.Semicolon) >= 0 && this.consumedLineTerminator == true) break; throw new JavaScriptException(this.engine, "SyntaxError", "Invalid use of prefix operator.", this.LineNumber, this.SourcePath); } newExpression.Push(lowPrecedenceOperator.Pop()); lowPrecedenceOperator.Push(newExpression); } } unboundOperator = newExpression; } } else { throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Unexpected token {0} in expression", Token.ToText(this.nextToken)), this.LineNumber, this.SourcePath); } // Read the next token. this.Consume(root != null && (unboundOperator == null || unboundOperator.AcceptingOperands == false) ? ParserExpressionState.Operator : ParserExpressionState.Literal); } // Empty expressions are invalid. if (root == null) throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Expected an expression but found {0} instead", Token.ToText(this.nextToken)), this.LineNumber, this.SourcePath); // Check the AST is valid. CheckASTValidity(root); // A literal is the next valid expression token. this.expressionState = ParserExpressionState.Literal; this.lexer.ParserExpressionState = expressionState; // Resolve all the unbound operators into real operators. return root; }
// TOKEN HELPERS //_________________________________________________________________________________________ /// <summary> /// Discards the current token and reads the next one. /// </summary> /// <param name="expressionState"> Indicates whether the next token can be a literal or an /// operator. </param> private void Consume(ParserExpressionState expressionState = ParserExpressionState.Literal) { this.expressionState = expressionState; this.lexer.ParserExpressionState = expressionState; this.consumedLineTerminator = false; this.positionBeforeWhitespace = new SourceCodePosition(this.lexer.LineNumber, this.lexer.ColumnNumber); this.positionAfterWhitespace = this.positionBeforeWhitespace; while (true) { this.nextToken = this.lexer.NextToken(); if ((this.nextToken is WhiteSpaceToken) == false) break; if (((WhiteSpaceToken)this.nextToken).LineTerminatorCount > 0) this.consumedLineTerminator = true; this.positionAfterWhitespace = new SourceCodePosition(this.lexer.LineNumber, this.lexer.ColumnNumber); } }
/// <summary> /// Parses a function declaration or a function expression. /// </summary> /// <param name="functionType"> The type of function to parse. </param> /// <param name="parentScope"> The parent scope for the function. </param> /// <returns> A function expression. </returns> private FunctionExpression ParseFunction(FunctionType functionType, Scope parentScope) { if (functionType != FunctionType.Getter && functionType != FunctionType.Setter) { // Consume the function keyword. this.Expect(KeywordToken.Function); } // Read the function name. var functionName = string.Empty; if (functionType == FunctionType.Declaration) { functionName = this.ExpectIdentifier(); } else if (functionType == FunctionType.Expression) { // The function name is optional for function expressions. if (this.nextToken is IdentifierToken) functionName = this.ExpectIdentifier(); } else if (functionType == FunctionType.Getter || functionType == FunctionType.Setter) { // Getters and setters can have any name that is allowed of a property. bool wasIdentifier; functionName = ReadPropertyName(out wasIdentifier); } else throw new ArgumentOutOfRangeException("functionType"); ValidateVariableName(functionName); // Read the left parenthesis. this.Expect(PunctuatorToken.LeftParenthesis); // Read zero or more argument names. var argumentNames = new List<string>(); // Read the first argument name. if (this.nextToken != PunctuatorToken.RightParenthesis) { var argumentName = this.ExpectIdentifier(); ValidateVariableName(argumentName); argumentNames.Add(argumentName); } while (true) { if (this.nextToken == PunctuatorToken.Comma) { // Consume the comma. this.Consume(); // Read and validate the argument name. var argumentName = this.ExpectIdentifier(); ValidateVariableName(argumentName); argumentNames.Add(argumentName); } else if (this.nextToken == PunctuatorToken.RightParenthesis) break; else throw new JavaScriptException(this.engine, "SyntaxError", "Expected ',' or ')'", this.LineNumber, this.SourcePath); } // Getters must have zero arguments. if (functionType == FunctionType.Getter && argumentNames.Count != 0) throw new JavaScriptException(this.engine, "SyntaxError", "Getters cannot have arguments", this.LineNumber, this.SourcePath); // Setters must have one argument. if (functionType == FunctionType.Setter && argumentNames.Count != 1) throw new JavaScriptException(this.engine, "SyntaxError", "Setters must have a single argument", this.LineNumber, this.SourcePath); // Read the right parenthesis. this.Expect(PunctuatorToken.RightParenthesis); // Record the start of the function body. var startPosition = this.PositionBeforeWhitespace; // Since the parser reads one token in advance, start capturing the function body here. var bodyTextBuilder = new System.Text.StringBuilder(); var originalBodyTextBuilder = this.lexer.InputCaptureStringBuilder; this.lexer.InputCaptureStringBuilder = bodyTextBuilder; // Read the start brace. this.Expect(PunctuatorToken.LeftBrace); // This context has a nested function. this.methodOptimizationHints.HasNestedFunction = true; // Create a new scope and assign variables within the function body to the scope. bool includeNameInScope = functionType != FunctionType.Getter && functionType != FunctionType.Setter; var scope = DeclarativeScope.CreateFunctionScope(parentScope, includeNameInScope ? functionName : string.Empty, argumentNames); // Read the function body. var functionParser = Parser.CreateFunctionBodyParser(this, scope); var body = functionParser.Parse(); // Transfer state back from the function parser. this.nextToken = functionParser.nextToken; this.lexer.StrictMode = this.StrictMode; this.lexer.InputCaptureStringBuilder = originalBodyTextBuilder; if (originalBodyTextBuilder != null) originalBodyTextBuilder.Append(bodyTextBuilder); SourceCodePosition endPosition; if (functionType == FunctionType.Expression) { // The end token '}' will be consumed by the parent function. if (this.nextToken != PunctuatorToken.RightBrace) throw new JavaScriptException(this.engine, "SyntaxError", "Expected '}'", this.LineNumber, this.SourcePath); // Record the end of the function body. endPosition = new SourceCodePosition(this.PositionAfterWhitespace.Line, this.PositionAfterWhitespace.Column + 1); } else { // Consume the '}'. this.Expect(PunctuatorToken.RightBrace); // Record the end of the function body. endPosition = new SourceCodePosition(this.PositionAfterWhitespace.Line, this.PositionAfterWhitespace.Column + 1); } // Create a new function expression. var options = this.options.Clone(); options.ForceStrictMode = functionParser.StrictMode; var context = new FunctionMethodGenerator(this.engine, scope, functionName, includeNameInScope, argumentNames, bodyTextBuilder.ToString(0, bodyTextBuilder.Length - 1), body, this.SourcePath, options); context.MethodOptimizationHints = functionParser.methodOptimizationHints; return new FunctionExpression(context); }
/// <summary> /// Finds a operator given a token and an indication whether the prefix or infix/postfix /// version is desired. /// </summary> /// <param name="token"> The token to search for. </param> /// <param name="postfixOrInfix"> <c>true</c> if the infix/postfix version of the operator /// is desired; <c>false</c> otherwise. </param> /// <returns> An Operator instance, or <c>null</c> if the operator could not be found. </returns> private static Operator OperatorFromToken(Token token, bool postfixOrInfix) { Operator result; if (operatorLookup == null) { // Initialize the operator lookup table. var temp = InitializeOperatorLookup(); System.Threading.Thread.MemoryBarrier(); operatorLookup = temp; } if (operatorLookup.TryGetValue(new OperatorKey() { Token = token, PostfixOrInfix = postfixOrInfix }, out result) == false) return null; return result; }
/// <summary> /// Initializes a lookup table by combining the base list with a second list of keywords. /// </summary> /// <param name="additionalKeywords"> A list of additional keywords. </param> /// <returns> A lookup table. </returns> private static Dictionary<string, Token> InitializeLookupTable(Token[] additionalKeywords) { var result = new Dictionary<string, Token>(keywords.Length + additionalKeywords.Length); foreach (var token in keywords) result.Add(token.Text, token); foreach (var token in additionalKeywords) result.Add(token.Text, token); return result; }
// INITIALIZATION //_________________________________________________________________________________________ /// <summary> /// Creates a new operator instance. /// </summary> /// <param name="token"> The token that corresponds to this operator. If the operator consists /// of multiple tokens (e.g. ?:) then this is the first token in the sequence. </param> /// <param name="precedence"> An integer that indicates the order of evaluation. Higher /// precedence operators are evaluated first. </param> /// <param name="placement"> A value that indicates where operands are allowed. </param> /// <param name="associativity"> Gets a value that indicates whether multiple operators of this /// type are grouped left-to-right or right-to-left. </param> /// <param name="type"> The type of operator: this decides what algorithm to use to calculate the result. </param> /// <param name="secondaryToken"> The second token in the sequence. </param> /// <param name="rhsPrecedence"> The precedence for the secondary or tertiary operand. </param> private Operator(Token token, int precedence, OperatorPlacement placement, OperatorAssociativity associativity, OperatorType type, Token secondaryToken = null, int rhsPrecedence = -1) { if (token == null) throw new ArgumentNullException("token"); if (token == secondaryToken) throw new ArgumentException("Token and secondaryToken must be different."); if ((placement & (OperatorPlacement.HasLHSOperand | OperatorPlacement.HasRHSOperand)) == 0) throw new ArgumentException("An operator must take at least one operand."); if (secondaryToken == null && (placement & OperatorPlacement.HasSecondaryRHSOperand) != 0) throw new ArgumentException("HasSecondaryRHSOperand can only be specified along with a secondary token."); if (secondaryToken == null && (placement & OperatorPlacement.InnerOperandIsOptional) != 0) throw new ArgumentException("InnerOperandIsOptional can only be specified along with a secondary token."); if ((placement & OperatorPlacement.InnerOperandIsOptional) != 0 && (placement & OperatorPlacement.HasRHSOperand) == 0) throw new ArgumentException("InnerOperandIsOptional can only be specified along with HasRHSOperand."); this.Token = token; this.HasLHSOperand = (placement & OperatorPlacement.HasLHSOperand) != 0; this.HasRHSOperand = (placement & OperatorPlacement.HasRHSOperand) != 0; this.Associativity = associativity; this.Type = type; this.SecondaryToken = secondaryToken; this.HasSecondaryRHSOperand = (placement & OperatorPlacement.HasSecondaryRHSOperand) != 0; this.InnerOperandIsOptional = (placement & OperatorPlacement.InnerOperandIsOptional) != 0; this.Precedence = this.SecondaryPrecedence = this.TertiaryPrecedence = precedence; if (rhsPrecedence >= 0 && this.HasRHSOperand) this.SecondaryPrecedence = rhsPrecedence; if (rhsPrecedence >= 0 && this.HasSecondaryRHSOperand) this.TertiaryPrecedence = rhsPrecedence; // Every operator instance is stored in a list for retrieval by the parser. allOperators.Add(this); }
/// <summary> /// Finds a operator given a token and an indication whether the prefix or infix/postfix /// version is desired. /// </summary> /// <param name="token"> The token to search for. </param> /// <param name="postfixOrInfix"> <c>true</c> if the infix/postfix version of the operator /// is desired; <c>false</c> otherwise. </param> /// <returns> An Operator instance, or <c>null</c> if the operator could not be found. </returns> private static Operator OperatorFromToken(Token token, bool postfixOrInfix) { Operator result; if (operatorLookup == null) { // Initialize the operator lookup table. var temp = InitializeOperatorLookup(); System.Threading.Thread.MemoryBarrier(); operatorLookup = temp; } if (operatorLookup.TryGetValue(new OperatorKey() { Token = token, PostfixOrInfix = postfixOrInfix }, out result) == false) { // Tagged template literals are treated like function calls. if (token is TemplateLiteralToken) return Operator.FunctionCall; return null; } return result; }
/// <summary> /// Parses a comma-separated list of function arguments. /// </summary> /// <param name="endToken"> The token that ends parsing. </param> /// <returns> A list of parsed arguments. </returns> internal List<FunctionArgument> ParseFunctionArguments(Token endToken) { var arguments = new List<FunctionArgument>(); if (this.nextToken != endToken) { while (true) { // Read the argument name. var argument = new FunctionArgument(); argument.Name = this.ExpectIdentifier(); ValidateVariableName(argument.Name); // Check if the argument has a default value. if (this.nextToken == PunctuatorToken.Assignment) { // Read past the assignment token. Consume(); // Parse the expression that follows. argument.DefaultValue = ParseExpression(PunctuatorToken.Comma, endToken); } // Add the variable to the scope so that it can be used in the default value of // the next argument. Do this *after* parsing the default value. this.currentVarScope.DeclareVariable(argument.Name); // Add the argument to the list. arguments.Add(argument); if (this.nextToken == PunctuatorToken.Comma) { // Consume the comma. this.Consume(); } else if (this.nextToken == endToken) break; else throw new JavaScriptException(this.engine, ErrorType.SyntaxError, "Expected ',' or ')'", this.LineNumber, this.SourcePath); } } return arguments; }
/// <summary> /// Parses a function declaration or a function expression. /// </summary> /// <param name="functionType"> The type of function to parse. </param> /// <param name="parentScope"> The parent scope for the function. </param> /// <param name="functionName"> The name of the function (can be empty). </param> /// <returns> A function expression. </returns> private FunctionExpression ParseFunction(FunctionDeclarationType functionType, Scope parentScope, string functionName) { // Read the left parenthesis. this.Expect(PunctuatorToken.LeftParenthesis); // Create a new scope and assign variables within the function body to the scope. bool includeNameInScope = functionType != FunctionDeclarationType.Getter && functionType != FunctionDeclarationType.Setter; var scope = DeclarativeScope.CreateFunctionScope(parentScope, includeNameInScope ? functionName : string.Empty, null); // Replace scope and methodOptimizationHints. var originalScope = this.currentVarScope; var originalMethodOptimizationHints = this.methodOptimizationHints; var newMethodOptimizationHints = new MethodOptimizationHints(); this.methodOptimizationHints = newMethodOptimizationHints; this.currentVarScope = scope; // Read zero or more arguments. var arguments = ParseFunctionArguments(PunctuatorToken.RightParenthesis); // Restore scope and methodOptimizationHints. this.methodOptimizationHints = originalMethodOptimizationHints; this.currentVarScope = originalScope; // Getters must have zero arguments. if (functionType == FunctionDeclarationType.Getter && arguments.Count != 0) throw new JavaScriptException(this.engine, ErrorType.SyntaxError, "Getters cannot have arguments", this.LineNumber, this.SourcePath); // Setters must have one argument. if (functionType == FunctionDeclarationType.Setter && arguments.Count != 1) throw new JavaScriptException(this.engine, ErrorType.SyntaxError, "Setters must have a single argument", this.LineNumber, this.SourcePath); // Read the right parenthesis. this.Expect(PunctuatorToken.RightParenthesis); // Record the start of the function body. var startPosition = this.PositionBeforeWhitespace; // Since the parser reads one token in advance, start capturing the function body here. var bodyTextBuilder = new System.Text.StringBuilder(); var originalBodyTextBuilder = this.lexer.InputCaptureStringBuilder; this.lexer.InputCaptureStringBuilder = bodyTextBuilder; // Read the start brace. this.Expect(PunctuatorToken.LeftBrace); // This context has a nested function. this.methodOptimizationHints.HasNestedFunction = true; // Read the function body. var functionParser = CreateFunctionBodyParser(this, scope, newMethodOptimizationHints); var body = functionParser.Parse(); // Transfer state back from the function parser. this.nextToken = functionParser.nextToken; this.lexer.StrictMode = this.StrictMode; this.lexer.InputCaptureStringBuilder = originalBodyTextBuilder; if (originalBodyTextBuilder != null) originalBodyTextBuilder.Append(bodyTextBuilder); SourceCodePosition endPosition; if (functionType == FunctionDeclarationType.Expression) { // The end token '}' will be consumed by the parent function. if (this.nextToken != PunctuatorToken.RightBrace) throw new JavaScriptException(this.engine, ErrorType.SyntaxError, "Expected '}'", this.LineNumber, this.SourcePath); // Record the end of the function body. endPosition = new SourceCodePosition(this.PositionAfterWhitespace.Line, this.PositionAfterWhitespace.Column + 1); } else { // Consume the '}'. this.Expect(PunctuatorToken.RightBrace); // Record the end of the function body. endPosition = new SourceCodePosition(this.PositionAfterWhitespace.Line, this.PositionAfterWhitespace.Column + 1); } // Create a new function expression. var options = this.options.Clone(); options.ForceStrictMode = functionParser.StrictMode; var context = new FunctionMethodGenerator(this.engine, scope, functionName, functionType, arguments, bodyTextBuilder.ToString(0, bodyTextBuilder.Length - 1), body, this.SourcePath, options); context.MethodOptimizationHints = functionParser.methodOptimizationHints; return new FunctionExpression(context); }