#nowarn "62" // This construct is for compatibility with OCaml. //------------------------------------------------------------------------------------------------- // Internal members namespace Microsoft.FSharp.Compiler.CodeDom.Internal open System open System.IO open System.Text open System.Collections open System.CodeDom open System.CodeDom.Compiler open Microsoft.FSharp.Compiler.CodeDom.Internal module AspNetUtils = /// Preprocessing of the CodeDom compile unit from ASP.NET let aspNetPreProcessCompileUnit (c:CodeCompileUnit) = // Remove partial calsses from ASP.NET generated code (we can leave empty namespaces) // F# doesn't support partial classes, so we need to remove generated 'second' part of the // handwritten class that contains fields for all controls - these have to be written // manually in the handwritten code... (* if false then *) begin for ns in c.Namespaces do // Phase 1. Find classes to remove let toRemove = ns.Types |> Seq.cast |> Seq.filter (fun (t:CodeTypeDeclaration) -> t.IsPartial ) |> Seq.to_list // Phase 2. remove (destructive). toRemove |> List.iter ns.Types.Remove; end; // Fix one very specific bug in ASP.NET generated CodeDom tree. // In one case it generates ".SetStringResourcePointer" instead of // "this.SetStringResourcePointer" (Reported here: // http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=164512) for ns in c.Namespaces do for t in ns.Types do for m in t.Members do match m with | :? CodeMemberMethod as meth -> if (meth.Name = "FrameworkInitialize") then for smt in meth.Statements do match smt with | :? CodeExpressionStatement as stm -> match stm.Expression with | :? CodeMethodInvokeExpression as inv -> if ((inv.Method.TargetObject = null) && (inv.Method.MethodName = "SetStringResourcePointer")) then inv.Method.TargetObject <- new CodeThisReferenceExpression(); | _ -> (); | _ -> (); else (); | _ -> (); // Sorts ASP.NET source files // ASP.NET compiler sends three kinds of files // - CodeDOM generated file from ASPX file // - handwritten file (codebehind for the previous) // - fast factory for creating the first one // // We need to send them to compiler in following order: // - handwritten, aspx, handwritten, aspx, ..., factory // // File name is: "Something.num.fs" where num is an index, but the array isn't // sorted so we sort them using the index. Then we find all ASPX and swap // ASPX and handwritten file. Factory should be the last file. /// Does the file contain the given text? let fileContains (text:string) (filename:string) = try use fs = new IO.StreamReader(filename) let rec testLine () = let line = fs.ReadLine() (line <> null) && (line.IndexOf(text) <> -1 or testLine()) testLine() with _ -> false /// Swap two elements of an array let swap (arr:'a[]) i j = let tmp = arr.[j] arr.[j] <- arr.[i] arr.[i] <- tmp /// Compiled forms of ASPX source files always contain "(__initialized:bool)". /// The factory file contains "static member Create_ASP_" let isFactoryFile filename = fileContains "static member Create_ASP_" filename && fileContains "FastObjectFactory" filename let isGeneratedFromAspxFile filename = fileContains "static val mutable __initialized:bool" filename && fileContains "" filename let isUserFile filename = not (isGeneratedFromAspxFile filename) && not (isFactoryFile filename) // REVIEW: will "Temporary ASP.NET Files" survive internationalization???? let indexOfAspNetFile(el:string) = let n = try let bnum = el.LastIndexOf(".", el.Length - 4) Int32.Parse(el.Substring(bnum + 1, el.Length - bnum - 4)) with _ -> -1 if (el.IndexOf("Temporary ASP.NET Files") <> -1) then n else -1 let aspNetSortFiles (files:string array) = // Verify that files are from ASP.NET & sort them. let aspFiles = files |> Array.map (fun el -> indexOfAspNetFile el, el) let allAspNet = aspFiles |> Array.forall (fun (n, a) -> n <> -1) let sortedFiles = if (allAspNet) then Array.sortInPlaceWith (fun (n1, _) (n2, _) -> n1 - n2) aspFiles let filesMod = aspFiles |> Array.map snd //System.Windows.Forms.MessageBox.Show(sprintf "filesMod = %A" filesMod) |> ignore // Rearrange files // NOTE: ack! surely there's a nicer way to do this! let mutable i = 0 while (i < filesMod.Length) do if (isGeneratedFromAspxFile filesMod.[i] && not (isFactoryFile filesMod.[i]) && isUserFile filesMod.[i+1]) then swap filesMod i (i+1) i <- i + 1 i <- i + 1 filesMod else files //System.Windows.Forms.MessageBox.Show(sprintf "sortedFiles = %A" sortedFiles) |> ignore sortedFiles // Generic code generator that can be specialized with preprocessing function and additional // CodeGenerator configuration options (this is used by ASP.NET generator) // preprocCu: module FSharpCodeGenerator = let Create(preprocCu, addop) = let usingStringWriter f = let sb = new StringBuilder() use sw = new StringWriter(sb) f (sw :> TextWriter); let res = sb.ToString(); res { new ICodeGenerator with // Identifier related functions member this.CreateEscapedIdentifier (value:string) : string = Generator.makeEscapedIdentifier value member this.CreateValidIdentifier (value:string) : string = Generator.makeValidIdentifier value member this.IsValidIdentifier (value:string) : bool = Generator.isValidIdentifier value; member this.ValidateIdentifier (value:string) : unit = if (not (Generator.isValidIdentifier value)) then raise (ArgumentException(sprintf "'%s' is not a valid F# identifier!" value)) // Implementations of code generation related functions member this.GenerateCodeFromCompileUnit(compileUnit, textWriter, options) = (Generator.createContext textWriter options addop) |> (Generator.generateCompileUnit compileUnit preprocCu) |> ignore member this.GenerateCodeFromExpression(codeExpr, textWriter, options) : unit = (Generator.createContext textWriter options addop) |> (Generator.generateExpression codeExpr) |> ignore member this.GenerateCodeFromNamespace(codeNamespace, textWriter, options) : unit = (Generator.createContext textWriter options addop) |> (Generator.generateNamespace codeNamespace) |> ignore member this.GenerateCodeFromStatement(codeStatement, textWriter, options) : unit = (Generator.createContext textWriter options addop) |> (Generator.generateStatement codeStatement) |> ignore member this.GenerateCodeFromType(codeTypeDecl, textWriter, options) : unit = (Generator.createContext textWriter options addop) |> (Generator.generateTypeDeclOnly codeTypeDecl) |> ignore member this.GetTypeOutput (t:CodeTypeReference) : string = usingStringWriter (fun sw -> (Generator.createContext sw (CodeGeneratorOptions()) addop) |> (Generator.generateTypeRef t) |> ignore) member this.Supports (supports:GeneratorSupport) : bool = (supports &&& (GeneratorSupport.ReturnTypeAttributes ||| GeneratorSupport.ParameterAttributes ||| GeneratorSupport.AssemblyAttributes ||| GeneratorSupport.StaticConstructors ||| GeneratorSupport.NestedTypes ||| GeneratorSupport.EntryPointMethod ||| GeneratorSupport.GotoStatements ||| GeneratorSupport.MultipleInterfaceMembers ||| GeneratorSupport.ChainedConstructorArguments ) = enum 0) } // Generic code compiler - the argument is a function for sorting files that can be used by // tool-specific providers if needed (like in case of ASP.NET) module FSharpCodeCompiler = let Create(sortFiles) = { new ICodeCompiler with member this.CompileAssemblyFromDom ((options:CompilerParameters),(compileUnit:CodeCompileUnit)) : CompilerResults = this.CompileAssemblyFromDomBatch (options, [|compileUnit|]); member this.CompileAssemblyFromSource ((options:CompilerParameters),(source:string)) : CompilerResults = this.CompileAssemblyFromSourceBatch (options, [|source|]); member this.CompileAssemblyFromFile ((options:CompilerParameters),(fileName:string)) : CompilerResults = this.CompileAssemblyFromFileBatch (options, [|fileName|]); member this.CompileAssemblyFromDomBatch ((options:CompilerParameters),(compilationUnits:CodeCompileUnit array)) : CompilerResults = let res = new CompilerResults(options.TempFiles); let files = compilationUnits |> Array.map ( fun cu -> let fn = res.TempFiles.AddExtension("fs", false) use wr = new StreamWriter(fn, false, Encoding.UTF8) Generator.createContext wr (CodeGeneratorOptions()) Generator.AdditionalOptions.None |> (Generator.generateCompileUnit cu (fun _ -> ())) |> ignore fn) Compiler.compileAssemblyFromFileBatch options files res sortFiles member this.CompileAssemblyFromSourceBatch ((options:CompilerParameters),(sources:string array)) : CompilerResults = let res = new CompilerResults(options.TempFiles); let files = sources |> Array.map ( fun src -> let fn = res.TempFiles.AddExtension("fs", false) use wr = new StreamWriter(fn) wr.Write(src) fn) Compiler.compileAssemblyFromFileBatch options files res sortFiles member this.CompileAssemblyFromFileBatch ((options:CompilerParameters),(fileNames:string array)) : CompilerResults = Compiler.compileAssemblyFromFileBatch options fileNames (new CompilerResults(options.TempFiles)) sortFiles } //------------------------------------------------------------------------------------------------- // Public types namespace Microsoft.FSharp.Compiler.CodeDom open System open System.IO open System.Text open System.Collections open System.CodeDom open System.CodeDom.Compiler open Microsoft.FSharp.Compiler.CodeDom.Internal type FSharpCodeProvider() = inherit CodeDomProvider() override this.FileExtension = "fs"; override this.CreateCompiler() = FSharpCodeCompiler.Create(AspNetUtils.aspNetSortFiles) override this.CreateGenerator() = FSharpCodeGenerator.Create((fun _ -> ()), Generator.AdditionalOptions.None) type FSharpAspNetCodeProvider() = inherit CodeDomProvider() override this.FileExtension = "fs"; override this.CreateCompiler() = FSharpCodeCompiler.Create(AspNetUtils.aspNetSortFiles) override this.CreateGenerator() = // If you set the options to "Generator.AdditionalOptions.UnknonwFieldsAsLocals" // than the generator will treat all fields that are not declared in the class as locals // (and will generated "fld" instead of "this.fld") // This will make it possible to use implicit class syntax in ASP.NET, but only when // the fields will have non-private visibility (because we need to access them from inherited class // AspNetArrays is workaround for ASP.NET which generates wrong type in array initializers // (according to the CodeDOM test suite it should be "int[]" for [| 1; 2 |], but ASP.NET gives us "int" !! let opts = Generator.AdditionalOptions.AspNetArrays FSharpCodeGenerator.Create(AspNetUtils.aspNetPreProcessCompileUnit, opts)