Beispiel #1
0
        public static TranslationResult Add(this TranslationResult source, TranslatedStatement toAdd)
        {
            if (source == null)
            {
                throw new ArgumentNullException("source");
            }
            if (toAdd == null)
            {
                throw new ArgumentNullException("toAdd");
            }

            return(new TranslationResult(
                       source.TranslatedStatements.Add(toAdd),
                       source.ExplicitVariableDeclarations,
                       source.UndeclaredVariablesAccessed
                       ));
        }
        public NonNullImmutableList <TranslatedStatement> Translate(NonNullImmutableList <ICodeBlock> blocks)
        {
            if (blocks == null)
            {
                throw new ArgumentNullException("blocks");
            }

            // There are some date literal values that need to be validated at runtime (they may vary by culture - if they include a month name, basically, which will
            // depend upon the current language). If any of these literals are invalid for the runtime culture then no further processing may take place (this is the
            // same as the VBScript interpreter refusing to process a script when it reads it, in whatever culture is active when it executes). We need to build this
            // list before we remove any duplicate functions since, although VBScript will "overwrite" functions with the same name in the outermost scope, if the
            // functions it overwrites / ignores contained any invalid date literals, the interpreter will still not execute. We'll use this data further down..
            var dateLiteralsToValidateAtRuntime = EnumerateAllDateLiteralTokens(blocks)
                                                  .Where(d => d.RequiresRuntimeValidation)
                                                  .GroupBy(d => d.Content)
                                                  .Select(g => new { DateLiteralValue = g.Key, LineNumbers = g.Select(d => d.LineIndex + 1) })
                                                  .ToArray();

            // Note: There is no need to check for identically-named classes since that would cause a "Name Redefined" error even if Option Explicit was not enabled
            blocks = RemoveDuplicateFunctions(blocks);

            // Group the code blocks that need to be executed (the functions from the outermost scope need to go into a "global references" class
            // which will appear after any other classes, which will appear after everything else)
            var commentBuffer      = new NonNullImmutableList <CommentStatement>();
            var annotatedFunctions = new NonNullImmutableList <Annotated <AbstractFunctionBlock> >();
            var annotatedClasses   = new NonNullImmutableList <Annotated <ClassBlock> >();
            var otherBlocks        = new NonNullImmutableList <ICodeBlock>();

            foreach (var block in blocks)
            {
                var comment = block as CommentStatement;
                if (comment != null)
                {
                    commentBuffer = commentBuffer.Add(comment);
                    continue;
                }
                var functionBlock = block as AbstractFunctionBlock;
                if (functionBlock != null)
                {
                    annotatedFunctions = annotatedFunctions.Add(new Annotated <AbstractFunctionBlock>(commentBuffer, functionBlock));
                    commentBuffer      = new NonNullImmutableList <CommentStatement>();
                    continue;
                }
                var classBlock = block as ClassBlock;
                if (classBlock != null)
                {
                    annotatedClasses = annotatedClasses.Add(new Annotated <ClassBlock>(commentBuffer, classBlock));
                    commentBuffer    = new NonNullImmutableList <CommentStatement>();
                    continue;
                }
                otherBlocks   = otherBlocks.AddRange(commentBuffer).Add(block);
                commentBuffer = new NonNullImmutableList <CommentStatement>();
            }
            if (commentBuffer.Any())
            {
                otherBlocks = otherBlocks.AddRange(commentBuffer);
            }

            // Ensure that functions are in valid configurations - properties are not valid outside of classes and any non-public functions will
            // be translated INTO public functions (since this there are no private external functions in VBScript)
            annotatedFunctions = annotatedFunctions
                                 .Select(f =>
            {
                if (f.CodeBlock is PropertyBlock)
                {
                    throw new ArgumentException("Property encountered in OuterMostScope - these may only appear within classes: " + f.CodeBlock.Name.Content);
                }
                if (!f.CodeBlock.IsPublic)
                {
                    _logger.Warning("OuterScope function \"" + f.CodeBlock.Name.Content + "\" is private, this is invalid and will be changed to public");
                    if (f.CodeBlock is FunctionBlock)
                    {
                        return(new Annotated <AbstractFunctionBlock>(
                                   f.LeadingComments,
                                   new FunctionBlock(true, f.CodeBlock.IsDefault, f.CodeBlock.Name, f.CodeBlock.Parameters, f.CodeBlock.Statements)
                                   ));
                    }
                    else if (f.CodeBlock is SubBlock)
                    {
                        return(new Annotated <AbstractFunctionBlock>(
                                   f.LeadingComments,
                                   new SubBlock(true, f.CodeBlock.IsDefault, f.CodeBlock.Name, f.CodeBlock.Parameters, f.CodeBlock.Statements)
                                   ));
                    }
                    else
                    {
                        throw new ArgumentException("Unsupported AbstractFunctionBlock type: " + f.GetType());
                    }
                }
                return(f);
            })
                                 .ToNonNullImmutableList();

            var scopeAccessInformation = ScopeAccessInformation.FromOutermostScope(
                _startClassName,                 // A placeholder name is required for an OutermostScope instance and so is required by this method
                blocks,
                _externalDependencies
                );

            if (blocks.DoesScopeContainOnErrorResumeNext())
            {
                scopeAccessInformation = scopeAccessInformation.SetErrorRegistrationToken(
                    _tempNameGenerator(new CSharpName("errOn"), scopeAccessInformation)
                    );
            }

            var outerExecutableBlocksTranslationResult = Translate(
                otherBlocks.ToNonNullImmutableList(),
                scopeAccessInformation,
                3                 // indentationDepth
                );
            var explicitVariableDeclarationsFromWithOuterScope = outerExecutableBlocksTranslationResult.ExplicitVariableDeclarations;

            outerExecutableBlocksTranslationResult = new TranslationResult(
                outerExecutableBlocksTranslationResult.TranslatedStatements,
                new NonNullImmutableList <VariableDeclaration>(),
                outerExecutableBlocksTranslationResult.UndeclaredVariablesAccessed
                );

            var translatedStatements = new NonNullImmutableList <TranslatedStatement>();

            if (_outputType == OutputTypeOptions.Executable)
            {
                translatedStatements = translatedStatements.AddRange(new[]
                {
                    new TranslatedStatement("using System;", 0, 0),
                    new TranslatedStatement("using System.Collections;", 0, 0)
                });
                if (dateLiteralsToValidateAtRuntime.Any())
                {
                    // System.Collections.ObjectModel is only required for the ReadOnlyCollection, which is only used when there are date literals that need validating at runtime
                    translatedStatements = translatedStatements.Add(new TranslatedStatement("using System.Collections.ObjectModel;", 0, 0));
                }
                translatedStatements = translatedStatements.Add(new TranslatedStatement("using System.Runtime.InteropServices;", 0, 0));
                translatedStatements = translatedStatements.AddRange(new[]
                {
                    new TranslatedStatement("using " + typeof(IProvideVBScriptCompatFunctionalityToIndividualRequests).Namespace + ";", 0, 0),
                    new TranslatedStatement("using " + typeof(SourceClassName).Namespace + ";", 0, 0),
                    new TranslatedStatement("using " + typeof(SpecificVBScriptException).Namespace + ";", 0, 0),
                    new TranslatedStatement("using " + typeof(TranslatedPropertyIReflectImplementation).Namespace + ";", 0, 0),
                    new TranslatedStatement("", 0, 0),
                    new TranslatedStatement("namespace " + _startNamespace.Name, 0, 0),
                    new TranslatedStatement("{", 0, 0),
                    new TranslatedStatement("public class " + _startClassName.Name, 1, 0),
                    new TranslatedStatement("{", 1, 0),
                    new TranslatedStatement("private readonly " + typeof(IProvideVBScriptCompatFunctionalityToIndividualRequests).Name + " " + _supportRefName.Name + ";", 2, 0),
                    new TranslatedStatement("public " + _startClassName.Name + "(" + typeof(IProvideVBScriptCompatFunctionalityToIndividualRequests).Name + " compatLayer)", 2, 0),
                    new TranslatedStatement("{", 2, 0),
                    new TranslatedStatement("if (compatLayer == null)", 3, 0),
                    new TranslatedStatement("throw new ArgumentNullException(\"compatLayer\");", 4, 0),
                    new TranslatedStatement(_supportRefName.Name + " = compatLayer;", 3, 0),
                    new TranslatedStatement("}", 2, 0)
                });
                translatedStatements = translatedStatements.AddRange(new[]
                {
                    new TranslatedStatement("", 0, 0),
                    new TranslatedStatement(
                        string.Format(
                            "public {0} {1}()",
                            _outerClassName.Name,
                            _startMethodName.Name
                            ),
                        2,
                        0
                        ),
                    new TranslatedStatement("{", 2, 0),
                    new TranslatedStatement(
                        string.Format(
                            "return {0}(new {1}());",
                            _startMethodName.Name,
                            _envClassName.Name
                            ),
                        3,
                        0
                        ),
                    new TranslatedStatement("}", 2, 0),
                    new TranslatedStatement(
                        string.Format(
                            "public {0} {1}({2} env)",
                            _outerClassName.Name,
                            _startMethodName.Name,
                            _envClassName.Name
                            ),
                        2,
                        0
                        ),
                    new TranslatedStatement("{", 2, 0),
                    new TranslatedStatement("if (env == null)", 3, 0),
                    new TranslatedStatement("throw new ArgumentNullException(\"env\");", 4, 0),
                    new TranslatedStatement("", 0, 0),
                    new TranslatedStatement(
                        string.Format("var {0} = env;", _envRefName.Name),
                        3,
                        0
                        ),
                    new TranslatedStatement(
                        string.Format("var {0} = new {1}({2}, {3});", _outerRefName.Name, _outerClassName.Name, _supportRefName.Name, _envRefName.Name),
                        3,
                        0
                        )
                });
                if (dateLiteralsToValidateAtRuntime.Any())
                {
                    // When rendering in full Executable format (not just in WithoutScaffolding), if there were any date literals in the source content that could not
                    // be confirmed as valid at translation time (meaning they include a month name, which will vary in validity depending upon the culture of the
                    // environment at runtime) then these literals need to be validated each run before any other work is attempted. (This is the equivalent of
                    // the VBScript interpreter reading the script for every execution and validating date literals against the current culture - if it finds
                    // any of them to be invalid then it will raise a syntax error and not attempt to execute any of the script).
                    translatedStatements = translatedStatements.Add(new TranslatedStatement(
                                                                        string.Format(
                                                                            "{0}.ValidateAgainstCurrentCulture({1});",
                                                                            _runtimeDateLiteralValidatorClassName.Name,
                                                                            _supportRefName.Name
                                                                            ),
                                                                        3,
                                                                        0
                                                                        ));
                }
                translatedStatements = translatedStatements.Add(new TranslatedStatement("", 0, 0));
            }

            if (scopeAccessInformation.ErrorRegistrationTokenIfAny != null)
            {
                translatedStatements = translatedStatements
                                       .Add(new TranslatedStatement(
                                                string.Format(
                                                    "var {0} = {1}.GETERRORTRAPPINGTOKEN();",
                                                    scopeAccessInformation.ErrorRegistrationTokenIfAny.Name,
                                                    _supportRefName.Name
                                                    ),
                                                3,
                                                0
                                                ));
            }
            translatedStatements = translatedStatements.AddRange(
                outerExecutableBlocksTranslationResult.TranslatedStatements
                );
            if (scopeAccessInformation.ErrorRegistrationTokenIfAny != null)
            {
                translatedStatements = translatedStatements
                                       .Add(new TranslatedStatement(
                                                string.Format(
                                                    "{0}.RELEASEERRORTRAPPINGTOKEN({1});",
                                                    _supportRefName.Name,
                                                    scopeAccessInformation.ErrorRegistrationTokenIfAny.Name
                                                    ),
                                                3,
                                                0
                                                ));
            }
            if (_outputType == OutputTypeOptions.Executable)
            {
                // Close the main "TranslatedProgram" function and then write out the runtime-date-literal-validation logic and the global references class (when a complete executable
                // is not required, none of this is of much benefit and detracts from the core of what's being translated - most tests will specify WithoutScaffolding rather than
                // Executable so that just the real meat of the source code is generated)
                translatedStatements = translatedStatements.AddRange(new[]
                {
                    new TranslatedStatement(string.Format("return {0};", _outerRefName.Name), 3, 0),
                    new TranslatedStatement("}", 2, 0),
                    new TranslatedStatement("", 0, 0)
                });

                if (dateLiteralsToValidateAtRuntime.Any())
                {
                    // Declare a static readonly immutable set of date literals that need validating against the current culture before any request can do any actual work
                    translatedStatements = translatedStatements.AddRange(new[]
                    {
                        new TranslatedStatement(
                            string.Format(
                                "private static class {0}",
                                _runtimeDateLiteralValidatorClassName.Name
                                ),
                            2,
                            0
                            ),
                        new TranslatedStatement("{", 2, 0),
                        new TranslatedStatement("private static readonly ReadOnlyCollection<Tuple<string, int[]>> _literalsToValidate =", 3, 0),
                        new TranslatedStatement("new ReadOnlyCollection<Tuple<string, int[]>>(new[] {", 3, 0)
                    });
                    foreach (var indexedDateLiteralToValidate in dateLiteralsToValidateAtRuntime.Select((d, i) => new { Index = i, DateLiteralValue = d.DateLiteralValue, LineNumbers = d.LineNumbers }))
                    {
                        translatedStatements = translatedStatements.Add(new TranslatedStatement(
                                                                            string.Format(
                                                                                "Tuple.Create({0}, new[] {{ {1} }}){2}",
                                                                                indexedDateLiteralToValidate.DateLiteralValue.ToLiteral(),
                                                                                string.Join <int>(", ", indexedDateLiteralToValidate.LineNumbers),
                                                                                (indexedDateLiteralToValidate.Index < (dateLiteralsToValidateAtRuntime.Length - 1)) ? "," : ""
                                                                                ),
                                                                            4,
                                                                            0
                                                                            ));
                    }
                    translatedStatements = translatedStatements.Add(new TranslatedStatement("});", 3, 0));
                    translatedStatements = translatedStatements.Add(new TranslatedStatement("", 0, 0));

                    // Declare the function that reads the data above and performs the validation work
                    translatedStatements = translatedStatements.AddRange(new[]
                    {
                        new TranslatedStatement("public static void ValidateAgainstCurrentCulture(IProvideVBScriptCompatFunctionalityToIndividualRequests compatLayer)", 3, 0),
                        new TranslatedStatement("{", 3, 0),
                        new TranslatedStatement("if (compatLayer == null)", 4, 0),
                        new TranslatedStatement("throw new ArgumentNullException(\"compatLayer\");", 5, 0),
                        new TranslatedStatement("foreach (var dateLiteralValueAndLineNumbers in _literalsToValidate)", 4, 0),
                        new TranslatedStatement("{", 4, 0),
                        new TranslatedStatement(
                            string.Format(
                                "try {{ compatLayer.DateLiteralParser.Parse(dateLiteralValueAndLineNumbers.Item1); }}",
                                _runtimeDateLiteralValidatorClassName.Name
                                ),
                            5,
                            0
                            ),
                        new TranslatedStatement("catch", 5, 0),
                        new TranslatedStatement("{", 5, 0),
                        new TranslatedStatement("throw new SyntaxError(string.Format(", 6, 0),
                        new TranslatedStatement("\"Invalid date literal #{0}# on line{1} {2}\",", 7, 0),
                        new TranslatedStatement("dateLiteralValueAndLineNumbers.Item1,", 7, 0),
                        new TranslatedStatement("(dateLiteralValueAndLineNumbers.Item2.Length == 1) ? \"\" : \"s\",", 7, 0),
                        new TranslatedStatement("string.Join<int>(\", \", dateLiteralValueAndLineNumbers.Item2)", 7, 0),
                        new TranslatedStatement("));", 6, 0),
                        new TranslatedStatement("}", 5, 0),
                        new TranslatedStatement("}", 4, 0),
                        new TranslatedStatement("}", 3, 0),
                        new TranslatedStatement("}", 2, 0),
                        new TranslatedStatement("", 0, 0)
                    });
                }

                translatedStatements = translatedStatements.AddRange(new[]
                {
                    new TranslatedStatement("public class " + _outerClassName.Name, 2, 0),
                    new TranslatedStatement("{", 2, 0),
                    new TranslatedStatement("private readonly " + typeof(IProvideVBScriptCompatFunctionalityToIndividualRequests).Name + " " + _supportRefName.Name + ";", 3, 0),
                    new TranslatedStatement("private readonly " + _outerClassName.Name + " " + _outerRefName.Name + ";", 3, 0),
                    new TranslatedStatement("private readonly " + _envClassName.Name + " " + _envRefName.Name + ";", 3, 0),
                    new TranslatedStatement("public " + _outerClassName.Name + "(" + typeof(IProvideVBScriptCompatFunctionalityToIndividualRequests).Name + " compatLayer, " + _envClassName.Name + " env)", 3, 0),
                    new TranslatedStatement("{", 3, 0),
                    new TranslatedStatement("if (compatLayer == null)", 4, 0),
                    new TranslatedStatement("throw new ArgumentNullException(\"compatLayer\");", 5, 0),
                    new TranslatedStatement("if (env == null)", 4, 0),
                    new TranslatedStatement("throw new ArgumentNullException(\"env\");", 5, 0),
                    new TranslatedStatement(_supportRefName.Name + " = compatLayer;", 4, 0),
                    new TranslatedStatement(_envRefName.Name + " = env;", 4, 0),
                    new TranslatedStatement(_outerRefName.Name + " = this;", 4, 0)
                });

                // Note: Any repeated "explicitVariableDeclarationsFromWithOuterScope" entries are ignored - this makes the ReDim translation process easier (where ReDim statements
                // may target already-declared variables or they may be considered to implicitly declare them) but it means that the Dim translation has to do some extra work to
                // pick up on "Name redefined" scenarios.
                var variableInitialisationStatements = new NonNullImmutableList <TranslatedStatement>();
                foreach (var explicitVariableDeclaration in explicitVariableDeclarationsFromWithOuterScope)
                {
                    var variableInitialisationStatement = new TranslatedStatement(
                        base.TranslateVariableInitialisation(explicitVariableDeclaration, ScopeLocationOptions.OutermostScope),
                        4,
                        explicitVariableDeclaration.Name.LineIndex
                        );
                    if (!variableInitialisationStatements.Any(s => s.Content == variableInitialisationStatement.Content))
                    {
                        variableInitialisationStatements = variableInitialisationStatements.Add(variableInitialisationStatement);
                    }
                }
                translatedStatements = translatedStatements.AddRange(variableInitialisationStatements);
                translatedStatements = translatedStatements
                                       .Add(
                    new TranslatedStatement("}", 3, 0)
                    );
                if (explicitVariableDeclarationsFromWithOuterScope.Any())
                {
                    translatedStatements = translatedStatements.Add(new TranslatedStatement("", 0, 0));
                    var variableDeclarationStatements = new NonNullImmutableList <TranslatedStatement>();
                    foreach (var explicitVariableDeclaration in explicitVariableDeclarationsFromWithOuterScope)
                    {
                        var variableDeclarationStatement = new TranslatedStatement(
                            "public object " + _nameRewriter.GetMemberAccessTokenName(explicitVariableDeclaration.Name) + " { get; set; }",
                            3,
                            explicitVariableDeclaration.Name.LineIndex
                            );
                        if (!variableDeclarationStatements.Any(s => s.Content == variableDeclarationStatement.Content))
                        {
                            variableDeclarationStatements = variableDeclarationStatements.Add(variableDeclarationStatement);
                        }
                    }
                    translatedStatements = translatedStatements.AddRange(variableDeclarationStatements);
                }
            }
            foreach (var annotatedFunctionBlock in annotatedFunctions)
            {
                // Note that any variables that were accessed by code in the outermost scope, but that were not explicitly declared, are considered to be IMPLICITLY declared.
                // So, if they are referenced within any of the functions, they must share the same reference unless explicit declared within those functions (so if "a" is
                // set in the outermost scope and then a function has a statement "ReDim a(2)", that "a" reference should be that one in the outermost scope). To achieve
                // this, the "implicitly declared" outermost scope variables are added to the ExternalDependencies set in the ScopeAccessInformation instance provided
                // to the function block translator. The same must be done for class block translation (see below).
                translatedStatements = translatedStatements.Add(new TranslatedStatement("", 1, 0));
                translatedStatements = translatedStatements.AddRange(
                    Translate(
                        annotatedFunctionBlock.LeadingComments.Cast <ICodeBlock>().Concat(new[] { annotatedFunctionBlock.CodeBlock }).ToNonNullImmutableList(),
                        scopeAccessInformation.ExtendExternalDependencies(outerExecutableBlocksTranslationResult.UndeclaredVariablesAccessed),
                        3                         // indentationDepth
                        ).TranslatedStatements
                    );
            }
            var classBlocksTranslationResult = Translate(
                TrimTrailingBlankLines(
                    annotatedClasses.SelectMany(c => c.LeadingComments.Cast <ICodeBlock>().Concat(new[] { c.CodeBlock })).ToNonNullImmutableList()
                    ),
                scopeAccessInformation.ExtendExternalDependencies(outerExecutableBlocksTranslationResult.UndeclaredVariablesAccessed), // See comment above relating to ExternalDependencies for function blocks
                2                                                                                                                      // indentationDepth
                );

            if (_outputType == OutputTypeOptions.Executable)
            {
                translatedStatements = translatedStatements.AddRange(new[]
                {
                    new TranslatedStatement("}", 2, 0),
                    new TranslatedStatement("", 0, 0)
                });

                // This has to be generated after all of the Translate calls to ensure that the UndeclaredVariablesAccessed data for all of the TranslationResults is available
                var allEnvironmentVariablesAccessed =
                    scopeAccessInformation.ExternalDependencies
                    .AddRange(
                        outerExecutableBlocksTranslationResult
                        .Add(classBlocksTranslationResult)
                        .UndeclaredVariablesAccessed
                        );
                translatedStatements = translatedStatements.AddRange(new[]
                {
                    new TranslatedStatement("public class " + _envClassName.Name, 2, 0),
                    new TranslatedStatement("{", 2, 0)
                });
                var allEnvironmentVariableNames = allEnvironmentVariablesAccessed.Select(v => new { RewrittenName = _nameRewriter.GetMemberAccessTokenName(v), LineIndex = v.LineIndex });
                var environmentVariableNamesThatHaveBeenAccountedFor = new HashSet <string>();
                foreach (var v in allEnvironmentVariableNames)
                {
                    if (environmentVariableNamesThatHaveBeenAccountedFor.Contains(v.RewrittenName))
                    {
                        continue;
                    }
                    translatedStatements = translatedStatements.Add(
                        new TranslatedStatement("public object " + v.RewrittenName + " { get; set; }", 3, v.LineIndex)
                        );
                    environmentVariableNamesThatHaveBeenAccountedFor.Add(v.RewrittenName);
                }
                translatedStatements = translatedStatements.Add(
                    new TranslatedStatement("}", 2, 0)
                    );
            }

            if (classBlocksTranslationResult.TranslatedStatements.Any())
            {
                translatedStatements = translatedStatements.Add(
                    new TranslatedStatement("", 0, 0)
                    );
                translatedStatements = translatedStatements.AddRange(
                    classBlocksTranslationResult.TranslatedStatements
                    );
            }

            if (_outputType == OutputTypeOptions.Executable)
            {
                translatedStatements = translatedStatements.AddRange(new[]
                {
                    new TranslatedStatement("}", 1, 0),                     // Close outer class
                    new TranslatedStatement("}", 0, 0),                     // Close namespace
                });
            }

            // Moving the functions and classes around can sometimes leaving extraneous blank lines in their wake. This tidies them up. (This would also result in
            // runs of blank lines in the source being reduced to a single line, not just runs that were introduced by the rearranging, but I can't see how that
            // could be a big problem).
            return(RemoveRunsOfBlankLines(translatedStatements));
        }
