From a8d7c1b5092823d01d7d720a1b22ef1f633cb0a7 Mon Sep 17 00:00:00 2001 From: David Dawkins Date: Wed, 1 Sep 2021 23:32:14 +0100 Subject: [PATCH 1/3] Support for multiple files --- src/fable-standalone/src/Worker/Shared.fs | 8 +- src/fable-standalone/src/Worker/Worker.fs | 139 +++++++++++++++------- 2 files changed, 103 insertions(+), 44 deletions(-) diff --git a/src/fable-standalone/src/Worker/Shared.fs b/src/fable-standalone/src/Worker/Shared.fs index 31ce886e42..fd08a008e0 100644 --- a/src/fable-standalone/src/Worker/Shared.fs +++ b/src/fable-standalone/src/Worker/Shared.fs @@ -6,11 +6,17 @@ open Fable.Core open Fable.Core.JsInterop open Thoth.Json +type FSharpCodeFile = { + Name : string + Content : string + } + type WorkerRequest = /// * refsExtraSuffix: e.g. add .txt extension to enable gzipping in Github Pages | CreateChecker of refsDirUrl: string * extraRefs: string[] * refsExtraSuffix: string option * otherFSharpOptions: string[] | ParseCode of fsharpCode: string * otherFSharpOptions: string[] | CompileCode of fsharpCode: string * otherFSharpOptions: string[] + | CompileCodeArray of fsharpCode: FSharpCodeFile[] * otherFSharpOptions: string[] | GetTooltip of id: Guid * line: int * column: int * lineText: string | GetCompletions of id: Guid * line: int * column: int * lineText: string | GetDeclarationLocation of id: Guid * line: int * column: int * lineText: string @@ -26,7 +32,7 @@ type WorkerAnswer = | Loaded of version: string | LoadFailed | ParsedCode of errors: Fable.Standalone.Error[] - | CompilationFinished of jsCode: string * errors: Fable.Standalone.Error[] * stats: CompileStats + | CompilationFinished of jsCode: string[] * errors: Fable.Standalone.Error[] * stats: CompileStats | CompilerCrashed of message: string | FoundTooltip of id: Guid * lines: string[] | FoundCompletions of id: Guid * Fable.Standalone.Completion[] diff --git a/src/fable-standalone/src/Worker/Worker.fs b/src/fable-standalone/src/Worker/Worker.fs index d4b322e5fc..6bddeeb32f 100644 --- a/src/fable-standalone/src/Worker/Worker.fs +++ b/src/fable-standalone/src/Worker/Worker.fs @@ -84,7 +84,74 @@ let makeFableState (config: FableStateConfig) otherFSharpOptions = OtherFSharpOptions = otherFSharpOptions } } +let private compileCode fable fileName fsharpNames fsharpCodes otherFSharpOptions = + async { + // detect (and remove) the non-F# compiler options to avoid changing msg contract + let nonFSharpOptions = set [ + "--typedArrays" + "--clampByteArrays" + "--typescript" + "--sourceMaps" + ] + let fableOptions, otherFSharpOptions = + otherFSharpOptions |> Array.partition (fun x -> Set.contains x nonFSharpOptions) + + //let fileName = fsharpNames |> Array.last + // Check if we need to recreate the FableState because otherFSharpOptions have changed + let! fable = makeFableState (Initialized fable) otherFSharpOptions + let (parseResults, parsingTime) = measureTime (fun () -> + // fable.Manager.ParseFSharpScript(fable.Checker, FILE_NAME, fsharpCode, otherFSharpOptions)) () + fable.Manager.ParseFSharpFileInProject(fable.Checker, fileName, PROJECT_NAME, fsharpNames, fsharpCodes, otherFSharpOptions)) () + + let! jsCode, errors, fableTransformTime = async { + if parseResults.Errors |> Array.exists (fun e -> not e.IsWarning) then + return "", parseResults.Errors, 0. + else + let options = {| + typedArrays = Array.contains "--typedArrays" fableOptions + typescript = Array.contains "--typescript" fableOptions + sourceMaps = Array.contains "--sourceMaps" fableOptions + |} + let (res, fableTransformTime) = + measureTime (fun () -> + fable.Manager.CompileToBabelAst("fable-library", parseResults, fileName, typedArrays = options.typedArrays, typescript = options.typescript) + ) () + // Print Babel AST + let writer = new SourceWriter(options.sourceMaps) + do! fable.Manager.PrintBabelAst(res, writer) + let jsCode = writer.Result + + return jsCode, Array.append parseResults.Errors res.FableErrors, fableTransformTime + } + + let stats : CompileStats = + { FCS_checker = fable.LoadTime + FCS_parsing = parsingTime + Fable_transform = fableTransformTime } + + return (jsCode, errors, stats) + } + +let private combineStats (a : CompileStats) (b : CompileStats) : CompileStats = + { + FCS_checker = a.FCS_checker + b.FCS_checker + FCS_parsing = a.FCS_parsing + b.FCS_parsing + Fable_transform = a.Fable_transform + b.Fable_transform + } + +let private asyncSequential (calc : Async<'T> array) : Async<'T array> = + async { + let mutable result = [] : 'T list + + for c in calc do + let! res = c + result <- result @ [ res ] + + return Array.ofList result + } + let rec loop (box: MailboxProcessor) (state: State) = async { + let! msg = box.Receive() match state.Fable, msg with | None, CreateChecker(refsDirUrl, extraRefs, refsExtraSuffix, otherFSharpOptions) -> @@ -112,49 +179,35 @@ let rec loop (box: MailboxProcessor) (state: State) = async { | Some fable, CompileCode(fsharpCode, otherFSharpOptions) -> try - // detect (and remove) the non-F# compiler options to avoid changing msg contract - let nonFSharpOptions = set [ - "--typedArrays" - "--clampByteArrays" - "--typescript" - "--sourceMaps" - ] - let fableOptions, otherFSharpOptions = - otherFSharpOptions |> Array.partition (fun x -> Set.contains x nonFSharpOptions) - - // Check if we need to recreate the FableState because otherFSharpOptions have changed - let! fable = makeFableState (Initialized fable) otherFSharpOptions - let (parseResults, parsingTime) = measureTime (fun () -> - // fable.Manager.ParseFSharpScript(fable.Checker, FILE_NAME, fsharpCode, otherFSharpOptions)) () - fable.Manager.ParseFSharpFileInProject(fable.Checker, FILE_NAME, PROJECT_NAME, [|FILE_NAME|], [|fsharpCode|], otherFSharpOptions)) () - - let! jsCode, errors, fableTransformTime = async { - if parseResults.Errors |> Array.exists (fun e -> not e.IsWarning) then - return "", parseResults.Errors, 0. - else - let options = {| - typedArrays = Array.contains "--typedArrays" fableOptions - typescript = Array.contains "--typescript" fableOptions - sourceMaps = Array.contains "--sourceMaps" fableOptions - |} - let (res, fableTransformTime) = - measureTime (fun () -> - fable.Manager.CompileToBabelAst("fable-library", parseResults, FILE_NAME, typedArrays = options.typedArrays, typescript = options.typescript) - ) () - // Print Babel AST - let writer = new SourceWriter(options.sourceMaps) - do! fable.Manager.PrintBabelAst(res, writer) - let jsCode = writer.Result - - return jsCode, Array.append parseResults.Errors res.FableErrors, fableTransformTime - } - - let stats : CompileStats = - { FCS_checker = fable.LoadTime - FCS_parsing = parsingTime - Fable_transform = fableTransformTime } - - CompilationFinished (jsCode, errors, stats) |> state.Worker.Post + let! (jsCode,errors,stats) = compileCode fable FILE_NAME ([| FILE_NAME |]) ([| fsharpCode |]) otherFSharpOptions + CompilationFinished ([| jsCode |], errors, stats) |> state.Worker.Post + with er -> + JS.console.error er + CompilerCrashed er.Message |> state.Worker.Post + return! loop box state + + | Some fable, CompileCodeArray(fsharpCode, otherFSharpOptions) -> + try + let codes = fsharpCode |> Array.map (fun c -> c.Content) + let names = fsharpCode |> Array.mapi (fun i c -> if c.Name = "" then $"test{i}.fs" else c.Name) + + let! results = + names + |> Array.map (fun name -> + compileCode fable name names codes otherFSharpOptions + ) + |> asyncSequential + + let combinedResults = + results + |> Array.map (fun (a,b,c) -> [| a |], b, c) + |> Array.reduce (fun (a,b,c) (d,e,f) -> + Array.append a d, // JS code + Array.append b e, // Erros + combineStats c f // Stats + ) + + CompilationFinished combinedResults |> state.Worker.Post with er -> JS.console.error er CompilerCrashed er.Message |> state.Worker.Post From b5d7345a2b0f6423e46f64bfef6a6132d527896f Mon Sep 17 00:00:00 2001 From: David Dawkins Date: Sun, 5 Sep 2021 22:02:34 +0100 Subject: [PATCH 2/3] Support for multiple files, attempting to keep backwards compatibility. Multiple-file messages are additional. --- src/fable-standalone/src/Worker/Shared.fs | 6 +- src/fable-standalone/src/Worker/Worker.fs | 77 ++++++++++++++++++++--- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/src/fable-standalone/src/Worker/Shared.fs b/src/fable-standalone/src/Worker/Shared.fs index fd08a008e0..667ba3e05e 100644 --- a/src/fable-standalone/src/Worker/Shared.fs +++ b/src/fable-standalone/src/Worker/Shared.fs @@ -15,11 +15,15 @@ type WorkerRequest = /// * refsExtraSuffix: e.g. add .txt extension to enable gzipping in Github Pages | CreateChecker of refsDirUrl: string * extraRefs: string[] * refsExtraSuffix: string option * otherFSharpOptions: string[] | ParseCode of fsharpCode: string * otherFSharpOptions: string[] + | ParseFile of file : string * fsharpCode: FSharpCodeFile[] * otherFSharpOptions: string[] | CompileCode of fsharpCode: string * otherFSharpOptions: string[] - | CompileCodeArray of fsharpCode: FSharpCodeFile[] * otherFSharpOptions: string[] + | CompileFiles of fsharpCode: FSharpCodeFile[] * otherFSharpOptions: string[] | GetTooltip of id: Guid * line: int * column: int * lineText: string | GetCompletions of id: Guid * line: int * column: int * lineText: string | GetDeclarationLocation of id: Guid * line: int * column: int * lineText: string + | GetTooltipForFile of id: Guid * file: string * line: int * column: int * lineText: string + | GetCompletionsForFile of id: Guid * file: string * line: int * column: int * lineText: string + | GetDeclarationLocationForFile of id: Guid * file: string * line: int * column: int * lineText: string static member Decoder = Decode.Auto.generateDecoder() diff --git a/src/fable-standalone/src/Worker/Worker.fs b/src/fable-standalone/src/Worker/Worker.fs index 6bddeeb32f..231817fca3 100644 --- a/src/fable-standalone/src/Worker/Worker.fs +++ b/src/fable-standalone/src/Worker/Worker.fs @@ -42,7 +42,7 @@ type FableStateConfig = type State = { Fable: FableState option Worker: ObservableWorker - CurrentResults: IParseResults option } + CurrentResults: Map } type SourceWriter(sourceMaps: bool) = let sb = System.Text.StringBuilder() @@ -150,17 +150,23 @@ let private asyncSequential (calc : Async<'T> array) : Async<'T array> = return Array.ofList result } +let private truncate (s : string) = + if s.Length > 80 then s.Substring(0,80) + "..." else s + let rec loop (box: MailboxProcessor) (state: State) = async { let! msg = box.Receive() + match state.Fable, msg with + | None, CreateChecker(refsDirUrl, extraRefs, refsExtraSuffix, otherFSharpOptions) -> + try let! fable = makeFableState (Init(refsDirUrl, extraRefs, refsExtraSuffix)) otherFSharpOptions state.Worker.Post(Loaded fable.Manager.Version) return! loop box { state with Fable = Some fable } with err -> - JS.console.error("Cannot create F# checker", err) + JS.console.error("Cannot create F# checker", err) // Beware, you might be catching an exception from the next recursion of loop state.Worker.Post LoadFailed return! loop box state @@ -175,7 +181,27 @@ let rec loop (box: MailboxProcessor) (state: State) = async { let res = fable.Manager.ParseFSharpFileInProject(fable.Checker, FILE_NAME, PROJECT_NAME, [|FILE_NAME|], [|fsharpCode|], otherFSharpOptions) ParsedCode res.Errors |> state.Worker.Post - return! loop box { state with CurrentResults = Some res } + return! loop box { state with CurrentResults = state.CurrentResults.Add(FILE_NAME,res) } + + | Some fable, ParseFile(file, fsharpCode, otherFSharpOptions) -> + try + // Check if we need to recreate the FableState because otherFSharpOptions have changed + let! fable = makeFableState (Initialized fable) otherFSharpOptions + + // let res = fable.Manager.ParseFSharpScript(fable.Checker, FILE_NAME, fsharpCode, otherFSharpOptions) + + let names = fsharpCode |> Array.map (fun x -> x.Name) + let contents = fsharpCode |> Array.map (fun x -> x.Content) + let res = fable.Manager.ParseFSharpFileInProject(fable.Checker, file, PROJECT_NAME, names, contents, otherFSharpOptions) + + ParsedCode res.Errors |> state.Worker.Post + + let newResults = state.CurrentResults.Add(file,res) + return! loop box { state with CurrentResults = newResults } + with + | err -> + JS.console.error("ParseNamedCode", err) + return! loop box state | Some fable, CompileCode(fsharpCode, otherFSharpOptions) -> try @@ -186,7 +212,7 @@ let rec loop (box: MailboxProcessor) (state: State) = async { CompilerCrashed er.Message |> state.Worker.Post return! loop box state - | Some fable, CompileCodeArray(fsharpCode, otherFSharpOptions) -> + | Some fable, CompileFiles(fsharpCode, otherFSharpOptions) -> try let codes = fsharpCode |> Array.map (fun c -> c.Content) let names = fsharpCode |> Array.mapi (fun i c -> if c.Name = "" then $"test{i}.fs" else c.Name) @@ -215,15 +241,17 @@ let rec loop (box: MailboxProcessor) (state: State) = async { | Some fable, GetTooltip(id, line, col, lineText) -> let tooltipLines = - match state.CurrentResults with - | None -> [||] - | Some res -> fable.Manager.GetToolTipText(res, int line, int col, lineText) + match FILE_NAME |> state.CurrentResults.TryFind with + | None -> + [||] + | Some res -> + fable.Manager.GetToolTipText(res, int line, int col, lineText) FoundTooltip(id, tooltipLines) |> state.Worker.Post return! loop box state | Some fable, GetCompletions(id, line, col, lineText) -> let completions = - match state.CurrentResults with + match FILE_NAME |> state.CurrentResults.TryFind with | None -> [||] | Some res -> fable.Manager.GetCompletionsAtLocation(res, int line, int col, lineText) FoundCompletions(id, completions) |> state.Worker.Post @@ -231,7 +259,36 @@ let rec loop (box: MailboxProcessor) (state: State) = async { | Some fable, GetDeclarationLocation(id, line, col, lineText) -> let result = - match state.CurrentResults with + match FILE_NAME |> state.CurrentResults.TryFind with + | None -> None + | Some res -> fable.Manager.GetDeclarationLocation(res, int line, int col, lineText) + match result with + | Some x -> FoundDeclarationLocation(id, Some(x.StartLine, x.StartColumn, x.EndLine, x.EndColumn)) + | None -> FoundDeclarationLocation(id, None) + |> state.Worker.Post + return! loop box state + + | Some fable, GetTooltipForFile(id, file, line, col, lineText) -> + let tooltipLines = + match file |> state.CurrentResults.TryFind with + | None -> + [||] + | Some res -> + fable.Manager.GetToolTipText(res, int line, int col, lineText) + FoundTooltip(id, tooltipLines) |> state.Worker.Post + return! loop box state + + | Some fable, GetCompletionsForFile(id, file, line, col, lineText) -> + let completions = + match file |> state.CurrentResults.TryFind with + | None -> [||] + | Some res -> fable.Manager.GetCompletionsAtLocation(res, int line, int col, lineText) + FoundCompletions(id, completions) |> state.Worker.Post + return! loop box state + + | Some fable, GetDeclarationLocationForFile(id, file, line, col, lineText) -> + let result = + match file |> state.CurrentResults.TryFind with | None -> None | Some res -> fable.Manager.GetDeclarationLocation(res, int line, int col, lineText) match result with @@ -245,7 +302,7 @@ let worker = ObservableWorker(self, WorkerRequest.Decoder) let box = MailboxProcessor.Start(fun box -> { Fable = None Worker = worker - CurrentResults = None } + CurrentResults = Map.empty } |> loop box) worker From a948b6c0af5645c3805538ad512864af895a3458 Mon Sep 17 00:00:00 2001 From: David Dawkins Date: Tue, 7 Sep 2021 00:21:35 +0100 Subject: [PATCH 3/3] Leave existing messages intact so that multiple-file messages are a strict extension. This will keep worker.min.js compatible with the main Fable repl --- src/fable-standalone/src/Worker/Shared.fs | 3 ++- src/fable-standalone/src/Worker/Worker.fs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/fable-standalone/src/Worker/Shared.fs b/src/fable-standalone/src/Worker/Shared.fs index 667ba3e05e..262e28d907 100644 --- a/src/fable-standalone/src/Worker/Shared.fs +++ b/src/fable-standalone/src/Worker/Shared.fs @@ -36,7 +36,8 @@ type WorkerAnswer = | Loaded of version: string | LoadFailed | ParsedCode of errors: Fable.Standalone.Error[] - | CompilationFinished of jsCode: string[] * errors: Fable.Standalone.Error[] * stats: CompileStats + | CompilationFinished of jsCode: string * errors: Fable.Standalone.Error[] * stats: CompileStats + | CompilationsFinished of jsCode: string[] * errors: Fable.Standalone.Error[] * stats: CompileStats | CompilerCrashed of message: string | FoundTooltip of id: Guid * lines: string[] | FoundCompletions of id: Guid * Fable.Standalone.Completion[] diff --git a/src/fable-standalone/src/Worker/Worker.fs b/src/fable-standalone/src/Worker/Worker.fs index 231817fca3..f701969218 100644 --- a/src/fable-standalone/src/Worker/Worker.fs +++ b/src/fable-standalone/src/Worker/Worker.fs @@ -206,7 +206,7 @@ let rec loop (box: MailboxProcessor) (state: State) = async { | Some fable, CompileCode(fsharpCode, otherFSharpOptions) -> try let! (jsCode,errors,stats) = compileCode fable FILE_NAME ([| FILE_NAME |]) ([| fsharpCode |]) otherFSharpOptions - CompilationFinished ([| jsCode |], errors, stats) |> state.Worker.Post + CompilationFinished (jsCode, errors, stats) |> state.Worker.Post with er -> JS.console.error er CompilerCrashed er.Message |> state.Worker.Post @@ -233,7 +233,7 @@ let rec loop (box: MailboxProcessor) (state: State) = async { combineStats c f // Stats ) - CompilationFinished combinedResults |> state.Worker.Post + CompilationsFinished combinedResults |> state.Worker.Post with er -> JS.console.error er CompilerCrashed er.Message |> state.Worker.Post