public void Warning(string content) { if (!string.IsNullOrWhiteSpace(content)) { content = "/* " + content.Replace("*/", "*") + " */"; } _logger.Warning(content); }
private TranslationResult TryToTranslateValueSettingStatementAsSimpleFunctionValueReturner( TranslationResult translationResult, ICodeBlock block, ScopeAccessInformation scopeAccessInformation, int indentationDepth) { if (translationResult == null) { throw new ArgumentNullException("translationResult"); } if (block == null) { throw new ArgumentNullException("block"); } if (scopeAccessInformation == null) { throw new ArgumentNullException("scopeAccessInformation"); } if (indentationDepth < 0) { throw new ArgumentOutOfRangeException("indentationDepth", "must be zero or greater"); } var valueSettingStatement = block as ValueSettingStatement; if (valueSettingStatement == null) { return(null); } var translatedStatementContentDetails = _statementTranslator.Translate( valueSettingStatement.Expression, scopeAccessInformation, (valueSettingStatement.ValueSetType == ValueSettingStatement.ValueSetTypeOptions.Set) ? ExpressionReturnTypeOptions.Reference : ExpressionReturnTypeOptions.Value, _logger.Warning ); var undeclaredVariables = translatedStatementContentDetails.VariablesAccessed .Where(v => !scopeAccessInformation.IsDeclaredReference(v, _nameRewriter)); foreach (var undeclaredVariable in undeclaredVariables) { _logger.Warning("Undeclared variable: \"" + undeclaredVariable.Content + "\" (line " + (undeclaredVariable.LineIndex + 1) + ")"); } return(translationResult .Add(new TranslatedStatement( "return " + translatedStatementContentDetails.TranslatedContent + ";", indentationDepth, valueSettingStatement.Expression.Tokens.First().LineIndex )) .AddUndeclaredVariables(undeclaredVariables)); }
public TranslationResult Translate(ForEachBlock forEachBlock, ScopeAccessInformation scopeAccessInformation, int indentationDepth) { if (forEachBlock == null) { throw new ArgumentNullException("forEachBlock"); } if (scopeAccessInformation == null) { throw new ArgumentNullException("scopeAccessInformation"); } if (indentationDepth < 0) { throw new ArgumentOutOfRangeException("indentationDepth", "must be zero or greater"); } // The approach here is to get an IEnumerator reference and then loop over it in a "while (true)" loop, exiting when there are no more items. It would // feel more natural to use a C# foreach loop but the loop variable may not be restricted in scope to the loop (in fact, in VBScript this is very unlikely) // and so a "Type and identifier are both required in a foreach statement" compile error would result - "foreach (i in a)" is not valid, it must be of the // form "foreach (var i in a)" which only works if "i" is limited in scope to that loop. The "while (true)" structure also works better when error-trapping // may be enabled since the call-MoveNext-and-set-loop-variable-to-enumerator-Current-value-if-not-reached-end-of-data can be bypassed entirely if an error // was caught while evaluating the enumerator (in which case the loop should be processed once but the loop variable not set). // Note: The looped-over content must be of type "Reference" since VBScript won't enumerate over strings, for example, whereas C# would be happy to. // However, to make the output marginally easier to read, the ENUMERABLE method will deal with this logic and so the ExpressionReturnTypeOptions // value passed to the statement translator is "NotSpecified". var loopSourceContent = _statementTranslator.Translate(forEachBlock.LoopSrc, scopeAccessInformation, ExpressionReturnTypeOptions.NotSpecified, _logger.Warning); var undeclaredVariablesInLoopSourceContent = loopSourceContent.GetUndeclaredVariablesAccessed(scopeAccessInformation, _nameRewriter); foreach (var undeclaredVariable in undeclaredVariablesInLoopSourceContent) { _logger.Warning("Undeclared variable: \"" + undeclaredVariable.Content + "\" (line " + (undeclaredVariable.LineIndex + 1) + ")"); } var translationResult = TranslationResult.Empty.AddUndeclaredVariables(undeclaredVariablesInLoopSourceContent); var enumerationContentVariableName = _tempNameGenerator(new CSharpName("enumerationContent"), scopeAccessInformation); var enumeratorInitialisationContent = string.Format( "{0} = {1}.ENUMERABLE({2}).GetEnumerator();", enumerationContentVariableName.Name, _supportRefName.Name, loopSourceContent.TranslatedContent ); if (!scopeAccessInformation.IsDeclaredReference(forEachBlock.LoopVar, _nameRewriter)) { translationResult = translationResult.AddUndeclaredVariables(new[] { forEachBlock.LoopVar }); } var rewrittenLoopVarName = _nameRewriter.GetMemberAccessTokenName(forEachBlock.LoopVar); var loopVarTargetContainer = scopeAccessInformation.GetNameOfTargetContainerIfAnyRequired(forEachBlock.LoopVar, _envRefName, _outerRefName, _nameRewriter); if (loopVarTargetContainer != null) { rewrittenLoopVarName = loopVarTargetContainer.Name + "." + rewrittenLoopVarName; } if (scopeAccessInformation.ErrorRegistrationTokenIfAny == null) { translationResult = translationResult.Add(new TranslatedStatement( "var " + enumeratorInitialisationContent, indentationDepth, forEachBlock.LoopVar.LineIndex )); } else { // If ON ERROR RESUME NEXT wraps a FOR EACH loop and there is an error in evaluating the enumerator, then the loop will be entered once. The // loop variable will not be altered - eg. // // On Error Resume Next // Dim i: For Each i in "12" // WScript.Echo "We're in the loop! i is " & TypeName(i) // Next // // VBScript can not enumerate a string, so the loop errors. But the ON ERROR RESUME NEXT causes the loop to be processed - only once. This is // approached by calling _.ENUMERABLE before the loop construct, setting a temporary variable to be the returned enumerable inside a call to // "HANDLEERROR" - meaning that it will be left as null if it fails. Null values are replaced with a single-item array, where the element is // the current value of the loop variable - so its value is not altered when the loop is entered. // - Note: The "error-trapping" wrapper functions ("HANDLEERROR") are used around code that MAY have error-trapping enabled (there are cases // where we can't know at translation time whether it will have been turned off with ON ERROR GOTO 0 or not - and there are probably some // cases that could be picked up if the translation process was more intelligent). If the above example had an ON ERROR GOTO 0 between the // ON ERROR RESUME NEXT and the FOR EACH loop then the error (about trying to enumerate a string) would be raised and so the FOR EACH would // not be entered, and so the single-element "fallback array" would never come to exist. If errors WERE still being captured, then the // translated FOR EACH loop WOULD be entered and enumerated through once (without the value of the loop variable being altered, as // is consistent with VBScript) translationResult = translationResult .Add(new TranslatedStatement( string.Format( "IEnumerator {0} = null;", enumerationContentVariableName.Name ), indentationDepth, forEachBlock.LoopVar.LineIndex )) .Add(new TranslatedStatement( string.Format( "{0}.HANDLEERROR({1}, () => {{", _supportRefName.Name, scopeAccessInformation.ErrorRegistrationTokenIfAny.Name ), indentationDepth, forEachBlock.LoopVar.LineIndex )) .Add(new TranslatedStatement( enumeratorInitialisationContent, indentationDepth + 1, forEachBlock.LoopVar.LineIndex )) .Add(new TranslatedStatement("});", indentationDepth, forEachBlock.LoopVar.LineIndex)); } translationResult = translationResult .Add(new TranslatedStatement("while (true)", indentationDepth, forEachBlock.LoopVar.LineIndex)) .Add(new TranslatedStatement("{", indentationDepth, forEachBlock.LoopVar.LineIndex)); if (scopeAccessInformation.ErrorRegistrationTokenIfAny != null) { // If error-trapping is enabled and an error was indeed trapped while trying evaluate the enumerator, then the enumerator will be null. // In this case, the loop should be executed once but the loop variable not set to anything. When this happens, there is no point trying // to call MoveNext (since the enumerator is null) and the loop-variable-setting should be skipped. So an is-null check is wrapper around // that work. If error-trapping is not enabled then this check is not required and a level of nesting in the translated output can be // avoided. translationResult = translationResult .Add(new TranslatedStatement( string.Format( "if ({0} != null)", enumerationContentVariableName.Name ), indentationDepth + 1, forEachBlock.LoopVar.LineIndex )) .Add(new TranslatedStatement("{", indentationDepth + 1, forEachBlock.LoopVar.LineIndex)); indentationDepth++; } translationResult = translationResult .Add(new TranslatedStatement(string.Format( "if (!{0}.MoveNext())", enumerationContentVariableName.Name ), indentationDepth + 1, forEachBlock.LoopVar.LineIndex )) .Add(new TranslatedStatement("break;", indentationDepth + 2, forEachBlock.LoopVar.LineIndex)) .Add(new TranslatedStatement( string.Format( "{0} = {1}.Current;", rewrittenLoopVarName, enumerationContentVariableName.Name ), indentationDepth + 1, forEachBlock.LoopVar.LineIndex )); if (scopeAccessInformation.ErrorRegistrationTokenIfAny != null) { // If error-trapping may be enabled then the above MoveNext and set-to-Current work was wrapped in a condition which must be closed translationResult = translationResult.Add(new TranslatedStatement("}", indentationDepth, forEachBlock.LoopVar.LineIndex)); indentationDepth--; } var earlyExitNameIfAny = GetEarlyExitNameIfRequired(forEachBlock, scopeAccessInformation); if (earlyExitNameIfAny != null) { translationResult = translationResult.Add(new TranslatedStatement( string.Format("var {0} = false;", earlyExitNameIfAny.Name), indentationDepth + 1, forEachBlock.LoopVar.LineIndex )); } translationResult = translationResult.Add( Translate( forEachBlock.Statements.ToNonNullImmutableList(), scopeAccessInformation.SetParent(forEachBlock), earlyExitNameIfAny, indentationDepth + 1 ) ); if (scopeAccessInformation.ErrorRegistrationTokenIfAny != null) { // If error-trapping was enabled and an error caught, then the loop should be processed once and only once. The enumerator reference // will be null - so check for that and exit if so. If there is no chance that error-trapping is enabled then this condition is not // required and there is no point emitting it. translationResult = translationResult .Add(new TranslatedStatement( string.Format( "if ({0} == null)", enumerationContentVariableName.Name ), indentationDepth + 1, forEachBlock.LoopVar.LineIndex )) .Add(new TranslatedStatement("break;", indentationDepth + 2, forEachBlock.LoopVar.LineIndex)); } translationResult = translationResult.Add(new TranslatedStatement("}", indentationDepth, forEachBlock.LoopVar.LineIndex)); var earlyExitFlagNamesToCheck = scopeAccessInformation.StructureExitPoints .Where(e => e.ExitEarlyBooleanNameIfAny != null) .Select(e => e.ExitEarlyBooleanNameIfAny.Name); if (earlyExitFlagNamesToCheck.Any()) { // Perform early-exit checks for any scopeAccessInformation.StructureExitPoints - if this is FOR loop inside a DO..LOOP loop and an // EXIT DO was encountered within the FOR that must refer to the containing DO, then the FOR loop will have been broken out of, but // also a flag set that means that we must break further to get out of the DO loop. translationResult = translationResult .Add(new TranslatedStatement( "if (" + string.Join(" || ", earlyExitFlagNamesToCheck) + ")", indentationDepth, forEachBlock.LoopVar.LineIndex )) .Add(new TranslatedStatement( "break;", indentationDepth + 1, forEachBlock.LoopVar.LineIndex )); } return(translationResult); }
public TranslationResult Translate(WithBlock withBlock, ScopeAccessInformation scopeAccessInformation, int indentationDepth) { if (withBlock == null) { throw new ArgumentNullException("withBlock"); } if (scopeAccessInformation == null) { throw new ArgumentNullException("scopeAccessInformation"); } if (indentationDepth < 0) { throw new ArgumentOutOfRangeException("indentationDepth", "must be zero or greater"); } var translatedTargetReference = _statementTranslator.Translate(withBlock.Target, scopeAccessInformation, ExpressionReturnTypeOptions.Reference, _logger.Warning); var undeclaredVariables = translatedTargetReference.VariablesAccessed .Where(v => !scopeAccessInformation.IsDeclaredReference(v, _nameRewriter)); foreach (var undeclaredVariable in undeclaredVariables) { _logger.Warning("Undeclared variable: \"" + undeclaredVariable.Content + "\" (line " + (undeclaredVariable.LineIndex + 1) + ")"); } var targetName = base._tempNameGenerator(new CSharpName("with"), scopeAccessInformation); var withBlockContentTranslationResult = Translate( withBlock.Content.ToNonNullImmutableList(), new ScopeAccessInformation( withBlock, scopeAccessInformation.ScopeDefiningParent, scopeAccessInformation.ParentReturnValueNameIfAny, scopeAccessInformation.ErrorRegistrationTokenIfAny, new ScopeAccessInformation.DirectedWithReferenceDetails( targetName, withBlock.Target.Tokens.First().LineIndex ), scopeAccessInformation.ExternalDependencies, scopeAccessInformation.Classes, scopeAccessInformation.Functions, scopeAccessInformation.Properties, scopeAccessInformation.Constants, scopeAccessInformation.Variables, scopeAccessInformation.StructureExitPoints ), indentationDepth ); return(new TranslationResult( withBlockContentTranslationResult.TranslatedStatements .Insert( new TranslatedStatement( string.Format( "var {0} = {1};", targetName.Name, translatedTargetReference.TranslatedContent ), indentationDepth, withBlock.Target.Tokens.First().LineIndex ), 0 ), withBlockContentTranslationResult.ExplicitVariableDeclarations, withBlockContentTranslationResult.UndeclaredVariablesAccessed.AddRange(undeclaredVariables) )); }
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); }
public TranslationResult Translate(EraseStatement eraseStatement, ScopeAccessInformation scopeAccessInformation, int indentationDepth) { if (eraseStatement == null) { throw new ArgumentNullException("eraseStatement"); } if (scopeAccessInformation == null) { throw new ArgumentNullException("scopeAccessInformation"); } if (indentationDepth < 0) { throw new ArgumentOutOfRangeException("indentationDepth", "must be zero or greater"); } // We need to work out what tokens in the target(s) or their argument(s) reference any by-ref arguments in the containing function (where applicable). For the case // where there is a single target which is itself a single NameToken, if this is a by-ref argument of the containing function then this will definitely need rewriting. // The FuncByRefArgumentMapper doesn't know about this since it is not aware that that token will be reference inside a "targetSetter" lambda, so this needs to be // checked explicitly first. In an ideal world, this would be done later on, with the other "success case" logic - but then we'd also have to replicate the functionality // in the FuncByRefArgumentMapper that removes any duplicates from the byRefArgumentsToRewrite set (preferring any with read-write mappings over read-only). After this // case is handled, all other targets (and any arguments they have) can be rewritten. These won't introduce any "surprising" lambdas, but if there is any error-trapping // involved then the erase target evaluation will be wrapped in a HANDLEERROR lambda, but the FuncByRefArgumentMapper IS aware of that and will deal with it accordingly. var byRefMapper = new FuncByRefArgumentMapper(_nameRewriter, _tempNameGenerator, _logger); var byRefArgumentsToRewrite = new NonNullImmutableList <FuncByRefMapping>(); if (eraseStatement.Targets.Count() == 1) { var singleEraseTargetForByRefAliasingConsideration = eraseStatement.Targets.Single(); if ((singleEraseTargetForByRefAliasingConsideration.Target.Tokens.Count() == 1) && (singleEraseTargetForByRefAliasingConsideration.ArgumentsIfAny == null) && !singleEraseTargetForByRefAliasingConsideration.WrappedInBraces) { var singleTargetNameToken = singleEraseTargetForByRefAliasingConsideration.Target.Tokens.Single() as NameToken; var containingFunctionOrProperty = scopeAccessInformation.ScopeDefiningParent as AbstractFunctionBlock; if ((singleTargetNameToken != null) && (containingFunctionOrProperty != null)) { var targetByRefFunctionArgumentIfApplicable = containingFunctionOrProperty.Parameters .Where(p => p.ByRef) .FirstOrDefault(p => _nameRewriter.GetMemberAccessTokenName(p.Name) == _nameRewriter.GetMemberAccessTokenName(singleTargetNameToken)); if (targetByRefFunctionArgumentIfApplicable != null) { byRefArgumentsToRewrite = byRefArgumentsToRewrite.Add(new FuncByRefMapping( targetByRefFunctionArgumentIfApplicable.Name, _tempNameGenerator(new CSharpName("byrefalias"), scopeAccessInformation), mappedValueIsReadOnly: false )); } } } } foreach (var targetDetails in eraseStatement.Targets) { byRefArgumentsToRewrite = byRefMapper.GetByRefArgumentsThatNeedRewriting( targetDetails.Target.ToStageTwoParserExpression(scopeAccessInformation, ExpressionReturnTypeOptions.NotSpecified, _logger.Warning), scopeAccessInformation, byRefArgumentsToRewrite ); if (targetDetails.ArgumentsIfAny != null) { foreach (var argument in targetDetails.ArgumentsIfAny) { byRefArgumentsToRewrite = byRefMapper.GetByRefArgumentsThatNeedRewriting( argument.ToStageTwoParserExpression(scopeAccessInformation, ExpressionReturnTypeOptions.NotSpecified, _logger.Warning), scopeAccessInformation, byRefArgumentsToRewrite ); } } } if (byRefArgumentsToRewrite.Any()) { eraseStatement = new EraseStatement( eraseStatement.Targets.Select(targetDetails => { var rewrittenTarget = byRefArgumentsToRewrite.RewriteExpressionUsingByRefArgumentMappings(targetDetails.Target, _nameRewriter); if (targetDetails.ArgumentsIfAny == null) { return(new EraseStatement.TargetDetails(rewrittenTarget, null, targetDetails.WrappedInBraces)); } return(new EraseStatement.TargetDetails( rewrittenTarget, targetDetails.ArgumentsIfAny.Select(argument => byRefArgumentsToRewrite.RewriteExpressionUsingByRefArgumentMappings(argument, _nameRewriter)), targetDetails.WrappedInBraces )); }), eraseStatement.KeywordLineIndex ); } // If the ERASE call is invalid (eg. zero targets "ERASE" or multiple "ERASE a, b" or not a possible by-ref target "ERASE (a)" or "ERASE a.Name" or an invalid array / // reference / method call "ERASE a()") then evaluate the targets (to be consistent with VBScript's behaviour) but then raise an error. string exceptionStatementIfTargetConfigurationIsInvalid; if (eraseStatement.Targets.Count() != 1) { exceptionStatementIfTargetConfigurationIsInvalid = string.Format( "throw new Exception(\"Wrong number of arguments: 'Erase' (line {0})\");", eraseStatement.KeywordLineIndex + 1 ); } else { var eraseTargetToValidate = eraseStatement.Targets.Single(); if ((eraseTargetToValidate.WrappedInBraces) || (eraseTargetToValidate.Target.Tokens.Count() > 1) || !(eraseTargetToValidate.Target.Tokens.Single() is NameToken)) { // "Erase (a)" is invalid, it would result in "a" being passed by-val, which would be senseless when trying to erase a dynamic array // "Erase a.Roles" is invalid, the target must be a direct reference (again, since an indirect reference like this would not be passed by-ref) exceptionStatementIfTargetConfigurationIsInvalid = string.Format( "throw new TypeMismatchException(\"'Erase' (line {0})\");", eraseStatement.KeywordLineIndex + 1 ); } else { // Ensure that the single NameToken in the single erase target is a variable (a function call will result in a "Type mismatch" error) var singleTargetNameToken = (NameToken)eraseTargetToValidate.Target.Tokens.Single(); var targetReferenceDetails = scopeAccessInformation.TryToGetDeclaredReferenceDetails(singleTargetNameToken, _nameRewriter); if ((targetReferenceDetails != null) && (targetReferenceDetails.ReferenceType != ReferenceTypeOptions.Variable)) { // Note: If the variable has not been declared then targetReferenceDetails will be null, but that means that it will become an undeclared variable later on, // it means that it's definitely not a function exceptionStatementIfTargetConfigurationIsInvalid = string.Format( "throw new TypeMismatchException(\"'Erase' (line {0})\");", eraseStatement.KeywordLineIndex + 1 ); } else { exceptionStatementIfTargetConfigurationIsInvalid = null; } } } var translationResult = TranslationResult.Empty; int numberOfIndentationLevelsToWithDrawAfterByRefArgumentsProcessed; if (byRefArgumentsToRewrite.Any()) { scopeAccessInformation = scopeAccessInformation.ExtendVariables( byRefArgumentsToRewrite .Select(r => new ScopedNameToken(r.To.Name, r.From.LineIndex, ScopeLocationOptions.WithinFunctionOrPropertyOrWith)) .ToNonNullImmutableList() ); var byRefMappingOpeningTranslationDetails = byRefArgumentsToRewrite.OpenByRefReplacementDefinitionWork(translationResult, indentationDepth, _nameRewriter); translationResult = byRefMappingOpeningTranslationDetails.TranslationResult; numberOfIndentationLevelsToWithDrawAfterByRefArgumentsProcessed = byRefMappingOpeningTranslationDetails.DistanceToIndentCodeWithMappedValues; indentationDepth += numberOfIndentationLevelsToWithDrawAfterByRefArgumentsProcessed; } else { numberOfIndentationLevelsToWithDrawAfterByRefArgumentsProcessed = 0; } if (exceptionStatementIfTargetConfigurationIsInvalid != null) { foreach (var target in eraseStatement.Targets) { var targetExpressionTokens = target.Target.Tokens.ToList(); if (target.ArgumentsIfAny != null) { targetExpressionTokens.Add(new OpenBrace(targetExpressionTokens.Last().LineIndex)); foreach (var indexedArgument in target.ArgumentsIfAny.Select((a, i) => new { Index = i, Argument = a })) { if (indexedArgument.Index > 0) { targetExpressionTokens.Add(new ArgumentSeparatorToken(targetExpressionTokens.Last().LineIndex)); } targetExpressionTokens.AddRange(indexedArgument.Argument.Tokens); } targetExpressionTokens.Add(new CloseBrace(targetExpressionTokens.Last().LineIndex)); } var translatedTarget = _statementTranslator.Translate( new Expression(targetExpressionTokens), scopeAccessInformation, ExpressionReturnTypeOptions.NotSpecified, _logger.Warning ); var undeclaredVariablesReferencedByTarget = translatedTarget.GetUndeclaredVariablesAccessed(scopeAccessInformation, _nameRewriter); foreach (var undeclaredVariable in undeclaredVariablesReferencedByTarget) { _logger.Warning("Undeclared variable: \"" + undeclaredVariable.Content + "\" (line " + (undeclaredVariable.LineIndex + 1) + ")"); } translationResult = translationResult.Add(new TranslatedStatement( string.Format( "var {0} = {1};", _tempNameGenerator(new CSharpName("invalidEraseTarget"), scopeAccessInformation).Name, translatedTarget.TranslatedContent ), indentationDepth, target.Target.Tokens.First().LineIndex )); translationResult = translationResult.AddUndeclaredVariables(undeclaredVariablesReferencedByTarget); } translationResult = translationResult.Add(new TranslatedStatement( exceptionStatementIfTargetConfigurationIsInvalid, indentationDepth, eraseStatement.KeywordLineIndex )); } else { // If there are no target arguments then we use the ERASE signature that takes only the target (by-ref). Otherwise call the signature that tries to map the // arguments as indices on an array and then erases that element (which must also be an array) - in this case the target need not be passed by-ref. // - We know that the ArgumentsIfAny set will be null if there are no items in it, since a non-null-but-empty set is an error condition handled above var singleEraseTarget = eraseStatement.Targets.Single(); var translatedSingleEraseTarget = _statementTranslator.Translate( singleEraseTarget.Target, scopeAccessInformation, ExpressionReturnTypeOptions.NotSpecified, _logger.Warning ); var undeclaredVariablesInSingleEraseTarget = translatedSingleEraseTarget.GetUndeclaredVariablesAccessed(scopeAccessInformation, _nameRewriter).ToArray(); foreach (var undeclaredVariable in undeclaredVariablesInSingleEraseTarget) { _logger.Warning("Undeclared variable: \"" + undeclaredVariable.Content + "\" (line " + (undeclaredVariable.LineIndex + 1) + ")"); } translationResult = translationResult.AddUndeclaredVariables(undeclaredVariablesInSingleEraseTarget); if (singleEraseTarget.ArgumentsIfAny == null) { translationResult = translationResult.Add(new TranslatedStatement( string.Format( "{0}.ERASE({1}, {2} => {{ {1} = {2}; }});", _supportRefName.Name, translatedSingleEraseTarget.TranslatedContent, _tempNameGenerator(new CSharpName("v"), scopeAccessInformation).Name ), indentationDepth, singleEraseTarget.Target.Tokens.First().LineIndex )); } else { // Note: "Erase a()" is a runtime error condition - either "a" is an array, in which case it will be a "Subscript out of range" or "a" is a variable that // is not an array, in which case it will be a "Type mismatch" (we verified earlier that "a" is in fact a variable - and not a function, for example). // We have no choice but to let the ERASE function work this out at runtime (which the non-by-ref-argument signature will do). var translatedArguments = singleEraseTarget.ArgumentsIfAny .Select(argument => _statementTranslator.Translate( argument, scopeAccessInformation, ExpressionReturnTypeOptions.NotSpecified, _logger.Warning )) .ToArray(); // Going to evaluate everything twice, might as well ToArray it translationResult = translationResult.Add(new TranslatedStatement( string.Format( "{0}.ERASE({1}{2}{3});", _supportRefName.Name, translatedSingleEraseTarget.TranslatedContent, translatedArguments.Any() ? ", " : "", string.Join(", ", translatedArguments.Select(a => a.TranslatedContent)) ), indentationDepth, singleEraseTarget.Target.Tokens.First().LineIndex )); var undeclaredVariablesInArguments = translatedArguments.SelectMany(arg => arg.GetUndeclaredVariablesAccessed(scopeAccessInformation, _nameRewriter)).ToArray(); foreach (var undeclaredVariable in undeclaredVariablesInArguments) { _logger.Warning("Undeclared variable: \"" + undeclaredVariable.Content + "\" (line " + (undeclaredVariable.LineIndex + 1) + ")"); } translationResult = translationResult.AddUndeclaredVariables(undeclaredVariablesInArguments); } } if (byRefArgumentsToRewrite.Any()) { indentationDepth -= numberOfIndentationLevelsToWithDrawAfterByRefArgumentsProcessed; translationResult = byRefArgumentsToRewrite.CloseByRefReplacementDefinitionWork(translationResult, indentationDepth, _nameRewriter); } return(translationResult); }