Beispiel #3
0
        private IEnumerable <TranslatedStatement> TranslateClassHeader(
            ClassBlock classBlock,
            ScopeAccessInformation scopeAccessInformation,
            NonNullImmutableList <VariableDeclaration> explicitVariableDeclarationsFromWithinClass,
            NameToken classInitializeMethodNameIfAny,
            NameToken classTerminateMethodNameIfAny,
            int indentationDepth)
        {
            if (classBlock == null)
            {
                throw new ArgumentNullException("classBlock");
            }
            if (scopeAccessInformation == null)
            {
                throw new ArgumentNullException("scopeAccessInformation");
            }
            if (explicitVariableDeclarationsFromWithinClass == null)
            {
                throw new ArgumentNullException("explicitVariableDeclarationsFromWithClass");
            }
            if (indentationDepth < 0)
            {
                throw new ArgumentOutOfRangeException("indentationDepth", "must be zero or greater");
            }

            // C# doesn't support nameed indexed properties, so if there are any Get properties with a parameter or any Let/Set properties
            // with multiple parameters (they need at least; the value to set) then we'll have to get creative - see notes in the
            // TranslatedPropertyIReflectImplementation class
            string inheritanceChainIfAny;

            if (classBlock.Statements.Where(s => s is PropertyBlock).Cast <PropertyBlock>().Any(p => p.IsPublic && p.IsIndexedProperty()))
            {
                inheritanceChainIfAny = " : " + typeof(TranslatedPropertyIReflectImplementation).Name;                 // Assume that the namespace is available for this attribute
            }
            else
            {
                inheritanceChainIfAny = "";
            }

            var className = _nameRewriter.GetMemberAccessTokenName(classBlock.Name);
            IEnumerable <TranslatedStatement> classInitializeCallStatements;

            if (classInitializeMethodNameIfAny == null)
            {
                classInitializeCallStatements = new TranslatedStatement[0];
            }
            else
            {
                // When Class_Initialize is called, it is treated as if ON ERROR RESUME NEXT is applied around the call - the first error will
                // result in the method exiting, but the error won't be propagated up (so the caller will continue as if it hadn't happened).
                // Wrapping the call in a try..catch simulates this behaviour (there is similar required for Class_Terminate).
                classInitializeCallStatements = new[] {
                    new TranslatedStatement(
                        "try { " + _nameRewriter.GetMemberAccessTokenName(classInitializeMethodNameIfAny) + "(); }",
                        indentationDepth + 2,
                        classBlock.Name.LineIndex
                        ),
                    new TranslatedStatement("catch(Exception e)", indentationDepth + 2, classBlock.Name.LineIndex),
                    new TranslatedStatement("{", indentationDepth + 2, classBlock.Name.LineIndex),
                    new TranslatedStatement(_supportRefName.Name + ".SETERROR(e);", indentationDepth + 3, classBlock.Name.LineIndex),
                    new TranslatedStatement("}", indentationDepth + 2, classBlock.Name.LineIndex)
                };
            }
            TranslatedStatement[] disposeImplementationStatements;
            CSharpName            disposedFlagNameIfAny;

            if (classTerminateMethodNameIfAny == null)
            {
                disposeImplementationStatements = new TranslatedStatement[0];
                disposedFlagNameIfAny           = null;
            }
            else
            {
                // If this class has a Clas_Terminate method, then the closest facsimile is a finalizer. However, in C# the garbage collector means
                // that the execution of the finalizer is non-deterministic, while the VBScript interpreter's garbage collector resulted in these
                // being executed predictably. In case a translated class requires this behaviour be maintained somehow, the best thing to do is
                // to make the class implement IDisposable. Translated calling code will not know when to call Dispose on the classes written
                // here, but if they are called from any other C# code written directly against the translated output, having the option of
                // taking advantage of the IDisposable interface may be useful.
                // - Note that when the finalizer is executed, the call to Dispose (which then calls the Class_Terminate method) is wrapped in
                //   a try..catch for the same reason as the Class_Initialize call, as explained above. The only difference is that here, when
                //   we call _.SETERROR(e) there is - technically - a chance that the "_" reference will have been finalised. Managed references
                //   should not be accessed in the finaliser if we're following the rules to the letter, but this entire structure around trying
                //   to emulate Class_Terminate with a finaliser is a series of compromises, so this is the best we can do. The SETERROR call is
                //   ALSO wrapped in a try..catch, just in case that reference really is no longer available.
                // - Also note that IDisposable's public Dispose() method is explicitly implemented and that the method that this and the
                //   finaliser call is named by "_tempNameGenerator" reference and that it is "private" instead of the more common (for
                //   correct implementations of the disposable pattern) "protected virtual". This is explained below, where the class
                //   header is generated.
                disposedFlagNameIfAny = _tempNameGenerator(new CSharpName("_disposed"), scopeAccessInformation);
                var disposeMethodName = _tempNameGenerator(new CSharpName("Dispose"), scopeAccessInformation);
                disposeImplementationStatements = new[]
                {
                    new TranslatedStatement("~" + className + "()", indentationDepth + 1, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("{", indentationDepth + 1, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("try { " + disposeMethodName.Name + "(false); }", indentationDepth + 2, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("catch(Exception e)", indentationDepth + 2, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("{", indentationDepth + 2, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("try { " + _supportRefName.Name + ".SETERROR(e); } catch { }", indentationDepth + 3, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("}", indentationDepth + 2, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("}", indentationDepth + 1, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("", indentationDepth + 1, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("void IDisposable.Dispose()", indentationDepth + 1, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("{", indentationDepth + 1, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement(disposeMethodName.Name + "(true);", indentationDepth + 2, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("GC.SuppressFinalize(this);", indentationDepth + 2, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("}", indentationDepth + 1, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("", indentationDepth + 1, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("private void " + disposeMethodName.Name + "(bool disposing)", indentationDepth + 1, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("{", indentationDepth + 1, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("if (" + disposedFlagNameIfAny.Name + ")", indentationDepth + 2, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("return;", indentationDepth + 3, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("if (disposing)", indentationDepth + 2, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement(_nameRewriter.GetMemberAccessTokenName(classTerminateMethodNameIfAny) + "();", indentationDepth + 3, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement(disposedFlagNameIfAny.Name + " = true;", indentationDepth + 2, classTerminateMethodNameIfAny.LineIndex),
                    new TranslatedStatement("}", indentationDepth + 1, classTerminateMethodNameIfAny.LineIndex)
                };
                if (inheritanceChainIfAny == "")
                {
                    inheritanceChainIfAny = " : ";
                }
                else
                {
                    inheritanceChainIfAny += ", ";
                }
                inheritanceChainIfAny += "IDisposable";
            }

            // The class is sealed to make the IDisposable implementation easier (where appropriate) - the recommended way to implement IDisposable
            // is for there to be a "protected virtual void Dispose(bool disposing)" method, for use by the IDisposable's public Dispose() method,
            // by the finaliser and by any derived types. However, there may be a "Dispose" method that comes from the VBScript source. We can work
            // around this by explicitly implementating "IDisposable.Dispose" and by using the "_tempNameGenerator" reference to get a safe-to-use
            // method name, for the boolean argument "Dispose" method - but then it won't follow the recommended pattern and be a method name
            // "Dispose" that derived types can use. The easiest way around that is to make the classes sealed and then there are no derived
            // types to worry about (this could be seen to be a limitation on the translated code, but since it's all being translated from
            // VBScript where all types are dynamic, one class could just be swapped out for another entirely different one as long as it
            // has the same methods and properties on it).
            var classHeaderStatements = new List <TranslatedStatement>
            {
                new TranslatedStatement("[ComVisible(true)]", indentationDepth, classBlock.Name.LineIndex),
                new TranslatedStatement("[SourceClassName(" + classBlock.Name.Content.ToLiteral() + ")]", indentationDepth, classBlock.Name.LineIndex),
                new TranslatedStatement("public sealed class " + className + inheritanceChainIfAny, indentationDepth, classBlock.Name.LineIndex),
                new TranslatedStatement("{", indentationDepth, classBlock.Name.LineIndex),
                new TranslatedStatement("private readonly " + typeof(IProvideVBScriptCompatFunctionalityToIndividualRequests).Name + " " + _supportRefName.Name + ";", indentationDepth + 1, classBlock.Name.LineIndex),
                new TranslatedStatement("private readonly " + _envClassName.Name + " " + _envRefName.Name + ";", indentationDepth + 1, classBlock.Name.LineIndex),
                new TranslatedStatement("private readonly " + _outerClassName.Name + " " + _outerRefName.Name + ";", indentationDepth + 1, classBlock.Name.LineIndex)
            };

            if (disposedFlagNameIfAny != null)
            {
                classHeaderStatements.Add(
                    new TranslatedStatement("private bool " + disposedFlagNameIfAny.Name + ";", indentationDepth + 1, classBlock.Name.LineIndex)
                    );
            }
            classHeaderStatements.AddRange(new[] {
                new TranslatedStatement(
                    string.Format(
                        "public {0}({1} compatLayer, {2} env, {3} outer)",
                        className,
                        typeof(IProvideVBScriptCompatFunctionalityToIndividualRequests).Name,
                        _envClassName.Name,
                        _outerClassName.Name
                        ),
                    indentationDepth + 1,
                    classBlock.Name.LineIndex
                    ),
                new TranslatedStatement("{", indentationDepth + 1, classBlock.Name.LineIndex),
                new TranslatedStatement("if (compatLayer == null)", indentationDepth + 2, classBlock.Name.LineIndex),
                new TranslatedStatement("throw new ArgumentNullException(\"compatLayer\");", indentationDepth + 3, classBlock.Name.LineIndex),
                new TranslatedStatement("if (env == null)", indentationDepth + 2, classBlock.Name.LineIndex),
                new TranslatedStatement("throw new ArgumentNullException(\"env\");", indentationDepth + 3, classBlock.Name.LineIndex),
                new TranslatedStatement("if (outer == null)", indentationDepth + 2, classBlock.Name.LineIndex),
                new TranslatedStatement("throw new ArgumentNullException(\"outer\");", indentationDepth + 3, classBlock.Name.LineIndex),
                new TranslatedStatement(_supportRefName.Name + " = compatLayer;", indentationDepth + 2, classBlock.Name.LineIndex),
                new TranslatedStatement(_envRefName.Name + " = env;", indentationDepth + 2, classBlock.Name.LineIndex),
                new TranslatedStatement(_outerRefName.Name + " = outer;", indentationDepth + 2, classBlock.Name.LineIndex)
            });
            if (disposedFlagNameIfAny != null)
            {
                classHeaderStatements.Add(
                    new TranslatedStatement(disposedFlagNameIfAny.Name + " = false;", indentationDepth + 2, classBlock.Name.LineIndex)
                    );
            }
            classHeaderStatements.AddRange(
                explicitVariableDeclarationsFromWithinClass.Select(
                    v => new TranslatedStatement(
                        base.TranslateVariableInitialisation(v, ScopeLocationOptions.WithinClass),
                        indentationDepth + 2,
                        v.Name.LineIndex
                        )
                    )
                );
            classHeaderStatements.AddRange(classInitializeCallStatements);
            classHeaderStatements.Add(
                new TranslatedStatement("}", indentationDepth + 1, classBlock.Name.LineIndex)
                );
            if (disposeImplementationStatements.Any())
            {
                classHeaderStatements.Add(new TranslatedStatement("", indentationDepth + 1, classTerminateMethodNameIfAny.LineIndex));
                classHeaderStatements.AddRange(disposeImplementationStatements);
            }
            if (explicitVariableDeclarationsFromWithinClass.Any())
            {
                classHeaderStatements.Add(new TranslatedStatement("", indentationDepth + 1, explicitVariableDeclarationsFromWithinClass.Min(v => v.Name.LineIndex)));
                classHeaderStatements.AddRange(
                    explicitVariableDeclarationsFromWithinClass.Select(declaredVariableToInitialise =>
                                                                       new TranslatedStatement(
                                                                           string.Format(
                                                                               "{0} object {1} {{ get; set; }}",
                                                                               (declaredVariableToInitialise.Scope == VariableDeclarationScopeOptions.Private) ? "private" : "public",
                                                                               _nameRewriter.GetMemberAccessTokenName(declaredVariableToInitialise.Name)
                                                                               ),
                                                                           indentationDepth + 1,
                                                                           declaredVariableToInitialise.Name.LineIndex
                                                                           )
                                                                       )
                    );
            }
            return(classHeaderStatements);
        }