/// <summary> /// Creates a new Scope instance. /// </summary> /// <param name="parentScope"> A reference to the parent scope, or <c>null</c> if this is /// the global scope. </param> /// <param name="declaredVariableCount"> The number of variables declared in this scope. </param> protected Scope(Scope parentScope, int declaredVariableCount) { this.ParentScope = parentScope; this.variables = new Dictionary<string, DeclaredVariable>(declaredVariableCount); this.CanDeclareVariables = true; this.ExistsAtRuntime = true; }
/// <summary> /// Creates a new VarStatement instance. /// </summary> /// <param name="labels"> The labels that are associated with this statement. </param> /// <param name="scope"> The scope the variables are defined within. </param> public VarStatement(IList<string> labels, Scope scope) : base(labels) { if (scope == null) throw new ArgumentNullException("scope"); this.Scope = scope; this.declarations = new List<VariableDeclaration>(1); }
/// <summary> /// Creates a new NameExpression instance. /// </summary> /// <param name="scope"> The current scope. </param> /// <param name="name"> The name of the variable or member that is being referenced. </param> public NameExpression(Scope scope, string name) { if (scope == null) throw new ArgumentNullException("scope"); if (name == null) throw new ArgumentNullException("name"); this.Scope = scope; this.Name = name; }
/// <summary> /// Creates a parser that can read the body of a function. /// </summary> /// <param name="parser"> The parser for the parent context. </param> /// <param name="scope"> The function scope. </param> /// <returns> A new parser. </returns> private static Parser CreateFunctionBodyParser(Parser parser, Scope scope) { var result = (Parser)parser.MemberwiseClone(); result.currentScope = result.initialScope = scope; result.methodOptimizationHints = new MethodOptimizationHints(); result.context = CodeContext.Function; result.endToken = PunctuatorToken.RightBrace; result.DirectivePrologueProcessedCallback = null; return result; }
/// <summary> /// Creates a new declarative scope for use inside a catch statement. /// </summary> /// <param name="parentScope"> A reference to the parent scope. Can not be <c>null</c>. </param> /// <param name="catchVariableName"> The name of the catch variable. </param> /// <returns> A new DeclarativeScope instance. </returns> internal static DeclarativeScope CreateCatchScope(Scope parentScope, string catchVariableName) { if (parentScope == null) throw new ArgumentNullException("parentScope", "Catch scopes must have a parent scope."); if (catchVariableName == null) throw new ArgumentNullException("catchVariableName"); var result = new DeclarativeScope(parentScope, 0); result.DeclareVariable(catchVariableName); result.CanDeclareVariables = false; // Only the catch variable can be declared in this scope. return result; }
/// <summary> /// Creates a new declarative scope for use at runtime. /// </summary> /// <param name="parentScope"> A reference to the parent scope. Can not be <c>null</c>. </param> /// <param name="declaredVariableNames"> The names of variables that were declared in this scope. </param> /// <returns> A new DeclarativeScope instance. </returns> public static DeclarativeScope CreateRuntimeScope(Scope parentScope, string[] declaredVariableNames) { if (parentScope == null) throw new ArgumentNullException("parentScope", "Function scopes must have a parent scope."); if (declaredVariableNames == null) throw new ArgumentNullException("declaredVariableNames"); var result = new DeclarativeScope(parentScope, declaredVariableNames.Length); foreach (string variableName in declaredVariableNames) result.DeclareVariable(variableName); result.values = new object[result.DeclaredVariableCount]; return result; }
/// <summary> /// Creates a new MethodGenerator instance. /// </summary> /// <param name="engine"> The script engine. </param> /// <param name="scope"> The initial scope. </param> /// <param name="source"> The source of javascript code. </param> /// <param name="options"> Options that influence the compiler. </param> protected MethodGenerator(ScriptEngine engine, Scope scope, ScriptSource source, CompilerOptions options) { if (engine == null) throw new ArgumentNullException("engine"); if (scope == null) throw new ArgumentNullException("scope"); if (source == null) throw new ArgumentNullException("source"); if (options == null) throw new ArgumentNullException("options"); this.Engine = engine; this.InitialScope = scope; this.Source = source; this.Options = options; this.StrictMode = this.Options.ForceStrictMode; }
/// <summary> /// Creates a new declarative scope for use inside a function body. /// </summary> /// <param name="parentScope"> A reference to the parent scope. Can not be <c>null</c>. </param> /// <param name="functionName"> The name of the function. Can be empty for an anonymous function. </param> /// <param name="argumentNames"> The names of each of the function arguments. </param> /// <returns> A new DeclarativeScope instance. </returns> internal static DeclarativeScope CreateFunctionScope(Scope parentScope, string functionName, IEnumerable<string> argumentNames) { if (parentScope == null) throw new ArgumentNullException("parentScope", "Function scopes must have a parent scope."); if (functionName == null) throw new ArgumentNullException("functionName"); if (argumentNames == null) throw new ArgumentNullException("argumentNames"); var result = new DeclarativeScope(parentScope, 0); if (string.IsNullOrEmpty(functionName) == false) result.DeclareVariable(functionName); result.DeclareVariable("this"); result.DeclareVariable("arguments"); foreach (var argumentName in argumentNames) result.DeclareVariable(argumentName); return result; }
// INITIALIZATION //_________________________________________________________________________________________ /// <summary> /// Creates a Parser instance with the given lexer supplying the tokens. /// </summary> /// <param name="engine"> The associated script engine. </param> /// <param name="lexer"> The lexical analyser that provides the tokens. </param> /// <param name="initialScope"> The initial variable scope. </param> /// <param name="options"> Options that influence the compiler. </param> /// <param name="context"> The context of the code (global, function or eval). </param> public Parser(ScriptEngine engine, Lexer lexer, Scope initialScope, CompilerOptions options, CodeContext context) { if (engine == null) throw new ArgumentNullException("engine"); if (lexer == null) throw new ArgumentNullException("lexer"); if (initialScope == null) throw new ArgumentNullException("initialScope"); this.engine = engine; this.lexer = lexer; this.lexer.ParserExpressionState = ParserExpressionState.Literal; this.currentScope = this.initialScope = initialScope; this.methodOptimizationHints = new MethodOptimizationHints(); this.options = options; this.context = context; this.StrictMode = options.ForceStrictMode; this.Consume(); }
// EVAL SUPPORT //_________________________________________________________________________________________ //private class EvalCacheKey //{ // public string Code; // public Compiler.Scope Scope; // public bool StrictMode; // public override int GetHashCode() // { // int bitValue = 1; // int hashCode = this.Code.GetHashCode(); // var scope = this.Scope; // do // { // if (scope is Compiler.DeclarativeScope) // hashCode ^= bitValue; // scope = scope.ParentScope; // bitValue *= 2; // } while (scope != null); // if (this.StrictMode == true) // hashCode ^= bitValue; // return hashCode; // } // public override bool Equals(object obj) // { // if ((obj is EvalCacheKey) == false) // return false; // var other = (EvalCacheKey) obj; // if (this.Code != other.Code || // this.StrictMode != other.StrictMode) // return false; // var scope1 = this.Scope; // var scope2 = other.Scope; // do // { // if (scope1.GetType() != scope2.GetType()) // return false; // scope1 = scope1.ParentScope; // scope2 = scope2.ParentScope; // if (scope1 == null && scope2 != null) // return false; // if (scope1 != null && scope2 == null) // return false; // } while (scope1 != null); // return true; // } //} //private Dictionary<EvalCacheKey, WeakReference> evalCache = // new Dictionary<EvalCacheKey, WeakReference>(); /// <summary> /// Evaluates the given javascript source code and returns the result. /// </summary> /// <param name="code"> The source code to evaluate. </param> /// <param name="scope"> The containing scope. </param> /// <param name="thisObject"> The value of the "this" keyword in the containing scope. </param> /// <param name="strictMode"> Indicates whether the eval statement is being called from /// strict mode code. </param> /// <returns> The value of the last statement that was executed, or <c>undefined</c> if /// there were no executed statements. </returns> internal object Eval(string code, Compiler.Scope scope, object thisObject, bool strictMode) { // Check if the cache contains the eval already. //var key = new EvalCacheKey() { Code = code, Scope = scope, StrictMode = strictMode }; //WeakReference cachedEvalGenRef; //if (evalCache.TryGetValue(key, out cachedEvalGenRef) == true) //{ // var cachedEvalGen = (Compiler.EvalMethodGenerator)cachedEvalGenRef.Target; // if (cachedEvalGen != null) // { // // Replace the "this object" before running. // cachedEvalGen.ThisObject = thisObject; // // Execute the cached code. // return ((Compiler.EvalMethodGenerator)cachedEvalGen).Execute(); // } //} // Parse the eval string into an AST. var options = new Compiler.CompilerOptions() { ForceStrictMode = strictMode }; var evalGen = new Jurassic.Compiler.EvalMethodGenerator( this, // The script engine. scope, // The scope to run the code in. new StringScriptSource(code), // The source code to execute. options, // Options. thisObject); // The value of the "this" keyword. // Make sure the eval cache doesn't get too big. TODO: add some sort of LRU strategy? //if (evalCache.Count > 100) // evalCache.Clear(); //// Add the eval method generator to the cache. //evalCache[key] = new WeakReference(evalGen); // Compile and run the eval code. return(evalGen.Execute()); }
public ScopeContext(Parser parser) { this.parser = parser; previousLetScope = parser.currentLetScope; previousVarScope = parser.currentVarScope; }
/// <summary> /// Creates a new EvalMethodGenerator instance. /// </summary> /// <param name="engine"> The script engine. </param> /// <param name="parentScope"> The scope of the calling code. </param> /// <param name="source"> The script code to execute. </param> /// <param name="options"> Options that influence the compiler. </param> /// <param name="thisObject"> The value of the "this" keyword in the calling code. </param> public EvalMethodGenerator(ScriptEngine engine, Scope parentScope, ScriptSource source, CompilerOptions options, object thisObject) : base(engine, parentScope, source, options) { this.ThisObject = thisObject; }
/// <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="parentScope"> The scope at the point the function is declared. </param> /// <param name="bodyText"> The source code for the function body. </param> /// <param name="generatedMethod"> A delegate which represents the body of the function plus any dependencies. </param> /// <param name="strictMode"> <c>true</c> if the function body is strict mode; <c>false</c> otherwise. </param> public UserDefinedFunction(ObjectInstance prototype, string name, IList<string> argumentNames, Scope parentScope, string bodyText, GeneratedMethod generatedMethod, bool strictMode) : base(prototype) { Init(name, argumentNames, parentScope, bodyText, generatedMethod, strictMode, true); }
/// <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="parentScope"> The scope at the point the function is declared. </param> /// <param name="bodyText"> The source code for the function body. </param> /// <param name="body"> A delegate which represents the body of the function. </param> /// <param name="strictMode"> <c>true</c> if the function body is strict mode; <c>false</c> otherwise. </param> internal UserDefinedFunction(ObjectInstance prototype, string name, IList<string> argumentNames, Scope parentScope, string bodyText, FunctionDelegate body, bool strictMode) : base(prototype) { Init(name, argumentNames, parentScope, bodyText, new GeneratedMethod(body, null), strictMode, true); }
/// <summary> /// Initializes a user-defined function. /// </summary> /// <param name="name"> The name of the function. </param> /// <param name="argumentNames"> The names of the arguments. </param> /// <param name="parentScope"> The scope at the point the function is declared. </param> /// <param name="bodyText"> The source code for the function body. </param> /// <param name="generatedMethod"> A delegate which represents the body of the function, plus any dependencies. </param> /// <param name="strictMode"> <c>true</c> if the function body is strict mode; <c>false</c> otherwise. </param> /// <param name="hasInstancePrototype"> <c>true</c> if the function should have a valid /// "prototype" property; <c>false</c> if the "prototype" property should be <c>null</c>. </param> private void Init(string name, IList<string> argumentNames, Scope parentScope, string bodyText, GeneratedMethod generatedMethod, bool strictMode, bool hasInstancePrototype) { if (name == null) throw new ArgumentNullException("name"); if (argumentNames == null) throw new ArgumentNullException("argumentNames"); if (bodyText == null) throw new ArgumentNullException("bodyText"); if (generatedMethod == null) throw new ArgumentNullException("generatedMethod"); if (parentScope == null) throw new ArgumentNullException("parentScope"); this.ArgumentNames = new System.Collections.ObjectModel.ReadOnlyCollection<string>(argumentNames); this.BodyText = bodyText; this.generatedMethod = generatedMethod; this.body = (FunctionDelegate)this.generatedMethod.GeneratedDelegate; this.ParentScope = parentScope; this.StrictMode = strictMode; // Add function properties. this.FastSetProperty("name", name); this.FastSetProperty("length", argumentNames.Count); // The empty function doesn't have an instance prototype. if (hasInstancePrototype == true) { this.FastSetProperty("prototype", this.Engine.Object.Construct(), PropertyAttributes.Writable); this.InstancePrototype.FastSetProperty("constructor", this, PropertyAttributes.NonEnumerable); } }
/// <summary> /// Parses a with statement. /// </summary> /// <returns> An expression representing the with statement. </returns> private WithStatement ParseWith() { // This statement is not allowed in strict mode. if (this.StrictMode == true) throw new JavaScriptException(this.engine, "SyntaxError", "The with statement is not supported in strict mode", this.LineNumber, this.SourcePath); var result = new WithStatement(this.labelsForCurrentStatement); // Read past the "with" token. this.Expect(KeywordToken.With); // Read a left parenthesis token "(". this.Expect(PunctuatorToken.LeftParenthesis); // Keep track of the start of the statement so that source debugging works correctly. var start = this.PositionAfterWhitespace; // Read an object reference. var objectEnvironment = ParseExpression(PunctuatorToken.RightParenthesis); // Record the portion of the source document that will be highlighted when debugging. result.SourceSpan = new SourceCodeSpan(start, this.PositionBeforeWhitespace); // Read a right parenthesis token ")". this.Expect(PunctuatorToken.RightParenthesis); // Create a new scope and assign variables within the with statement to the scope. result.Scope = ObjectScope.CreateWithScope(this.currentScope, objectEnvironment); this.currentScope = result.Scope; // Read the body of the with statement. result.Body = ParseStatement(); // Revert the scope. this.currentScope = this.currentScope.ParentScope; return result; }
///// <summary> ///// Creates a new object scope for use inside a with statement. ///// </summary> ///// <param name="parentScope"> A reference to the parent scope. Can not be <c>null</c>. </param> ///// <param name="scopeObject"> An expression that evaluates to the object to use. </param> ///// <returns> A new ObjectScope instance. </returns> //public static ObjectScope CreateWithScope(Scope parentScope, Library.ObjectInstance scopeObject) //{ // if (parentScope == null) // throw new ArgumentException("With scopes must have a parent scope."); // return new ObjectScope(parentScope) { ScopeObject = scopeObject, ProvidesImplicitThisValue = true }; //} /// <summary> /// Creates a new object scope for use at runtime. /// </summary> /// <param name="parentScope"> A reference to the parent scope. Can not be <c>null</c>. </param> /// <param name="scopeObject"> An expression that evaluates to the object to use. </param> /// <param name="providesImplicitThisValue"> Indicates whether an implicit "this" value is /// supplied to function calls in this scope. </param> /// <param name="canDeclareVariables"> Indicates whether variables can be declared within /// the scope. </param> /// <returns> A new ObjectScope instance. </returns> public static ObjectScope CreateRuntimeScope(Scope parentScope, Library.ObjectInstance scopeObject, bool providesImplicitThisValue, bool canDeclareVariables) { return new ObjectScope(parentScope) { ScopeObject = scopeObject, ProvidesImplicitThisValue = providesImplicitThisValue, CanDeclareVariables = canDeclareVariables }; }
/// <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> /// Sets the current scope and returns an object which can be disposed to restore the /// previous scope. /// </summary> /// <param name="letScope"> The new let scope. </param> /// <param name="varScope"> The new var scope. </param> /// <returns> An object which can be disposed to restore the previous scope. </returns> private ScopeContext CreateScopeContext(Scope letScope, Scope varScope = null) { if (letScope == null) throw new ArgumentNullException("letScope"); var result = new ScopeContext(this); this.currentLetScope = letScope; if (varScope != null) this.currentVarScope = varScope; return result; }
/// <summary> /// Creates a new object scope for use inside a with statement. /// </summary> /// <param name="parentScope"> A reference to the parent scope. Can not be <c>null</c>. </param> /// <param name="scopeObject"> An expression that evaluates to the object to use. </param> /// <returns> A new ObjectScope instance. </returns> internal static ObjectScope CreateWithScope(Scope parentScope, Expression scopeObject) { if (parentScope == null) throw new ArgumentException("With scopes must have a parent scope."); return new ObjectScope(parentScope) { ScopeObjectExpression = scopeObject, ProvidesImplicitThisValue = true, CanDeclareVariables = false }; }
/// <summary> /// Creates a new Scope instance. /// </summary> /// <param name="parentScope"> A reference to the parent scope, or <c>null</c> if this is /// the global scope. </param> protected Scope(Scope parentScope) : this(parentScope, 0) { }
/// <summary> /// Creates a simple variable assignment expression. /// </summary> /// <param name="scope"> The scope the variable is defined within. </param> /// <param name="name"> The name of the variable to set. </param> /// <param name="value"> The value to set the variable to. </param> public AssignmentExpression(Scope scope, string name, Expression value) : base(Operator.Assignment) { this.Push(new NameExpression(scope, name)); this.Push(value); }
// SCOPE HELPERS //_________________________________________________________________________________________ /// <summary> /// Sets the initial scope. /// </summary> /// <param name="initialScope"> The initial scope </param> private void SetInitialScope(Scope initialScope) { if (initialScope == null) throw new ArgumentNullException("initialScope"); this.currentLetScope = this.currentVarScope = this.initialScope = initialScope; }
/// <summary> /// Creates a parser that can read the body of a function. /// </summary> /// <param name="parser"> The parser for the parent context. </param> /// <param name="scope"> The function scope. </param> /// <param name="optimizationHints"> Hints about whether optimization is possible. </param> /// <returns> A new parser. </returns> private static Parser CreateFunctionBodyParser(Parser parser, Scope scope, MethodOptimizationHints optimizationHints) { var result = (Parser)parser.MemberwiseClone(); result.SetInitialScope(scope); result.methodOptimizationHints = optimizationHints; result.context = CodeContext.Function; result.endToken = PunctuatorToken.RightBrace; return result; }
/// <summary> /// Creates a new ObjectScope instance. /// </summary> private ObjectScope(Scope parentScope) : base(parentScope) { this.ScopeObjectExpression = null; this.ProvidesImplicitThisValue = false; }
/// <summary> /// Creates a new declarative scope for use inside a strict mode eval statement. /// </summary> /// <param name="parentScope"> A reference to the parent scope. Can not be <c>null</c>. </param> /// <returns> A new DeclarativeScope instance. </returns> internal static DeclarativeScope CreateEvalScope(Scope parentScope) { if (parentScope == null) throw new ArgumentNullException("parentScope", "Eval scopes must have a parent scope."); return new DeclarativeScope(parentScope, 0); }
/// <summary> /// Parses a try statement. /// </summary> /// <returns> A try-catch-finally statement. </returns> private TryCatchFinallyStatement ParseTry() { var result = new TryCatchFinallyStatement(this.labelsForCurrentStatement); // Consume the try keyword. this.Expect(KeywordToken.Try); // Parse the try block. result.TryBlock = ParseBlock(); // The next token is either 'catch' or 'finally'. if (this.nextToken == KeywordToken.Catch) { // Consume the catch token. this.Expect(KeywordToken.Catch); // Read the left parenthesis. this.Expect(PunctuatorToken.LeftParenthesis); // Read the name of the variable to assign the exception to. result.CatchVariableName = this.ExpectIdentifier(); this.ValidateVariableName(result.CatchVariableName); // Read the right parenthesis. this.Expect(PunctuatorToken.RightParenthesis); // Create a new scope for the catch variable. this.currentScope = result.CatchScope = DeclarativeScope.CreateCatchScope(this.currentScope, result.CatchVariableName); // Parse the statements inside the catch block. result.CatchBlock = ParseBlock(); // Revert the scope. this.currentScope = this.currentScope.ParentScope; } if (this.nextToken == KeywordToken.Finally) { // Consume the finally token. this.Expect(KeywordToken.Finally); // Read the finally statements. result.FinallyBlock = ParseBlock(); } // There must be a catch or finally block. if (result.CatchBlock == null && result.FinallyBlock == null) throw new JavaScriptException(this.engine, "SyntaxError", "Missing catch or finally after try", this.LineNumber, this.SourcePath); return result; }
/// <summary> /// Creates a new DeclarativeScope instance. /// </summary> /// <param name="parentScope"> A reference to the parent scope, or <c>null</c> if this is /// the global scope. </param> /// <param name="declaredVariableCount"> The number of variables declared in this scope. </param> private DeclarativeScope(Scope parentScope, int declaredVariableCount) : base(parentScope, declaredVariableCount) { }
/// <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); }