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); }
/// <summary> /// If a function or property only contains a single executable block, which is a return statement, then this can be translated into a simple return /// statement in the C# output (as opposed to having to maintain a temporary variable for the return value in case there are various manipulations /// of it or error-handling or any other VBScript oddnes required) /// </summary> private bool IsSingleReturnValueStatementFunctionWithoutAnyByRefMappings(AbstractFunctionBlock functionBlock, ScopeAccessInformation scopeAccessInformation) { if (functionBlock == null) { throw new ArgumentNullException("functionBlock"); } if (scopeAccessInformation == null) { throw new ArgumentNullException("scopeAccessInformation"); } var executableStatements = functionBlock.Statements.Where(s => !(s is INonExecutableCodeBlock)); if (executableStatements.Count() != 1) { return(false); } var valueSettingStatement = executableStatements.Single() as ValueSettingStatement; if (valueSettingStatement == null) { return(false); } if (valueSettingStatement.ValueToSet.Tokens.Count() != 1) { return(false); } var valueToSetTokenAsNameToken = valueSettingStatement.ValueToSet.Tokens.Single() as NameToken; if (valueToSetTokenAsNameToken == null) { return(false); } if (_nameRewriter.GetMemberAccessTokenName(valueToSetTokenAsNameToken) != _nameRewriter.GetMemberAccessTokenName(functionBlock.Name)) { return(false); } // If there is no return value (ie. it's a SUB or a LET/SET PROPERTY accessor) then this can't apply (not only can this simple single-line // return format not be used but a runtime error is required if the value-setting statement targets the name of a SUB) if (!functionBlock.HasReturnValue) { return(false); } // If any values need aliasing in order to perform this "one liner" then it won't be possible to represent it a simple one-line return, it will // need a try..finally setting up to create the alias(es), use where required and then map the values back over the original(s). scopeAccessInformation = scopeAccessInformation.Extend(functionBlock, functionBlock.Statements.ToNonNullImmutableList()); var byRefArgumentMapper = new FuncByRefArgumentMapper(_nameRewriter, _tempNameGenerator, _logger); var byRefArgumentsToMap = byRefArgumentMapper.GetByRefArgumentsThatNeedRewriting( valueSettingStatement.Expression.ToStageTwoParserExpression(scopeAccessInformation, ExpressionReturnTypeOptions.NotSpecified, _logger.Warning), scopeAccessInformation, new NonNullImmutableList <FuncByRefMapping>() ); if (byRefArgumentsToMap.Any()) { return(false); } return(!valueSettingStatement.Expression.Tokens.Any( t => (t is NameToken) && (_nameRewriter.GetMemberAccessTokenName(t) == _nameRewriter.GetMemberAccessTokenName(functionBlock.Name)) )); }
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); }