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