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);
        }