/// <summary> /// Creates a new instance of FunctionExpression. /// </summary> /// <param name="functionContext"> The function context to base this expression on. </param> public FunctionExpression(FunctionMethodGenerator functionContext) { if (functionContext == null) { throw new ArgumentNullException(nameof(functionContext)); } this.context = functionContext; }
// INITIALIZATION //_________________________________________________________________________________________ /// <summary> /// Creates a new instance of a user-defined function. /// </summary> /// <param name="prototype"> The next object in the prototype chain. </param> /// <param name="name"> The name of the function. </param> /// <param name="argumentNames"> The names of the arguments. </param> /// <param name="bodyText"> The source code for the body of the function. </param> internal UserDefinedFunction(ObjectInstance prototype, string name, IList<string> argumentNames, string bodyText) : base(prototype) { if (name == null) throw new ArgumentNullException("name"); if (argumentNames == null) throw new ArgumentNullException("argumentNames"); if (bodyText == null) throw new ArgumentNullException("bodyText"); // Set up a new function scope. var scope = DeclarativeScope.CreateFunctionScope(this.Engine.CreateGlobalScope(), name, argumentNames); // Compile the code. var context = new FunctionMethodGenerator(this.Engine, scope, name, argumentNames, bodyText, new CompilerOptions()); context.GenerateCode(); // Create a new user defined function. Init(name, argumentNames, this.Engine.CreateGlobalScope(), bodyText, context.GeneratedMethod, context.StrictMode, true); }
/// <summary> /// Compiles the function (if it hasn't already been compiled) and returns a delegate /// representing the compiled function. /// </summary> private FunctionDelegate Compile() { if (this.body == null) { // Compile the function. var scope = DeclarativeScope.CreateFunctionScope(this.Engine.CreateGlobalScope(), this.Name, this.ArgumentNames); var functionGenerator = new FunctionMethodGenerator(this.Engine, scope, this.Name, this.ArgumentNames, this.BodyText, new CompilerOptions()); functionGenerator.GenerateCode(); this.generatedMethod = functionGenerator.GeneratedMethod; this.body = (FunctionDelegate)this.generatedMethod.GeneratedDelegate; } return this.body; }
/// <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> /// Creates a new instance of FunctionExpression. /// </summary> /// <param name="functionContext"> The function context to base this expression on. </param> public FunctionExpression(FunctionMethodGenerator functionContext) { if (functionContext == null) throw new ArgumentNullException("functionContext"); this.context = functionContext; }
/// <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); }
/// <summary> /// Creates a new instance of FunctionExpression. /// </summary> /// <param name="functionContext"> The function context to base this expression on. </param> /// <param name="scope"> The scope that was in effect where the function was declared. </param> public FunctionExpression(FunctionMethodGenerator functionContext, Scope scope) { this.context = functionContext ?? throw new ArgumentNullException(nameof(functionContext)); this.Scope = scope; }