/// <summary> /// Where variables must be stored in temporary references in order to be accessed within lambas (which is the case for variables that are "ref" arguments of the containing function, the common cases /// where lambdas may be required are for passing as REF into an IProvideCallArguments implementation or when accessed within a HANDLEERROR call), the temporary references must be defined and a try /// opened before the work attempted. After the work is completed, in a finally, the aliases values must be mapped back onto the source values - this is what the CloseByRefReplacementDefinitionWork /// method is for. /// </summary> public static ByRefReplacementTranslationResultDetails OpenByRefReplacementDefinitionWork( this NonNullImmutableList <FuncByRefMapping> byRefArgumentsToRewrite, TranslationResult translationResult, int indentationDepth, VBScriptNameRewriter nameRewriter) { if (byRefArgumentsToRewrite == null) { throw new ArgumentNullException("byRefArgumentsToRewrite"); } if (translationResult == null) { throw new ArgumentNullException("translationResult"); } if (indentationDepth < 0) { throw new ArgumentOutOfRangeException("indentationDepth"); } if (nameRewriter == null) { throw new ArgumentNullException("nameRewriter"); } // Originally, this would throw an exception if there were no by-ref arguments (why bother calling this if there are no by-ref arguments to deal with; does this indicate an error in the calling // code?) but in some cases it's easier to be able to call it without having check whether there were any value that need rewriting and the cases where being so strict may catch unintentional // calls are few if (!byRefArgumentsToRewrite.Any()) { return(new ByRefReplacementTranslationResultDetails(translationResult, 0)); } var lineIndexForStartOfContent = byRefArgumentsToRewrite.Min(a => a.From.LineIndex); translationResult = translationResult.Add(new TranslatedStatement( string.Format( "object {0};", string.Join( ", ", byRefArgumentsToRewrite.Select(r => r.To.Name + " = " + nameRewriter(r.From).Name) ) ), indentationDepth, lineIndexForStartOfContent )); if (byRefArgumentsToRewrite.All(mapping => mapping.MappedValueIsReadOnly)) { return(new ByRefReplacementTranslationResultDetails(translationResult, distanceToIndentCodeWithMappedValues: 0)); } return(new ByRefReplacementTranslationResultDetails( translationResult .Add(new TranslatedStatement("try", indentationDepth, lineIndexForStartOfContent)) .Add(new TranslatedStatement("{", indentationDepth, lineIndexForStartOfContent)), distanceToIndentCodeWithMappedValues: 1 )); }
private TranslationResult TryToTranslateValueSettingStatementAsSimpleFunctionValueReturner( TranslationResult translationResult, ICodeBlock block, ScopeAccessInformation scopeAccessInformation, int indentationDepth) { if (translationResult == null) { throw new ArgumentNullException("translationResult"); } if (block == null) { throw new ArgumentNullException("block"); } if (scopeAccessInformation == null) { throw new ArgumentNullException("scopeAccessInformation"); } if (indentationDepth < 0) { throw new ArgumentOutOfRangeException("indentationDepth", "must be zero or greater"); } var valueSettingStatement = block as ValueSettingStatement; if (valueSettingStatement == null) { return(null); } var translatedStatementContentDetails = _statementTranslator.Translate( valueSettingStatement.Expression, scopeAccessInformation, (valueSettingStatement.ValueSetType == ValueSettingStatement.ValueSetTypeOptions.Set) ? ExpressionReturnTypeOptions.Reference : ExpressionReturnTypeOptions.Value, _logger.Warning ); var undeclaredVariables = translatedStatementContentDetails.VariablesAccessed .Where(v => !scopeAccessInformation.IsDeclaredReference(v, _nameRewriter)); foreach (var undeclaredVariable in undeclaredVariables) { _logger.Warning("Undeclared variable: \"" + undeclaredVariable.Content + "\" (line " + (undeclaredVariable.LineIndex + 1) + ")"); } return(translationResult .Add(new TranslatedStatement( "return " + translatedStatementContentDetails.TranslatedContent + ";", indentationDepth, valueSettingStatement.Expression.Tokens.First().LineIndex )) .AddUndeclaredVariables(undeclaredVariables)); }
public 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)); }