public void IfThereAreZeroArgumentsThenSpecifyingArgumentProviderIsNotRequired() { var expressionToSet = new Expression(new IToken[] { new NameToken("a", 0), new MemberAccessorOrDecimalPointToken(".", 0), new NameToken("b", 0) }); var expressionToSetTo = new Expression(new[] { new NumericValueToken("1", 0) }); var expected = new TranslatedStatementContentDetails( "_.SET((Int16)1, this, _env.a, \"b\")", new NonNullImmutableList <NameToken>(new[] { new NameToken("a", 0) }) ); var scopeAccessInformation = GetEmptyScopeAccessInformation(); var actual = GetDefaultValueSettingStatementTranslator().Translate( new ValueSettingStatement( expressionToSet, expressionToSetTo, ValueSettingStatement.ValueSetTypeOptions.Let ), scopeAccessInformation ); Assert.Equal(expected, actual, new TranslatedStatementContentDetailsComparer()); }
public void ValueSettingTargetOfTypePropertyWithZeroArgumentBracketsResultsInTypeMismatch() { var expressionToSet = new Expression(new IToken[] { new NameToken("Name", 0), new OpenBrace(0), new CloseBrace(0) }); var expressionToSetTo = new Expression(new[] { new NumericValueToken("1", 0) }); var expected = new TranslatedStatementContentDetails( "_.SET((Int16)1, this, this, \"Name\")", new NonNullImmutableList <NameToken>(new[] { new NameToken("Name", 0) }) ); var scopeAccessInformation = AddPropertyToScope(GetEmptyScopeAccessInformation(), "Name", 0); var actual = GetDefaultValueSettingStatementTranslator().Translate( new ValueSettingStatement( expressionToSet, expressionToSetTo, ValueSettingStatement.ValueSetTypeOptions.Let ), scopeAccessInformation ); Assert.Equal(expected, actual, new TranslatedStatementContentDetailsComparer()); }
public void UndeclaredSimpleValueTypeUpdateToBoolean() { var expressionToSet = new Expression(new IToken[] { new NameToken("a", 0) }); var expressionToSetTo = new Expression(new[] { new BuiltInValueToken("true", 0) }); var expected = new TranslatedStatementContentDetails( "_env.a = true", new NonNullImmutableList <NameToken>(new[] { new NameToken("a", 0) }) ); var scopeAccessInformation = GetEmptyScopeAccessInformation(); var actual = GetDefaultValueSettingStatementTranslator().Translate( new ValueSettingStatement( expressionToSet, expressionToSetTo, ValueSettingStatement.ValueSetTypeOptions.Let ), scopeAccessInformation ); Assert.Equal(expected, actual, new TranslatedStatementContentDetailsComparer()); }
public void BuiltInFunctionsAreMappedToTheSupportClassButMayNotBeCalledDirectlyIfArgumentCountsMatch() { // This is a complement to BuiltInFunctionsAreMappedToTheSupportClassAndMayBeCalledDirectlyIfArgumentCountsMatch, where an incorrect number of // arguments is being passed to a support function. As such, it may not be called directly and must pass through the "CALL" method, so that the // mistake becomes a runtime error rather than compile time. On the plus side, all of the support functions may be called with ByVal parameters, // so the translated code is slightly more succinct that it would be if they had to support ByRef. var expressionToSet = new Expression(new IToken[] { new NameToken("a", 0) }); var expressionToSetTo = new Expression(new IToken[] { new BuiltInFunctionToken("CDate", 0), new OpenBrace(0), new NameToken("a", 0), new ArgumentSeparatorToken(0), new NameToken("b", 0), new CloseBrace(0) }); var expected = new TranslatedStatementContentDetails( "_env.a = _.VAL(_.CALL(this, _, \"CDATE\", _.ARGS.Val(_env.a).Val(_env.b)))", new NonNullImmutableList <NameToken>(new[] { new NameToken("a", 0), new NameToken("b", 0) }) ); var scopeAccessInformation = GetEmptyScopeAccessInformation(); var actual = GetDefaultValueSettingStatementTranslator().Translate( new ValueSettingStatement( expressionToSet, expressionToSetTo, ValueSettingStatement.ValueSetTypeOptions.Let ), scopeAccessInformation ); Assert.Equal(expected, actual, new TranslatedStatementContentDetailsComparer()); }
public void InvalidFunctionSettingMustCompileThoughFailAtRunTime() { var expressionToSet = new Expression(new IToken[] { new NameToken("a", 0), new OpenBrace(0), new NumericValueToken("1", 0), new CloseBrace(0) }); var expressionToSetTo = new Expression(new[] { new NumericValueToken("1", 0) }); var expected = new TranslatedStatementContentDetails( "_.SET((Int16)1, this, _.RAISEERROR(new IllegalAssignmentException(\"'a'\")), null, _.ARGS.Val((Int16)1))", new NonNullImmutableList <NameToken>(new[] { new NameToken("a", 0) }) ); var scopeAccessInformation = AddOutermostScopeFunction( GetEmptyScopeAccessInformation(), "a", 0 ); var actual = GetDefaultValueSettingStatementTranslator().Translate( new ValueSettingStatement( expressionToSet, expressionToSetTo, ValueSettingStatement.ValueSetTypeOptions.Let ), scopeAccessInformation ); Assert.Equal(expected, actual, new TranslatedStatementContentDetailsComparer()); }
public void BuiltInFunctionsAreMappedToTheSupportClassAndMayBeCalledDirectlyIfArgumentCountsMatch() { // CDate(..) needs to be mapped to _.CDATE(..) - this may be called directly if the correct number of arguments are specified. If an incorrect number // of arguments is passed then the support function must be executed via the "CALL" method (so that the error arises at runtime, rather than compile // time, in order to be consistent with VBScript), see BuiltInFunctionsAreMappedToTheSupportClassButMayNotBeCalledDirectlyIfArgumentCountsMatch. var expressionToSet = new Expression(new IToken[] { new NameToken("a", 0) }); var expressionToSetTo = new Expression(new IToken[] { new BuiltInFunctionToken("CDate", 0), new OpenBrace(0), new NameToken("a", 0), new CloseBrace(0) }); var expected = new TranslatedStatementContentDetails( "_env.a = _.CDATE(_env.a)", new NonNullImmutableList <NameToken>(new[] { new NameToken("a", 0) }) ); var scopeAccessInformation = GetEmptyScopeAccessInformation(); var actual = GetDefaultValueSettingStatementTranslator().Translate( new ValueSettingStatement( expressionToSet, expressionToSetTo, ValueSettingStatement.ValueSetTypeOptions.Let ), scopeAccessInformation ); Assert.Equal(expected, actual, new TranslatedStatementContentDetailsComparer()); }
public void OutermostScopeDeclaredSimpleValueTypeUpdate() { var expressionToSet = new Expression(new IToken[] { new NameToken("a", 0) }); var expressionToSetTo = new Expression(new[] { new NumericValueToken("1", 0) }); var expected = new TranslatedStatementContentDetails( "_outer.a = (Int16)1", new NonNullImmutableList <NameToken>(new[] { new NameToken("a", 0) }) ); var scopeAccessInformation = AddOutermostScopeVariable( GetEmptyScopeAccessInformation(), "a", 0 ); var actual = GetDefaultValueSettingStatementTranslator().Translate( new ValueSettingStatement( expressionToSet, expressionToSetTo, ValueSettingStatement.ValueSetTypeOptions.Let ), scopeAccessInformation ); Assert.Equal(expected, actual, new TranslatedStatementContentDetailsComparer()); }
public void UndeclaredSimpleValueTypeUpdateOfArray() { var expressionToSet = new Expression(new IToken[] { new NameToken("a", 0), new OpenBrace(0), new NumericValueToken("1", 0), new CloseBrace(0) }); var expressionToSetTo = new Expression(new[] { new NumericValueToken("1", 0) }); var expected = new TranslatedStatementContentDetails( "_.SET((Int16)1, this, _env.a, null, _.ARGS.Val((Int16)1))", new NonNullImmutableList <NameToken>(new[] { new NameToken("a", 0) }) ); var scopeAccessInformation = GetEmptyScopeAccessInformation(); var actual = GetDefaultValueSettingStatementTranslator().Translate( new ValueSettingStatement( expressionToSet, expressionToSetTo, ValueSettingStatement.ValueSetTypeOptions.Let ), scopeAccessInformation ); Assert.Equal(expected, actual, new TranslatedStatementContentDetailsComparer()); }
public void NestedBracketAndFurtherMemberAccessExpressionShouldBePassedByValIntoFunctions() { // When "a.b(0).c" is considered as an argument, it should be identified as ByVal (since only a direct reference - eg. "a" or even "a(0)" if "a" // is an array - can be changed ByRef as a function argument). Before adding this test, there was an issue where "a.b(0).c" would throw an exception // during translation. var expression = new Expression(new[] { new CallSetExpressionSegment(new[] { new CallSetItemExpressionSegment( new[] { new NameToken("a", 0), new NameToken("b", 0) }, new[] { new Expression(new[] { new NumericValueExpressionSegment(new NumericValueToken("0", 0)) }) }, zeroArgumentBracketsPresence: null ), new CallSetItemExpressionSegment( new[] { new NameToken("c", 0) }, new Expression[0], CallSetItemExpressionSegment.ArgumentBracketPresenceOptions.Absent ) }) }); var expected = new TranslatedStatementContentDetails( "_.ARGS.Val(_.CALL(this, _.CALL(this, _env.a, \"b\", _.ARGS.Val((Int16)0)), \"c\"))", new NonNullImmutableList <NameToken>(new[] { new NameToken("a", 0) }) ); Assert.Equal( expected, GetDefaultStatementTranslator().TranslateAsArgumentProvider(new[] { expression }, GetEmptyScopeAccessInformation(), forceAllArgumentsToBeByVal: false), new TranslatedStatementContentDetailsComparer() ); }
public void IsolatedFunctionCallAccordingToScopeDoesNotHaveValueTypeAccessLogic() { // "o" (where there is a function in scope called "o") var expression = new Expression(new[] { new CallExpressionSegment( new[] { new NameToken("o", 0) }, new Expression[0], CallExpressionSegment.ArgumentBracketPresenceOptions.Absent ) }); var scopeAccessInformation = AddOutermostScopeFunction( GetEmptyScopeAccessInformation(), "o", 0 ); var expected = new TranslatedStatementContentDetails( "_.CALL(this, _outer, \"o\")", new NonNullImmutableList <NameToken>(new[] { new NameToken("o", 0) }) ); Assert.Equal( expected, GetDefaultStatementTranslator().Translate(expression, scopeAccessInformation, ExpressionReturnTypeOptions.None), new TranslatedStatementContentDetailsComparer() ); }
public void UndeclaredSetTargetsWithinFunctionsAreScopeRestrictedToThatFunction() { // The ValueSettingStatementsTranslator wasn't using the ScopeAccessInformation's GetNameOfTargetContainerIfAnyRequired extension method and // was incorrectly applying the logic that it should have gotten for free by using that method - if an undeclared variable was being accessed // within a method (for the to-set target) then it was being mapped back to the "Environment References" class instead of being treated as // local to the function. var expressionToSet = new Expression(new IToken[] { new NameToken("a", 0) }); var expressionToSetTo = new Expression(new IToken[] { new NumericValueToken("1", 0) }); var valueSettingStatement = new ValueSettingStatement( expressionToSet, expressionToSetTo, ValueSettingStatement.ValueSetTypeOptions.Let ); var containingFunction = new FunctionBlock( isPublic: true, isDefault: false, name: new NameToken("F1", 0), parameters: new AbstractFunctionBlock.Parameter[0], statements: new[] { valueSettingStatement } ); var expected = new TranslatedStatementContentDetails( "a = (Int16)1", new NonNullImmutableList <NameToken>(new[] { new NameToken("a", 0) }) ); var scopeAccessInformation = GetEmptyScopeAccessInformation(); scopeAccessInformation = new ScopeAccessInformation( containingFunction, // parent containingFunction, // scopeDefiningParent new CSharpName("F1"), // parentReturnValueName scopeAccessInformation.ErrorRegistrationTokenIfAny, scopeAccessInformation.DirectedWithReferenceIfAny, scopeAccessInformation.ExternalDependencies, scopeAccessInformation.Classes, scopeAccessInformation.Functions.Add(new ScopedNameToken("F1", 0, ScopeLocationOptions.WithinFunctionOrPropertyOrWith)), scopeAccessInformation.Properties, scopeAccessInformation.Constants, scopeAccessInformation.Variables, scopeAccessInformation.StructureExitPoints ); var actual = GetDefaultValueSettingStatementTranslator().Translate(valueSettingStatement, scopeAccessInformation); Assert.Equal(expected, actual, new TranslatedStatementContentDetailsComparer()); }
public void NestedFunctionOrArrayAccess() { // "a(0)(b)" (where neither a nor b are defined and so there could be method calls OR array accesses) var expression = new Expression(new[] { new CallSetExpressionSegment(new[] { new CallSetItemExpressionSegment( new[] { new NameToken("a", 0) }, new[] { new Expression(new[] { new NumericValueExpressionSegment(new NumericValueToken("0", 0)) }) }, null ), new CallSetItemExpressionSegment( new IToken[0], new[] { new Expression(new[] { new CallExpressionSegment( new[] { new NameToken("b", 0) }, new Expression[0], CallSetItemExpressionSegment.ArgumentBracketPresenceOptions.Absent ) }) }, null ) }) }); // Since we can't know until runtime if "a" is an array that is being accessed or a function/property, the arguments need to // be constructed to work as ByVal or ByRef if it IS a function or property. Since "0" is a constant it will be ByVal but // since "b" is a variable it has to be marked as exligible for ByRef (this will not have any effect if "a(0)" is an // array or if it is an object with a default function or property whose argument is marked as ByVal, but we won't // know that until runtime). var expected = new TranslatedStatementContentDetails( "_.CALL(this, _.CALL(this, _env.a, _.ARGS.Val((Int16)0)), _.ARGS.Ref(_env.b, v0 => { _env.b = v0; }))", new NonNullImmutableList <NameToken>(new[] { new NameToken("a", 0), new NameToken("b", 0) }) ); Assert.Equal( expected, GetDefaultStatementTranslator().Translate(expression, GetEmptyScopeAccessInformation(), ExpressionReturnTypeOptions.None), new TranslatedStatementContentDetailsComparer() ); }
public void KnownVariablePassedAsArgumentToKnownFunctionIsPassedByValIfWrappedInBrackets() { // "o((a))" (where there is a function in scope called "o" and a variable "a") var expression = new Expression(new[] { new CallExpressionSegment( new[] { new NameToken("o", 0) }, new[] { new Expression(new[] { new BracketedExpressionSegment(new[] { new CallExpressionSegment(new[] { new NameToken("a", 0) }, new Expression[0], CallSetItemExpressionSegment.ArgumentBracketPresenceOptions.Absent) }) }) }, null ) }); var scopeAccessInformation = AddOutermostScopeVariable( AddOutermostScopeFunction( GetEmptyScopeAccessInformation(), "o", 0 ), "a", 0 ); var expected = new TranslatedStatementContentDetails( "_.CALL(this, _outer, \"o\", _.ARGS.Val(_outer.a))", new NonNullImmutableList <NameToken>(new[] { new NameToken("a", 0), new NameToken("o", 0) }) ); Assert.Equal( expected, GetDefaultStatementTranslator().Translate(expression, scopeAccessInformation, ExpressionReturnTypeOptions.None), new TranslatedStatementContentDetailsComparer() ); }
public void IsolatedNonFunctionOrPropertyReferenceHasValueTypeAccessLogic() { // "o" (where there is no function or property in scope called "o") var expression = new Expression(new[] { new CallExpressionSegment( new[] { new NameToken("o", 0) }, new Expression[0], CallExpressionSegment.ArgumentBracketPresenceOptions.Absent ) }); var expected = new TranslatedStatementContentDetails( "_.VAL(_env.o)", new NonNullImmutableList <NameToken>(new[] { new NameToken("o", 0) }) ); var scopeAccessInformation = GetEmptyScopeAccessInformation(); Assert.Equal( expected, GetDefaultStatementTranslator().Translate(expression, scopeAccessInformation, ExpressionReturnTypeOptions.None), new TranslatedStatementContentDetailsComparer() ); }
public TranslationResult Translate(IfBlock ifBlock, ScopeAccessInformation scopeAccessInformation, int indentationDepth) { if (ifBlock == null) { throw new ArgumentNullException("ifBlock"); } if (scopeAccessInformation == null) { throw new ArgumentNullException("scopeAccessInformation"); } if (indentationDepth < 0) { throw new ArgumentOutOfRangeException("indentationDepth", "must be zero or greater"); } // These TranslatedContent values are the content that will ultimately be forced into a boolean and used to construct an "if" conditional, it it C# code. If there is no // error -trapping to worry about then we just have to wrap this in an IF call (the "IF" method in the runtime support class) and be done with it. If error-trapping MAY // be involved, though, then it's more complicated - we might be able to just use the IF extension method that takes an error registration token as an argument (this is // the second least-complicated code path) but if we're within a function (or property) and the there any function or property calls within this translated content that // take by-ref arguments and any of those arguments are by-ref arguments of the containing function, then it will have to be re-written since C# will not allow "ref" // arguments to be manipulated in lambas (and that is how by-ref arguments are dealt with when calling nested functions or properties). This third arrangement is // the most complicated code path. var byRefArgumentIdentifier = new FuncByRefArgumentMapper(_nameRewriter, _tempNameGenerator, _logger); var conditionalClausesWithTranslatedConditions = ifBlock.ConditionalClauses .Select((conditional, index) => new { Index = index, Conditional = conditional, TranslatedContent = _statementTranslator.Translate( conditional.Condition, scopeAccessInformation, ExpressionReturnTypeOptions.NotSpecified, _logger.Warning ), ByRefArgumentsToRewriteInTranslatedContent = byRefArgumentIdentifier.GetByRefArgumentsThatNeedRewriting( conditional.Condition.ToStageTwoParserExpression(scopeAccessInformation, ExpressionReturnTypeOptions.NotSpecified, _logger.Warning), scopeAccessInformation, new NonNullImmutableList <FuncByRefMapping>() ) }) .ToArray(); var translationResult = TranslationResult.Empty; var numberOfAdditionalBlocksInjectedForErrorTrapping = 0; foreach (var conditionalEntry in conditionalClausesWithTranslatedConditions) { var conditionalContent = conditionalEntry.TranslatedContent; var previousConditionalEntry = (conditionalEntry.Index == 0) ? null : conditionalClausesWithTranslatedConditions[conditionalEntry.Index - 1]; // If we're dealing with multiple if (a).. elseif (b).. elseif (c).. [else] blocks then these would be most simply represented by if (a).. else if (b).. // else if (c).. [else] blocks in C#. However, if error-trapping is involved then some of the conditions may have to be rewritten to deal with by-ref arguments // and then (after the condition is evaluated) those rewritten arguments need to be pushed back on to the original references. In this case, each subsequent "if" // condition must be within its own "else" block in order for the the rewritten condition to be evaluated when required (and not before). When this happens, there // will be greater levels of nesting required. This nesting is injected here and tracked with the variable "numberOfAdditionalBlocksInjectedForErrorTrapping" (this // will be used further down to ensure that any extra levels of nesting are closed off). bool requiresNewScopeWithinElseBlock; if (previousConditionalEntry == null) { requiresNewScopeWithinElseBlock = false; } else { requiresNewScopeWithinElseBlock = previousConditionalEntry.ByRefArgumentsToRewriteInTranslatedContent.Any() || conditionalEntry.ByRefArgumentsToRewriteInTranslatedContent.Any(); } if (requiresNewScopeWithinElseBlock) { translationResult = translationResult.Add(new TranslatedStatement("else", indentationDepth, translationResult.TranslatedStatements.Last().LineIndexOfStatementStartInSource)); translationResult = translationResult.Add(new TranslatedStatement("{", indentationDepth, translationResult.TranslatedStatements.Last().LineIndexOfStatementStartInSource)); indentationDepth++; numberOfAdditionalBlocksInjectedForErrorTrapping++; } // Check whether there are any "ref" arguments that need rewriting - this is only applicable if we're within a function or property that has ByRef arguments. // If this is the case then we need to ensure that we do not emit code that tries to include those references within a lambda since that is not valid C#. One // way in which this may occur is the passing of a "ref" argument into another function as ByRef as part of the condition evaluation (since the argument provider // will update the value after the call completes using a lambda). The other way is if error-trapping might be enabled at runtime - in this case, the evaluation // of the condition is performed within a lambda so that any errors can be swallowed if necessary. If any such reference rewriting is required then the code that // must be emitted is more complex. var byRefArgumentsToRewrite = conditionalEntry.ByRefArgumentsToRewriteInTranslatedContent; if (byRefArgumentsToRewrite.Any()) { // If we're in a function or property and that function / property has by-ref arguments that we then need to pass into further function / property calls // in order to evaluate the current conditional, then we need to record those values in temporary references, try to evaulate the condition and then push // the temporary values back into the original references. This is required in order to be consistent with VBScript and yet also produce code that compiles // as C# (which will not let "ref" arguments of the containing function be used in lambdas, which is how we deal with updating by-ref arguments after function // or property calls complete). var evaluatedResultName = _tempNameGenerator(new CSharpName("ifResult"), scopeAccessInformation); scopeAccessInformation = scopeAccessInformation.ExtendVariables( byRefArgumentsToRewrite .Select(r => new ScopedNameToken(r.To.Name, r.From.LineIndex, ScopeLocationOptions.WithinFunctionOrPropertyOrWith)) .ToNonNullImmutableList() ); translationResult = translationResult.Add(new TranslatedStatement( "bool " + evaluatedResultName.Name + ";", indentationDepth, conditionalEntry.Conditional.Condition.Tokens.First().LineIndex )); var byRefMappingOpeningTranslationDetails = byRefArgumentsToRewrite.OpenByRefReplacementDefinitionWork(translationResult, indentationDepth, _nameRewriter); translationResult = byRefMappingOpeningTranslationDetails.TranslationResult; indentationDepth += byRefMappingOpeningTranslationDetails.DistanceToIndentCodeWithMappedValues; var rewrittenConditionalContent = _statementTranslator.Translate( byRefArgumentsToRewrite.RewriteExpressionUsingByRefArgumentMappings(conditionalEntry.Conditional.Condition, _nameRewriter), scopeAccessInformation, ExpressionReturnTypeOptions.NotSpecified, _logger.Warning ); var ifStatementFormat = (scopeAccessInformation.ErrorRegistrationTokenIfAny == null) ? "{0} = {1}.IF({2});" : "{0} = {1}.IF(() => {2}, {3});"; translationResult = translationResult.Add(new TranslatedStatement( string.Format( ifStatementFormat, evaluatedResultName.Name, _supportRefName.Name, rewrittenConditionalContent.TranslatedContent, (scopeAccessInformation.ErrorRegistrationTokenIfAny == null) ? "" : scopeAccessInformation.ErrorRegistrationTokenIfAny.Name ), indentationDepth, conditionalEntry.Conditional.Condition.Tokens.First().LineIndex )); indentationDepth -= byRefMappingOpeningTranslationDetails.DistanceToIndentCodeWithMappedValues; translationResult = byRefArgumentsToRewrite.CloseByRefReplacementDefinitionWork(translationResult, indentationDepth, _nameRewriter); conditionalContent = new TranslatedStatementContentDetails( evaluatedResultName.Name, rewrittenConditionalContent.VariablesAccessed ); } else if (scopeAccessInformation.MayRequireErrorWrapping(ifBlock)) { // If we're not in a function or property or if that function or property does not have any by-ref arguments that we need to pass in as by-ref arguments // to further functions or properties, then we're in the less complicate error-trapping scenario; we only have to use the IF extension method that deals // with error-trapping. conditionalContent = new TranslatedStatementContentDetails( string.Format( "{0}.IF(() => {1}, {2})", _supportRefName.Name, conditionalContent.TranslatedContent, scopeAccessInformation.ErrorRegistrationTokenIfAny.Name ), conditionalContent.VariablesAccessed ); } else { conditionalContent = new TranslatedStatementContentDetails( string.Format( "{0}.IF({1})", _supportRefName.Name, conditionalContent.TranslatedContent ), conditionalContent.VariablesAccessed ); } var undeclaredVariablesAccessed = conditionalContent.GetUndeclaredVariablesAccessed(scopeAccessInformation, _nameRewriter); foreach (var undeclaredVariable in undeclaredVariablesAccessed) { _logger.Warning("Undeclared variable: \"" + undeclaredVariable.Content + "\" (line " + (undeclaredVariable.LineIndex + 1) + ")"); } translationResult = translationResult.AddUndeclaredVariables(undeclaredVariablesAccessed); var innerStatements = conditionalEntry.Conditional.Statements.ToNonNullImmutableList(); var conditionalInlineCommentIfAny = !innerStatements.Any() ? null : (innerStatements.First() as InlineCommentStatement); if (conditionalInlineCommentIfAny != null) { innerStatements = innerStatements.RemoveAt(0); } translationResult = translationResult.Add( new TranslatedStatement( string.Format( "{0} ({1}){2}", (previousConditionalEntry == null) || requiresNewScopeWithinElseBlock ? "if" : "else if", conditionalContent.TranslatedContent, (conditionalInlineCommentIfAny == null) ? "" : (" //" + conditionalInlineCommentIfAny.Content) ), indentationDepth, conditionalEntry.Conditional.Condition.Tokens.First().LineIndex ) ); translationResult = translationResult.Add(new TranslatedStatement("{", indentationDepth, conditionalEntry.Conditional.Condition.Tokens.First().LineIndex)); translationResult = translationResult.Add( Translate( innerStatements, scopeAccessInformation.SetParent(ifBlock), indentationDepth + 1 ) ); translationResult = translationResult.Add(new TranslatedStatement("}", indentationDepth, translationResult.TranslatedStatements.Last().LineIndexOfStatementStartInSource)); } if (ifBlock.OptionalElseClause != null) { // Unlike the IF or ELSE IF lines, we don't have a LineIndex for the final ELSE block, so we'll just use the LineIndex of the previous line (we know that there // is one since it's not valid for an IfBlock to have ONLY an ELSE, it must have at least one IF before if). Note: We only have a LineIndex for the IF and ELSE // IF lines since those lines have conditions, which have tokens, and we use the LineIndex of the first token. translationResult = translationResult.Add(new TranslatedStatement("else", indentationDepth, translationResult.TranslatedStatements.Last().LineIndexOfStatementStartInSource)); translationResult = translationResult.Add(new TranslatedStatement("{", indentationDepth, translationResult.TranslatedStatements.Last().LineIndexOfStatementStartInSource)); translationResult = translationResult.Add( Translate( ifBlock.OptionalElseClause.Statements.ToNonNullImmutableList(), scopeAccessInformation.SetParent(ifBlock), indentationDepth + 1 ) ); translationResult = translationResult.Add(new TranslatedStatement("}", indentationDepth, translationResult.TranslatedStatements.Last().LineIndexOfStatementStartInSource)); } // If any additional levels of nesting were required above (for error-trapping scenarios), ensure they are closed off here for (var index = 0; index < numberOfAdditionalBlocksInjectedForErrorTrapping; index++) { indentationDepth--; translationResult = translationResult.Add(new TranslatedStatement("}", indentationDepth, translationResult.TranslatedStatements.Last().LineIndexOfStatementStartInSource)); } return(translationResult); }
public TranslationResult Translate(DoBlock doBlock, ScopeAccessInformation scopeAccessInformation, int indentationDepth) { if (doBlock == null) { throw new ArgumentNullException("doBlock"); } if (scopeAccessInformation == null) { throw new ArgumentNullException("scopeAccessInformation"); } if (indentationDepth < 0) { throw new ArgumentOutOfRangeException("indentationDepth", "must be zero or greater"); } if ((doBlock.ConditionIfAny == null) && !doBlock.Statements.Any()) { _logger.Warning("Infinite DO/WHILE loop at line " + (doBlock.LineIndexOfStartOfConstruct + 1)); return(TranslationResult.Empty.Add(new TranslatedStatement( "while (true) { }", indentationDepth, doBlock.LineIndexOfStartOfConstruct ))); } var earlyExitNameIfAny = GetEarlyExitNameIfRequired(doBlock, scopeAccessInformation); var loopStatementsTranslationResult = Translate( doBlock.Statements.ToNonNullImmutableList(), scopeAccessInformation.SetParent(doBlock), doBlock.SupportsExit, earlyExitNameIfAny, indentationDepth + 1 ); TranslatedStatementContentDetails whileConditionExpressionContentIfAny; if (doBlock.ConditionIfAny == null) { whileConditionExpressionContentIfAny = null; } else { whileConditionExpressionContentIfAny = _statementTranslator.Translate( doBlock.ConditionIfAny, scopeAccessInformation, ExpressionReturnTypeOptions.Boolean, _logger.Warning ); if (!doBlock.IsDoWhileCondition) { // C# doesn't support "DO UNTIL x" but it's equivalent to "DO WHILE !x" whileConditionExpressionContentIfAny = new TranslatedStatementContentDetails( "!" + whileConditionExpressionContentIfAny.TranslatedContent, whileConditionExpressionContentIfAny.VariablesAccessed ); } if (scopeAccessInformation.ErrorRegistrationTokenIfAny != null) { // Ensure that the frankly ludicrous VBScript error-handling is applied where required. As the IProvideVBScriptCompatFunctionality's IF method // signature describes, if an error occurs in retrieving the value, it will be evaluated as true. So, given a function // // FUNCTION GetValue() // Err.Raise vbObjectError, "Test", "Test" // END FUNCTION // // both of the following loops will be entered: // // ON ERROR RESUME NEXT // DO WHILE GetValue() // WScript.Echo "True" // EXIT DO // LOOP // // ON ERROR RESUME NEXT // DO UNTIL GetValue() // WScript.Echo "True" // EXIT DO // LOOP // // This is why an additional IF call must be wrapped around the IF above - since the negation of a DO UNTIL (as opposed to a DO WHILE) loop // must be within the outer, error-handling IF call. (A simpler example of the above is to replace the GetValue() call with 1/0, which will // result in a "Division by zero" error if ON ERROR RESUME NEXT is not present, but which will result in both of the above loops being // entered if it IS present). whileConditionExpressionContentIfAny = new TranslatedStatementContentDetails( string.Format( "{0}.IF(() => {1}, {2})", _supportRefName.Name, whileConditionExpressionContentIfAny.TranslatedContent, scopeAccessInformation.ErrorRegistrationTokenIfAny.Name ), whileConditionExpressionContentIfAny.VariablesAccessed ); } } var translationResult = TranslationResult.Empty; if (whileConditionExpressionContentIfAny != null) { translationResult = translationResult.AddUndeclaredVariables( whileConditionExpressionContentIfAny.GetUndeclaredVariablesAccessed(scopeAccessInformation, _nameRewriter) ); } if (whileConditionExpressionContentIfAny == null) { translationResult = translationResult.Add(new TranslatedStatement( "while (true)", indentationDepth, doBlock.LineIndexOfStartOfConstruct )); } else if (doBlock.IsPreCondition) { translationResult = translationResult.Add(new TranslatedStatement( "while (" + whileConditionExpressionContentIfAny.TranslatedContent + ")", indentationDepth, doBlock.LineIndexOfStartOfConstruct )); } else { translationResult = translationResult.Add(new TranslatedStatement( "do", indentationDepth, doBlock.LineIndexOfStartOfConstruct )); } translationResult = translationResult.Add(new TranslatedStatement("{", indentationDepth, doBlock.LineIndexOfStartOfConstruct)); if (earlyExitNameIfAny != null) { translationResult = translationResult.Add(new TranslatedStatement( string.Format("var {0} = false;", earlyExitNameIfAny.Name), indentationDepth + 1, doBlock.LineIndexOfStartOfConstruct )); } translationResult = translationResult.Add(loopStatementsTranslationResult); var lineIndexForClosingCode = loopStatementsTranslationResult.TranslatedStatements.Any() ? loopStatementsTranslationResult.TranslatedStatements.Last().LineIndexOfStatementStartInSource : doBlock.LineIndexOfStartOfConstruct; if ((whileConditionExpressionContentIfAny == null) || doBlock.IsPreCondition) { translationResult = translationResult.Add(new TranslatedStatement("}", indentationDepth, lineIndexForClosingCode)); } else { translationResult = translationResult.Add(new TranslatedStatement( "} while (" + whileConditionExpressionContentIfAny.TranslatedContent + ");", indentationDepth, doBlock.ConditionIfAny.Tokens.First().LineIndex )); } var earlyExitFlagNamesToCheck = scopeAccessInformation.StructureExitPoints .Where(e => e.ExitEarlyBooleanNameIfAny != null) .Select(e => e.ExitEarlyBooleanNameIfAny.Name); if (earlyExitFlagNamesToCheck.Any()) { // These lines do not directly have equivalents in the source, so just take the line index of the previous line that was generated // by the above code var lineIndexForEarlyExitCode = translationResult.TranslatedStatements.Last().LineIndexOfStatementStartInSource; // Perform early-exit checks for any scopeAccessInformation.StructureExitPoints - if this is DO..LOOP loop inside a FOR loop and an // EXIT FOR was encountered within the DO..LOOP that must refer to the containing FOR, then the DO..LOOP will have been broken out // of, but also a flag set that means that we must break further to get out of the FOR loop. translationResult = translationResult .Add(new TranslatedStatement( "if (" + string.Join(" || ", earlyExitFlagNamesToCheck) + ")", indentationDepth, lineIndexForEarlyExitCode )) .Add(new TranslatedStatement( "break;", indentationDepth + 1, lineIndexForEarlyExitCode )); } return(translationResult); }