diff --git a/.gitignore b/.gitignore index 6162593d1e9..d3e1c08bf0c 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,5 @@ msbuild.binlog /fcs/FSharp.Compiler.Service.netstandard/*.fs /fcs/FSharp.Compiler.Service.netstandard/*.fsi /.ionide/ +Session.vim +coc-settings.json diff --git a/src/fsharp/FSharp.Core/printf.fs b/src/fsharp/FSharp.Core/printf.fs index 141c9752da9..909144f7922 100644 --- a/src/fsharp/FSharp.Core/printf.fs +++ b/src/fsharp/FSharp.Core/printf.fs @@ -2,13 +2,18 @@ namespace Microsoft.FSharp.Core -type PrintfFormat<'Printer,'State,'Residue,'Result>(value:string) = - member x.Value = value +open System - override __.ToString() = value - -type PrintfFormat<'Printer,'State,'Residue,'Result,'Tuple>(value:string) = - inherit PrintfFormat<'Printer,'State,'Residue,'Result>(value) +type PrintfFormat<'Printer,'State,'Residue,'Result>(value: string, captures: obj[], types: Type[]) = + new(value) = new PrintfFormat<'Printer,'State,'Residue,'Result>(value, null, null) + member __.Value = value + member __.Captures = captures + member __.Types = types + override __.ToString() = value + +type PrintfFormat<'Printer,'State,'Residue,'Result,'Tuple>(value: string, captures: obj[], types: Type[]) = + inherit PrintfFormat<'Printer,'State,'Residue,'Result>(value, captures, types) + new(value) = new PrintfFormat<'Printer,'State,'Residue,'Result, 'Tuple>(value, null, null) type Format<'Printer,'State,'Residue,'Result> = PrintfFormat<'Printer,'State,'Residue,'Result> type Format<'Printer,'State,'Residue,'Result,'Tuple> = PrintfFormat<'Printer,'State,'Residue,'Result,'Tuple> @@ -17,13 +22,13 @@ module internal PrintfImpl = /// Basic idea of implementation: /// Every Printf.* family should returns curried function that collects arguments and then somehow prints them. - /// Idea - instead of building functions on fly argument by argument we instead introduce some predefined parts and then construct functions from these parts + /// Idea - instead of building functions on the fly argument by argument we instead introduce some predefined parts and then construct functions from these parts /// Parts include: /// Plain ones: /// 1. Final pieces (1..5) - set of functions with arguments number 1..5. /// Primary characteristic - these functions produce final result of the *printf* operation /// 2. Chained pieces (1..5) - set of functions with arguments number 1..5. - /// Primary characteristic - these functions doesn not produce final result by itself, instead they tailed with some another piece (chained or final). + /// Primary characteristic - these functions do not produce final result by itself, instead they are tailed with another piece (chained or final). /// Plain parts correspond to simple format specifiers (that are projected to just one parameter of the function, say %d or %s). However we also have /// format specifiers that can be projected to more than one argument (i.e %a, %t or any simple format specified with * width or precision). /// For them we add special cases (both chained and final to denote that they can either return value themselves or continue with some other piece) @@ -36,6 +41,10 @@ module internal PrintfImpl = /// with just one reflection call /// 2. we can make combinable parts independent from particular printf implementation. Thus final result can be cached and shared. /// i.e when first call to printf "%s %s" will trigger creation of the specialization. Subsequent calls will pick existing specialization + /// Note, immediate specifiers will interrupt the aggregation of arguments. For example: + /// - function that corresponds to %s%s%{123}%s%s%s (string -> string -> string -> string -> string -> T) will have to be broken as: + /// chained2 -> finalCapture4 + /// The captures should always appear at the beginning of a chained group. open System open System.IO open System.Text @@ -83,21 +92,24 @@ module internal PrintfImpl = Precision: int Width: int Flags: FormatFlags + Capture: int option } member this.IsStarPrecision = this.Precision = StarValue member this.IsPrecisionSpecified = this.Precision <> NotSpecifiedValue member this.IsStarWidth = this.Width = StarValue member this.IsWidthSpecified = this.Width <> NotSpecifiedValue + member this.HasCapture = this.Capture.IsSome override this.ToString() = let valueOf n = match n with StarValue -> "*" | NotSpecifiedValue -> "-" | n -> n.ToString() System.String.Format ( - "'{0}', Precision={1}, Width={2}, Flags={3}", + "'{0}', Precision={1}, Width={2}, Flags={3}, Captures={4}", this.TypeChar, (valueOf this.Precision), (valueOf this.Width), - this.Flags + this.Flags, + this.Capture ) /// Set of helpers to parse format string @@ -135,6 +147,37 @@ module internal PrintfImpl = let parseTypeChar (s: string) i = s.[i], (i + 1) + + let rec parseFormatSpecifier (s:string) (i:int) = + + // when called recursively for %{nn:FFF} i could point to ':' + System.Diagnostics.Debug.Assert(s.[i] = '%' || s.[i] = ':', "s.[i] = '%' || s.[i] = ':'") + + let isImm, i = if s.[i+1] = '{' + then true, i+1 + else false, i + + if isImm then + let rbrace = s.IndexOf('}', i) + if rbrace < 0 then raise (ArgumentException("invalid immediate specifier")) + let colon = s.IndexOf(':', i) + if colon < 0 || colon > rbrace + then + // defaults to %A + let cap, i = intFromString s i + { TypeChar = 'A'; Precision = NotSpecifiedValue; Flags = FormatFlags.None; Width = NotSpecifiedValue; Capture = Some cap }, i + else + let spec, i = parseFormatSpecifier s i + System.Diagnostics.Debug.Assert((i = colon), "i = colon") + let cap, i = intFromString s (i + 1) + { spec with Capture = Some cap }, i + else + let flags, i = parseFlags s (i + 1) + let width, i = parseWidth s i + let precision, i = parsePrecision s i + let typeChar, i = parseTypeChar s i + { TypeChar = typeChar; Precision = precision; Flags = flags; Width = width; Capture = None }, i + let findNextFormatSpecifier (s: string) i = let rec go i (buf: Text.StringBuilder) = @@ -144,6 +187,9 @@ module internal PrintfImpl = let c = s.[i] if c = '%' then if i + 1 < s.Length then + if s.[i+1] = '{' + then i, buf.ToString() + else let _, i1 = parseFlags s (i + 1) let w, i2 = parseWidth s i1 let p, i3 = parsePrecision s i2 @@ -166,7 +212,9 @@ module internal PrintfImpl = [] type PrintfEnv<'State, 'Residue, 'Result> = val State: 'State - new(s: 'State) = { State = s } + val Captures: obj[] + new(s: 'State) = { State = s; Captures = Unchecked.defaultof<_> } + new(s: 'State, caps: obj[]) = { State = s; Captures = caps } abstract Finish: unit -> 'Result abstract Write: string -> unit abstract WriteT: 'Residue -> unit @@ -214,13 +262,473 @@ module internal PrintfImpl = [] let MaxArgumentsInSpecialization = 5 - /// Specializations are created via factory methods. These methods accepts 2 kinds of arguments + /// Specializations are created via factory methods. These methods accepts 3 kinds of arguments /// - parts of format string that corresponds to raw text /// - functions that can transform collected values to strings + /// - captured immediate indices /// basic shape of the signature of specialization /// + + + ... + type Specializations<'State, 'Residue, 'Result> private ()= - + + static member FinalCapture1<'A> + ( + s0, cap, conv1, s1 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun () -> + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap]), s1) + env.Finish() + ) + ) + + static member FinalFastEndCapture1<'A> + ( + s0, cap, conv1 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun () -> + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap])) + env.Finish() + ) + ) + + static member FinalFastStartCapture1<'A> + ( + cap, conv1, s1 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun () -> + let env = env() + Utils.Write(env, (conv1 env.Captures.[cap]), s1) + env.Finish() + ) + ) + + static member FinalFastCapture1<'A> + ( + cap, conv1 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun () -> + let env = env() + env.Write (conv1 env.Captures.[cap]) + env.Finish() + ) + ) + + static member FinalCapture2<'A, 'B> + ( + s0, cap, conv1, s1, conv2, s2 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) -> + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap]), s1, (conv2 b), s2) + env.Finish() + ) + ) + + static member FinalFastEndCapture2<'A, 'B> + ( + s0, cap, conv1, s1, conv2 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) -> + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap]), s1, (conv2 b)) + env.Finish() + ) + ) + + static member FinalFastStartCapture2<'A, 'B> + ( + cap, conv1, s1, conv2, s2 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) -> + let env = env() + Utils.Write(env, (conv1 env.Captures.[cap]), s1, (conv2 b), s2) + env.Finish() + ) + ) + + static member FinalFastCapture2<'A, 'B> + ( + cap, conv1, s1, conv2 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) -> + let env = env() + Utils.Write(env, (conv1 env.Captures.[cap]), s1, (conv2 b)) + env.Finish() + ) + ) + + static member FinalCapture3<'A, 'B, 'C> + ( + s0, cap, conv1, s1, conv2, s2, conv3, s3 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) -> + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c), s3) + env.Finish() + ) + ) + + static member FinalFastEndCapture3<'A, 'B, 'C> + ( + s0, cap, conv1, s1, conv2, s2, conv3 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) -> + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c)) + env.Finish() + ) + ) + + static member FinalFastStartCapture3<'A, 'B, 'C> + ( + cap, conv1, s1, conv2, s2, conv3, s3 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) -> + let env = env() + Utils.Write(env, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c), s3) + env.Finish() + ) + ) + + static member FinalFastCapture3<'A, 'B, 'C> + ( + cap, conv1, s1, conv2, s2, conv3 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) -> + let env = env() + Utils.Write(env, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c)) + env.Finish() + ) + ) + + static member FinalCapture4<'A, 'B, 'C, 'D> + ( + s0, cap, conv1, s1, conv2, s2, conv3, s3, conv4, s4 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) (d: 'D)-> + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c), s3, (conv4 d), s4) + env.Finish() + ) + ) + + static member FinalFastEndCapture4<'A, 'B, 'C, 'D> + ( + s0, cap, conv1, s1, conv2, s2, conv3, s3, conv4 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) (d: 'D) -> + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c), s3, (conv4 d)) + env.Finish() + ) + ) + + static member FinalFastStartCapture4<'A, 'B, 'C, 'D> + ( + cap, conv1, s1, conv2, s2, conv3, s3, conv4, s4 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) (d: 'D) -> + let env = env() + Utils.Write(env, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c), s3, (conv4 d), s4) + env.Finish() + ) + ) + + static member FinalFastCapture4<'A, 'B, 'C, 'D> + ( + cap, conv1, s1, conv2, s2, conv3, s3, conv4 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) (d: 'D) -> + let env = env() + Utils.Write(env, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c), s3, (conv4 d)) + env.Finish() + ) + ) + + static member FinalCapture5<'A, 'B, 'C, 'D, 'E> + ( + s0, cap, conv1, s1, conv2, s2, conv3, s3, conv4, s4, conv5, s5 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) (d: 'D) (e: 'E) -> + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c), s3, (conv4 d), s4, (conv5 e), s5) + env.Finish() + ) + ) + + static member FinalFastEndCapture5<'A, 'B, 'C, 'D, 'E> + ( + s0, cap, conv1, s1, conv2, s2, conv3, s3, conv4, s4, conv5 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) (d: 'D) (e: 'E) -> + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c), s3, (conv4 d), s4, (conv5 e)) + env.Finish() + ) + ) + + static member FinalFastStartCapture5<'A, 'B, 'C, 'D, 'E> + ( + cap, conv1, s1, conv2, s2, conv3, s3, conv4, s4, conv5, s5 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) (d: 'D) (e: 'E) -> + let env = env() + Utils.Write(env, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c), s3, (conv4 d), s4, (conv5 e), s5) + env.Finish() + ) + ) + + static member FinalFastCapture5<'A, 'B, 'C, 'D, 'E> + ( + cap, conv1, s1, conv2, s2, conv3, s3, conv4, s4, conv5 + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) (d: 'D) (e: 'E) -> + let env = env() + Utils.Write(env, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c), s3, (conv4 d), s4, (conv5 e)) + env.Finish() + ) + ) + + static member ChainedCapture1<'A, 'Tail> + ( + s0, cap, conv1, + next + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun () -> + let env() = + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap])) + env + next env : 'Tail + ) + ) + + static member ChainedFastStartCapture1<'A, 'Tail> + ( + cap, conv1, + next + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun () -> + let env() = + let env = env() + env.Write(conv1 env.Captures.[cap]) + env + next env : 'Tail + ) + ) + + static member ChainedCapture2<'A, 'B, 'Tail> + ( + s0, cap, conv1, s1, conv2, + next + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) -> + let env() = + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap]), s1, (conv2 b)) + env + next env : 'Tail + ) + ) + + static member ChainedFastStartCapture2<'A, 'B, 'Tail> + ( + cap, conv1, s1, conv2, + next + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) -> + let env() = + let env = env() + Utils.Write(env, conv1 env.Captures.[cap], s1, (conv2 b)) + env + next env : 'Tail + ) + ) + + static member ChainedCapture3<'A, 'B, 'C, 'Tail> + ( + s0, cap, conv1, s1, conv2, s2, conv3, + next + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) -> + let env() = + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c)) + env + next env : 'Tail + ) + ) + + static member ChainedFastStartCapture3<'A, 'B, 'C, 'Tail> + ( + cap, conv1, s1, conv2, s2, conv3, + next + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) -> + let env() = + let env = env() + Utils.Write(env, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c)) + env + next env : 'Tail + ) + ) + + static member ChainedCapture4<'A, 'B, 'C, 'D, 'Tail> + ( + s0, cap, conv1, s1, conv2, s2, conv3, s3, conv4, + next + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) (d: 'D)-> + let env() = + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c), s3, (conv4 d)) + env + next env : 'Tail + ) + ) + + static member ChainedFastStartCapture4<'A, 'B, 'C, 'D, 'Tail> + ( + cap, conv1, s1, conv2, s2, conv3, s3, conv4, + next + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) (d: 'D)-> + let env() = + let env = env() + Utils.Write(env, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c), s3, (conv4 d)) + env + next env : 'Tail + ) + ) + + static member ChainedCapture5<'A, 'B, 'C, 'D, 'E, 'Tail> + ( + s0, cap, conv1, s1, conv2, s2, conv3, s3, conv4, s4, conv5, + next + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) (d: 'D) (e: 'E)-> + let env() = + let env = env() + Utils.Write(env, s0, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c), s3, (conv4 d), s4, (conv5 e)) + env + next env : 'Tail + ) + ) + + static member ChainedFastStartCapture5<'A, 'B, 'C, 'D, 'E, 'Tail> + ( + cap, conv1, s1, conv2, s2, conv3, s3, conv4, s4, conv5, + next + ) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (b: 'B) (c: 'C) (d: 'D) (e: 'E)-> + let env() = + let env = env() + Utils.Write(env, (conv1 env.Captures.[cap]), s1, (conv2 b), s2, (conv3 c), s3, (conv4 d), s4, (conv5 e)) + env + next env : 'Tail + ) + ) + + static member LittleAFinalCapture<'A>(s1: string, cap, s2: string) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (f: 'State -> 'A ->'Residue) -> + let env = env() + env.Write s1 + env.WriteT(f env.State (env.Captures.[cap] :?> 'A)) + env.Write s2 + env.Finish() + ) + ) + + static member LittleAChainedCapture<'A, 'Tail>(s1: string, cap, next: PrintfFactory<'State, 'Residue, 'Result,'Tail>) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (f: 'State -> 'A ->'Residue) -> + let env() = + let env = env() + env.Write s1 + env.WriteT(f env.State (env.Captures.[cap] :?> 'A)) + env + next env: 'Tail + ) + ) + + static member StarFinalCapture1<'A>(s1: string, cap, conv, s2: string) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (star1: int) -> + let env = env() + env.Write s1 + env.Write (conv (env.Captures.[cap] :?> 'A) star1: string) + env.Write s2 + env.Finish() + ) + ) + + static member StarFinalCapture2<'A>(s1: string, cap, conv, s2: string) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (star1: int) (star2: int) -> + let env = env() + env.Write s1 + env.Write (conv (env.Captures.[cap] :?> 'A) star1 star2: string) + env.Write s2 + env.Finish() + ) + ) + + static member StarChainedCapture1<'A, 'Tail>(s1: string, cap, conv, next: PrintfFactory<'State, 'Residue, 'Result,'Tail>) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (star1: int) -> + let env() = + let env = env() + env.Write s1 + env.Write(conv (env.Captures.[cap] :?> 'A) star1 : string) + env + next env : 'Tail + ) + ) + + static member StarChainedCapture2<'A, 'Tail>(s1: string, cap, conv, next: PrintfFactory<'State, 'Residue, 'Result,'Tail>) = + (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> + (fun (star1: int) (star2: int) -> + let env() = + let env = env() + env.Write s1 + env.Write(conv (env.Captures.[cap] :?> 'A) star1 star2 : string) + env + next env : 'Tail + ) + ) + static member Final1<'A> ( s0, conv1, s1 @@ -621,6 +1129,7 @@ module internal PrintfImpl = env.Finish() ) ) + static member TChained<'Tail>(s1: string, next: PrintfFactory<'State, 'Residue, 'Result,'Tail>) = (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> (fun (f: 'State -> 'Residue) -> @@ -643,6 +1152,7 @@ module internal PrintfImpl = env.Finish() ) ) + static member LittleAChained<'A, 'Tail>(s1: string, next: PrintfFactory<'State, 'Residue, 'Result,'Tail>) = (fun (env: unit -> PrintfEnv<'State, 'Residue, 'Result>) -> (fun (f: 'State -> 'A ->'Residue) (a: 'A) -> @@ -1178,7 +1688,7 @@ module internal PrintfImpl = raise (ArgumentException(SR.GetString(SR.printfBadFormatSpecifier))) let extractCurriedArguments (ty: Type) n = - System.Diagnostics.Debug.Assert(n = 1 || n = 2 || n = 3, "n = 1 || n = 2 || n = 3") + System.Diagnostics.Debug.Assert(n = 0 || n = 1 || n = 2 || n = 3, "n = 0 || n = 1 || n = 2 || n = 3") let buf = Array.zeroCreate (n + 1) let rec go (ty: Type) i = if i < n then @@ -1255,6 +1765,7 @@ module internal PrintfImpl = // or grab the content of stack, build intermediate printer and push it back to stack (so it can later be consumed by as argument) type private PrintfBuilder<'S, 'Re, 'Res>() = + let builderStack = PrintfBuilderStack() let mutable count = 0 let mutable optimizedArgCount = 0 #if DEBUG @@ -1383,10 +1894,6 @@ module internal PrintfImpl = let mi = mi.MakeGenericMethod argTypes mi.Invoke(null, args) - let builderStack = PrintfBuilderStack() - - let ContinuationOnStack = -1 - let buildPlain numberOfArgs prefix = let n = numberOfArgs * 2 let hasCont = builderStack.HasContinuationOnStack numberOfArgs @@ -1406,20 +1913,182 @@ module internal PrintfImpl = else buildPlainFinal(plainArgs, plainTypes) - let rec parseFromFormatSpecifier (prefix: string) (s: string) (funcTy: Type) i: int = + let buildCaptureSpecialChained(spec: FormatSpecifier, cTy: Type, prefix: string, tail: obj, retTy) = + let capture = box spec.Capture.Value + if spec.TypeChar = 'a' then + let mi = typeof>.GetMethod("LittleAChainedCapture", NonPublicStatics) +#if DEBUG + verifyMethodInfoWasTaken mi +#endif + + let mi = mi.MakeGenericMethod([| cTy; retTy |]) + let args = [| box prefix; capture; tail |] + mi.Invoke(null, args) + elif spec.TypeChar = 't' then raise (ArgumentException("format specifier '%t' may not be used with immediate captures")) + else + System.Diagnostics.Debug.Assert(spec.IsStarPrecision || spec.IsStarWidth, "spec.IsStarPrecision || spec.IsStarWidth ") + System.Diagnostics.Debug.Assert(spec.TypeChar <> '%', "spec.TypeChar <> '%'") + + let mi = + let n = if spec.IsStarWidth = spec.IsStarPrecision then 2 else 1 + let name = "StarChainedCapture" + (string n) + typeof>.GetMethod(name, NonPublicStatics) +#if DEBUG + verifyMethodInfoWasTaken mi +#endif + let conv = getValueConverter cTy spec + let args = [| box prefix; capture; box conv; tail |] + let mi = mi.MakeGenericMethod([| cTy; retTy |]) + mi.Invoke(null, args) + + let buildCaptureSpecialFinal(spec: FormatSpecifier, cTy: Type, prefix: string, suffix: string) = + let capture = box spec.Capture.Value + if spec.TypeChar = 'a' then + let mi = typeof>.GetMethod("LittleAFinalCapture", NonPublicStatics) +#if DEBUG + verifyMethodInfoWasTaken mi +#endif + let mi = mi.MakeGenericMethod(cTy) + let args = [| box prefix; capture; box suffix |] + mi.Invoke(null, args) + elif spec.TypeChar = 't' then raise (ArgumentException("format specifier '%t' may not be used with immediate captures")) + else + System.Diagnostics.Debug.Assert(spec.IsStarPrecision || spec.IsStarWidth, "spec.IsStarPrecision || spec.IsStarWidth ") + System.Diagnostics.Debug.Assert(spec.TypeChar <> '%', "spec.TypeChar <> '%'") + + let mi = + let n = if spec.IsStarWidth = spec.IsStarPrecision then 2 else 1 + let name = "StarFinalCapture" + (string n) + typeof>.GetMethod(name, NonPublicStatics) +#if DEBUG + verifyMethodInfoWasTaken mi +#endif + + let conv = getValueConverter cTy spec + let args = [| box prefix; capture; box conv; box suffix |] + let mi = mi.MakeGenericMethod(cTy) + mi.Invoke(null, args) + + let buildCaptureFinal(spec, prefix, suffix, cTy, pendingArgs: obj[], pendingTypes: Type[]) = + let capture = box(spec.Capture.Value) + let conv = getValueConverter cTy spec + let argsCount = pendingArgs.Length + let suffix' = if argsCount > 0 then pendingArgs.[argsCount - 1].ToString() else suffix + + let methodName, (args: obj seq) = + match prefix, suffix' with + | "", "" -> + optimizedArgCount <- optimizedArgCount + 2 + "FinalFastCapture", seq { + yield capture + yield conv + if argsCount > 0 then + yield box suffix + yield! Seq.take (argsCount-1) pendingArgs + } + | "", _ -> + optimizedArgCount <- optimizedArgCount + 1 + "FinalFastStartCapture", seq { + yield capture + yield conv + yield box suffix + yield! pendingArgs + } + | _, "" -> + optimizedArgCount <- optimizedArgCount + 1 + "FinalFastEndCapture", seq { + yield prefix + yield capture + yield conv + if argsCount > 0 then + yield box suffix + yield! Seq.take (argsCount-1) pendingArgs + } + | _, _ -> + "FinalCapture", seq { + yield prefix + yield capture + yield conv + yield suffix + yield! pendingArgs + } + + let mi = typeof>.GetMethod(methodName + (pendingTypes.Length + 1).ToString(), NonPublicStatics) +#if DEBUG + verifyMethodInfoWasTaken mi +#endif + let mi = mi.MakeGenericMethod(Array.append [| cTy |] pendingTypes) + mi.Invoke(null, Array.ofSeq args) + + let buildCaptureChained(spec, prefix, cTy, tail, retTy, pendingArgs, pendingTypes: Type[]) = + let capture = box(spec.Capture.Value) + let conv = getValueConverter cTy spec + let methodName,args = + if prefix = "" then + optimizedArgCount <- optimizedArgCount + 1 + "ChainedFastStartCapture", seq { + yield capture + yield conv + yield! pendingArgs + yield tail + } + else + "ChainedCapture", seq { + yield prefix + yield capture + yield conv + yield! pendingArgs + yield tail + } + + let mi = typeof>.GetMethod(methodName + (pendingTypes.Length + 1).ToString(), NonPublicStatics) +#if DEBUG + verifyMethodInfoWasTaken mi +#endif + let mi = mi.MakeGenericMethod(Array.ofSeq <| seq { + yield cTy + yield! pendingTypes + yield retTy + }) + mi.Invoke(null, Array.ofSeq args) + + let buildCapture(spec, prefix, suffix, cTy, numberOfArgs) = + let n = numberOfArgs * 2 + let hasCont = builderStack.HasContinuationOnStack numberOfArgs + let plainArgs, plainTypes = + builderStack.GetArgumentAndTypesAsArrays(n, 0, n, numberOfArgs, 0, numberOfArgs) + + if hasCont then + let cont, contTy = builderStack.PopContinuationWithType() + + buildCaptureChained(spec, prefix, cTy, cont, contTy, plainArgs, plainTypes) + else + buildCaptureFinal(spec, prefix, suffix, cTy, plainArgs, plainTypes) + + /// + /// A sentinel value for parseFromFormatSpecifier return value, + /// indicating that a continuation is at the stack top. + /// + [] + let ContinuationOnStack = -1 + /// + /// A sentinel value for parseFromFormatSpecifier return value, + /// indicating that the format string completes scanning and + /// no more specifiers are found. + /// + [] + let EndOfString = 0 + + /// The number of pending arguments on the builder stack + let rec parseFromFormatSpecifier (prefix: string) (s: string) (funcTy: Type) (i: int) (cTy: Type[]) = - if i >= s.Length then 0 + if i >= s.Length then EndOfString else - System.Diagnostics.Debug.Assert(s.[i] = '%', "s.[i] = '%'") count <- count + 1 - let flags, i = FormatString.parseFlags s (i + 1) - let width, i = FormatString.parseWidth s i - let precision, i = FormatString.parsePrecision s i - let typeChar, i = FormatString.parseTypeChar s i - let spec = { TypeChar = typeChar; Precision = precision; Flags = flags; Width = width} - + let spec, i = FormatString.parseFormatSpecifier s i + let isImm, _ = spec.Capture.IsSome, Option.defaultValue -1 spec.Capture let next, suffix = FormatString.findNextFormatSpecifier s i let argTys = @@ -1430,29 +2099,37 @@ module internal PrintfImpl = else 2 else 1 - let n = if spec.TypeChar = '%' then n - 1 else n - - System.Diagnostics.Debug.Assert(n <> 0, "n <> 0") + // immediate spec doesn't get payload from arg + let n = if isImm then n - 1 else n + System.Diagnostics.Debug.Assert(n >= 0, "n >= 0") extractCurriedArguments funcTy n + // curry on let retTy = argTys.[argTys.Length - 1] - let numberOfArgs = parseFromFormatSpecifier suffix s retTy next - + let numberOfArgs = parseFromFormatSpecifier suffix s retTy next cTy if spec.TypeChar = 'a' || spec.TypeChar = 't' || spec.IsStarWidth || spec.IsStarPrecision then + let cTy = + if spec.HasCapture then Some cTy.[spec.Capture.Value] + else None + if numberOfArgs = ContinuationOnStack then let cont, contTy = builderStack.PopContinuationWithType() - let currentCont = buildSpecialChained(spec, argTys, prefix, cont, contTy) + let currentCont = + if spec.HasCapture then buildCaptureSpecialChained(spec, cTy.Value, prefix, cont, contTy) + else buildSpecialChained(spec, argTys, prefix, cont, contTy) builderStack.PushContinuationWithType(currentCont, funcTy) ContinuationOnStack else - if numberOfArgs = 0 then + if numberOfArgs = EndOfString then System.Diagnostics.Debug.Assert(builderStack.IsEmpty, "builderStack.IsEmpty") - let currentCont = buildSpecialFinal(spec, argTys, prefix, suffix) + let currentCont = + if spec.HasCapture then buildCaptureSpecialFinal(spec, cTy.Value, prefix, suffix) + else buildSpecialFinal(spec, argTys, prefix, suffix) builderStack.PushContinuationWithType(currentCont, funcTy) ContinuationOnStack else @@ -1477,10 +2154,33 @@ module internal PrintfImpl = else buildPlainFinal(plainArgs, plainTypes) - let next = buildSpecialChained(spec, argTys, prefix, next, retTy) + let next = + if spec.HasCapture then buildCaptureSpecialChained(spec, cTy.Value, prefix, next, retTy) + else buildSpecialChained(spec, argTys, prefix, next, retTy) builderStack.PushContinuationWithType(next, funcTy) ContinuationOnStack + + elif spec.HasCapture then + let capture = spec.Capture.Value + if numberOfArgs = ContinuationOnStack then + // no pending args between capture and cont + let cont, contTy = builderStack.PopContinuationWithType() + let currentCont = buildCaptureChained(spec, prefix, cTy.[capture], cont, contTy, [||], [||]) + builderStack.PushContinuationWithType(currentCont, funcTy) + + elif numberOfArgs = EndOfString then + // a "final" starting with a "capture" must not have pending args + System.Diagnostics.Debug.Assert(builderStack.IsEmpty, "builderStack.IsEmpty") + let currentCont = buildCaptureFinal(spec, prefix, suffix, cTy.[capture], [||], [||]) + builderStack.PushContinuationWithType(currentCont, funcTy) + else + //build a "capture-starting" continuation with the pending params + let cont = buildCapture(spec, prefix, suffix, cTy.[capture], numberOfArgs) + builderStack.PushContinuationWithType(cont, funcTy) + + ContinuationOnStack + else if numberOfArgs = ContinuationOnStack then let idx = argTys.Length - 2 @@ -1498,7 +2198,7 @@ module internal PrintfImpl = else numberOfArgs + 1 - let parseFormatString (s: string) (funcTy: System.Type) : obj = + let parseFormatString (s: string) (funcTy: System.Type) (cTy: System.Type[]) : obj = optimizedArgCount <- 0 let prefixPos, prefix = FormatString.findNextFormatSpecifier s 0 if prefixPos = s.Length then @@ -1508,15 +2208,15 @@ module internal PrintfImpl = env.Finish() ) else - let n = parseFromFormatSpecifier prefix s funcTy prefixPos + let n = parseFromFormatSpecifier prefix s funcTy prefixPos cTy if n = ContinuationOnStack || n = 0 then builderStack.PopValueUnsafe() else buildPlain n prefix - member __.Build<'T>(s: string) : PrintfFactory<'S, 'Re, 'Res, 'T> * int = - parseFormatString s typeof<'T> :?> _, (2 * count + 1) - optimizedArgCount // second component is used in SprintfEnv as value for internal buffer + member __.Build<'T>(s: string, cTy: Type[]) : PrintfFactory<'S, 'Re, 'Res, 'T> * int = + parseFormatString s (typeof<'T>) cTy :?> _, (2 * count + 1) - optimizedArgCount // second component is used in SprintfEnv as value for internal buffer /// Type of element that is stored in cache /// Pair: factory for the printer + number of text blocks that printer will produce (used to preallocate buffers) @@ -1527,10 +2227,10 @@ module internal PrintfImpl = /// printf is called in tight loop /// 2nd level is global dictionary that maps format string to the corresponding PrintfFactory type Cache<'T, 'State, 'Residue, 'Result>() = - static let generate fmt = PrintfBuilder<'State, 'Residue, 'Result>().Build<'T>(fmt) - static let mutable map = System.Collections.Concurrent.ConcurrentDictionary>() + static let generate(fmt, capTypes) = PrintfBuilder<'State, 'Residue, 'Result>().Build<'T>(fmt, capTypes) + static let mutable map = System.Collections.Concurrent.ConcurrentDictionary>() static let getOrAddFunc = Func<_, _>(generate) - static let get (key: string) = map.GetOrAdd(key, getOrAddFunc) + static let get(key: string*Type[]) = map.GetOrAdd(key, getOrAddFunc) [] [] @@ -1541,12 +2241,12 @@ module internal PrintfImpl = && key.Value.Equals (fst Cache<'T, 'State, 'Residue, 'Result>.last) then snd Cache<'T, 'State, 'Residue, 'Result>.last else - let v = get key.Value + let v = get(key.Value, key.Types) Cache<'T, 'State, 'Residue, 'Result>.last <- (key.Value, v) v - type StringPrintfEnv<'Result>(k, n) = - inherit PrintfEnv(()) + type StringPrintfEnv<'Result>(k, n, caps) = + inherit PrintfEnv((), caps) let buf: string[] = Array.zeroCreate n let mutable ptr = 0 @@ -1559,8 +2259,8 @@ module internal PrintfImpl = buf.[ptr] <- s ptr <- ptr + 1 - type SmallStringPrintfEnv<'Result>(k) = - inherit PrintfEnv(()) + type SmallStringPrintfEnv<'Result>(k, caps) = + inherit PrintfEnv((), caps) let mutable c = null @@ -1568,14 +2268,14 @@ module internal PrintfImpl = override __.Write(s: string) = if isNull c then c <- s else c <- c + s override __.WriteT s = if isNull c then c <- s else c <- c + s - type StringBuilderPrintfEnv<'Result>(k, buf) = - inherit PrintfEnv(buf) + type StringBuilderPrintfEnv<'Result>(k, buf, caps) = + inherit PrintfEnv(buf, caps) override __.Finish() : 'Result = k () override __.Write(s: string) = ignore(buf.Append s) override __.WriteT(()) = () - type TextWriterPrintfEnv<'Result>(k, tw: IO.TextWriter) = - inherit PrintfEnv(tw) + type TextWriterPrintfEnv<'Result>(k, tw: IO.TextWriter, caps) = + inherit PrintfEnv(tw, caps) override __.Finish() : 'Result = k() override __.Write(s: string) = tw.Write s override __.WriteT(()) = () @@ -1604,18 +2304,18 @@ module Printf = let ksprintf continuation (format: StringFormat<'T, 'Result>) : 'T = doPrintf format (fun n -> if n <= 2 then - SmallStringPrintfEnv continuation :> PrintfEnv<_, _, _> + SmallStringPrintfEnv(continuation, format.Captures) :> PrintfEnv<_, _, _> else - StringPrintfEnv(continuation, n) :> PrintfEnv<_, _, _> + StringPrintfEnv(continuation, n, format.Captures) :> PrintfEnv<_, _, _> ) [] let sprintf (format: StringFormat<'T>) = doPrintf format (fun n -> if n <= 2 then - SmallStringPrintfEnv id :> PrintfEnv<_, _, _> + SmallStringPrintfEnv(id, format.Captures) :> PrintfEnv<_, _, _> else - StringPrintfEnv(id, n) :> PrintfEnv<_, _, _> + StringPrintfEnv(id, n, format.Captures) :> PrintfEnv<_, _, _> ) [] @@ -1624,13 +2324,13 @@ module Printf = [] let kbprintf continuation (builder: StringBuilder) format = doPrintf format (fun _ -> - StringBuilderPrintfEnv(continuation, builder) :> PrintfEnv<_, _, _> + StringBuilderPrintfEnv(continuation, builder, format.Captures) :> PrintfEnv<_, _, _> ) [] let kfprintf continuation textWriter format = doPrintf format (fun _ -> - TextWriterPrintfEnv(continuation, textWriter) :> PrintfEnv<_, _, _> + TextWriterPrintfEnv(continuation, textWriter, format.Captures) :> PrintfEnv<_, _, _> ) [] diff --git a/src/fsharp/ast.fs b/src/fsharp/ast.fs index 3c341131fba..46ff3e39674 100644 --- a/src/fsharp/ast.fs +++ b/src/fsharp/ast.fs @@ -801,6 +801,10 @@ and /// 'use x = fixed expr' | Fixed of expr: SynExpr * range: range + /// F# syntax: interpolated string, e.g. "abc%{123}" + | InterpolatedString of text: string * range: range * captures: SynExpr list + + /// Get the syntactic range of source code covered by this construct. member e.Range = match e with @@ -866,7 +870,8 @@ and | SynExpr.LetOrUseBang (range=m) | SynExpr.MatchBang (range=m) | SynExpr.DoBang (range=m) - | SynExpr.Fixed (range=m) -> m + | SynExpr.Fixed (range=m) + | SynExpr.InterpolatedString (range=m) -> m | SynExpr.Ident id -> id.idRange /// range ignoring any (parse error) extra trailing dots @@ -932,6 +937,7 @@ and | SynExpr.LongIdent (_, lidwd, _, _) -> lidwd.RangeSansAnyExtraDot | SynExpr.DiscardAfterMissingQualificationAfterDot (expr, _) -> expr.Range | SynExpr.Fixed (_, m) -> m + | SynExpr.InterpolatedString (range=m) -> m | SynExpr.Ident id -> id.idRange /// Attempt to get the range of the first token or initial portion only - this is extremely ad-hoc, just a cheap way to improve a certain 'query custom operation' error range @@ -993,6 +999,7 @@ and | SynExpr.LetOrUseBang (range=m) | SynExpr.MatchBang (range=m) | SynExpr.DoBang (range=m) -> m + | SynExpr.InterpolatedString (range=m) -> m // these are better than just .Range, and also commonly applicable inside queries | SynExpr.Paren (_, m, _, _) -> m | SynExpr.Sequential (_, _, e1, _, _) diff --git a/src/fsharp/pars.fsy b/src/fsharp/pars.fsy index 56e6dbfb404..d107c91150f 100644 --- a/src/fsharp/pars.fsy +++ b/src/fsharp/pars.fsy @@ -156,6 +156,9 @@ let rangeOfLongIdent(lid:LongIdent) = %token BYTEARRAY %token STRING +%token STRING_INTEROP_LEFT +%token STRING_INTEROP_MID +%token STRING_INTEROP_RIGHT %token KEYWORD_STRING // Like __SOURCE_DIRECTORY__ %token IDENT %token INFIX_STAR_STAR_OP @@ -378,7 +381,7 @@ let rangeOfLongIdent(lid:LongIdent) = * - TRUE,FALSE */ %nonassoc prec_atompat_pathop -%nonassoc INT8 UINT8 INT16 UINT16 INT32 UINT32 INT64 UINT64 NATIVEINT UNATIVEINT IEEE32 IEEE64 CHAR KEYWORD_STRING STRING BYTEARRAY BIGNUM DECIMAL +%nonassoc INT8 UINT8 INT16 UINT16 INT32 UINT32 INT64 UINT64 NATIVEINT UNATIVEINT IEEE32 IEEE64 CHAR KEYWORD_STRING STRING STRING_INTEROP_LEFT STRING_INTEROP_MID STRING_INTEROP_RIGHT BYTEARRAY BIGNUM DECIMAL %nonassoc LPAREN LBRACE LBRACK_BAR %nonassoc TRUE FALSE UNDERSCORE NULL @@ -4996,7 +4999,7 @@ colonOrEquals: | COLON { mlCompatWarning (FSComp.SR.parsSyntaxModuleSigEndDeprecated()) (lhs parseState); } | EQUALS { } -/* A literal string or a string fromm a keyword like __SOURCE_FILE__ */ +/* A literal string or a string from a keyword like __SOURCE_FILE__ */ stringOrKeywordString: | STRING { $1 } | KEYWORD_STRING { $1 }