/// <summary> /// Generates CIL for the statement. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> public override void GenerateCode(ILGenerator generator, OptimizationInfo optimizationInfo) { // Generate code for the start of the statement. var statementLocals = new StatementLocals() { NonDefaultSourceSpanBehavior = true }; GenerateStartOfStatement(generator, optimizationInfo, statementLocals); // Unlike in .NET, in javascript there are no restrictions on what can appear inside // try, catch and finally blocks. The one restriction which causes problems is the // inability to jump out of .NET finally blocks. This is required when break, continue // or return statements appear inside of a finally block. To work around this, when // inside a finally block these instructions throw an exception instead. // Setting the InsideTryCatchOrFinally flag converts BR instructions into LEAVE // instructions so that the finally block is executed correctly. var previousInsideTryCatchOrFinally = optimizationInfo.InsideTryCatchOrFinally; optimizationInfo.InsideTryCatchOrFinally = true; // Finally requires two exception nested blocks. if (this.FinallyBlock != null) generator.BeginExceptionBlock(); // Begin the exception block. generator.BeginExceptionBlock(); // Generate code for the try block. this.TryBlock.GenerateCode(generator, optimizationInfo); // Generate code for the catch block. if (this.CatchBlock != null) { // Begin a catch block. The exception is on the top of the stack. generator.BeginCatchBlock(typeof(JavaScriptException)); // Create a new DeclarativeScope. this.CatchScope.GenerateScopeCreation(generator, optimizationInfo); // Store the error object in the variable provided. generator.Call(ReflectionHelpers.JavaScriptException_ErrorObject); var catchVariable = new NameExpression(this.CatchScope, this.CatchVariableName); catchVariable.GenerateSet(generator, optimizationInfo, PrimitiveType.Any, false); // Make sure the scope is reverted even if an exception is thrown. generator.BeginExceptionBlock(); // Emit code for the statements within the catch block. this.CatchBlock.GenerateCode(generator, optimizationInfo); // Revert the scope. generator.BeginFinallyBlock(); this.CatchScope.GenerateScopeDestruction(generator, optimizationInfo); generator.EndExceptionBlock(); } // Generate code for the finally block. if (this.FinallyBlock != null) { generator.BeginFinallyBlock(); var branches = new List<ILLabel>(); var previousStackSize = optimizationInfo.LongJumpStackSizeThreshold; optimizationInfo.LongJumpStackSizeThreshold = optimizationInfo.BreakOrContinueStackSize; var previousCallback = optimizationInfo.LongJumpCallback; optimizationInfo.LongJumpCallback = (generator2, label) => { // It is not possible to branch out of a finally block - therefore instead of // generating LEAVE instructions we throw an exception then catch it to transfer // control out of the finally block. generator2.LoadInt32(branches.Count); generator2.NewObject(ReflectionHelpers.LongJumpException_Constructor); generator2.Throw(); // Record any branches that are made within the finally code. branches.Add(label); }; // Emit code for the finally block. this.FinallyBlock.GenerateCode(generator, optimizationInfo); // End the main exception block. generator.EndExceptionBlock(); // Begin a catch block to catch any LongJumpExceptions. The exception object is on // the top of the stack. generator.BeginCatchBlock(typeof(LongJumpException)); if (branches.Count > 0) { // switch (exception.RouteID) // { // case 0: goto label1; // case 1: goto label2; // } ILLabel[] switchLabels = new ILLabel[branches.Count]; for (int i = 0; i < branches.Count; i++) switchLabels[i] = generator.CreateLabel(); generator.Call(ReflectionHelpers.LongJumpException_RouteID); generator.Switch(switchLabels); for (int i = 0; i < branches.Count; i++) { generator.DefineLabelPosition(switchLabels[i]); generator.Leave(branches[i]); } } // Reset the state we clobbered. optimizationInfo.LongJumpStackSizeThreshold = previousStackSize; optimizationInfo.LongJumpCallback = previousCallback; } // End the exception block. generator.EndExceptionBlock(); // Reset the InsideTryCatchOrFinally flag. optimizationInfo.InsideTryCatchOrFinally = previousInsideTryCatchOrFinally; // Generate code for the end of the statement. GenerateEndOfStatement(generator, optimizationInfo, statementLocals); }
/// <summary> /// Parses a for statement or a for-in statement. /// </summary> /// <returns> A for statement or a for-in statement. </returns> private Statement ParseFor() { // Consume the for keyword. this.Expect(KeywordToken.For); // Read the left parenthesis. this.Expect(PunctuatorToken.LeftParenthesis); // The initialization statement. Statement initializationStatement = null; // The for-in expression needs a variable to assign to. Is null for a regular for statement. IReferenceExpression forInReference = null; // Keep track of the start of the statement so that source debugging works correctly. var start = this.PositionAfterWhitespace; if (this.nextToken == KeywordToken.Var) { // Read past the var token. this.Expect(KeywordToken.Var); // There can be multiple initializers (but not for for-in statements). var varStatement = new VarStatement(this.labelsForCurrentStatement, this.currentScope); initializationStatement = varStatement; // Only a simple variable name is allowed for for-in statements. bool cannotBeForIn = false; while (true) { var declaration = new VariableDeclaration(); // The next token must be a variable name. declaration.VariableName = this.ExpectIdentifier(); ValidateVariableName(declaration.VariableName); // Add the variable to the current function's list of local variables. this.currentScope.DeclareVariable(declaration.VariableName, this.context == CodeContext.Function ? null : new LiteralExpression(Undefined.Value), writable: true, deletable: this.context == CodeContext.Eval); // The next token is either an equals sign (=), a semi-colon, a comma, or the "in" keyword. if (this.nextToken == PunctuatorToken.Assignment) { // Read past the equals token (=). this.Expect(PunctuatorToken.Assignment); // Read the setter expression. declaration.InitExpression = ParseExpression(PunctuatorToken.Semicolon, PunctuatorToken.Comma); // Record the portion of the source document that will be highlighted when debugging. declaration.SourceSpan = new SourceCodeSpan(start, this.PositionBeforeWhitespace); // This is a regular for statement. cannotBeForIn = true; } // Add the declaration to the initialization statement. varStatement.Declarations.Add(declaration); if (this.nextToken == PunctuatorToken.Semicolon) { // This is a regular for statement. break; } else if (this.nextToken == KeywordToken.In && cannotBeForIn == false) { // This is a for-in statement. forInReference = new NameExpression(this.currentScope, declaration.VariableName); break; } else if (this.nextToken != PunctuatorToken.Comma) throw new JavaScriptException(this.engine, "SyntaxError", string.Format("Unexpected token {0}", Token.ToText(this.nextToken)), this.LineNumber, this.SourcePath); // Read past the comma token. this.Expect(PunctuatorToken.Comma); // Keep track of the start of the statement so that source debugging works correctly. start = this.PositionAfterWhitespace; // Multiple initializers are not allowed in for-in statements. cannotBeForIn = true; } // Record the portion of the source document that will be highlighted when debugging. varStatement.SourceSpan = new SourceCodeSpan(start, this.PositionBeforeWhitespace); } else { // Not a var initializer - can be a simple variable name then "in" or any expression ending with a semi-colon. // The expression can be empty. if (this.nextToken != PunctuatorToken.Semicolon) { // Parse an expression. var initializationExpression = ParseExpression(PunctuatorToken.Semicolon, KeywordToken.In); // Record debug info for the expression. initializationStatement = new ExpressionStatement(initializationExpression); initializationStatement.SourceSpan = new SourceCodeSpan(start, this.PositionBeforeWhitespace); if (this.nextToken == KeywordToken.In) { // This is a for-in statement. if ((initializationExpression is IReferenceExpression) == false) throw new JavaScriptException(this.engine, "ReferenceError", "Invalid left-hand side in for-in", this.LineNumber, this.SourcePath); forInReference = (IReferenceExpression)initializationExpression; } } } if (forInReference != null) { // for (x in y) // for (var x in y) var result = new ForInStatement(this.labelsForCurrentStatement); result.Variable = forInReference; result.VariableSourceSpan = initializationStatement.SourceSpan; // Consume the "in". this.Expect(KeywordToken.In); // Parse the right-hand-side expression. start = this.PositionAfterWhitespace; result.TargetObject = ParseExpression(PunctuatorToken.RightParenthesis); result.TargetObjectSourceSpan = new SourceCodeSpan(start, this.PositionBeforeWhitespace); // Read the right parenthesis. this.Expect(PunctuatorToken.RightParenthesis); // Read the statements that will be executed in the loop body. result.Body = ParseStatement(); return result; } else { var result = new ForStatement(this.labelsForCurrentStatement); // Set the initialization statement. if (initializationStatement != null) result.InitStatement = initializationStatement; // Read the semicolon. this.Expect(PunctuatorToken.Semicolon); // Parse the optional condition expression. // Note: if the condition is omitted then it is considered to always be true. if (this.nextToken != PunctuatorToken.Semicolon) { start = this.PositionAfterWhitespace; result.ConditionStatement = new ExpressionStatement(ParseExpression(PunctuatorToken.Semicolon)); result.ConditionStatement.SourceSpan = new SourceCodeSpan(start, this.PositionBeforeWhitespace); } // Read the semicolon. // Note: automatic semicolon insertion never inserts a semicolon in the header of a // for statement. this.Expect(PunctuatorToken.Semicolon); // Parse the optional increment expression. if (this.nextToken != PunctuatorToken.RightParenthesis) { start = this.PositionAfterWhitespace; result.IncrementStatement = new ExpressionStatement(ParseExpression(PunctuatorToken.RightParenthesis)); result.IncrementStatement.SourceSpan = new SourceCodeSpan(start, this.PositionBeforeWhitespace); } // Read the right parenthesis. this.Expect(PunctuatorToken.RightParenthesis); // Read the statements that will be executed in the loop body. result.Body = ParseStatement(); return result; } }
/// <summary> /// Generates IL for the script. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> protected override void GenerateCode(ILGenerator generator, OptimizationInfo optimizationInfo) { // Method signature: object FunctionDelegate(Compiler.Scope scope, object thisObject, Library.FunctionInstance functionObject, object[] arguments) // Initialize the scope (note: the initial scope for a function is always declarative). this.InitialScope.GenerateScopeCreation(generator, optimizationInfo); // Verify the scope is correct. VerifyScope(generator); // In ES3 the "this" value must be an object. See 10.4.3 in the spec. if (this.StrictMode == false && this.MethodOptimizationHints.HasThis == true) { // if (thisObject == null || thisObject == Null.Value || thisObject == Undefined.Value) EmitHelpers.LoadThis(generator); generator.LoadNull(); generator.CompareEqual(); EmitHelpers.LoadThis(generator); EmitHelpers.EmitNull(generator); generator.CompareEqual(); generator.BitwiseOr(); EmitHelpers.LoadThis(generator); EmitHelpers.EmitUndefined(generator); generator.CompareEqual(); generator.BitwiseOr(); // { var startOfFalse = generator.CreateLabel(); generator.BranchIfFalse(startOfFalse); // thisObject = engine.Global; EmitHelpers.LoadScriptEngine(generator); generator.Call(ReflectionHelpers.ScriptEngine_Global); // } else { var endOfIf = generator.CreateLabel(); generator.Branch(endOfIf); generator.DefineLabelPosition(startOfFalse); // thisObject = TypeConverter.ToObject(thisObject); EmitHelpers.LoadThis(generator); EmitConversion.ToObject(generator, PrimitiveType.Any); // } generator.DefineLabelPosition(endOfIf); EmitHelpers.StoreThis(generator); } // Transfer the function name into the scope. if (string.IsNullOrEmpty(this.Name) == false && this.ArgumentNames.Contains(this.Name) == false && optimizationInfo.MethodOptimizationHints.HasVariable(this.Name)) { EmitHelpers.LoadFunction(generator); var functionName = new NameExpression(this.InitialScope, this.Name); functionName.GenerateSet(generator, optimizationInfo, PrimitiveType.Any, false); } // Transfer the arguments object into the scope. if (this.MethodOptimizationHints.HasArguments == true && this.ArgumentNames.Contains("arguments") == false) { // prototype EmitHelpers.LoadScriptEngine(generator); generator.Call(ReflectionHelpers.ScriptEngine_Object); generator.Call(ReflectionHelpers.FunctionInstance_InstancePrototype); // callee EmitHelpers.LoadFunction(generator); generator.CastClass(typeof(Library.UserDefinedFunction)); // scope EmitHelpers.LoadScope(generator); generator.CastClass(typeof(DeclarativeScope)); // argumentValues EmitHelpers.LoadArgumentsArray(generator); generator.NewObject(ReflectionHelpers.Arguments_Constructor); var arguments = new NameExpression(this.InitialScope, "arguments"); arguments.GenerateSet(generator, optimizationInfo, PrimitiveType.Any, false); } // Transfer the argument values into the scope. // Note: the arguments array can be smaller than expected. if (this.ArgumentNames.Count > 0) { var endOfArguments = generator.CreateLabel(); for (int i = 0; i < this.ArgumentNames.Count; i++) { // Check if a duplicate argument name exists. bool duplicate = false; for (int j = i + 1; j < this.ArgumentNames.Count; j++) if (this.ArgumentNames[i] == this.ArgumentNames[j]) { duplicate = true; break; } if (duplicate == true) continue; // Check if an array element exists. EmitHelpers.LoadArgumentsArray(generator); generator.LoadArrayLength(); generator.LoadInt32(i); generator.BranchIfLessThanOrEqual(endOfArguments); // Store the array element in the scope. EmitHelpers.LoadArgumentsArray(generator); generator.LoadInt32(i); generator.LoadArrayElement(typeof(object)); var argument = new NameExpression(this.InitialScope, this.ArgumentNames[i]); argument.GenerateSet(generator, optimizationInfo, PrimitiveType.Any, false); } generator.DefineLabelPosition(endOfArguments); } // Initialize any declarations. this.InitialScope.GenerateDeclarations(generator, optimizationInfo); //EmitHelpers.LoadScope(generator); //EmitConversion.ToObject(generator, PrimitiveType.Any); //generator.Pop(); // Generate code for the body of the function. this.AbstractSyntaxTree.GenerateCode(generator, optimizationInfo); // Define the return target - this is where the return statement jumps to. // ReturnTarget can be null if there were no return statements. if (optimizationInfo.ReturnTarget != null) generator.DefineLabelPosition(optimizationInfo.ReturnTarget); // Load the return value. If the variable is null, there were no return statements. if (optimizationInfo.ReturnVariable != null) // Return the value stored in the variable. Will be null if execution hits the end // of the function without encountering any return statements. generator.LoadVariable(optimizationInfo.ReturnVariable); else // There were no return statements - return null. generator.LoadNull(); }
/// <summary> /// Generates code that initializes the variable and function declarations. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> internal virtual void GenerateDeclarations(ILGenerator generator, OptimizationInfo optimizationInfo) { // Initialize the declared variables and functions. foreach (var variable in this.variables.Values) { // When a scope is reused, i.e. with an eval(), do not reinitialize the variables. if (variable.Initialized == true) continue; if (variable.ValueAtTopOfScope != null) { // Emit the initialization code. if (this is ObjectScope) { // Determine the property attributes. var attributes = Library.PropertyAttributes.Enumerable; if (variable.Writable == true) attributes |= Library.PropertyAttributes.Writable; if (variable.Deletable == true) attributes |= Library.PropertyAttributes.Configurable; // bool DefineProperty(string propertyName, PropertyDescriptor descriptor, bool throwOnError) EmitHelpers.LoadScope(generator); generator.CastClass(typeof(ObjectScope)); generator.Call(ReflectionHelpers.ObjectScope_ScopeObject); generator.LoadString(variable.Name); variable.ValueAtTopOfScope.GenerateCode(generator, optimizationInfo); EmitConversion.Convert(generator, variable.ValueAtTopOfScope.ResultType, PrimitiveType.Any, optimizationInfo); generator.LoadInt32((int)attributes); generator.NewObject(ReflectionHelpers.PropertyDescriptor_Constructor2); generator.LoadBoolean(false); generator.Call(ReflectionHelpers.ObjectInstance_DefineProperty); generator.Pop(); } else { variable.ValueAtTopOfScope.GenerateCode(generator, optimizationInfo); var name = new NameExpression(this, variable.Name); name.GenerateSet(generator, optimizationInfo, variable.ValueAtTopOfScope.ResultType, false); } // Mark the variable as having been initialized. variable.Initialized = true; } } }
/// <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; }
/// <summary> /// Generates CIL for the statement. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> public override void GenerateCode(ILGenerator generator, OptimizationInfo optimizationInfo) { // Generate code for the start of the statement. var statementLocals = new StatementLocals() { NonDefaultBreakStatementBehavior = true, NonDefaultSourceSpanBehavior = true }; GenerateStartOfStatement(generator, optimizationInfo, statementLocals); // <initializer> // if (<condition>) // { // <loop body> // <increment> // while (true) { // if (<condition> == false) // break; // // <body statements> // // continue-target: // <increment> // } // } // break-target: // Set up some labels. var continueTarget = generator.CreateLabel(); var breakTarget1 = generator.CreateLabel(); var breakTarget2 = generator.CreateLabel(); // Emit the initialization statement. if (this.InitStatement != null) this.InitStatement.GenerateCode(generator, optimizationInfo); // Check the condition and jump to the end if it is false. if (this.CheckConditionAtEnd == false && this.ConditionStatement != null) { optimizationInfo.MarkSequencePoint(generator, this.ConditionStatement.SourceSpan); this.Condition.GenerateCode(generator, optimizationInfo); EmitConversion.ToBool(generator, this.Condition.ResultType); generator.BranchIfFalse(breakTarget1); } // Emit the loop body. optimizationInfo.PushBreakOrContinueInfo(this.Labels, breakTarget1, continueTarget, false); this.Body.GenerateCode(generator, optimizationInfo); optimizationInfo.PopBreakOrContinueInfo(); // Increment the loop variable. if (this.IncrementStatement != null) this.IncrementStatement.GenerateCode(generator, optimizationInfo); // Strengthen the variable types. List<KeyValuePair<Scope.DeclaredVariable, RevertInfo>> previousVariableTypes = null; var previousInsideTryCatchOrFinally = optimizationInfo.InsideTryCatchOrFinally; if (optimizationInfo.OptimizeInferredTypes == true) { // Keep a record of the variable types before strengthening. previousVariableTypes = new List<KeyValuePair<Scope.DeclaredVariable, RevertInfo>>(); var typedVariables = FindTypedVariables(); foreach (var variableAndType in typedVariables) { var variable = variableAndType.Key; var variableInfo = variableAndType.Value; if (variableInfo.Conditional == false && variableInfo.Type != variable.Type) { // Save the previous type so we can restore it later. var previousType = variable.Type; previousVariableTypes.Add(new KeyValuePair<Scope.DeclaredVariable, RevertInfo>(variable, new RevertInfo() { Type = previousType, Variable = variable.Store })); // Load the existing value. var nameExpression = new NameExpression(variable.Scope, variable.Name); nameExpression.GenerateGet(generator, optimizationInfo, false); // Store the typed value. variable.Store = generator.DeclareVariable(variableInfo.Type); variable.Type = variableInfo.Type; nameExpression.GenerateSet(generator, optimizationInfo, previousType, false); } } // The variables must be reverted even in the presence of exceptions. if (previousVariableTypes.Count > 0) { generator.BeginExceptionBlock(); // Setting the InsideTryCatchOrFinally flag converts BR instructions into LEAVE // instructions so that the finally block is executed correctly. optimizationInfo.InsideTryCatchOrFinally = true; } } // The inner loop starts here. var startOfLoop = generator.DefineLabelPosition(); // Check the condition and jump to the end if it is false. if (this.ConditionStatement != null) { optimizationInfo.MarkSequencePoint(generator, this.ConditionStatement.SourceSpan); this.Condition.GenerateCode(generator, optimizationInfo); EmitConversion.ToBool(generator, this.Condition.ResultType); generator.BranchIfFalse(breakTarget2); } // Emit the loop body. optimizationInfo.PushBreakOrContinueInfo(this.Labels, breakTarget2, continueTarget, labelledOnly: false); this.Body.GenerateCode(generator, optimizationInfo); optimizationInfo.PopBreakOrContinueInfo(); // The continue statement jumps here. generator.DefineLabelPosition(continueTarget); // Increment the loop variable. if (this.IncrementStatement != null) this.IncrementStatement.GenerateCode(generator, optimizationInfo); // Unconditionally branch back to the start of the loop. generator.Branch(startOfLoop); // Define the end of the loop (actually just after). generator.DefineLabelPosition(breakTarget2); // Revert the variable types. if (previousVariableTypes != null && previousVariableTypes.Count > 0) { // Revert the InsideTryCatchOrFinally flag. optimizationInfo.InsideTryCatchOrFinally = previousInsideTryCatchOrFinally; // Revert the variable types within a finally block. generator.BeginFinallyBlock(); foreach (var previousVariableAndType in previousVariableTypes) { var variable = previousVariableAndType.Key; var variableRevertInfo = previousVariableAndType.Value; // Load the existing value. var nameExpression = new NameExpression(variable.Scope, variable.Name); nameExpression.GenerateGet(generator, optimizationInfo, false); // Store the typed value. var previousType = variable.Type; variable.Store = variableRevertInfo.Variable; variable.Type = variableRevertInfo.Type; nameExpression.GenerateSet(generator, optimizationInfo, previousType, false); } // End the exception block. generator.EndExceptionBlock(); } // Define the end of the loop (actually just after). generator.DefineLabelPosition(breakTarget1); // Generate code for the end of the statement. GenerateEndOfStatement(generator, optimizationInfo, statementLocals); }
/// <summary> /// Generates CIL for the statement. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> public override void GenerateCode(ILGenerator generator, OptimizationInfo optimizationInfo) { // Generate code for the start of the statement. var statementLocals = new StatementLocals() { NonDefaultSourceSpanBehavior = true }; GenerateStartOfStatement(generator, optimizationInfo, statementLocals); // Unlike in .NET, in javascript there are no restrictions on what can appear inside // try, catch and finally blocks. The one restriction which causes problems is the // inability to jump out of .NET finally blocks. This is required when break, continue // or return statements appear inside of a finally block. To work around this, when // inside a finally block these instructions throw an exception instead. // Setting the InsideTryCatchOrFinally flag converts BR instructions into LEAVE // instructions so that the finally block is executed correctly. var previousInsideTryCatchOrFinally = optimizationInfo.InsideTryCatchOrFinally; optimizationInfo.InsideTryCatchOrFinally = true; // Finally requires two exception nested blocks. if (this.FinallyBlock != null) { generator.BeginExceptionBlock(); } // Begin the exception block. generator.BeginExceptionBlock(); // Generate code for the try block. this.TryBlock.GenerateCode(generator, optimizationInfo); // Generate code for the catch block. ILLocalVariable skipFinallyBlock = null; if (this.CatchBlock != null) { // Begin a catch block. The exception is on the top of the stack. generator.BeginCatchBlock(typeof(Exception)); // Check the exception is catchable by calling CanCatchException(ex). // We need to handle the case where JS code calls into .NET code which then throws // a JavaScriptException from a different ScriptEngine. var endOfIfLabel = generator.CreateLabel(); generator.Duplicate(); // ex var exceptionTemporary = generator.CreateTemporaryVariable(typeof(Exception)); generator.StoreVariable(exceptionTemporary); EmitHelpers.LoadScriptEngine(generator); generator.LoadVariable(exceptionTemporary); generator.ReleaseTemporaryVariable(exceptionTemporary); generator.Call(ReflectionHelpers.ScriptEngine_CanCatchException); generator.BranchIfTrue(endOfIfLabel); generator.LoadBoolean(true); if (this.FinallyBlock != null) { skipFinallyBlock = generator.DeclareVariable(typeof(bool), "skipFinallyBlock"); generator.StoreVariable(skipFinallyBlock); } generator.Rethrow(); generator.DefineLabelPosition(endOfIfLabel); // Create a new DeclarativeScope. this.CatchScope.GenerateScopeCreation(generator, optimizationInfo); // Store the error object in the variable provided. generator.Call(ReflectionHelpers.JavaScriptException_ErrorObject); var catchVariable = new NameExpression(this.CatchScope, this.CatchVariableName); catchVariable.GenerateSet(generator, optimizationInfo, PrimitiveType.Any, false); // Make sure the scope is reverted even if an exception is thrown. generator.BeginExceptionBlock(); // Emit code for the statements within the catch block. this.CatchBlock.GenerateCode(generator, optimizationInfo); // Revert the scope. generator.BeginFinallyBlock(); this.CatchScope.GenerateScopeDestruction(generator, optimizationInfo); generator.EndExceptionBlock(); } // Generate code for the finally block. if (this.FinallyBlock != null) { generator.BeginFinallyBlock(); // If an exception was thrown that wasn't handled by the catch block, then don't // run the finally block either. This prevents user code from being run when a // ThreadAbortException is thrown. var endOfFinallyBlock = generator.CreateLabel(); if (skipFinallyBlock != null) { var endOfSkipFinallyBlockLabel = generator.CreateLabel(); generator.LoadVariable(skipFinallyBlock); generator.BranchIfTrue(endOfFinallyBlock); } var branches = new List <ILLabel>(); var previousStackSize = optimizationInfo.LongJumpStackSizeThreshold; optimizationInfo.LongJumpStackSizeThreshold = optimizationInfo.BreakOrContinueStackSize; var previousCallback = optimizationInfo.LongJumpCallback; optimizationInfo.LongJumpCallback = (generator2, label) => { // It is not possible to branch out of a finally block - therefore instead of // generating LEAVE instructions we throw an exception then catch it to transfer // control out of the finally block. generator2.LoadInt32(branches.Count); generator2.NewObject(ReflectionHelpers.LongJumpException_Constructor); generator2.Throw(); // Record any branches that are made within the finally code. branches.Add(label); }; // Emit code for the finally block. this.FinallyBlock.GenerateCode(generator, optimizationInfo); // Define the position at the end of the finally block. generator.DefineLabelPosition(endOfFinallyBlock); // End the main exception block. generator.EndExceptionBlock(); // Begin a catch block to catch any LongJumpExceptions. The exception object is on // the top of the stack. generator.BeginCatchBlock(typeof(LongJumpException)); if (branches.Count > 0) { // switch (exception.RouteID) // { // case 0: goto label1; // case 1: goto label2; // } ILLabel[] switchLabels = new ILLabel[branches.Count]; for (int i = 0; i < branches.Count; i++) { switchLabels[i] = generator.CreateLabel(); } generator.Call(ReflectionHelpers.LongJumpException_RouteID); generator.Switch(switchLabels); for (int i = 0; i < branches.Count; i++) { generator.DefineLabelPosition(switchLabels[i]); generator.Leave(branches[i]); } } // Reset the state we clobbered. optimizationInfo.LongJumpStackSizeThreshold = previousStackSize; optimizationInfo.LongJumpCallback = previousCallback; } // End the exception block. generator.EndExceptionBlock(); // Reset the InsideTryCatchOrFinally flag. optimizationInfo.InsideTryCatchOrFinally = previousInsideTryCatchOrFinally; // Generate code for the end of the statement. GenerateEndOfStatement(generator, optimizationInfo, statementLocals); }
/// <summary> /// Generates CIL for the statement. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> public override void GenerateCode(ILGenerator generator, OptimizationInfo optimizationInfo) { // Generate code for the start of the statement. var statementLocals = new StatementLocals(); GenerateStartOfStatement(generator, optimizationInfo, statementLocals); // Unlike in .NET, in javascript there are no restrictions on what can appear inside // try, catch and finally blocks. The one restriction which causes problems is the // inability to jump out of .NET finally blocks. This is required when break, continue // or return statements appear inside of a finally block. To work around this, when // inside a finally block these instructions throw an exception instead. // Setting the InsideTryCatchOrFinally flag converts BR instructions into LEAVE // instructions so that the finally block is executed correctly. var previousInsideTryCatchOrFinally = optimizationInfo.InsideTryCatchOrFinally; optimizationInfo.InsideTryCatchOrFinally = true; // Finally requires two exception nested blocks. if (this.FinallyBlock != null) { generator.BeginExceptionBlock(); } // Begin the exception block. generator.BeginExceptionBlock(); // Generate code for the try block. this.TryBlock.GenerateCode(generator, optimizationInfo); // Generate code for the catch block. if (this.CatchBlock != null) { // Begin a catch block. The exception is on the top of the stack. generator.BeginCatchBlock(typeof(JavaScriptException)); // Create a new DeclarativeScope. this.CatchScope.GenerateScopeCreation(generator, optimizationInfo); // Store the error object in the variable provided. generator.Call(ReflectionHelpers.JavaScriptException_ErrorObject); var catchVariable = new NameExpression(this.CatchScope, this.CatchVariableName); catchVariable.GenerateSet(generator, optimizationInfo, PrimitiveType.Any, false); // Make sure the scope is reverted even if an exception is thrown. generator.BeginExceptionBlock(); // Emit code for the statements within the catch block. this.CatchBlock.GenerateCode(generator, optimizationInfo); // Revert the scope. generator.BeginFinallyBlock(); this.CatchScope.GenerateScopeDestruction(generator, optimizationInfo); generator.EndExceptionBlock(); } // Generate code for the finally block. if (this.FinallyBlock != null) { generator.BeginFinallyBlock(); var branches = new List <ILLabel>(); var previousStackSize = optimizationInfo.LongJumpStackSizeThreshold; optimizationInfo.LongJumpStackSizeThreshold = optimizationInfo.BreakOrContinueStackSize; var previousCallback = optimizationInfo.LongJumpCallback; optimizationInfo.LongJumpCallback = (generator2, label) => { // It is not possible to branch out of a finally block - therefore instead of // generating LEAVE instructions we throw an exception then catch it to transfer // control out of the finally block. generator2.LoadInt32(branches.Count); generator2.NewObject(ReflectionHelpers.LongJumpException_Constructor); generator2.Throw(); // Record any branches that are made within the finally code. branches.Add(label); }; // Emit code for the finally block. this.FinallyBlock.GenerateCode(generator, optimizationInfo); // End the main exception block. generator.EndExceptionBlock(); // Begin a catch block to catch any LongJumpExceptions. The exception object is on // the top of the stack. generator.BeginCatchBlock(typeof(LongJumpException)); if (branches.Count > 0) { // switch (exception.RouteID) // { // case 0: goto label1; // case 1: goto label2; // } ILLabel[] switchLabels = new ILLabel[branches.Count]; for (int i = 0; i < branches.Count; i++) { switchLabels[i] = generator.CreateLabel(); } generator.Call(ReflectionHelpers.LongJumpException_RouteID); generator.Switch(switchLabels); for (int i = 0; i < branches.Count; i++) { generator.DefineLabelPosition(switchLabels[i]); generator.Leave(branches[i]); } } // Reset the state we clobbered. optimizationInfo.LongJumpStackSizeThreshold = previousStackSize; optimizationInfo.LongJumpCallback = previousCallback; } // End the exception block. generator.EndExceptionBlock(); // Reset the InsideTryCatchOrFinally flag. optimizationInfo.InsideTryCatchOrFinally = previousInsideTryCatchOrFinally; // Generate code for the end of the statement. GenerateEndOfStatement(generator, optimizationInfo, statementLocals); }
/// <summary> /// Generates IL for the script. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> protected override void GenerateCode(ILGenerator generator, OptimizationInfo optimizationInfo) { // Method signature: object FunctionDelegate(Compiler.Scope scope, object thisObject, Library.FunctionInstance functionObject, object[] arguments) // Initialize the scope (note: the initial scope for a function is always declarative). this.InitialScope.GenerateScopeCreation(generator, optimizationInfo); // In ES3 the "this" value must be an object. See 10.4.3 in the spec. if (this.StrictMode == false && this.MethodOptimizationHints.HasThis == true) { // if (thisObject == null || thisObject == Null.Value || thisObject == Undefined.Value) EmitHelpers.LoadThis(generator); generator.LoadNull(); generator.CompareEqual(); EmitHelpers.LoadThis(generator); EmitHelpers.EmitNull(generator); generator.CompareEqual(); generator.BitwiseOr(); EmitHelpers.LoadThis(generator); EmitHelpers.EmitUndefined(generator); generator.CompareEqual(); generator.BitwiseOr(); // { var startOfFalse = generator.CreateLabel(); generator.BranchIfFalse(startOfFalse); // thisObject = engine.Global; EmitHelpers.LoadScriptEngine(generator); generator.Call(ReflectionHelpers.ScriptEngine_Global); // } else { var endOfIf = generator.CreateLabel(); generator.Branch(endOfIf); generator.DefineLabelPosition(startOfFalse); // thisObject = TypeConverter.ToObject(thisObject); EmitHelpers.LoadThis(generator); EmitConversion.ToObject(generator, PrimitiveType.Any, optimizationInfo); // } generator.DefineLabelPosition(endOfIf); EmitHelpers.StoreThis(generator); } // Transfer the function name into the scope. if (string.IsNullOrEmpty(this.Name) == false && (this.DeclarationType != FunctionDeclarationType.Getter && this.DeclarationType != FunctionDeclarationType.Setter) && this.Arguments.Any(a => a.Name == this.Name) == false && optimizationInfo.MethodOptimizationHints.HasVariable(this.Name)) { EmitHelpers.LoadFunction(generator); var functionName = new NameExpression(this.InitialScope, this.Name); functionName.GenerateSet(generator, optimizationInfo, PrimitiveType.Any, false); } // Transfer the arguments object into the scope. if (this.MethodOptimizationHints.HasArguments == true && this.Arguments.Any(a => a.Name == "arguments") == false) { // prototype EmitHelpers.LoadScriptEngine(generator); generator.Call(ReflectionHelpers.ScriptEngine_Object); generator.Call(ReflectionHelpers.FunctionInstance_InstancePrototype); // callee EmitHelpers.LoadFunction(generator); generator.CastClass(typeof(Library.UserDefinedFunction)); // scope EmitHelpers.LoadScope(generator); generator.CastClass(typeof(DeclarativeScope)); // argumentValues EmitHelpers.LoadArgumentsArray(generator); generator.NewObject(ReflectionHelpers.Arguments_Constructor); var arguments = new NameExpression(this.InitialScope, "arguments"); arguments.GenerateSet(generator, optimizationInfo, PrimitiveType.Any, false); } // Transfer the argument values into the scope. // Note: the arguments array can be smaller than expected. if (this.Arguments.Count > 0) { for (int i = 0; i < this.Arguments.Count; i++) { // Check if a duplicate argument name exists. bool duplicate = false; for (int j = i + 1; j < this.Arguments.Count; j++) { if (this.Arguments[i].Name == this.Arguments[j].Name) { duplicate = true; break; } } if (duplicate == true) { continue; } var loadDefaultValue = generator.CreateLabel(); var storeValue = generator.CreateLabel(); // Check if an array element exists. EmitHelpers.LoadArgumentsArray(generator); generator.LoadArrayLength(); generator.LoadInt32(i); generator.BranchIfLessThanOrEqual(loadDefaultValue); // Load the parameter value from the parameters array. EmitHelpers.LoadArgumentsArray(generator); generator.LoadInt32(i); generator.LoadArrayElement(typeof(object)); if (this.Arguments[i].DefaultValue == null) { // Branch to the part where it stores the value. generator.Branch(storeValue); // Load undefined. generator.DefineLabelPosition(loadDefaultValue); EmitHelpers.EmitUndefined(generator); } else { // Check if it's undefined. generator.Duplicate(); EmitHelpers.EmitUndefined(generator); generator.BranchIfNotEqual(storeValue); generator.Pop(); // Load the default value. generator.DefineLabelPosition(loadDefaultValue); this.Arguments[i].DefaultValue.GenerateCode(generator, optimizationInfo); EmitConversion.ToAny(generator, this.Arguments[i].DefaultValue.ResultType); } // Store the value in the scope. generator.DefineLabelPosition(storeValue); var argument = new NameExpression(this.InitialScope, this.Arguments[i].Name); argument.GenerateSet(generator, optimizationInfo, PrimitiveType.Any, false); } } // Initialize any declarations. this.InitialScope.GenerateDeclarations(generator, optimizationInfo); // Generate code for the body of the function. this.AbstractSyntaxTree.GenerateCode(generator, optimizationInfo); // Define the return target - this is where the return statement jumps to. // ReturnTarget can be null if there were no return statements. if (optimizationInfo.ReturnTarget != null) { generator.DefineLabelPosition(optimizationInfo.ReturnTarget); } // Load the return value. If the variable is null, there were no return statements. if (optimizationInfo.ReturnVariable != null) { // Return the value stored in the variable. Will be null if execution hits the end // of the function without encountering any return statements. generator.LoadVariable(optimizationInfo.ReturnVariable); } else { // There were no return statements - return null. generator.LoadNull(); } }
/// <summary> /// Generates CIL for the statement. /// </summary> /// <param name="generator"> The generator to output the CIL to. </param> /// <param name="optimizationInfo"> Information about any optimizations that should be performed. </param> public override void GenerateCode(ILGenerator generator, OptimizationInfo optimizationInfo) { // Generate code for the start of the statement. var statementLocals = new StatementLocals() { NonDefaultBreakStatementBehavior = true, NonDefaultDebugInfoBehavior = true }; GenerateStartOfStatement(generator, optimizationInfo, statementLocals); // <initializer> // if (<condition>) // { // <loop body> // <increment> // while (true) { // if (<condition> == false) // break; // // <body statements> // // continue-target: // <increment> // } // } // break-target: // Set up some labels. var continueTarget = generator.CreateLabel(); var breakTarget1 = generator.CreateLabel(); var breakTarget2 = generator.CreateLabel(); // Emit the initialization statement. if (this.InitStatement != null) { this.InitStatement.GenerateCode(generator, optimizationInfo); } // Check the condition and jump to the end if it is false. if (this.CheckConditionAtEnd == false && this.ConditionStatement != null) { if (optimizationInfo.DebugDocument != null) { generator.MarkSequencePoint(optimizationInfo.DebugDocument, this.ConditionStatement.DebugInfo); } this.Condition.GenerateCode(generator, optimizationInfo); EmitConversion.ToBool(generator, this.Condition.ResultType); generator.BranchIfFalse(breakTarget1); } // Emit the loop body. optimizationInfo.PushBreakOrContinueInfo(this.Labels, breakTarget1, continueTarget, false); this.Body.GenerateCode(generator, optimizationInfo); optimizationInfo.PopBreakOrContinueInfo(); // Increment the loop variable. if (this.IncrementStatement != null) { this.IncrementStatement.GenerateCode(generator, optimizationInfo); } // Strengthen the variable types. List <KeyValuePair <Scope.DeclaredVariable, RevertInfo> > previousVariableTypes = null; if (optimizationInfo.OptimizeInferredTypes == true) { // Keep a record of the variable types before strengthening. previousVariableTypes = new List <KeyValuePair <Scope.DeclaredVariable, RevertInfo> >(); var typedVariables = FindTypedVariables(); foreach (var variableAndType in typedVariables) { var variable = variableAndType.Key; var variableInfo = variableAndType.Value; if (variableInfo.Conditional == false && variableInfo.Type != variable.Type) { // Save the previous type so we can restore it later. var previousType = variable.Type; previousVariableTypes.Add(new KeyValuePair <Scope.DeclaredVariable, RevertInfo>(variable, new RevertInfo() { Type = previousType, Variable = variable.Store })); // Load the existing value. var nameExpression = new NameExpression(variable.Scope, variable.Name); nameExpression.GenerateGet(generator, optimizationInfo, false); // Store the typed value. variable.Store = generator.DeclareVariable(variableInfo.Type); variable.Type = variableInfo.Type; nameExpression.GenerateSet(generator, optimizationInfo, previousType, false); } } // The variables must be reverted even in the presence of exceptions. if (previousVariableTypes.Count > 0) { generator.BeginExceptionBlock(); } } // The inner loop starts here. var startOfLoop = generator.DefineLabelPosition(); // Check the condition and jump to the end if it is false. if (this.ConditionStatement != null) { if (optimizationInfo.DebugDocument != null) { generator.MarkSequencePoint(optimizationInfo.DebugDocument, this.ConditionStatement.DebugInfo); } this.Condition.GenerateCode(generator, optimizationInfo); EmitConversion.ToBool(generator, this.Condition.ResultType); generator.BranchIfFalse(breakTarget2); } // Emit the loop body. optimizationInfo.PushBreakOrContinueInfo(this.Labels, breakTarget2, continueTarget, labelledOnly: false); this.Body.GenerateCode(generator, optimizationInfo); optimizationInfo.PopBreakOrContinueInfo(); // The continue statement jumps here. generator.DefineLabelPosition(continueTarget); // Increment the loop variable. if (this.IncrementStatement != null) { this.IncrementStatement.GenerateCode(generator, optimizationInfo); } // Unconditionally branch back to the start of the loop. generator.Branch(startOfLoop); // Define the end of the loop (actually just after). generator.DefineLabelPosition(breakTarget2); // Revert the variable types. if (previousVariableTypes != null && previousVariableTypes.Count > 0) { // Revert the variable types within a finally block. generator.BeginFinallyBlock(); foreach (var previousVariableAndType in previousVariableTypes) { var variable = previousVariableAndType.Key; var variableRevertInfo = previousVariableAndType.Value; // Load the existing value. var nameExpression = new NameExpression(variable.Scope, variable.Name); nameExpression.GenerateGet(generator, optimizationInfo, false); // Store the typed value. var previousType = variable.Type; variable.Store = variableRevertInfo.Variable; variable.Type = variableRevertInfo.Type; nameExpression.GenerateSet(generator, optimizationInfo, previousType, false); } // End the exception block. generator.EndExceptionBlock(); } // Define the end of the loop (actually just after). generator.DefineLabelPosition(breakTarget1); // Generate code for the end of the statement. GenerateEndOfStatement(generator, optimizationInfo, statementLocals); }