From 09e40d76e08b8e9f645da6fc33fcc83d42211444 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Fri, 21 Oct 2022 18:43:03 +0200 Subject: [PATCH 01/26] POC for improved interpolation --- src/Compiler/Service/ServiceLexing.fs | 19 +-- src/Compiler/SyntaxTree/LexHelpers.fs | 2 + src/Compiler/SyntaxTree/LexHelpers.fsi | 3 +- src/Compiler/SyntaxTree/ParseHelpers.fs | 11 +- src/Compiler/SyntaxTree/ParseHelpers.fsi | 3 +- src/Compiler/lex.fsl | 188 +++++++++++++++-------- 6 files changed, 143 insertions(+), 83 deletions(-) diff --git a/src/Compiler/Service/ServiceLexing.fs b/src/Compiler/Service/ServiceLexing.fs index ac28ec245a..b87e2b2b30 100644 --- a/src/Compiler/Service/ServiceLexing.fs +++ b/src/Compiler/Service/ServiceLexing.fs @@ -608,12 +608,12 @@ module internal LexerStateEncoding = let tag1, i1, kind1, rest = match stringNest with | [] -> false, 0, 0, [] - | (i1, kind1, _) :: rest -> true, i1, encodeStringStyle kind1, rest + | (i1, kind1, _, _) :: rest -> true, i1, encodeStringStyle kind1, rest let tag2, i2, kind2 = match rest with | [] -> false, 0, 0 - | (i2, kind2, _) :: _ -> true, i2, encodeStringStyle kind2 + | (i2, kind2, _, _) :: _ -> true, i2, encodeStringStyle kind2 (if tag1 then 0b100000000000 else 0) ||| (if tag2 then 0b010000000000 else 0) @@ -679,9 +679,9 @@ module internal LexerStateEncoding = [ if tag1 then - i1, decodeStringStyle kind1, range0 + i1, decodeStringStyle kind1, 0, range0 if tag2 then - i2, decodeStringStyle kind2, range0 + i2, decodeStringStyle kind2, 0, range0 ] (colorState, ncomments, pos, ifDefs, hardwhite, stringKind, stringNest) @@ -722,7 +722,7 @@ module internal LexerStateEncoding = LexerStringKind.String, stringNest ) - | LexCont.String (ifdefs, stringNest, style, kind, m) -> + | LexCont.String (ifdefs, stringNest, style, kind, _, m) -> let state = match style with | LexerStringStyle.SingleQuote -> FSharpTokenizerColorState.String @@ -778,7 +778,7 @@ module internal LexerStateEncoding = | FSharpTokenizerColorState.Token -> LexCont.Token(ifdefs, stringNest) | FSharpTokenizerColorState.IfDefSkip -> LexCont.IfDefSkip(ifdefs, stringNest, n1, mkRange "file" p1 p1) | FSharpTokenizerColorState.String -> - LexCont.String(ifdefs, stringNest, LexerStringStyle.SingleQuote, stringKind, mkRange "file" p1 p1) + LexCont.String(ifdefs, stringNest, LexerStringStyle.SingleQuote, stringKind, 0, mkRange "file" p1 p1) | FSharpTokenizerColorState.Comment -> LexCont.Comment(ifdefs, stringNest, n1, mkRange "file" p1 p1) | FSharpTokenizerColorState.SingleLineComment -> LexCont.SingleLineComment(ifdefs, stringNest, n1, mkRange "file" p1 p1) | FSharpTokenizerColorState.StringInComment -> @@ -789,9 +789,9 @@ module internal LexerStateEncoding = LexCont.StringInComment(ifdefs, stringNest, LexerStringStyle.TripleQuote, n1, mkRange "file" p1 p1) | FSharpTokenizerColorState.CamlOnly -> LexCont.MLOnly(ifdefs, stringNest, mkRange "file" p1 p1) | FSharpTokenizerColorState.VerbatimString -> - LexCont.String(ifdefs, stringNest, LexerStringStyle.Verbatim, stringKind, mkRange "file" p1 p1) + LexCont.String(ifdefs, stringNest, LexerStringStyle.Verbatim, stringKind, 0, mkRange "file" p1 p1) | FSharpTokenizerColorState.TripleQuoteString -> - LexCont.String(ifdefs, stringNest, LexerStringStyle.TripleQuote, stringKind, mkRange "file" p1 p1) + LexCont.String(ifdefs, stringNest, LexerStringStyle.TripleQuote, stringKind, 0, mkRange "file" p1 p1) | FSharpTokenizerColorState.EndLineThenSkip -> LexCont.EndLine(ifdefs, stringNest, LexerEndlineContinuation.Skip(n1, mkRange "file" p1 p1)) | FSharpTokenizerColorState.EndLineThenToken -> LexCont.EndLine(ifdefs, stringNest, LexerEndlineContinuation.Token) @@ -923,9 +923,10 @@ type FSharpLineTokenizer(lexbuf: UnicodeLexing.Lexbuf, maxLength: int option, fi lexargs.stringNest <- stringNest Lexer.ifdefSkip n m lexargs skip lexbuf - | LexCont.String (ifdefs, stringNest, style, kind, m) -> + | LexCont.String (ifdefs, stringNest, style, kind, numdol, m) -> lexargs.ifdefStack <- ifdefs lexargs.stringNest <- stringNest + lexargs.numDollars <- numdol use buf = ByteBuffer.Create Lexer.StringCapacity let args = (buf, LexerStringFinisher.Default, m, kind, lexargs) diff --git a/src/Compiler/SyntaxTree/LexHelpers.fs b/src/Compiler/SyntaxTree/LexHelpers.fs index f5c8375247..1f7d00221d 100644 --- a/src/Compiler/SyntaxTree/LexHelpers.fs +++ b/src/Compiler/SyntaxTree/LexHelpers.fs @@ -65,6 +65,7 @@ type LexArgs = mutable ifdefStack: LexerIfdefStack mutable indentationSyntaxStatus: IndentationAwareSyntaxStatus mutable stringNest: LexerInterpolatedStringNesting + mutable numDollars: int } /// possible results of lexing a long Unicode escape sequence in a string literal, e.g. "\U0001F47D", @@ -93,6 +94,7 @@ let mkLexargs applyLineDirectives = applyLineDirectives stringNest = [] pathMap = pathMap + numDollars = 0 } /// Register the lexbuf and call the given function diff --git a/src/Compiler/SyntaxTree/LexHelpers.fsi b/src/Compiler/SyntaxTree/LexHelpers.fsi index 49f73abbab..ad745d12bb 100644 --- a/src/Compiler/SyntaxTree/LexHelpers.fsi +++ b/src/Compiler/SyntaxTree/LexHelpers.fsi @@ -37,7 +37,8 @@ type LexArgs = pathMap: PathMap mutable ifdefStack: LexerIfdefStack mutable indentationSyntaxStatus: IndentationAwareSyntaxStatus - mutable stringNest: LexerInterpolatedStringNesting } + mutable stringNest: LexerInterpolatedStringNesting + mutable numDollars: int } type LongUnicodeLexResult = | SurrogatePair of uint16 * uint16 diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fs b/src/Compiler/SyntaxTree/ParseHelpers.fs index e181801324..d32837858e 100644 --- a/src/Compiler/SyntaxTree/ParseHelpers.fs +++ b/src/Compiler/SyntaxTree/ParseHelpers.fs @@ -307,7 +307,7 @@ type LexerStringKind = /// Represents the degree of nesting of '{..}' and the style of the string to continue afterwards, in an interpolation fill. /// Nesting counters and styles of outer interpolating strings are pushed on this stack. -type LexerInterpolatedStringNesting = (int * LexerStringStyle * range) list +type LexerInterpolatedStringNesting = (int * LexerStringStyle * int * range) list /// The parser defines a number of tokens for whitespace and /// comments eliminated by the lexer. These carry a specification of @@ -323,6 +323,7 @@ type LexerContinuation = nesting: LexerInterpolatedStringNesting * style: LexerStringStyle * kind: LexerStringKind * + numdol: int * range: range | Comment of ifdef: LexerIfdefStackEntries * nesting: LexerInterpolatedStringNesting * int * range: range | SingleLineComment of ifdef: LexerIfdefStackEntries * nesting: LexerInterpolatedStringNesting * int * range: range @@ -924,19 +925,19 @@ let checkEndOfFileError t = match t with | LexCont.IfDefSkip (_, _, _, m) -> reportParseErrorAt m (FSComp.SR.parsEofInHashIf ()) - | LexCont.String (_, _, LexerStringStyle.SingleQuote, kind, m) -> + | LexCont.String (_, _, LexerStringStyle.SingleQuote, kind, _, m) -> if kind.IsInterpolated then reportParseErrorAt m (FSComp.SR.parsEofInInterpolatedString ()) else reportParseErrorAt m (FSComp.SR.parsEofInString ()) - | LexCont.String (_, _, LexerStringStyle.TripleQuote, kind, m) -> + | LexCont.String (_, _, LexerStringStyle.TripleQuote, kind, _, m) -> if kind.IsInterpolated then reportParseErrorAt m (FSComp.SR.parsEofInInterpolatedTripleQuoteString ()) else reportParseErrorAt m (FSComp.SR.parsEofInTripleQuoteString ()) - | LexCont.String (_, _, LexerStringStyle.Verbatim, kind, m) -> + | LexCont.String (_, _, LexerStringStyle.Verbatim, kind, _, m) -> if kind.IsInterpolated then reportParseErrorAt m (FSComp.SR.parsEofInInterpolatedVerbatimString ()) else @@ -966,7 +967,7 @@ let checkEndOfFileError t = match nesting with | [] -> () - | (_, _, m) :: _ -> reportParseErrorAt m (FSComp.SR.parsEofInInterpolatedStringFill ()) + | (_, _, _, m) :: _ -> reportParseErrorAt m (FSComp.SR.parsEofInInterpolatedStringFill ()) type BindingSet = BindingSetPreAttrs of range * bool * bool * (SynAttributes -> SynAccess option -> SynAttributes * SynBinding list) * range diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fsi b/src/Compiler/SyntaxTree/ParseHelpers.fsi index 46ae73f1ab..cbf2067496 100644 --- a/src/Compiler/SyntaxTree/ParseHelpers.fsi +++ b/src/Compiler/SyntaxTree/ParseHelpers.fsi @@ -117,7 +117,7 @@ type LexerStringKind = static member String: LexerStringKind -type LexerInterpolatedStringNesting = (int * LexerStringStyle * range) list +type LexerInterpolatedStringNesting = (int * LexerStringStyle * int * range) list [] type LexerContinuation = @@ -128,6 +128,7 @@ type LexerContinuation = nesting: LexerInterpolatedStringNesting * style: LexerStringStyle * kind: LexerStringKind * + numdol: int * range: range | Comment of ifdef: LexerIfdefStackEntries * nesting: LexerInterpolatedStringNesting * int * range: range | SingleLineComment of ifdef: LexerIfdefStackEntries * nesting: LexerInterpolatedStringNesting * int * range: range diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl index c9900ea1ac..8d12bd2ebe 100644 --- a/src/Compiler/lex.fsl +++ b/src/Compiler/lex.fsl @@ -600,14 +600,14 @@ rule token args skip = parse // Single quote in triple quote ok, others disallowed match args.stringNest with - | (_, LexerStringStyle.TripleQuote, _) :: _ -> () + | (_, LexerStringStyle.TripleQuote, _, _) :: _ -> () | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | [] -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.String, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.String, args.numDollars, m)) else singleQuoteString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } - | '$' '"' '"' '"' + | ('$'+) '"' '"' '"' { let buf, fin, m = startString args lexbuf // Single quote in triple quote ok, others disallowed @@ -615,7 +615,9 @@ rule token args skip = parse | _ :: _ -> errorR(Error(FSComp.SR.lexTripleQuoteInTripleQuote(), m)) | [] -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.InterpolatedStringFirst, m)) + let numDollars = lexeme lexbuf |> Seq.takeWhile (fun c -> c = '$') |> Seq.length + args.numDollars <- numDollars + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.InterpolatedStringFirst, numDollars, m)) else tripleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } | '$' '"' @@ -623,22 +625,24 @@ rule token args skip = parse // Single quote in triple quote ok, others disallowed match args.stringNest with - | (_, LexerStringStyle.TripleQuote, _) :: _ -> () + | (_, LexerStringStyle.TripleQuote, _, _) :: _ -> () | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.InterpolatedStringFirst, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.InterpolatedStringFirst, args.numDollars, m)) else singleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } | '"' '"' '"' { let buf, fin, m = startString args lexbuf + args.numDollars <- 0 + // Single quote in triple quote ok, others disallowed match args.stringNest with | _ :: _ -> errorR(Error(FSComp.SR.lexTripleQuoteInTripleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.String, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.String, args.numDollars, m)) else tripleQuoteString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } | '@' '"' @@ -646,11 +650,11 @@ rule token args skip = parse // Single quote in triple quote ok, others disallowed match args.stringNest with - | (_, LexerStringStyle.TripleQuote, _) :: _ -> () + | (_, LexerStringStyle.TripleQuote, _, _) :: _ -> () | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.String, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.String, args.numDollars, m)) else verbatimString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } | ("$@" | "@$") '"' @@ -658,11 +662,11 @@ rule token args skip = parse // Single quote in triple quote ok, others disallowed match args.stringNest with - | (_, LexerStringStyle.TripleQuote, _) :: _ -> () + | (_, LexerStringStyle.TripleQuote, _, _) :: _ -> () | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.InterpolatedStringFirst, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.InterpolatedStringFirst, args.numDollars, m)) else verbatimString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } | truewhite+ @@ -861,10 +865,10 @@ rule token args skip = parse { match args.stringNest with | [] -> () - | (counter, style, m) :: rest -> + | (counter, style, d, m) :: rest -> // Note, we do not update the 'm', any incomplete-interpolation error // will be reported w.r.t. the first '{' - args.stringNest <- (counter + 1, style, m) :: rest + args.stringNest <- (counter + 1, style, d, m) :: rest // To continue token-by-token lexing may involve picking up the new args.stringNes let cont = LexCont.Token(args.ifdefStack, args.stringNest) LBRACE cont @@ -877,21 +881,24 @@ rule token args skip = parse // We encounter a '}' in the expression token stream. First check if we're in an interpolated string expression // and continue the string if necessary match args.stringNest with - | (1, style, _) :: rest -> + | (1, LexerStringStyle.TripleQuote, x, r) :: rest when x > 1 -> + args.stringNest <- (1, LexerStringStyle.TripleQuote, x-1, r) :: rest + token args skip lexbuf + | (1, style, _, _) :: rest -> args.stringNest <- rest let buf, fin, m = startString args lexbuf if not skip then - STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, style, LexerStringKind.InterpolatedStringPart, m)) + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, style, LexerStringKind.InterpolatedStringPart, args.numDollars, m)) else match style with | LexerStringStyle.Verbatim -> verbatimString (buf, fin, m, LexerStringKind.InterpolatedStringPart, args) skip lexbuf | LexerStringStyle.SingleQuote -> singleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringPart, args) skip lexbuf | LexerStringStyle.TripleQuote -> tripleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringPart, args) skip lexbuf - | (counter, style, m) :: rest -> + | (counter, style, d, m) :: rest -> // Note, we do not update the 'm', any incomplete-interpolation error // will be reported w.r.t. the first '{' - args.stringNest <- (counter - 1, style, m) :: rest + args.stringNest <- (counter - 1, style, d, m) :: rest let cont = LexCont.Token(args.ifdefStack, args.stringNest) RBRACE cont @@ -1142,39 +1149,39 @@ and singleQuoteString sargs skip = parse let text = lexeme lexbuf let text2 = text |> String.filter (fun c -> c <> ' ' && c <> '\t') advanceColumnBy lexbuf (text.Length - text2.Length) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) else singleQuoteString sargs skip lexbuf } | escape_char { let (buf, _fin, m, kind, args) = sargs addByteChar buf (escape (lexeme lexbuf).[1]) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) else singleQuoteString sargs skip lexbuf } | trigraph { let (buf, _fin, m, kind, args) = sargs let s = lexeme lexbuf addByteChar buf (trigraph s.[1] s.[2] s.[3]) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) else singleQuoteString sargs skip lexbuf } | hexGraphShort { let (buf, _fin, m, kind, args) = sargs addUnicodeChar buf (int (hexGraphShort (lexemeTrimLeft lexbuf 2))) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) else singleQuoteString sargs skip lexbuf } | unicodeGraphShort { let (buf, _fin, m, kind, args) = sargs addUnicodeChar buf (int (unicodeGraphShort (lexemeTrimLeft lexbuf 2))) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) else singleQuoteString sargs skip lexbuf } | unicodeGraphLong { let (buf, _fin, m, kind, args) = sargs let hexChars = lexemeTrimLeft lexbuf 2 let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) else singleQuoteString sargs skip lexbuf match unicodeGraphLong hexChars with | Invalid -> @@ -1203,7 +1210,7 @@ and singleQuoteString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs let s = lexeme lexbuf addUnicodeString buf (if kind.IsInterpolated then s.[0..0] else s) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) else singleQuoteString sargs skip lexbuf } | "{" @@ -1211,19 +1218,19 @@ and singleQuoteString sargs skip = parse if kind.IsInterpolated then // get a new range for where the fill starts let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.SingleQuote, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.SingleQuote, args.numDollars, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind LexerStringFinisherContext.InterpolatedPart cont else addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) else singleQuoteString sargs skip lexbuf } | "}" { let (buf, _fin, m, kind, args) = sargs let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) else singleQuoteString sargs skip lexbuf if kind.IsInterpolated then fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) @@ -1236,45 +1243,45 @@ and singleQuoteString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs newline lexbuf addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) else singleQuoteString sargs skip lexbuf } | ident { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) else singleQuoteString sargs skip lexbuf } | integer | xinteger { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) else singleQuoteString sargs skip lexbuf } | anywhite + { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) else singleQuoteString sargs skip lexbuf } | eof { let (_buf, _fin, m, kind, args) = sargs - EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) } + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) } | surrogateChar surrogateChar // surrogate code points always come in pairs | _ { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) else singleQuoteString sargs skip lexbuf } and verbatimString sargs skip = parse | '"' '"' { let (buf, _fin, m, kind, args) = sargs addByteChar buf '\"' - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) else verbatimString sargs skip lexbuf } | '"' @@ -1293,14 +1300,14 @@ and verbatimString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs newline lexbuf addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) else verbatimString sargs skip lexbuf } | ("{{" | "}}") { let (buf, _fin, m, kind, args) = sargs let s = lexeme lexbuf addUnicodeString buf (if kind.IsInterpolated then s.[0..0] else s) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) else verbatimString sargs skip lexbuf } | "{" @@ -1308,19 +1315,19 @@ and verbatimString sargs skip = parse if kind.IsInterpolated then // get a new range for where the fill starts let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.Verbatim, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.Verbatim, args.numDollars, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind (enum(3)) cont else addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) else verbatimString sargs skip lexbuf } | "}" { let (buf, _fin, m, kind, args) = sargs let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) else verbatimString sargs skip lexbuf if kind.IsInterpolated then fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) @@ -1332,31 +1339,31 @@ and verbatimString sargs skip = parse | ident { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) else verbatimString sargs skip lexbuf } | integer | xinteger { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) else verbatimString sargs skip lexbuf } | anywhite + { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) else verbatimString sargs skip lexbuf } | eof { let (_buf, _fin, m, kind, args) = sargs - EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) } + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) } | surrogateChar surrogateChar // surrogate code points always come in pairs | _ { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) else verbatimString sargs skip lexbuf } and tripleQuoteString sargs skip = parse @@ -1369,71 +1376,118 @@ and tripleQuoteString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs newline lexbuf addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) else tripleQuoteString sargs skip lexbuf } // The rest is to break into pieces to allow double-click-on-word and other such things | ident { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) else tripleQuoteString sargs skip lexbuf } | integer | xinteger { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) else tripleQuoteString sargs skip lexbuf } | anywhite + { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) else tripleQuoteString sargs skip lexbuf } - | ("{{" | "}}") - { let (buf, _fin, m, kind, args) = sargs - let s = lexeme lexbuf - addUnicodeString buf (if kind.IsInterpolated then s.[0..0] else s) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) - else tripleQuoteString sargs skip lexbuf } + //| ("{{" | "}}") + // { let (buf, _fin, m, kind, args) = sargs + // let s = lexeme lexbuf + // addUnicodeString buf (if kind.IsInterpolated then s.[0..0] else s) + // if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) + // else tripleQuoteString sargs skip lexbuf } - | "{" + | "{" + { let (buf, fin, m, kind, args) = sargs - if kind.IsInterpolated then - // get a new range for where the fill starts - let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.TripleQuote, m2) :: args.stringNest - let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fin.Finish buf kind (enum(5)) cont + let numBraces = String.length (lexeme lexbuf) + if kind.IsInterpolated && args.numDollars = 1 then + for _ in 2..2..numBraces do + addUnicodeString buf "{" + if numBraces % 2 = 0 then + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) + else tripleQuoteString sargs skip lexbuf + else + let m2 = lexbuf.LexemeRange + args.stringNest <- (1, LexerStringStyle.TripleQuote, args.numDollars, m2) :: args.stringNest + let cont = LexCont.Token(args.ifdefStack, args.stringNest) + fin.Finish buf kind (enum(5)) cont + elif kind.IsInterpolated then + if args.numDollars < 1 then + fail args lexbuf (0, FSComp.SR.lexCharNotAllowedInOperatorNames("@")) () // XXX + let maxBraces = 2 * args.numDollars - 1 + if numBraces > maxBraces then + fail args lexbuf (0, FSComp.SR.lexCharNotAllowedInOperatorNames("#")) (tripleQuoteString sargs skip lexbuf) // XXX + elif numBraces < args.numDollars then + addUnicodeString buf (lexeme lexbuf) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) + else tripleQuoteString sargs skip lexbuf + else + let s = lexeme lexbuf + addUnicodeString buf s[1..(numBraces - args.numDollars)] // XXX is it ok to addUnicode empty string + // get a new range for where the fill starts + let m2 = lexbuf.LexemeRange + args.stringNest <- (1, LexerStringStyle.TripleQuote, args.numDollars, m2) :: args.stringNest + let cont = LexCont.Token(args.ifdefStack, args.stringNest) + fin.Finish buf kind (enum(5)) cont else addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) else tripleQuoteString sargs skip lexbuf } - | "}" + | "}" + { let (buf, _fin, m, kind, args) = sargs + let numBraces = lexeme lexbuf |> String.length let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) else tripleQuoteString sargs skip lexbuf if kind.IsInterpolated then - fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) + if args.numDollars = 1 then + if numBraces % 2 = 1 then + args.numDollars <- 0 // XXX + fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) + else + for _ in 2..2..numBraces do + addUnicodeString buf "}" + (result()) + elif args.numDollars > numBraces then + for _ in 1..numBraces do + addUnicodeString buf "}" + (result()) + else + args.numDollars <- 0 // XXX + fail args lexbuf (0, FSComp.SR.lexCharNotAllowedInOperatorNames("^")) (result()) else addUnicodeString buf (lexeme lexbuf) (result()) + // let result() = + // if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) + // else tripleQuoteString sargs skip lexbuf + // if kind.IsInterpolated then + // fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) + // else + // addUnicodeString buf (lexeme lexbuf) + // (result()) } | eof { let (_buf, _fin, m, kind, args) = sargs - EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) } + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) } | surrogateChar surrogateChar // surrogate code points always come in pairs | _ { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) else tripleQuoteString sargs skip lexbuf } // Parsing single-line comment - we need to split it into words for Visual Studio IDE From c4e3308460db3ce95ca6a902d5128e0558c31e6b Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Wed, 26 Oct 2022 14:19:24 +0200 Subject: [PATCH 02/26] Add new error for too many consecutive braces In interpolated triple quoted strings that start with N times $ character, then can be max 2*N-1 consecutive { or } characters. New diagnostic is added for this error case. --- src/Compiler/FSComp.txt | 2 ++ src/Compiler/lex.fsl | 4 ++-- src/Compiler/xlf/FSComp.txt.cs.xlf | 10 ++++++++++ src/Compiler/xlf/FSComp.txt.de.xlf | 10 ++++++++++ src/Compiler/xlf/FSComp.txt.es.xlf | 10 ++++++++++ src/Compiler/xlf/FSComp.txt.fr.xlf | 10 ++++++++++ src/Compiler/xlf/FSComp.txt.it.xlf | 10 ++++++++++ src/Compiler/xlf/FSComp.txt.ja.xlf | 10 ++++++++++ src/Compiler/xlf/FSComp.txt.ko.xlf | 10 ++++++++++ src/Compiler/xlf/FSComp.txt.pl.xlf | 10 ++++++++++ src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 10 ++++++++++ src/Compiler/xlf/FSComp.txt.ru.xlf | 10 ++++++++++ src/Compiler/xlf/FSComp.txt.tr.xlf | 10 ++++++++++ src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 10 ++++++++++ src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 10 ++++++++++ 15 files changed, 134 insertions(+), 2 deletions(-) diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 26ef0736dc..13c0fe4759 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1599,6 +1599,8 @@ forFormatInvalidForInterpolated4,"Interpolated strings used as type IFormattable 3372,tcInvalidAlignmentInInterpolatedString,"Invalid alignment in interpolated string" 3373,lexSingleQuoteInSingleQuote,"Invalid interpolated string. Single quote or verbatim string literals may not be used in interpolated expressions in single quote or verbatim strings. Consider using an explicit 'let' binding for the interpolation expression or use a triple quote string as the outer string literal." 3374,lexTripleQuoteInTripleQuote,"Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression." +3375,lexTooManyLBracesInTripleQuote,"The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content." +3375,lexTooManyRBracesInTripleQuote,"The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content." 3376,tcUnableToParseInterpolatedString,"Invalid interpolated string. %s" 3377,lexByteStringMayNotBeInterpolated,"a byte string may not be interpolated" 3378,parsEofInInterpolatedStringFill,"Incomplete interpolated string expression fill begun at or before here" diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl index 8d12bd2ebe..1a06f9d27e 100644 --- a/src/Compiler/lex.fsl +++ b/src/Compiler/lex.fsl @@ -1425,7 +1425,7 @@ and tripleQuoteString sargs skip = parse fail args lexbuf (0, FSComp.SR.lexCharNotAllowedInOperatorNames("@")) () // XXX let maxBraces = 2 * args.numDollars - 1 if numBraces > maxBraces then - fail args lexbuf (0, FSComp.SR.lexCharNotAllowedInOperatorNames("#")) (tripleQuoteString sargs skip lexbuf) // XXX + fail args lexbuf (FSComp.SR.lexTooManyLBracesInTripleQuote()) (tripleQuoteString sargs skip lexbuf) // XXX elif numBraces < args.numDollars then addUnicodeString buf (lexeme lexbuf) if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) @@ -1465,7 +1465,7 @@ and tripleQuoteString sargs skip = parse (result()) else args.numDollars <- 0 // XXX - fail args lexbuf (0, FSComp.SR.lexCharNotAllowedInOperatorNames("^")) (result()) + fail args lexbuf (FSComp.SR.lexTooManyRBracesInTripleQuote()) (result()) else addUnicodeString buf (lexeme lexbuf) (result()) diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 669a1cf177..138c102192 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -542,6 +542,16 @@ Neplatný interpolovaný řetězec. Literály s jednoduchou uvozovkou nebo doslovné řetězcové literály se nedají použít v interpolovaných výrazech v řetězcích s jednoduchou uvozovkou nebo v doslovných řetězcích. Zvažte možnost použít pro výraz interpolace explicitní vazbu let nebo řetězec s trojitými uvozovkami jako vnější řetězcový literál. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Neplatný interpolovaný řetězec. V interpolovaných výrazech se nedají použít řetězcové literály s trojitými uvozovkami. Zvažte možnost použít pro interpolovaný výraz explicitní vazbu let. diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 99cf7d8407..0e16675189 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -542,6 +542,16 @@ Ungültige interpolierte Zeichenfolge. Literale mit einzelnen Anführungszeichen oder ausführliche Zeichenfolgenliterale dürfen nicht in interpolierten Ausdrücken in Zeichenfolgen mit einfachen Anführungszeichen oder ausführlichen Zeichenfolgen verwendet werden. Erwägen Sie die Verwendung einer expliziten let-Bindung für den Interpolationsausdruck, oder verwenden Sie eine Zeichenfolge mit dreifachen Anführungszeichen als äußeres Zeichenfolgenliteral. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Ungültige interpolierte Zeichenfolge. Zeichenfolgenliterale mit dreifachen Anführungszeichen dürfen in interpolierten Ausdrücken nicht verwendet werden. Erwägen Sie die Verwendung einer expliziten let-Bindung für den Interpolationsausdruck. diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 1e889b6e32..737134f7a4 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -542,6 +542,16 @@ Cadena interpolada no válida. No se pueden usar literales de cadena textual o de comillas simples en expresiones interpoladas en cadenas textuales o de comillas simples. Puede usar un enlace "let" explícito para la expresión de interpolación o utilizar una cadena de comillas triples como literal de cadena externo. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Cadena interpolada no válida. No se pueden usar literales de cadena de comillas triples en las expresiones interpoladas. Puede usar un enlace "let" explícito para la expresión de interpolación. diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index cb6d5cfe07..bd53e93bc8 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -542,6 +542,16 @@ Chaîne interpolée non valide. Les littéraux de chaîne verbatim ou à guillemet simple ne peuvent pas être utilisés dans les expressions interpolées dans des chaînes verbatim ou à guillemet simple. Utilisez une liaison 'let' explicite pour l'expression d'interpolation ou une chaîne à guillemets triples comme littéral de chaîne externe. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Chaîne interpolée non valide. Les littéraux de chaîne à guillemets triples ne peuvent pas être utilisés dans des expressions interpolées. Utilisez une liaison 'let' explicite pour l'expression d'interpolation. diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 48a1e719e8..4482a5ede8 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -542,6 +542,16 @@ La stringa interpolata non è valida. Non è possibile usare valori letterali stringa tra virgolette singole o verbatim in espressioni interpolate in stringhe verbatim o tra virgolette singole. Provare a usare un binding 'let' esplicito per l'espressione di interpolazione oppure usare una stringa tra virgolette triple come valore letterale stringa esterno. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. La stringa interpolata non è valida. Non è possibile usare valori letterali stringa tra virgolette triple in espressioni interpolate. Provare a usare un binding 'let' esplicito per l'espressione di interpolazione. diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index 126d2e4709..88acca577d 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -542,6 +542,16 @@ 補間された文字列が無効です。単一引用符または逐語的文字列リテラルは、単一引用符または逐語的文字列内の補間された式では使用できません。補間式に対して明示的な 'let' バインドを使用するか、外部文字列リテラルとして三重引用符文字列を使用することをご検討ください。 + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. 補間された文字列が無効です。三重引用符文字列リテラルは、補間された式では使用できません。補間式に対して明示的な 'let' バインドを使用することをご検討ください。 diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 639df27e2f..12510ea6ee 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -542,6 +542,16 @@ 잘못된 보간 문자열. 작은 따옴표 또는 축자 문자열 리터럴은 작은 따옴표 또는 축자 문자열의 보간 식에 사용할 수 없습니다. 보간 식에 명시적 'let' 바인딩을 사용하거나 삼중 따옴표 문자열을 외부 문자열 리터럴로 사용해 보세요. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. 잘못된 보간 문자열. 삼중 따옴표 문자열 리터럴은 보간 식에 사용할 수 없습니다. 보간 식에 명시적 'let' 바인딩을 사용해 보세요. diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index c137d76cc4..78a3186ce2 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -542,6 +542,16 @@ Nieprawidłowy ciąg interpolowany. Literały z pojedynczymi cudzysłowami lub literały ciągów dosłownych nie mogą być używane w wyrażeniach interpolowanych w ciągach z pojedynczymi cudzysłowami lub ciągach dosłownych. Rozważ użycie jawnego powiązania „let” dla wyrażenia interpolacji lub użycie ciągu z potrójnymi cudzysłowami jako zewnętrznego literału ciągu. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Nieprawidłowy ciąg interpolowany. Literały ciągów z potrójnymi cudzysłowami nie mogą być używane w wyrażeniach interpolowanych. Rozważ użycie jawnego powiązania „let” dla wyrażenia interpolacji. diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 09419ca738..6ce9f8b18c 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -542,6 +542,16 @@ Cadeia de caracteres interpolada inválida. Literais de cadeia de caracteres verbatim ou com aspas simples não podem ser usados em expressões interpoladas em cadeias de caracteres verbatim ou com aspas simples. Considere usar uma associação 'let' explícita para a expressão de interpolação ou use uma cadeia de caracteres de aspas triplas como o literal da cadeia de caracteres externa. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Cadeia de caracteres interpolada inválida. Literais de cadeia de caracteres de aspas triplas não podem ser usados em expressões interpoladas. Considere usar uma associação 'let' explícita para a expressão de interpolação. diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 5a9d0aae1f..ef5a72a4e0 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -542,6 +542,16 @@ Недопустимая интерполированная строка. Строковые литералы с одинарными кавычками или буквальные строковые литералы запрещено использовать в интерполированных выражениях в строках с одинарными кавычками или буквальных строках. Рекомендуется использовать явную привязку "let" для выражения интерполяции или использовать строку с тройными кавычками как внешний строковый литерал. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Недопустимая интерполированная строка. Строковые литералы с тройными кавычками запрещено использовать в интерполированных выражениях. Рекомендуется использовать явную привязку "let" для выражения интерполяции. diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index b40b29f543..8a2f6586bf 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -542,6 +542,16 @@ Geçersiz düz metin arasına kod eklenmiş dize. Tek tırnaklı veya düz metin dizesi sabitleri, tek tırnaklı veya düz metin dizelerinde düz metin arasına kod eklenmiş ifadelerde kullanılamaz. Düz metin arasına kod ekleme ifadesi için açık bir 'let' bağlaması kullanmayı düşünün veya dış dize sabiti olarak üç tırnaklı bir dize kullanın. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Geçersiz düz metin arasına kod eklenmiş dize. Üç tırnaklı dize sabitleri, düz metin arasına kod eklenmiş ifadelerde kullanılamaz. Düz metin arasına kod ekleme ifadesi için açık bir 'let' bağlaması kullanmayı düşünün. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 7a660c0c0a..8e4259c24a 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -542,6 +542,16 @@ 内插字符串无效。在单引号字符串或逐字字符串的内插表达式中不能使用单引号或逐字字符串文本。请考虑对内插表达式使用显式 "let" 绑定,或使用三重引号字符串作为外部字符串文本。 + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. 内插字符串无效。在内插表达式中不能使用三重引号字符串文字。请考虑对内插表达式使用显式的 "let" 绑定。 diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 105c38cd0b..470cd0424a 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -542,6 +542,16 @@ 插補字串無效。單引號或逐字字串常值不能用於單引號或逐字字串的插補運算式中。請考慮為內插補點運算式使用明確的 'let' 繫結,或使用三引號字串作為外部字串常值。 + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. + + Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. 插補字串無效。三引號字串常值不可用於插補運算式。請考慮為內插補點運算式使用明確的 'let' 繫結。 From 295056ebd0e94251cdaacceaa1bf8b14b1a8e3b9 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Wed, 7 Dec 2022 16:28:04 +0100 Subject: [PATCH 03/26] Handle percent sign characters Add new error for too many % --- src/Compiler/FSComp.txt | 5 +++-- src/Compiler/lex.fsl | 27 ++++++++++++++++++++++++- src/Compiler/xlf/FSComp.txt.cs.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.de.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.es.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.fr.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.it.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.ja.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.ko.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.pl.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.ru.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.tr.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 5 +++++ 15 files changed, 94 insertions(+), 3 deletions(-) diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 13c0fe4759..3926b89d5e 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1122,6 +1122,9 @@ lexIfOCaml,"IF-FSHARP/IF-CAML regions are no longer supported" 1245,lexInvalidUnicodeLiteral,"\U%s is not a valid Unicode character escape sequence" 1246,tcCallerInfoWrongType,"'%s' must be applied to an argument of type '%s', but has been applied to an argument of type '%s'" 1247,tcCallerInfoNotOptional,"'%s' can only be applied to optional arguments" +1248,lexTooManyLBracesInTripleQuote,"The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content." +1249,lexTooManyRBracesInTripleQuote,"The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content." +1250,lexTooManyPercentsInTripleQuote,"The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%%' characters." # reshapedmsbuild.fs 1300,toolLocationHelperUnsupportedFrameworkVersion,"The specified .NET Framework version '%s' is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion." # ----------------------------------------------------------------------------- @@ -1599,8 +1602,6 @@ forFormatInvalidForInterpolated4,"Interpolated strings used as type IFormattable 3372,tcInvalidAlignmentInInterpolatedString,"Invalid alignment in interpolated string" 3373,lexSingleQuoteInSingleQuote,"Invalid interpolated string. Single quote or verbatim string literals may not be used in interpolated expressions in single quote or verbatim strings. Consider using an explicit 'let' binding for the interpolation expression or use a triple quote string as the outer string literal." 3374,lexTripleQuoteInTripleQuote,"Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression." -3375,lexTooManyLBracesInTripleQuote,"The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content." -3375,lexTooManyRBracesInTripleQuote,"The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content." 3376,tcUnableToParseInterpolatedString,"Invalid interpolated string. %s" 3377,lexByteStringMayNotBeInterpolated,"a byte string may not be interpolated" 3378,parsEofInInterpolatedStringFill,"Incomplete interpolated string expression fill begun at or before here" diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl index 1a06f9d27e..a92e26db3d 100644 --- a/src/Compiler/lex.fsl +++ b/src/Compiler/lex.fsl @@ -1406,6 +1406,31 @@ and tripleQuoteString sargs skip = parse // if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) // else tripleQuoteString sargs skip lexbuf } + | "%" + + { let (buf, _fin, m, kind, args) = sargs + let numPercents = lexeme lexbuf |> String.length + let result() = + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) + else tripleQuoteString sargs skip lexbuf + if kind.IsInterpolated && args.numDollars > 1 then + let maxPercents = 2 * args.numDollars - 1 + if numPercents > maxPercents then + let m2 = lexbuf.LexemeRange + let rest = result() + args.diagnosticsLogger.ErrorR(Error(FSComp.SR.lexTooManyPercentsInTripleQuote(), m2)) + rest + else + let n = if numPercents < args.numDollars then + 2 * numPercents + else + 2 * (numPercents - args.numDollars) + 1 + let s = String.replicate n "%" + addUnicodeString buf s + result() + else + addUnicodeString buf (lexeme lexbuf) + result() } + | "{" + { let (buf, fin, m, kind, args) = sargs let numBraces = String.length (lexeme lexbuf) @@ -1432,7 +1457,7 @@ and tripleQuoteString sargs skip = parse else tripleQuoteString sargs skip lexbuf else let s = lexeme lexbuf - addUnicodeString buf s[1..(numBraces - args.numDollars)] // XXX is it ok to addUnicode empty string + addUnicodeString buf s[1..(numBraces - args.numDollars)] // XXX is it ok to addUnicode empty string? Also why start at 1? // get a new range for where the fill starts let m2 = lexbuf.LexemeRange args.stringNest <- (1, LexerStringStyle.TripleQuote, args.numDollars, m2) :: args.stringNest diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 138c102192..6133f8ab27 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -547,6 +547,11 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 0e16675189..f4fdbc62cf 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -547,6 +547,11 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 737134f7a4..79fd6e8ea4 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -547,6 +547,11 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index bd53e93bc8..8ab21c164e 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -547,6 +547,11 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 4482a5ede8..07ae1e5944 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -547,6 +547,11 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index 88acca577d..b478c498f4 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -547,6 +547,11 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 12510ea6ee..53f253c927 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -547,6 +547,11 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 78a3186ce2..d792b84c61 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -547,6 +547,11 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 6ce9f8b18c..df818ce98d 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -547,6 +547,11 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index ef5a72a4e0..12b6645453 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -547,6 +547,11 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 8a2f6586bf..da63fd2763 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -547,6 +547,11 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 8e4259c24a..b86b814277 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -547,6 +547,11 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 470cd0424a..660588a770 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -547,6 +547,11 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. + + The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. From 457098734d8dcab903a9f3b14b2c2a1fe51a7cb3 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Thu, 8 Dec 2022 14:32:09 +0100 Subject: [PATCH 04/26] Fix closing braces error --- src/Compiler/FSComp.txt | 2 +- src/Compiler/lex.fsl | 7 +++---- src/Compiler/xlf/FSComp.txt.cs.xlf | 10 +++++----- src/Compiler/xlf/FSComp.txt.de.xlf | 10 +++++----- src/Compiler/xlf/FSComp.txt.es.xlf | 10 +++++----- src/Compiler/xlf/FSComp.txt.fr.xlf | 10 +++++----- src/Compiler/xlf/FSComp.txt.it.xlf | 10 +++++----- src/Compiler/xlf/FSComp.txt.ja.xlf | 10 +++++----- src/Compiler/xlf/FSComp.txt.ko.xlf | 10 +++++----- src/Compiler/xlf/FSComp.txt.pl.xlf | 10 +++++----- src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 10 +++++----- src/Compiler/xlf/FSComp.txt.ru.xlf | 10 +++++----- src/Compiler/xlf/FSComp.txt.tr.xlf | 10 +++++----- src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 10 +++++----- src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 10 +++++----- 15 files changed, 69 insertions(+), 70 deletions(-) diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 3926b89d5e..8acdad3822 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1123,7 +1123,7 @@ lexIfOCaml,"IF-FSHARP/IF-CAML regions are no longer supported" 1246,tcCallerInfoWrongType,"'%s' must be applied to an argument of type '%s', but has been applied to an argument of type '%s'" 1247,tcCallerInfoNotOptional,"'%s' can only be applied to optional arguments" 1248,lexTooManyLBracesInTripleQuote,"The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content." -1249,lexTooManyRBracesInTripleQuote,"The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content." +1249,lexUnmatchedRBracesInTripleQuote,"The interpolated string contains unmatched closing braces." 1250,lexTooManyPercentsInTripleQuote,"The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%%' characters." # reshapedmsbuild.fs 1300,toolLocationHelperUnsupportedFrameworkVersion,"The specified .NET Framework version '%s' is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion." diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl index a92e26db3d..d8a414b8f6 100644 --- a/src/Compiler/lex.fsl +++ b/src/Compiler/lex.fsl @@ -1369,6 +1369,7 @@ and verbatimString sargs skip = parse and tripleQuoteString sargs skip = parse | '"' '"' '"' { let (buf, fin, _m, kind, args) = sargs + args.numDollars <- 0 let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind (enum(4)) cont } @@ -1478,7 +1479,6 @@ and tripleQuoteString sargs skip = parse if kind.IsInterpolated then if args.numDollars = 1 then if numBraces % 2 = 1 then - args.numDollars <- 0 // XXX fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) else for _ in 2..2..numBraces do @@ -1488,9 +1488,8 @@ and tripleQuoteString sargs skip = parse for _ in 1..numBraces do addUnicodeString buf "}" (result()) - else - args.numDollars <- 0 // XXX - fail args lexbuf (FSComp.SR.lexTooManyRBracesInTripleQuote()) (result()) + else // numBraces >= args.numDollars + fail args lexbuf (FSComp.SR.lexUnmatchedRBracesInTripleQuote()) (result()) else addUnicodeString buf (lexeme lexbuf) (result()) diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 6133f8ab27..1032649574 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -552,16 +552,16 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. - - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - - Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Neplatný interpolovaný řetězec. V interpolovaných výrazech se nedají použít řetězcové literály s trojitými uvozovkami. Zvažte možnost použít pro interpolovaný výraz explicitní vazbu let. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Všechny prvky seznamu musí být implicitně převoditelné na typ prvního prvku, což je řazená kolekce členů o délce {0} typu\n {1} \nTento element je řazená kolekce členů o délce {2} typu\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index f4fdbc62cf..9a2d9f5964 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -552,16 +552,16 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. - - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - - Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Ungültige interpolierte Zeichenfolge. Zeichenfolgenliterale mit dreifachen Anführungszeichen dürfen in interpolierten Ausdrücken nicht verwendet werden. Erwägen Sie die Verwendung einer expliziten let-Bindung für den Interpolationsausdruck. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Alle Elemente einer Liste müssen implizit in den Typ des ersten Elements konvertiert werden. Hierbei handelt es sich um ein Tupel der Länge {0} vom Typ\n {1} \nDieses Element ist ein Tupel der Länge {2} vom Typ\n {3}. \n diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 79fd6e8ea4..7b3401c341 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -552,16 +552,16 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. - - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - - Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Cadena interpolada no válida. No se pueden usar literales de cadena de comillas triples en las expresiones interpoladas. Puede usar un enlace "let" explícito para la expresión de interpolación. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Todos los elementos de una lista deben convertirse implícitamente en el tipo del primer elemento, que aquí es una tupla de longitud {0} de tipo\n {1} \nEste elemento es una tupla de longitud {2} de tipo\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 8ab21c164e..c84b06fcc8 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -552,16 +552,16 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. - - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - - Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Chaîne interpolée non valide. Les littéraux de chaîne à guillemets triples ne peuvent pas être utilisés dans des expressions interpolées. Utilisez une liaison 'let' explicite pour l'expression d'interpolation. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Tous les éléments d’une liste doivent être implicitement convertibles en type du premier élément, qui est ici un tuple de longueur {0} de type\n {1} \nCet élément est un tuple de longueur {2} de type\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 07ae1e5944..6540be7c08 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -552,16 +552,16 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. - - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - - Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. La stringa interpolata non è valida. Non è possibile usare valori letterali stringa tra virgolette triple in espressioni interpolate. Provare a usare un binding 'let' esplicito per l'espressione di interpolazione. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Tutti gli elementi di un elenco devono essere convertibili in modo implicito nel tipo del primo elemento, che qui è una tupla di lunghezza {0} di tipo\n {1} \nQuesto elemento è una tupla di lunghezza {2} di tipo\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index b478c498f4..cba4119e81 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -552,16 +552,16 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. - - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - - Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. 補間された文字列が無効です。三重引用符文字列リテラルは、補間された式では使用できません。補間式に対して明示的な 'let' バインドを使用することをご検討ください。 + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n リストのすべての要素は、最初の要素の型に暗黙的に変換できる必要があります。これは、型の長さ {0} のタプルです\n {1} \nこの要素は、型の長さ {2} のタプルです\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 53f253c927..29ee74a759 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -552,16 +552,16 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. - - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - - Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. 잘못된 보간 문자열. 삼중 따옴표 문자열 리터럴은 보간 식에 사용할 수 없습니다. 보간 식에 명시적 'let' 바인딩을 사용해 보세요. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n 목록의 모든 요소는 첫 번째 요소의 형식으로 암시적으로 변환할 수 있어야 합니다. 여기서는 형식이 \n {1}이고 길이가 {0}인 튜플입니다. \n이 요소는 형식이 \n {3}이고 길이가 {2}인 튜플입니다. \n diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index d792b84c61..adee72a150 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -552,16 +552,16 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. - - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - - Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Nieprawidłowy ciąg interpolowany. Literały ciągów z potrójnymi cudzysłowami nie mogą być używane w wyrażeniach interpolowanych. Rozważ użycie jawnego powiązania „let” dla wyrażenia interpolacji. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Wszystkie elementy tablicy muszą być niejawnie konwertowalne na typ pierwszego elementu, który w tym miejscu jest krotką o długości {0} typu\n {1} \nTen element jest krotką o długości {2} typu\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index df818ce98d..6d01558c4f 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -552,16 +552,16 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. - - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - - Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Cadeia de caracteres interpolada inválida. Literais de cadeia de caracteres de aspas triplas não podem ser usados em expressões interpoladas. Considere usar uma associação 'let' explícita para a expressão de interpolação. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Todos os elementos de uma lista devem ser implicitamente conversíveis ao tipo do primeiro elemento, que aqui é uma tupla de comprimento {0} do tipo\n {1} \nEste elemento é uma tupla de comprimento {2} do tipo\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 12b6645453..6c06bb91f5 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -552,16 +552,16 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. - - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - - Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Недопустимая интерполированная строка. Строковые литералы с тройными кавычками запрещено использовать в интерполированных выражениях. Рекомендуется использовать явную привязку "let" для выражения интерполяции. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Все элементы списка должны поддерживать неявное преобразование в тип первого элемента, который здесь является кортежем длиной {0} типа\n {1} \nЭтот элемент является кортежем длиной {2} типа\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index da63fd2763..57aca3025d 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -552,16 +552,16 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. - - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - - Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. Geçersiz düz metin arasına kod eklenmiş dize. Üç tırnaklı dize sabitleri, düz metin arasına kod eklenmiş ifadelerde kullanılamaz. Düz metin arasına kod ekleme ifadesi için açık bir 'let' bağlaması kullanmayı düşünün. + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n Bir listenin tüm öğeleri örtük olarak ilk öğenin türüne dönüştürülebilir olmalıdır. Burada ilk öğe {0} uzunluğunda türü\n {1} \nolan bir demet. Bu öğe ise {2} uzunluğunda türü\n {3} \nolan bir demet. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index b86b814277..26d35e5caa 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -552,16 +552,16 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. - - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - - Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. 内插字符串无效。在内插表达式中不能使用三重引号字符串文字。请考虑对内插表达式使用显式的 "let" 绑定。 + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n 列表的所有元素必须可隐式转换为第一个元素的类型,这是一个长度为 {0} 的类型的元组\n {1} \n此元素是长度为 {2} 类型的元组\n {3} \n diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 660588a770..f853f1477f 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -552,16 +552,16 @@ The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%' characters. - - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive closing braces as content. - - Invalid interpolated string. Triple quote string literals may not be used in interpolated expressions. Consider using an explicit 'let' binding for the interpolation expression. 插補字串無效。三引號字串常值不可用於插補運算式。請考慮為內插補點運算式使用明確的 'let' 繫結。 + + The interpolated string contains unmatched closing braces. + The interpolated string contains unmatched closing braces. + + All elements of a list must be implicitly convertible to the type of the first element, which here is a tuple of length {0} of type\n {1} \nThis element is a tuple of length {2} of type\n {3} \n 清單的所有元素必須以隱含方式轉換成第一個元素的類型,這是類型為\n {1} \n的元組長度 {0}此元素是類型為\n {3} \n的元組長度 {2} From c7391a3125c06a684bd17c6d9750eb56e991e750 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Thu, 8 Dec 2022 17:05:16 +0100 Subject: [PATCH 05/26] Fix error reporting on too many lbraces --- src/Compiler/lex.fsl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl index d8a414b8f6..991eee338d 100644 --- a/src/Compiler/lex.fsl +++ b/src/Compiler/lex.fsl @@ -1451,7 +1451,10 @@ and tripleQuoteString sargs skip = parse fail args lexbuf (0, FSComp.SR.lexCharNotAllowedInOperatorNames("@")) () // XXX let maxBraces = 2 * args.numDollars - 1 if numBraces > maxBraces then - fail args lexbuf (FSComp.SR.lexTooManyLBracesInTripleQuote()) (tripleQuoteString sargs skip lexbuf) // XXX + let m2 = lexbuf.LexemeRange + args.stringNest <- (1, LexerStringStyle.TripleQuote, args.numDollars, m2) :: args.stringNest + let cont = LexCont.Token(args.ifdefStack, args.stringNest) + fail args lexbuf (FSComp.SR.lexTooManyLBracesInTripleQuote()) (fin.Finish buf kind (enum(5)) cont) // XXX elif numBraces < args.numDollars then addUnicodeString buf (lexeme lexbuf) if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) From 821a62e18099c0c709fd13df01dd6701813f3d99 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Fri, 13 Jan 2023 18:22:31 +0100 Subject: [PATCH 06/26] Encode # of $ in interpolated strings Using 3 bits --- src/Compiler/Service/ServiceLexing.fs | 71 ++++++++++++++++++--------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/src/Compiler/Service/ServiceLexing.fs b/src/Compiler/Service/ServiceLexing.fs index b87e2b2b30..d78a4abdef 100644 --- a/src/Compiler/Service/ServiceLexing.fs +++ b/src/Compiler/Service/ServiceLexing.fs @@ -512,6 +512,7 @@ module internal LexerStateEncoding = let ifdefstackNumBits = 24 // 0 means if, 1 means else let stringKindBits = 3 let nestingBits = 12 + let ndolBits = 3 let _ = assert @@ -522,6 +523,7 @@ module internal LexerStateEncoding = + ifdefstackNumBits + stringKindBits + nestingBits + + ndolBits <= 64) let lexstateStart = 0 @@ -547,6 +549,15 @@ module internal LexerStateEncoding = + ifdefstackNumBits + stringKindBits + let ndolStart = + lexstateNumBits + + ncommentsNumBits + + hardwhiteNumBits + + ifdefstackCountNumBits + + ifdefstackNumBits + + stringKindBits + + nestingBits + let lexstateMask = Bits.mask64 lexstateStart lexstateNumBits let ncommentsMask = Bits.mask64 ncommentsStart ncommentsNumBits let hardwhitePosMask = Bits.mask64 hardwhitePosStart hardwhiteNumBits @@ -554,6 +565,7 @@ module internal LexerStateEncoding = let ifdefstackMask = Bits.mask64 ifdefstackStart ifdefstackNumBits let stringKindMask = Bits.mask64 stringKindStart stringKindBits let nestingMask = Bits.mask64 nestingStart nestingBits + let ndolMask = Bits.mask64 ndolStart ndolBits let bitOfBool b = if b then 1 else 0 let boolOfBit n = (n = 1L) @@ -587,7 +599,8 @@ module internal LexerStateEncoding = ifdefStack, light, stringKind: LexerStringKind, - stringNest + stringNest, + ndol : int ) = let mutable ifdefStackCount = 0 let mutable ifdefStackBits = 0 @@ -615,13 +628,15 @@ module internal LexerStateEncoding = | [] -> false, 0, 0 | (i2, kind2, _, _) :: _ -> true, i2, encodeStringStyle kind2 - (if tag1 then 0b100000000000 else 0) - ||| (if tag2 then 0b010000000000 else 0) - ||| ((i1 <<< 7) &&& 0b001110000000) - ||| ((i2 <<< 4) &&& 0b000001110000) + (if tag1 then 0b100000000000 else 0) + ||| (if tag2 then 0b010000000000 else 0) + ||| ((i1 <<< 7) &&& 0b001110000000) + ||| ((i2 <<< 4) &&& 0b000001110000) ||| ((kind1 <<< 2) &&& 0b000000001100) ||| ((kind2 <<< 0) &&& 0b000000000011) + let ndol = min ndol (Bits.pown32 ndolBits) + let bits = lexStateOfColorState colorState ||| ((numComments <<< ncommentsStart) &&& ncommentsMask) @@ -630,6 +645,7 @@ module internal LexerStateEncoding = ||| ((int64 ifdefStackBits <<< ifdefstackStart) &&& ifdefstackMask) ||| ((int64 stringKindValue <<< stringKindStart) &&& stringKindMask) ||| ((int64 nestingValue <<< nestingStart) &&& nestingMask) + ||| ((int64 ndol <<< ndolStart) &&& ndolMask) { PosBits = b.Encoding @@ -670,26 +686,29 @@ module internal LexerStateEncoding = let nestingValue = int32 ((bits &&& nestingMask) >>> nestingStart) let stringNest: LexerInterpolatedStringNesting = - let tag1 = ((nestingValue &&& 0b100000000000) = 0b100000000000) - let tag2 = ((nestingValue &&& 0b010000000000) = 0b010000000000) - let i1 = ((nestingValue &&& 0b001110000000) >>> 7) - let i2 = ((nestingValue &&& 0b000001110000) >>> 4) + let tag1 = ((nestingValue &&& 0b100000000000) = 0b100000000000) + let tag2 = ((nestingValue &&& 0b010000000000) = 0b010000000000) + let i1 = ((nestingValue &&& 0b001110000000) >>> 7) + let i2 = ((nestingValue &&& 0b000001110000) >>> 4) let kind1 = ((nestingValue &&& 0b000000001100) >>> 2) let kind2 = ((nestingValue &&& 0b000000000011) >>> 0) - [ + let nest = [ if tag1 then i1, decodeStringStyle kind1, 0, range0 if tag2 then i2, decodeStringStyle kind2, 0, range0 ] + nest + + let ndol = int32 ((bits &&& ndolMask) >>> ndolStart) - (colorState, ncomments, pos, ifDefs, hardwhite, stringKind, stringNest) + (colorState, ncomments, pos, ifDefs, hardwhite, stringKind, stringNest, ndol) let encodeLexInt indentationSyntaxStatus (lexcont: LexerContinuation) = match lexcont with | LexCont.Token (ifdefs, stringNest) -> - encodeLexCont (FSharpTokenizerColorState.Token, 0L, pos0, ifdefs, indentationSyntaxStatus, LexerStringKind.String, stringNest) + encodeLexCont (FSharpTokenizerColorState.Token, 0L, pos0, ifdefs, indentationSyntaxStatus, LexerStringKind.String, stringNest, 0) | LexCont.IfDefSkip (ifdefs, stringNest, n, m) -> encodeLexCont ( FSharpTokenizerColorState.IfDefSkip, @@ -698,7 +717,8 @@ module internal LexerStateEncoding = ifdefs, indentationSyntaxStatus, LexerStringKind.String, - stringNest + stringNest, + 0 ) | LexCont.EndLine (ifdefs, stringNest, econt) -> match econt with @@ -710,7 +730,8 @@ module internal LexerStateEncoding = ifdefs, indentationSyntaxStatus, LexerStringKind.String, - stringNest + stringNest, + 0 ) | LexerEndlineContinuation.Token -> encodeLexCont ( @@ -720,16 +741,17 @@ module internal LexerStateEncoding = ifdefs, indentationSyntaxStatus, LexerStringKind.String, - stringNest + stringNest, + 0 ) - | LexCont.String (ifdefs, stringNest, style, kind, _, m) -> + | LexCont.String (ifdefs, stringNest, style, kind, ndol, m) -> let state = match style with | LexerStringStyle.SingleQuote -> FSharpTokenizerColorState.String | LexerStringStyle.Verbatim -> FSharpTokenizerColorState.VerbatimString | LexerStringStyle.TripleQuote -> FSharpTokenizerColorState.TripleQuoteString - encodeLexCont (state, 0L, m.Start, ifdefs, indentationSyntaxStatus, kind, stringNest) + encodeLexCont (state, 0L, m.Start, ifdefs, indentationSyntaxStatus, kind, stringNest, ndol) | LexCont.Comment (ifdefs, stringNest, n, m) -> encodeLexCont ( FSharpTokenizerColorState.Comment, @@ -738,7 +760,8 @@ module internal LexerStateEncoding = ifdefs, indentationSyntaxStatus, LexerStringKind.String, - stringNest + stringNest, + 0 ) | LexCont.SingleLineComment (ifdefs, stringNest, n, m) -> encodeLexCont ( @@ -748,7 +771,8 @@ module internal LexerStateEncoding = ifdefs, indentationSyntaxStatus, LexerStringKind.String, - stringNest + stringNest, + 0 ) | LexCont.StringInComment (ifdefs, stringNest, style, n, m) -> let state = @@ -757,7 +781,7 @@ module internal LexerStateEncoding = | LexerStringStyle.Verbatim -> FSharpTokenizerColorState.VerbatimStringInComment | LexerStringStyle.TripleQuote -> FSharpTokenizerColorState.TripleQuoteStringInComment - encodeLexCont (state, int64 n, m.Start, ifdefs, indentationSyntaxStatus, LexerStringKind.String, stringNest) + encodeLexCont (state, int64 n, m.Start, ifdefs, indentationSyntaxStatus, LexerStringKind.String, stringNest, 0) | LexCont.MLOnly (ifdefs, stringNest, m) -> encodeLexCont ( FSharpTokenizerColorState.CamlOnly, @@ -766,11 +790,12 @@ module internal LexerStateEncoding = ifdefs, indentationSyntaxStatus, LexerStringKind.String, - stringNest + stringNest, + 0 ) let decodeLexInt (state: FSharpTokenizerLexState) = - let tag, n1, p1, ifdefs, lightSyntaxStatusInitial, stringKind, stringNest = + let tag, n1, p1, ifdefs, lightSyntaxStatusInitial, stringKind, stringNest, ndol = decodeLexCont state let lexcont = @@ -791,7 +816,7 @@ module internal LexerStateEncoding = | FSharpTokenizerColorState.VerbatimString -> LexCont.String(ifdefs, stringNest, LexerStringStyle.Verbatim, stringKind, 0, mkRange "file" p1 p1) | FSharpTokenizerColorState.TripleQuoteString -> - LexCont.String(ifdefs, stringNest, LexerStringStyle.TripleQuote, stringKind, 0, mkRange "file" p1 p1) + LexCont.String(ifdefs, stringNest, LexerStringStyle.TripleQuote, stringKind, ndol, mkRange "file" p1 p1) | FSharpTokenizerColorState.EndLineThenSkip -> LexCont.EndLine(ifdefs, stringNest, LexerEndlineContinuation.Skip(n1, mkRange "file" p1 p1)) | FSharpTokenizerColorState.EndLineThenToken -> LexCont.EndLine(ifdefs, stringNest, LexerEndlineContinuation.Token) From 9a47de24edfdeaf51de325caf74301ae6edbae2e Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Wed, 18 Jan 2023 14:15:04 +0100 Subject: [PATCH 07/26] Rename numDollars to delimLength --- src/Compiler/Service/ServiceLexing.fs | 30 ++--- src/Compiler/SyntaxTree/LexHelpers.fs | 4 +- src/Compiler/SyntaxTree/LexHelpers.fsi | 2 +- src/Compiler/SyntaxTree/ParseHelpers.fs | 2 +- src/Compiler/SyntaxTree/ParseHelpers.fsi | 2 +- src/Compiler/lex.fsl | 138 +++++++++++------------ 6 files changed, 85 insertions(+), 93 deletions(-) diff --git a/src/Compiler/Service/ServiceLexing.fs b/src/Compiler/Service/ServiceLexing.fs index d78a4abdef..9cf8e1d13c 100644 --- a/src/Compiler/Service/ServiceLexing.fs +++ b/src/Compiler/Service/ServiceLexing.fs @@ -512,7 +512,7 @@ module internal LexerStateEncoding = let ifdefstackNumBits = 24 // 0 means if, 1 means else let stringKindBits = 3 let nestingBits = 12 - let ndolBits = 3 + let dlenBits = 3 let _ = assert @@ -523,7 +523,7 @@ module internal LexerStateEncoding = + ifdefstackNumBits + stringKindBits + nestingBits - + ndolBits + + dlenBits <= 64) let lexstateStart = 0 @@ -549,7 +549,7 @@ module internal LexerStateEncoding = + ifdefstackNumBits + stringKindBits - let ndolStart = + let dlenStart = lexstateNumBits + ncommentsNumBits + hardwhiteNumBits @@ -565,7 +565,7 @@ module internal LexerStateEncoding = let ifdefstackMask = Bits.mask64 ifdefstackStart ifdefstackNumBits let stringKindMask = Bits.mask64 stringKindStart stringKindBits let nestingMask = Bits.mask64 nestingStart nestingBits - let ndolMask = Bits.mask64 ndolStart ndolBits + let dlenMask = Bits.mask64 dlenStart dlenBits let bitOfBool b = if b then 1 else 0 let boolOfBit n = (n = 1L) @@ -600,7 +600,7 @@ module internal LexerStateEncoding = light, stringKind: LexerStringKind, stringNest, - ndol : int + delimLen : int ) = let mutable ifdefStackCount = 0 let mutable ifdefStackBits = 0 @@ -635,7 +635,7 @@ module internal LexerStateEncoding = ||| ((kind1 <<< 2) &&& 0b000000001100) ||| ((kind2 <<< 0) &&& 0b000000000011) - let ndol = min ndol (Bits.pown32 ndolBits) + let delimLen = min delimLen (Bits.pown32 dlenBits) let bits = lexStateOfColorState colorState @@ -645,7 +645,7 @@ module internal LexerStateEncoding = ||| ((int64 ifdefStackBits <<< ifdefstackStart) &&& ifdefstackMask) ||| ((int64 stringKindValue <<< stringKindStart) &&& stringKindMask) ||| ((int64 nestingValue <<< nestingStart) &&& nestingMask) - ||| ((int64 ndol <<< ndolStart) &&& ndolMask) + ||| ((int64 delimLen <<< dlenStart) &&& dlenMask) { PosBits = b.Encoding @@ -701,9 +701,9 @@ module internal LexerStateEncoding = ] nest - let ndol = int32 ((bits &&& ndolMask) >>> ndolStart) + let delimLen = int32 ((bits &&& dlenMask) >>> dlenStart) - (colorState, ncomments, pos, ifDefs, hardwhite, stringKind, stringNest, ndol) + (colorState, ncomments, pos, ifDefs, hardwhite, stringKind, stringNest, delimLen) let encodeLexInt indentationSyntaxStatus (lexcont: LexerContinuation) = match lexcont with @@ -744,14 +744,14 @@ module internal LexerStateEncoding = stringNest, 0 ) - | LexCont.String (ifdefs, stringNest, style, kind, ndol, m) -> + | LexCont.String (ifdefs, stringNest, style, kind, delimLen, m) -> let state = match style with | LexerStringStyle.SingleQuote -> FSharpTokenizerColorState.String | LexerStringStyle.Verbatim -> FSharpTokenizerColorState.VerbatimString | LexerStringStyle.TripleQuote -> FSharpTokenizerColorState.TripleQuoteString - encodeLexCont (state, 0L, m.Start, ifdefs, indentationSyntaxStatus, kind, stringNest, ndol) + encodeLexCont (state, 0L, m.Start, ifdefs, indentationSyntaxStatus, kind, stringNest, delimLen) | LexCont.Comment (ifdefs, stringNest, n, m) -> encodeLexCont ( FSharpTokenizerColorState.Comment, @@ -795,7 +795,7 @@ module internal LexerStateEncoding = ) let decodeLexInt (state: FSharpTokenizerLexState) = - let tag, n1, p1, ifdefs, lightSyntaxStatusInitial, stringKind, stringNest, ndol = + let tag, n1, p1, ifdefs, lightSyntaxStatusInitial, stringKind, stringNest, delimLen = decodeLexCont state let lexcont = @@ -816,7 +816,7 @@ module internal LexerStateEncoding = | FSharpTokenizerColorState.VerbatimString -> LexCont.String(ifdefs, stringNest, LexerStringStyle.Verbatim, stringKind, 0, mkRange "file" p1 p1) | FSharpTokenizerColorState.TripleQuoteString -> - LexCont.String(ifdefs, stringNest, LexerStringStyle.TripleQuote, stringKind, ndol, mkRange "file" p1 p1) + LexCont.String(ifdefs, stringNest, LexerStringStyle.TripleQuote, stringKind, delimLen, mkRange "file" p1 p1) | FSharpTokenizerColorState.EndLineThenSkip -> LexCont.EndLine(ifdefs, stringNest, LexerEndlineContinuation.Skip(n1, mkRange "file" p1 p1)) | FSharpTokenizerColorState.EndLineThenToken -> LexCont.EndLine(ifdefs, stringNest, LexerEndlineContinuation.Token) @@ -948,10 +948,10 @@ type FSharpLineTokenizer(lexbuf: UnicodeLexing.Lexbuf, maxLength: int option, fi lexargs.stringNest <- stringNest Lexer.ifdefSkip n m lexargs skip lexbuf - | LexCont.String (ifdefs, stringNest, style, kind, numdol, m) -> + | LexCont.String (ifdefs, stringNest, style, kind, delimLen, m) -> lexargs.ifdefStack <- ifdefs lexargs.stringNest <- stringNest - lexargs.numDollars <- numdol + lexargs.delimLength <- delimLen use buf = ByteBuffer.Create Lexer.StringCapacity let args = (buf, LexerStringFinisher.Default, m, kind, lexargs) diff --git a/src/Compiler/SyntaxTree/LexHelpers.fs b/src/Compiler/SyntaxTree/LexHelpers.fs index 1f7d00221d..e1185786a3 100644 --- a/src/Compiler/SyntaxTree/LexHelpers.fs +++ b/src/Compiler/SyntaxTree/LexHelpers.fs @@ -65,7 +65,7 @@ type LexArgs = mutable ifdefStack: LexerIfdefStack mutable indentationSyntaxStatus: IndentationAwareSyntaxStatus mutable stringNest: LexerInterpolatedStringNesting - mutable numDollars: int + mutable delimLength: int } /// possible results of lexing a long Unicode escape sequence in a string literal, e.g. "\U0001F47D", @@ -94,7 +94,7 @@ let mkLexargs applyLineDirectives = applyLineDirectives stringNest = [] pathMap = pathMap - numDollars = 0 + delimLength = 0 } /// Register the lexbuf and call the given function diff --git a/src/Compiler/SyntaxTree/LexHelpers.fsi b/src/Compiler/SyntaxTree/LexHelpers.fsi index ad745d12bb..45df24b19d 100644 --- a/src/Compiler/SyntaxTree/LexHelpers.fsi +++ b/src/Compiler/SyntaxTree/LexHelpers.fsi @@ -38,7 +38,7 @@ type LexArgs = mutable ifdefStack: LexerIfdefStack mutable indentationSyntaxStatus: IndentationAwareSyntaxStatus mutable stringNest: LexerInterpolatedStringNesting - mutable numDollars: int } + mutable delimLength: int } type LongUnicodeLexResult = | SurrogatePair of uint16 * uint16 diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fs b/src/Compiler/SyntaxTree/ParseHelpers.fs index d32837858e..7e3fdb2924 100644 --- a/src/Compiler/SyntaxTree/ParseHelpers.fs +++ b/src/Compiler/SyntaxTree/ParseHelpers.fs @@ -323,7 +323,7 @@ type LexerContinuation = nesting: LexerInterpolatedStringNesting * style: LexerStringStyle * kind: LexerStringKind * - numdol: int * + delimLen: int * range: range | Comment of ifdef: LexerIfdefStackEntries * nesting: LexerInterpolatedStringNesting * int * range: range | SingleLineComment of ifdef: LexerIfdefStackEntries * nesting: LexerInterpolatedStringNesting * int * range: range diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fsi b/src/Compiler/SyntaxTree/ParseHelpers.fsi index cbf2067496..8e27dea500 100644 --- a/src/Compiler/SyntaxTree/ParseHelpers.fsi +++ b/src/Compiler/SyntaxTree/ParseHelpers.fsi @@ -128,7 +128,7 @@ type LexerContinuation = nesting: LexerInterpolatedStringNesting * style: LexerStringStyle * kind: LexerStringKind * - numdol: int * + delimLen: int * range: range | Comment of ifdef: LexerIfdefStackEntries * nesting: LexerInterpolatedStringNesting * int * range: range | SingleLineComment of ifdef: LexerIfdefStackEntries * nesting: LexerInterpolatedStringNesting * int * range: range diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl index 991eee338d..fb087fb48f 100644 --- a/src/Compiler/lex.fsl +++ b/src/Compiler/lex.fsl @@ -604,7 +604,7 @@ rule token args skip = parse | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | [] -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.String, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.String, args.delimLength, m)) else singleQuoteString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } | ('$'+) '"' '"' '"' @@ -615,9 +615,8 @@ rule token args skip = parse | _ :: _ -> errorR(Error(FSComp.SR.lexTripleQuoteInTripleQuote(), m)) | [] -> () - let numDollars = lexeme lexbuf |> Seq.takeWhile (fun c -> c = '$') |> Seq.length - args.numDollars <- numDollars - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.InterpolatedStringFirst, numDollars, m)) + args.delimLength <- lexeme lexbuf |> Seq.takeWhile (fun c -> c = '$') |> Seq.length + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.InterpolatedStringFirst, args.delimLength, m)) else tripleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } | '$' '"' @@ -629,20 +628,20 @@ rule token args skip = parse | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.InterpolatedStringFirst, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.InterpolatedStringFirst, args.delimLength, m)) else singleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } | '"' '"' '"' { let buf, fin, m = startString args lexbuf - args.numDollars <- 0 + args.delimLength <- 0 // Single quote in triple quote ok, others disallowed match args.stringNest with | _ :: _ -> errorR(Error(FSComp.SR.lexTripleQuoteInTripleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.String, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.String, args.delimLength, m)) else tripleQuoteString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } | '@' '"' @@ -654,7 +653,7 @@ rule token args skip = parse | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.String, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.String, args.delimLength, m)) else verbatimString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } | ("$@" | "@$") '"' @@ -666,7 +665,7 @@ rule token args skip = parse | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.InterpolatedStringFirst, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.InterpolatedStringFirst, args.delimLength, m)) else verbatimString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } | truewhite+ @@ -881,14 +880,14 @@ rule token args skip = parse // We encounter a '}' in the expression token stream. First check if we're in an interpolated string expression // and continue the string if necessary match args.stringNest with - | (1, LexerStringStyle.TripleQuote, x, r) :: rest when x > 1 -> - args.stringNest <- (1, LexerStringStyle.TripleQuote, x-1, r) :: rest + | (1, LexerStringStyle.TripleQuote, delimLength, r) :: rest when delimLength > 1 -> + args.stringNest <- (1, LexerStringStyle.TripleQuote, delimLength - 1, r) :: rest token args skip lexbuf | (1, style, _, _) :: rest -> args.stringNest <- rest let buf, fin, m = startString args lexbuf if not skip then - STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, style, LexerStringKind.InterpolatedStringPart, args.numDollars, m)) + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, style, LexerStringKind.InterpolatedStringPart, args.delimLength, m)) else match style with | LexerStringStyle.Verbatim -> verbatimString (buf, fin, m, LexerStringKind.InterpolatedStringPart, args) skip lexbuf @@ -1149,39 +1148,39 @@ and singleQuoteString sargs skip = parse let text = lexeme lexbuf let text2 = text |> String.filter (fun c -> c <> ' ' && c <> '\t') advanceColumnBy lexbuf (text.Length - text2.Length) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) else singleQuoteString sargs skip lexbuf } | escape_char { let (buf, _fin, m, kind, args) = sargs addByteChar buf (escape (lexeme lexbuf).[1]) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) else singleQuoteString sargs skip lexbuf } | trigraph { let (buf, _fin, m, kind, args) = sargs let s = lexeme lexbuf addByteChar buf (trigraph s.[1] s.[2] s.[3]) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) else singleQuoteString sargs skip lexbuf } | hexGraphShort { let (buf, _fin, m, kind, args) = sargs addUnicodeChar buf (int (hexGraphShort (lexemeTrimLeft lexbuf 2))) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) else singleQuoteString sargs skip lexbuf } | unicodeGraphShort { let (buf, _fin, m, kind, args) = sargs addUnicodeChar buf (int (unicodeGraphShort (lexemeTrimLeft lexbuf 2))) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) else singleQuoteString sargs skip lexbuf } | unicodeGraphLong { let (buf, _fin, m, kind, args) = sargs let hexChars = lexemeTrimLeft lexbuf 2 let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) else singleQuoteString sargs skip lexbuf match unicodeGraphLong hexChars with | Invalid -> @@ -1210,7 +1209,7 @@ and singleQuoteString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs let s = lexeme lexbuf addUnicodeString buf (if kind.IsInterpolated then s.[0..0] else s) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) else singleQuoteString sargs skip lexbuf } | "{" @@ -1218,19 +1217,19 @@ and singleQuoteString sargs skip = parse if kind.IsInterpolated then // get a new range for where the fill starts let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.SingleQuote, args.numDollars, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.SingleQuote, args.delimLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind LexerStringFinisherContext.InterpolatedPart cont else addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) else singleQuoteString sargs skip lexbuf } | "}" { let (buf, _fin, m, kind, args) = sargs let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) else singleQuoteString sargs skip lexbuf if kind.IsInterpolated then fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) @@ -1243,45 +1242,45 @@ and singleQuoteString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs newline lexbuf addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) else singleQuoteString sargs skip lexbuf } | ident { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) else singleQuoteString sargs skip lexbuf } | integer | xinteger { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) else singleQuoteString sargs skip lexbuf } | anywhite + { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) else singleQuoteString sargs skip lexbuf } | eof { let (_buf, _fin, m, kind, args) = sargs - EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) } + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) } | surrogateChar surrogateChar // surrogate code points always come in pairs | _ { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) else singleQuoteString sargs skip lexbuf } and verbatimString sargs skip = parse | '"' '"' { let (buf, _fin, m, kind, args) = sargs addByteChar buf '\"' - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) else verbatimString sargs skip lexbuf } | '"' @@ -1300,14 +1299,14 @@ and verbatimString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs newline lexbuf addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) else verbatimString sargs skip lexbuf } | ("{{" | "}}") { let (buf, _fin, m, kind, args) = sargs let s = lexeme lexbuf addUnicodeString buf (if kind.IsInterpolated then s.[0..0] else s) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) else verbatimString sargs skip lexbuf } | "{" @@ -1315,19 +1314,19 @@ and verbatimString sargs skip = parse if kind.IsInterpolated then // get a new range for where the fill starts let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.Verbatim, args.numDollars, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.Verbatim, args.delimLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind (enum(3)) cont else addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) else verbatimString sargs skip lexbuf } | "}" { let (buf, _fin, m, kind, args) = sargs let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) else verbatimString sargs skip lexbuf if kind.IsInterpolated then fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) @@ -1339,37 +1338,37 @@ and verbatimString sargs skip = parse | ident { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) else verbatimString sargs skip lexbuf } | integer | xinteger { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) else verbatimString sargs skip lexbuf } | anywhite + { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) else verbatimString sargs skip lexbuf } | eof { let (_buf, _fin, m, kind, args) = sargs - EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) } + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) } | surrogateChar surrogateChar // surrogate code points always come in pairs | _ { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) else verbatimString sargs skip lexbuf } and tripleQuoteString sargs skip = parse | '"' '"' '"' { let (buf, fin, _m, kind, args) = sargs - args.numDollars <- 0 + args.delimLength <- 0 let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind (enum(4)) cont } @@ -1377,54 +1376,47 @@ and tripleQuoteString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs newline lexbuf addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf } // The rest is to break into pieces to allow double-click-on-word and other such things | ident { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf } | integer | xinteger { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf } | anywhite + { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf } - //| ("{{" | "}}") - // { let (buf, _fin, m, kind, args) = sargs - // let s = lexeme lexbuf - // addUnicodeString buf (if kind.IsInterpolated then s.[0..0] else s) - // if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) - // else tripleQuoteString sargs skip lexbuf } - | "%" + { let (buf, _fin, m, kind, args) = sargs let numPercents = lexeme lexbuf |> String.length let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf - if kind.IsInterpolated && args.numDollars > 1 then - let maxPercents = 2 * args.numDollars - 1 + if kind.IsInterpolated && args.delimLength > 1 then + let maxPercents = 2 * args.delimLength - 1 if numPercents > maxPercents then let m2 = lexbuf.LexemeRange let rest = result() args.diagnosticsLogger.ErrorR(Error(FSComp.SR.lexTooManyPercentsInTripleQuote(), m2)) rest else - let n = if numPercents < args.numDollars then + let n = if numPercents < args.delimLength then 2 * numPercents else - 2 * (numPercents - args.numDollars) + 1 + 2 * (numPercents - args.delimLength) + 1 let s = String.replicate n "%" addUnicodeString buf s result() @@ -1435,41 +1427,41 @@ and tripleQuoteString sargs skip = parse | "{" + { let (buf, fin, m, kind, args) = sargs let numBraces = String.length (lexeme lexbuf) - if kind.IsInterpolated && args.numDollars = 1 then + if kind.IsInterpolated && args.delimLength = 1 then for _ in 2..2..numBraces do addUnicodeString buf "{" if numBraces % 2 = 0 then - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf else let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.TripleQuote, args.numDollars, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.TripleQuote, args.delimLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind (enum(5)) cont elif kind.IsInterpolated then - if args.numDollars < 1 then + if args.delimLength < 1 then fail args lexbuf (0, FSComp.SR.lexCharNotAllowedInOperatorNames("@")) () // XXX - let maxBraces = 2 * args.numDollars - 1 + let maxBraces = 2 * args.delimLength - 1 if numBraces > maxBraces then let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.TripleQuote, args.numDollars, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.TripleQuote, args.delimLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) fail args lexbuf (FSComp.SR.lexTooManyLBracesInTripleQuote()) (fin.Finish buf kind (enum(5)) cont) // XXX - elif numBraces < args.numDollars then + elif numBraces < args.delimLength then addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf else let s = lexeme lexbuf - addUnicodeString buf s[1..(numBraces - args.numDollars)] // XXX is it ok to addUnicode empty string? Also why start at 1? + addUnicodeString buf s[1..(numBraces - args.delimLength)] // XXX is it ok to addUnicode empty string? Also why start at 1? // get a new range for where the fill starts let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.TripleQuote, args.numDollars, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.TripleQuote, args.delimLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind (enum(5)) cont else addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf } @@ -1477,21 +1469,21 @@ and tripleQuoteString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs let numBraces = lexeme lexbuf |> String.length let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf if kind.IsInterpolated then - if args.numDollars = 1 then + if args.delimLength = 1 then if numBraces % 2 = 1 then fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) else for _ in 2..2..numBraces do addUnicodeString buf "}" (result()) - elif args.numDollars > numBraces then + elif args.delimLength > numBraces then for _ in 1..numBraces do addUnicodeString buf "}" (result()) - else // numBraces >= args.numDollars + else // numBraces >= args.delimLength fail args lexbuf (FSComp.SR.lexUnmatchedRBracesInTripleQuote()) (result()) else addUnicodeString buf (lexeme lexbuf) @@ -1508,13 +1500,13 @@ and tripleQuoteString sargs skip = parse | eof { let (_buf, _fin, m, kind, args) = sargs - EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) } + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) } | surrogateChar surrogateChar // surrogate code points always come in pairs | _ { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.numDollars, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf } // Parsing single-line comment - we need to split it into words for Visual Studio IDE From ca20665646ee1340a05c3e77b8372bce1f403aee Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Wed, 18 Jan 2023 17:17:27 +0100 Subject: [PATCH 08/26] Add unit tests for multi-$ interpolated strings --- .../Language/InterpolatedStringsTests.fs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs index fa320ea3f7..f29bfcdbe9 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs @@ -43,12 +43,47 @@ printf $"{a.Format}" |> shouldSucceed |> withStdOutContains "{{hello}} world" + [] + let ``Interpolated string with 2 leading dollar characters uses double braces for delimiters`` () = + // let s = $$"""{{42 + 0}} = {41 + 1}""" + // printfn "%s" s + Fsx "let s = $$\"\"\"{{42 + 0}} = {41 + 1}\"\"\"\n\ +printfn \"%s\" s" + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains "42 = {41 + 1}" + + [] + let ``Too many consecutive opening braces in interpolated string result in an error`` () = + // $$"""{{{{42 - 0}}""" + Fsx "$$\"\"\"{{{{42 - 0}}\"\"\"" + |> withLangVersionPreview + |> compile + |> shouldFail + |> withSingleDiagnostic (Error 1248, Line 1, Col 1, Line 1, Col 10, "The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content.") + + [] + let ``Too many consecutive closing braces in interpolated string result in an error`` () = + // $$"""{{42 - 0}}}}""" + Fsx "$$\"\"\"{{42 - 0}}}}\"\"\"" + |> withLangVersionPreview + |> compile + |> shouldFail + |> withSingleDiagnostic (Error 1249, Line 1, Col 15, Line 1, Col 21, "The interpolated string contains unmatched closing braces.") + [] let ``Percent sign characters in interpolated strings`` () = Assert.Equal("%", $"%%") Assert.Equal("42%", $"{42}%%") Assert.Equal("% 42", $"%%%3d{42}") + [] + let ``Percent sign characters in triple quote interpolated strings`` () = + Assert.Equal("%%", $$$"""%%""") + Assert.Equal("42%", $$"""{{42}}%""") + Assert.Equal("% 42", $$"""%%%3d{{42}}""") + [] let ``Percent signs separated by format specifier's flags`` () = Fsx """ From 7fd2f50e76ffd3fdd3113cd3e393a8e31409f62c Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Thu, 19 Jan 2023 17:19:39 +0100 Subject: [PATCH 09/26] Add tests for coloring interpolated strings --- .../SyntacticColorizationServiceTests.fs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs index 6fcb469ff8..e96266d6fc 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs @@ -1203,3 +1203,58 @@ type SyntacticClassificationServiceTests() = defines = [], classificationType = ClassificationTypeNames.Keyword ) + + [] + member public this.InterpolatedString_1Dollar() = + this.VerifyColorizerAtEndOfMarker( + fileContents = "$\"\"\"{{41+1}} = {42}\"\"\"", + marker = "42", + defines = [], + classificationType = ClassificationTypeNames.NumericLiteral + ) + + this.VerifyColorizerAtEndOfMarker( + fileContents = "$\"\"\"{{41+1}} = {42}\"\"\"", + marker = "41+1", + defines = [], + classificationType = ClassificationTypeNames.StringLiteral + ) + + [] + member public this.InterpolatedString_2Dollars() = + this.VerifyColorizerAtEndOfMarker( + fileContents = "$$\"\"\"{{41+1}} = {42}\"\"\"", + marker = "42", + defines = [], + classificationType = ClassificationTypeNames.StringLiteral + ) + + this.VerifyColorizerAtEndOfMarker( + fileContents = "$$\"\"\"{{41+1}} = {42}\"\"\"", + marker = "41+1", + defines = [], + classificationType = ClassificationTypeNames.NumericLiteral + ) + + [] + member public this.InterpolatedString_6Dollars() = + this.VerifyColorizerAtEndOfMarker( + fileContents = "$$$$$$\"\"\"{{41+1}} = {42} = {{{{{{40+2}}}}}}\"\"\"", + marker = "42", + defines = [], + classificationType = ClassificationTypeNames.StringLiteral + ) + + this.VerifyColorizerAtEndOfMarker( + fileContents = "$$$$$$\"\"\"{{41+1}} = {42} = {{{{{{40+2}}}}}}\"\"\"", + marker = "41+1", + defines = [], + classificationType = ClassificationTypeNames.StringLiteral + ) + + this.VerifyColorizerAtEndOfMarker( + fileContents = "$$$$$$\"\"\"{{41+1}} = {42} = {{{{{{40+2}}}}}}\"\"\"", + marker = "40+2", + defines = [], + classificationType = ClassificationTypeNames.NumericLiteral + ) From 8ebfacedadcd72dc3880429301e84ec4638513a8 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Fri, 20 Jan 2023 15:58:59 +0100 Subject: [PATCH 10/26] Add comments in lex.fsl, fix linter errors Replace enum<..>(..) usage in lex.fsl Use F.A ||| F.B instead of enum(some int literal) in lex.fsl --- src/Compiler/Service/ServiceLexing.fs | 43 ++++++++----- src/Compiler/lex.fsl | 92 ++++++++++++++++----------- 2 files changed, 81 insertions(+), 54 deletions(-) diff --git a/src/Compiler/Service/ServiceLexing.fs b/src/Compiler/Service/ServiceLexing.fs index 9cf8e1d13c..1c6795b418 100644 --- a/src/Compiler/Service/ServiceLexing.fs +++ b/src/Compiler/Service/ServiceLexing.fs @@ -600,7 +600,7 @@ module internal LexerStateEncoding = light, stringKind: LexerStringKind, stringNest, - delimLen : int + delimLen: int ) = let mutable ifdefStackCount = 0 let mutable ifdefStackBits = 0 @@ -628,10 +628,10 @@ module internal LexerStateEncoding = | [] -> false, 0, 0 | (i2, kind2, _, _) :: _ -> true, i2, encodeStringStyle kind2 - (if tag1 then 0b100000000000 else 0) - ||| (if tag2 then 0b010000000000 else 0) - ||| ((i1 <<< 7) &&& 0b001110000000) - ||| ((i2 <<< 4) &&& 0b000001110000) + (if tag1 then 0b100000000000 else 0) + ||| (if tag2 then 0b010000000000 else 0) + ||| ((i1 <<< 7) &&& 0b001110000000) + ||| ((i2 <<< 4) &&& 0b000001110000) ||| ((kind1 <<< 2) &&& 0b000000001100) ||| ((kind2 <<< 0) &&& 0b000000000011) @@ -686,19 +686,21 @@ module internal LexerStateEncoding = let nestingValue = int32 ((bits &&& nestingMask) >>> nestingStart) let stringNest: LexerInterpolatedStringNesting = - let tag1 = ((nestingValue &&& 0b100000000000) = 0b100000000000) - let tag2 = ((nestingValue &&& 0b010000000000) = 0b010000000000) - let i1 = ((nestingValue &&& 0b001110000000) >>> 7) - let i2 = ((nestingValue &&& 0b000001110000) >>> 4) + let tag1 = ((nestingValue &&& 0b100000000000) = 0b100000000000) + let tag2 = ((nestingValue &&& 0b010000000000) = 0b010000000000) + let i1 = ((nestingValue &&& 0b001110000000) >>> 7) + let i2 = ((nestingValue &&& 0b000001110000) >>> 4) let kind1 = ((nestingValue &&& 0b000000001100) >>> 2) let kind2 = ((nestingValue &&& 0b000000000011) >>> 0) - let nest = [ - if tag1 then - i1, decodeStringStyle kind1, 0, range0 - if tag2 then - i2, decodeStringStyle kind2, 0, range0 - ] + let nest = + [ + if tag1 then + i1, decodeStringStyle kind1, 0, range0 + if tag2 then + i2, decodeStringStyle kind2, 0, range0 + ] + nest let delimLen = int32 ((bits &&& dlenMask) >>> dlenStart) @@ -708,7 +710,16 @@ module internal LexerStateEncoding = let encodeLexInt indentationSyntaxStatus (lexcont: LexerContinuation) = match lexcont with | LexCont.Token (ifdefs, stringNest) -> - encodeLexCont (FSharpTokenizerColorState.Token, 0L, pos0, ifdefs, indentationSyntaxStatus, LexerStringKind.String, stringNest, 0) + encodeLexCont ( + FSharpTokenizerColorState.Token, + 0L, + pos0, + ifdefs, + indentationSyntaxStatus, + LexerStringKind.String, + stringNest, + 0 + ) | LexCont.IfDefSkip (ifdefs, stringNest, n, m) -> encodeLexCont ( FSharpTokenizerColorState.IfDefSkip, diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl index fb087fb48f..2fdd4037ad 100644 --- a/src/Compiler/lex.fsl +++ b/src/Compiler/lex.fsl @@ -1196,13 +1196,13 @@ and singleQuoteString sargs skip = parse | '"' { let (buf, fin, _m, kind, args) = sargs let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fin.Finish buf kind (enum(0)) cont + fin.Finish buf kind (LexerStringFinisherContext()) cont } | '"''B' { let (buf, fin, _m, kind, args) = sargs let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fin.Finish buf { kind with IsByteString = true } (enum(0)) cont + fin.Finish buf { kind with IsByteString = true } (LexerStringFinisherContext()) cont } | ("{{" | "}}") @@ -1316,7 +1316,7 @@ and verbatimString sargs skip = parse let m2 = lexbuf.LexemeRange args.stringNest <- (1, LexerStringStyle.Verbatim, args.delimLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fin.Finish buf kind (enum(3)) cont + fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.Verbatim) cont else addUnicodeString buf (lexeme lexbuf) if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) @@ -1370,7 +1370,7 @@ and tripleQuoteString sargs skip = parse { let (buf, fin, _m, kind, args) = sargs args.delimLength <- 0 let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fin.Finish buf kind (enum(4)) cont } + fin.Finish buf kind LexerStringFinisherContext.TripleQuote cont } | newline { let (buf, _fin, m, kind, args) = sargs @@ -1406,6 +1406,12 @@ and tripleQuoteString sargs skip = parse if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf if kind.IsInterpolated && args.delimLength > 1 then + // delimLength is number of $ chars prepended to opening quotes + // If number of consecutive % chars in content is equal to delimLength, + // then that sequence is treated as a format specifier, + // as in $"""%3d{42}""" or (equivalent) $$"""%%3d{{42}}""". + // Any extra % chars up to delimLength, are treated simply as regular string content. + // 2x delimLength or more % chars in a sequence will result in an error. let maxPercents = 2 * args.delimLength - 1 if numPercents > maxPercents then let m2 = lexbuf.LexemeRange @@ -1413,23 +1419,29 @@ and tripleQuoteString sargs skip = parse args.diagnosticsLogger.ErrorR(Error(FSComp.SR.lexTooManyPercentsInTripleQuote(), m2)) rest else - let n = if numPercents < args.delimLength then - 2 * numPercents - else - 2 * (numPercents - args.delimLength) + 1 - let s = String.replicate n "%" + // Add two % chars for each % that is supposed to be treated as regular string content + // + 1 for a format specifier. + let percentsToEmit = + if numPercents < args.delimLength then 2 * numPercents + else 2 * (numPercents - args.delimLength) + 1 + let s = String.replicate percentsToEmit "%" addUnicodeString buf s result() else + // For a non-interpolated string or an interpolated string with only single leading $ + // we can leave it as is. For interpolated strings, % chars are dealt with in later stages, + // single % is treated as format specifier and %% as escaped % to be put in content. addUnicodeString buf (lexeme lexbuf) result() } | "{" + { let (buf, fin, m, kind, args) = sargs let numBraces = String.length (lexeme lexbuf) - if kind.IsInterpolated && args.delimLength = 1 then - for _ in 2..2..numBraces do - addUnicodeString buf "{" + // Regular interpolated string: $"""...""" + if kind.IsInterpolated && args.delimLength < 2 then + // For every 2 '{' emit 1. Then, if there's an odd '{', start an interpolation expression. + if numBraces > 1 then + String.replicate (numBraces/2) "{" |> addUnicodeString buf if numBraces % 2 = 0 then if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf @@ -1437,28 +1449,38 @@ and tripleQuoteString sargs skip = parse let m2 = lexbuf.LexemeRange args.stringNest <- (1, LexerStringStyle.TripleQuote, args.delimLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fin.Finish buf kind (enum(5)) cont + fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont + // Interpolated string with 2+ `$`: $$"""....""" elif kind.IsInterpolated then - if args.delimLength < 1 then - fail args lexbuf (0, FSComp.SR.lexCharNotAllowedInOperatorNames("@")) () // XXX + // We are in an interpolated string with 2+ $ characters before the opening quotes. + // delimLength is total number of leading $s + // which is also the number of '{' needed to open interpolation expression. let maxBraces = 2 * args.delimLength - 1 if numBraces > maxBraces then + // 2 * delimLength '{' in a row is dissallowed let m2 = lexbuf.LexemeRange args.stringNest <- (1, LexerStringStyle.TripleQuote, args.delimLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fail args lexbuf (FSComp.SR.lexTooManyLBracesInTripleQuote()) (fin.Finish buf kind (enum(5)) cont) // XXX + fail args lexbuf + (FSComp.SR.lexTooManyLBracesInTripleQuote()) + (fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont) elif numBraces < args.delimLength then + // Less than delimLength means we treat '{' as normal content addUnicodeString buf (lexeme lexbuf) if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf + // numBraces in [delimLength; maxBraces) else - let s = lexeme lexbuf - addUnicodeString buf s[1..(numBraces - args.delimLength)] // XXX is it ok to addUnicode empty string? Also why start at 1? + // A sequence of delimLength * '{' starts interpolation expression. + // Any extra '{' are treated as normal string content. + if numBraces > args.delimLength then + String.replicate (numBraces - args.delimLength) "{" |> addUnicodeString buf // get a new range for where the fill starts let m2 = lexbuf.LexemeRange args.stringNest <- (1, LexerStringStyle.TripleQuote, args.delimLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fin.Finish buf kind (enum(5)) cont + fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont + // Not an interpolated string: """...""" else addUnicodeString buf (lexeme lexbuf) if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) @@ -1471,31 +1493,25 @@ and tripleQuoteString sargs skip = parse let result() = if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf - if kind.IsInterpolated then - if args.delimLength = 1 then - if numBraces % 2 = 1 then - fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) - else - for _ in 2..2..numBraces do - addUnicodeString buf "}" - (result()) - elif args.delimLength > numBraces then - for _ in 1..numBraces do - addUnicodeString buf "}" + // Regular interpolated string: $"""...""" + if kind.IsInterpolated && args.delimLength < 2 then + if numBraces % 2 = 1 then + fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) + else + if numBraces > 1 then + String.replicate (numBraces/2) "}" |> addUnicodeString buf + (result()) + // Interpolated string with 2+ `$`: $$"""....""" + elif kind.IsInterpolated then + if args.delimLength > numBraces then + lexeme lexbuf |> addUnicodeString buf (result()) else // numBraces >= args.delimLength fail args lexbuf (FSComp.SR.lexUnmatchedRBracesInTripleQuote()) (result()) + // Not an interpolated string: """...""" else addUnicodeString buf (lexeme lexbuf) (result()) - // let result() = - // if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, m)) - // else tripleQuoteString sargs skip lexbuf - // if kind.IsInterpolated then - // fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) - // else - // addUnicodeString buf (lexeme lexbuf) - // (result()) } | eof From f80e1366da2898507e8b691e359ac5e2c7c7444d Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Tue, 21 Feb 2023 15:37:31 +0100 Subject: [PATCH 11/26] Highlighting format specs in interpolated strings --- src/Compiler/Checking/CheckFormatStrings.fs | 64 +++++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/src/Compiler/Checking/CheckFormatStrings.fs b/src/Compiler/Checking/CheckFormatStrings.fs index e9224ebd58..072843c5cd 100644 --- a/src/Compiler/Checking/CheckFormatStrings.fs +++ b/src/Compiler/Checking/CheckFormatStrings.fs @@ -72,6 +72,11 @@ let makeFmts (context: FormatStringCheckContext) (fragRanges: range list) (fmt: let sourceText = context.SourceText let lineStartPositions = context.LineStartPositions + // Number of curly braces required to delimiter interpolation holes + // = Number of $ chars starting a (triple quoted) string literal + // Set when we process first fragment range, default = 1 + let mutable delimLen = 1 + let mutable nQuotes = 1 [ for i, r in List.indexed fragRanges do if r.StartLine - 1 < lineStartPositions.Length && r.EndLine - 1 < lineStartPositions.Length then @@ -79,8 +84,17 @@ let makeFmts (context: FormatStringCheckContext) (fragRanges: range list) (fmt: let rLength = lineStartPositions[r.EndLine - 1] + r.EndColumn - startIndex let offset = if i = 0 then - match sourceText.GetSubTextString(startIndex, rLength) with - | PrefixedBy "$\"\"\"" len + let fullRangeText = sourceText.GetSubTextString(startIndex, rLength) + delimLen <- + fullRangeText + |> Seq.takeWhile (fun c -> c = '$') + |> Seq.length + |> max delimLen + let interpolatedTripleQuotePrefix = + [String.replicate delimLen "$"; "\"\"\""] + |> String.concat "" + match fullRangeText with + | PrefixedBy interpolatedTripleQuotePrefix len | PrefixedBy "\"\"\"" len -> nQuotes <- 3 len @@ -91,13 +105,13 @@ let makeFmts (context: FormatStringCheckContext) (fragRanges: range list) (fmt: | _ -> 1 else 1 // <- corresponds to '}' that's closing an interpolation hole - let fragLen = rLength - offset - (if i = numFrags - 1 then nQuotes else 1) + let fragLen = rLength - offset - (if i = numFrags - 1 then nQuotes else delimLen) (offset, sourceText.GetSubTextString(startIndex + offset, fragLen), r) else (1, fmt, r) - ] + ], delimLen -module internal Parsing = +module internal Parse = let flags (info: FormatInfoRegister) (fmt: string) (fmtPos: int) = let len = fmt.Length @@ -231,10 +245,10 @@ let parseFormatStringInternal // let escapeFormatStringEnabled = g.langVersion.SupportsFeature Features.LanguageFeature.EscapeDotnetFormattableStrings - let fmt, fragments = + let fmt, fragments, delimLen = match context with | Some context when fragRanges.Length > 0 -> - let fmts = makeFmts context fragRanges fmt + let fmts, delimLen = makeFmts context fragRanges fmt // Join the fragments with holes. Note this join is only used on the IDE path, // the CheckExpressions.fs does its own joining with the right alignments etc. substituted @@ -245,11 +259,11 @@ let parseFormatStringInternal (0, fmts) ||> List.mapFold (fun i (offset, fmt, fragRange) -> (i, offset, fragRange), i + fmt.Length + 4) // the '4' is the length of '%P()' joins - fmt, fragments + fmt, fragments, delimLen | _ -> // Don't muck with the fmt when there is no source code context to go get the original // source code (i.e. when compiling or background checking) - (if escapeFormatStringEnabled then escapeDotnetFormatString fmt else fmt), [ (0, 1, m) ] + (if escapeFormatStringEnabled then escapeDotnetFormatString fmt else fmt), [ (0, 1, m) ], 1 let len = fmt.Length @@ -299,32 +313,44 @@ let parseFormatStringInternal and parseSpecifier acc (i, fragLine, fragCol) fragments = let startFragCol = fragCol - let fragCol = fragCol+1 - if fmt[i..(i+1)] = "%%" then + let nPercent = + fmt[i..] + |> Seq.takeWhile (fun c -> c = '%') + |> Seq.length + if delimLen <= 1 && fmt[i..(i+1)] = "%%" then match context with | Some _ -> specifierLocations.Add( (Range.mkFileIndexRange m.FileIndex - (Position.mkPos fragLine startFragCol) - (Position.mkPos fragLine (fragCol + 1))), 0) + (Position.mkPos fragLine fragCol) + (Position.mkPos fragLine (fragCol+2))), 0) | None -> () appendToDotnetFormatString "%" - parseLoop acc (i+2, fragLine, fragCol+1) fragments + parseLoop acc (i+2, fragLine, fragCol+2) fragments + elif delimLen > 1 && nPercent < delimLen then + appendToDotnetFormatString fmt[i..(i+nPercent-1)] + parseLoop acc (i + nPercent, fragLine, fragCol + nPercent) fragments else - let i = i+1 + let fragCol, i = + if delimLen > 1 then + if nPercent > delimLen then + "%" |> String.replicate (nPercent - delimLen) |> appendToDotnetFormatString + fragCol + nPercent, i + nPercent + else + fragCol + 1, i + 1 if i >= len then failwith (FSComp.SR.forMissingFormatSpecifier()) let info = newInfo() let oldI = i - let posi, i = Parsing.position fmt i + let posi, i = Parse.position fmt i let fragCol = fragCol + i - oldI let oldI = i - let i = Parsing.flags info fmt i + let i = Parse.flags info fmt i let fragCol = fragCol + i - oldI let oldI = i - let widthArg,(widthValue, (precisionArg,i)) = Parsing.widthAndPrecision info fmt i + let widthArg,(widthValue, (precisionArg,i)) = Parse.widthAndPrecision info fmt i let fragCol = fragCol + i - oldI if i >= len then failwith (FSComp.SR.forBadPrecision()) @@ -340,7 +366,7 @@ let parseFormatStringInternal | Some n -> failwith (FSComp.SR.forDoesNotSupportPrefixFlag(c.ToString(), n.ToString())) | None -> () - let skipPossibleInterpolationHole pos = Parsing.skipPossibleInterpolationHole isInterpolated isFormattableString fmt pos + let skipPossibleInterpolationHole pos = Parse.skipPossibleInterpolationHole isInterpolated isFormattableString fmt pos // Implicitly typed holes in interpolated strings are translated to '... %P(...)...' in the // type checker. They should always have '(...)' after for format string. From e7b664088dd20f82dbe0afe247ee3e8503833871 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Fri, 24 Feb 2023 12:25:12 +0100 Subject: [PATCH 12/26] Add tests for coloring of format specifiers --- tests/service/EditorTests.fs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/service/EditorTests.fs b/tests/service/EditorTests.fs index 9a50646ed3..74a96b665d 100644 --- a/tests/service/EditorTests.fs +++ b/tests/service/EditorTests.fs @@ -586,7 +586,9 @@ let s3 = $"abc %d{s.Length} [] let ``Printf specifiers for triple quote interpolated strings`` () = let input = - "let _ = $\"\"\"abc %d{1} and %d{2+3}def\"\"\" " + "let _ = $\"\"\"abc %d{1} and %d{2+3}def\"\"\" +let _ = $$\"\"\"abc %%d{{1}} and %%d{{2}}def\"\"\" +let _ = $$$\"\"\"%% %%%d{{{4}}} % %%%d{{{5}}}\"\"\" " let file = "/home/user/Test.fsx" let parseResult, typeCheckResults = parseAndCheckScriptWithOptions(file, input, [| "/langversion:preview" |]) @@ -595,7 +597,9 @@ let ``Printf specifiers for triple quote interpolated strings`` () = typeCheckResults.GetFormatSpecifierLocationsAndArity() |> Array.map (fun (range,numArgs) -> range.StartLine, range.StartColumn, range.EndLine, range.EndColumn, numArgs) |> shouldEqual - [|(1, 16, 1, 18, 1); (1, 26, 1, 28, 1)|] + [|(1, 16, 1, 18, 1); (1, 26, 1, 28, 1) + (2, 17, 2, 20, 1); (2, 30, 2, 33, 1) + (3, 17, 3, 21, 1); (3, 31, 3, 35, 1)|] #endif // ASSUME_PREVIEW_FSHARP_CORE From 50d74237d017f04375c97cf0993dd1385c7e25c9 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Wed, 1 Mar 2023 19:03:50 +0100 Subject: [PATCH 13/26] Fix brace matching for many braces Make brace matching work for interpolated strings with many braces. Adds one tests, but perhaps more tests would be good. --- src/Compiler/Service/FSharpCheckerResults.fs | 24 ++++++++++----- .../BraceMatchingServiceTests.fs | 29 +++++++++++++++++++ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index c0dcd3f296..8480305a43 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -2352,7 +2352,7 @@ module internal ParseAndCheckFile = let rec matchBraces stack = match lexfun lexbuf, stack with - | tok2, (tok1, m1) :: stackAfterMatch when parenTokensBalance tok1 tok2 -> + | tok2, (tok1, braceOffset, m1) :: stackAfterMatch when parenTokensBalance tok1 tok2 -> let m2 = lexbuf.LexemeRange // For INTERP_STRING_PART and INTERP_STRING_END grab the one character @@ -2360,7 +2360,11 @@ module internal ParseAndCheckFile = let m2Start = match tok2 with | INTERP_STRING_PART _ - | INTERP_STRING_END _ -> mkFileIndexRange m2.FileIndex m2.Start (mkPos m2.Start.Line (m2.Start.Column + 1)) + | INTERP_STRING_END _ -> + mkFileIndexRange + m2.FileIndex + (mkPos m2.Start.Line (m2.Start.Column - braceOffset)) + (mkPos m2.Start.Line (m2.Start.Column + 1)) | _ -> m2 matchingBraces.Add(m1, m2Start) @@ -2371,15 +2375,15 @@ module internal ParseAndCheckFile = match tok2 with | INTERP_STRING_PART _ -> let m2End = - mkFileIndexRange m2.FileIndex (mkPos m2.End.Line (max (m2.End.Column - 1) 0)) m2.End + mkFileIndexRange m2.FileIndex (mkPos m2.End.Line (max (m2.End.Column - 1 - braceOffset) 0)) m2.End - (tok2, m2End) :: stackAfterMatch + (tok2, braceOffset, m2End) :: stackAfterMatch | _ -> stackAfterMatch matchBraces stackAfterMatch | LPAREN | LBRACE _ | LBRACK | LBRACE_BAR | LBRACK_BAR | LQUOTE _ | LBRACK_LESS as tok, _ -> - matchBraces ((tok, lexbuf.LexemeRange) :: stack) + matchBraces ((tok, 0, lexbuf.LexemeRange) :: stack) // INTERP_STRING_BEGIN_PART corresponds to $"... {" at the start of an interpolated string // @@ -2389,12 +2393,18 @@ module internal ParseAndCheckFile = // // Either way we start a new potential match at the last character | INTERP_STRING_BEGIN_PART _ | INTERP_STRING_PART _ as tok, _ -> + let braceOffset = + match tok with + | INTERP_STRING_BEGIN_PART (_, SynStringKind.TripleQuote, (LexerContinuation.Token (_, (_, _, dl, _) :: _))) -> + max 0 (dl - 1) + | _ -> 0 + let m = lexbuf.LexemeRange let m2 = - mkFileIndexRange m.FileIndex (mkPos m.End.Line (max (m.End.Column - 1) 0)) m.End + mkFileIndexRange m.FileIndex (mkPos m.End.Line (max (m.End.Column - 1 - braceOffset) 0)) m.End - matchBraces ((tok, m2) :: stack) + matchBraces ((tok, braceOffset, m2) :: stack) | (EOF _ | LEX_FAILURE _), _ -> () | _ -> matchBraces stack diff --git a/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs index 9e8bce2aba..f5a84873dc 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs @@ -92,6 +92,35 @@ type BraceMatchingServiceTests() = member this.BraceInInterpolatedStringSimple() = this.VerifyBraceMatch("let x = $\"abc{1}def\"", "{1", "}def") + [] + member this.BraceInInterpolatedStringWith3Dollars() = + this.VerifyBraceMatch("let x = $$$\"\"\"abc{{{1}}}def\"\"\"", "{{{", "}}}") + + [] + [] + [] + [] + [] + member this.BraceNoMatchInNestedInterpolatedStrings(marker) = + let source = + "let x = $$$\"\"\"{{not a }}match +e{{{4$\"f{56}g\"}}}h +\"\"\"" + + this.VerifyNoBraceMatch(source, marker) + + [] + [] + [] + [] + member this.BraceMatchInNestedInterpolatedStrings(startMark, endMark) = + let source = + "let x = $$$\"\"\"a{{{01}}}b --- c{{{23}}}d +e{{{4$\"f{56}g\"}}}h +\"\"\"" + + this.VerifyBraceMatch(source, startMark, endMark) + [] member this.BraceInInterpolatedStringTwoHoles() = this.VerifyBraceMatch("let x = $\"abc{1}def{2+3}hij\"", "{2", "}hij") From 88cce364d07a58e26dc259529a2d906e2ae29e7a Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Tue, 21 Mar 2023 19:09:26 +0100 Subject: [PATCH 14/26] Add baseline tests for syntax tree --- ...tedStringWithTripleQuoteMultipleDollars.fs | 2 ++ ...tringWithTripleQuoteMultipleDollars.fs.bsl | 30 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs create mode 100644 tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs.bsl diff --git a/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs b/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs new file mode 100644 index 0000000000..6006b58162 --- /dev/null +++ b/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs @@ -0,0 +1,2 @@ + +let s = $$$"""1 + {{{41}}} = {{{6}}} * 7""" \ No newline at end of file diff --git a/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs.bsl b/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs.bsl new file mode 100644 index 0000000000..b6550f1482 --- /dev/null +++ b/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs.bsl @@ -0,0 +1,30 @@ +ImplFile + (ParsedImplFileInput + ("/root/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs", + false, + QualifiedNameOfFile + SynExprInterpolatedStringWithTripleQuoteMultipleDollars, [], [], + [SynModuleOrNamespace + ([SynExprInterpolatedStringWithTripleQuoteMultipleDollars], false, + AnonModule, + [Let + (false, + [SynBinding + (None, Normal, false, false, [], + PreXmlDoc ((2,0), FSharp.Compiler.Xml.XmlDocCollector), + SynValData + (None, SynValInfo ([], SynArgInfo ([], false, None)), None), + Named (SynIdent (s, None), false, None, (2,4--2,5)), None, + InterpolatedString + ([String ("1 + ", (2,8--2,21)); + FillExpr (Const (Int32 41, (2,21--2,23)), None); + String (" = ", (2,25--2,32)); + FillExpr (Const (Int32 6, (2,32--2,33)), None); + String (" * 7", (2,35--2,43))], TripleQuote, (2,8--2,43)), + (2,4--2,5), Yes (2,0--2,43), { LeadingKeyword = Let (2,0--2,3) + InlineKeyword = None + EqualsRange = Some (2,6--2,7) })], + (2,0--2,43))], PreXmlDocEmpty, [], None, (2,0--2,43), + { LeadingKeyword = None })], (true, false), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) From 92d8c3428cffe5912c6f449825ca7cb2186b4b30 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Wed, 22 Mar 2023 14:30:33 +0100 Subject: [PATCH 15/26] Rename variable --- src/Compiler/Checking/CheckFormatStrings.fs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Compiler/Checking/CheckFormatStrings.fs b/src/Compiler/Checking/CheckFormatStrings.fs index 072843c5cd..5f2a0f12b6 100644 --- a/src/Compiler/Checking/CheckFormatStrings.fs +++ b/src/Compiler/Checking/CheckFormatStrings.fs @@ -313,7 +313,7 @@ let parseFormatStringInternal and parseSpecifier acc (i, fragLine, fragCol) fragments = let startFragCol = fragCol - let nPercent = + let nPercentSigns = fmt[i..] |> Seq.takeWhile (fun c -> c = '%') |> Seq.length @@ -327,15 +327,15 @@ let parseFormatStringInternal | None -> () appendToDotnetFormatString "%" parseLoop acc (i+2, fragLine, fragCol+2) fragments - elif delimLen > 1 && nPercent < delimLen then - appendToDotnetFormatString fmt[i..(i+nPercent-1)] - parseLoop acc (i + nPercent, fragLine, fragCol + nPercent) fragments + elif delimLen > 1 && nPercentSigns < delimLen then + appendToDotnetFormatString fmt[i..(i+nPercentSigns-1)] + parseLoop acc (i + nPercentSigns, fragLine, fragCol + nPercentSigns) fragments else let fragCol, i = if delimLen > 1 then - if nPercent > delimLen then - "%" |> String.replicate (nPercent - delimLen) |> appendToDotnetFormatString - fragCol + nPercent, i + nPercent + if nPercentSigns > delimLen then + "%" |> String.replicate (nPercentSigns - delimLen) |> appendToDotnetFormatString + fragCol + nPercentSigns, i + nPercentSigns else fragCol + 1, i + 1 if i >= len then failwith (FSComp.SR.forMissingFormatSpecifier()) From 6175016571c09f46ff13cabf936608d349624d38 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Mon, 27 Mar 2023 17:23:20 +0200 Subject: [PATCH 16/26] Add test for nested interpolated strings coloring --- .../SyntacticColorizationServiceTests.fs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs index e96266d6fc..1245859207 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs @@ -1258,3 +1258,15 @@ type SyntacticClassificationServiceTests() = defines = [], classificationType = ClassificationTypeNames.NumericLiteral ) + + [] + [] + [] + [] + member public this.InterpolatedString_1DollarNestedIn2Dollars(marker: string, classificationType: string) = + this.VerifyColorizerAtEndOfMarker( + fileContents = "$$\"\"\"{{ $\"{10+1}\" }} {20+2} {{30+3}}\"\"\"", + marker = marker, + defines = [], + classificationType = classificationType + ) From 50a572e725e6f0ec4d13e3950473fb40e6fbef2b Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Mon, 17 Apr 2023 15:40:38 +0200 Subject: [PATCH 17/26] Fix syntax highlighting with nested strings --- src/Compiler/Service/ServiceLexing.fs | 4 ++-- ...xprInterpolatedStringWithTripleQuoteMultipleDollars.fs.bsl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Compiler/Service/ServiceLexing.fs b/src/Compiler/Service/ServiceLexing.fs index 1c6795b418..2ca6c6f739 100644 --- a/src/Compiler/Service/ServiceLexing.fs +++ b/src/Compiler/Service/ServiceLexing.fs @@ -814,7 +814,7 @@ module internal LexerStateEncoding = | FSharpTokenizerColorState.Token -> LexCont.Token(ifdefs, stringNest) | FSharpTokenizerColorState.IfDefSkip -> LexCont.IfDefSkip(ifdefs, stringNest, n1, mkRange "file" p1 p1) | FSharpTokenizerColorState.String -> - LexCont.String(ifdefs, stringNest, LexerStringStyle.SingleQuote, stringKind, 0, mkRange "file" p1 p1) + LexCont.String(ifdefs, stringNest, LexerStringStyle.SingleQuote, stringKind, delimLen, mkRange "file" p1 p1) | FSharpTokenizerColorState.Comment -> LexCont.Comment(ifdefs, stringNest, n1, mkRange "file" p1 p1) | FSharpTokenizerColorState.SingleLineComment -> LexCont.SingleLineComment(ifdefs, stringNest, n1, mkRange "file" p1 p1) | FSharpTokenizerColorState.StringInComment -> @@ -825,7 +825,7 @@ module internal LexerStateEncoding = LexCont.StringInComment(ifdefs, stringNest, LexerStringStyle.TripleQuote, n1, mkRange "file" p1 p1) | FSharpTokenizerColorState.CamlOnly -> LexCont.MLOnly(ifdefs, stringNest, mkRange "file" p1 p1) | FSharpTokenizerColorState.VerbatimString -> - LexCont.String(ifdefs, stringNest, LexerStringStyle.Verbatim, stringKind, 0, mkRange "file" p1 p1) + LexCont.String(ifdefs, stringNest, LexerStringStyle.Verbatim, stringKind, delimLen, mkRange "file" p1 p1) | FSharpTokenizerColorState.TripleQuoteString -> LexCont.String(ifdefs, stringNest, LexerStringStyle.TripleQuote, stringKind, delimLen, mkRange "file" p1 p1) | FSharpTokenizerColorState.EndLineThenSkip -> diff --git a/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs.bsl b/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs.bsl index b6550f1482..8fbc4d9d76 100644 --- a/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs.bsl +++ b/tests/service/data/SyntaxTree/String/SynExprInterpolatedStringWithTripleQuoteMultipleDollars.fs.bsl @@ -25,6 +25,6 @@ ImplFile InlineKeyword = None EqualsRange = Some (2,6--2,7) })], (2,0--2,43))], PreXmlDocEmpty, [], None, (2,0--2,43), - { LeadingKeyword = None })], (true, false), + { LeadingKeyword = None })], (true, true), { ConditionalDirectives = [] CodeComments = [] }, set [])) From 2d13f31143d91def1de6c7662af0112d8332a419 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Mon, 17 Apr 2023 17:40:42 +0200 Subject: [PATCH 18/26] Extract lexer changes into a new lexer rule Refactor to keep the changes mostly contained into a new lexer rule. This makes it less likely to cause regression in regular interpolated strings and easier to put the feature behind a language version check, at a cost of some boiler-plate. --- src/Compiler/Service/ServiceLexing.fs | 13 +- src/Compiler/SyntaxTree/ParseHelpers.fs | 3 + src/Compiler/SyntaxTree/ParseHelpers.fsi | 1 + src/Compiler/lex.fsl | 255 ++++++++++-------- .../Language/InterpolatedStringsTests.fs | 26 +- ...vice.SurfaceArea.netstandard20.release.bsl | 1 + 6 files changed, 186 insertions(+), 113 deletions(-) diff --git a/src/Compiler/Service/ServiceLexing.fs b/src/Compiler/Service/ServiceLexing.fs index 2ca6c6f739..e5bc9ad6b2 100644 --- a/src/Compiler/Service/ServiceLexing.fs +++ b/src/Compiler/Service/ServiceLexing.fs @@ -475,6 +475,7 @@ type FSharpTokenizerColorState = | EndLineThenToken = 12 | TripleQuoteString = 13 | TripleQuoteStringInComment = 14 + | ExtendedInterpolatedString = 15 | InitialState = 0 module internal LexerStateEncoding = @@ -581,12 +582,14 @@ module internal LexerStateEncoding = | LexerStringStyle.SingleQuote -> 0 | LexerStringStyle.Verbatim -> 1 | LexerStringStyle.TripleQuote -> 2 + | LexerStringStyle.ExtendedInterpolated -> 3 let decodeStringStyle kind = match kind with | 0 -> LexerStringStyle.SingleQuote | 1 -> LexerStringStyle.Verbatim | 2 -> LexerStringStyle.TripleQuote + | 3 -> LexerStringStyle.ExtendedInterpolated | _ -> assert false LexerStringStyle.SingleQuote @@ -761,6 +764,7 @@ module internal LexerStateEncoding = | LexerStringStyle.SingleQuote -> FSharpTokenizerColorState.String | LexerStringStyle.Verbatim -> FSharpTokenizerColorState.VerbatimString | LexerStringStyle.TripleQuote -> FSharpTokenizerColorState.TripleQuoteString + | LexerStringStyle.ExtendedInterpolated -> FSharpTokenizerColorState.ExtendedInterpolatedString encodeLexCont (state, 0L, m.Start, ifdefs, indentationSyntaxStatus, kind, stringNest, delimLen) | LexCont.Comment (ifdefs, stringNest, n, m) -> @@ -790,7 +794,8 @@ module internal LexerStateEncoding = match style with | LexerStringStyle.SingleQuote -> FSharpTokenizerColorState.StringInComment | LexerStringStyle.Verbatim -> FSharpTokenizerColorState.VerbatimStringInComment - | LexerStringStyle.TripleQuote -> FSharpTokenizerColorState.TripleQuoteStringInComment + | LexerStringStyle.TripleQuote + | LexerStringStyle.ExtendedInterpolated -> FSharpTokenizerColorState.TripleQuoteStringInComment encodeLexCont (state, int64 n, m.Start, ifdefs, indentationSyntaxStatus, LexerStringKind.String, stringNest, 0) | LexCont.MLOnly (ifdefs, stringNest, m) -> @@ -828,6 +833,8 @@ module internal LexerStateEncoding = LexCont.String(ifdefs, stringNest, LexerStringStyle.Verbatim, stringKind, delimLen, mkRange "file" p1 p1) | FSharpTokenizerColorState.TripleQuoteString -> LexCont.String(ifdefs, stringNest, LexerStringStyle.TripleQuote, stringKind, delimLen, mkRange "file" p1 p1) + | FSharpTokenizerColorState.ExtendedInterpolatedString -> + LexCont.String(ifdefs, stringNest, LexerStringStyle.ExtendedInterpolated, stringKind, delimLen, mkRange "file" p1 p1) | FSharpTokenizerColorState.EndLineThenSkip -> LexCont.EndLine(ifdefs, stringNest, LexerEndlineContinuation.Skip(n1, mkRange "file" p1 p1)) | FSharpTokenizerColorState.EndLineThenToken -> LexCont.EndLine(ifdefs, stringNest, LexerEndlineContinuation.Token) @@ -970,6 +977,7 @@ type FSharpLineTokenizer(lexbuf: UnicodeLexing.Lexbuf, maxLength: int option, fi | LexerStringStyle.SingleQuote -> Lexer.singleQuoteString args skip lexbuf | LexerStringStyle.Verbatim -> Lexer.verbatimString args skip lexbuf | LexerStringStyle.TripleQuote -> Lexer.tripleQuoteString args skip lexbuf + | LexerStringStyle.ExtendedInterpolated -> Lexer.extendedInterpolatedString args skip lexbuf | LexCont.Comment (ifdefs, stringNest, n, m) -> lexargs.ifdefStack <- ifdefs @@ -989,7 +997,8 @@ type FSharpLineTokenizer(lexbuf: UnicodeLexing.Lexbuf, maxLength: int option, fi match style with | LexerStringStyle.SingleQuote -> Lexer.stringInComment n m lexargs skip lexbuf | LexerStringStyle.Verbatim -> Lexer.verbatimStringInComment n m lexargs skip lexbuf - | LexerStringStyle.TripleQuote -> Lexer.tripleQuoteStringInComment n m lexargs skip lexbuf + | LexerStringStyle.TripleQuote + | LexerStringStyle.ExtendedInterpolated -> Lexer.tripleQuoteStringInComment n m lexargs skip lexbuf | LexCont.MLOnly (ifdefs, stringNest, m) -> lexargs.ifdefStack <- ifdefs diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fs b/src/Compiler/SyntaxTree/ParseHelpers.fs index 7e3fdb2924..7c3a68644b 100644 --- a/src/Compiler/SyntaxTree/ParseHelpers.fs +++ b/src/Compiler/SyntaxTree/ParseHelpers.fs @@ -268,6 +268,7 @@ type LexerStringStyle = | Verbatim | TripleQuote | SingleQuote + | ExtendedInterpolated [] type LexerStringKind = @@ -931,6 +932,7 @@ let checkEndOfFileError t = else reportParseErrorAt m (FSComp.SR.parsEofInString ()) + | LexCont.String (_, _, LexerStringStyle.ExtendedInterpolated, kind, _, m) | LexCont.String (_, _, LexerStringStyle.TripleQuote, kind, _, m) -> if kind.IsInterpolated then reportParseErrorAt m (FSComp.SR.parsEofInInterpolatedTripleQuoteString ()) @@ -952,6 +954,7 @@ let checkEndOfFileError t = | LexCont.StringInComment (_, _, LexerStringStyle.Verbatim, _, m) -> reportParseErrorAt m (FSComp.SR.parsEofInVerbatimStringInComment ()) + | LexCont.StringInComment (_, _, LexerStringStyle.ExtendedInterpolated, _, m) | LexCont.StringInComment (_, _, LexerStringStyle.TripleQuote, _, m) -> reportParseErrorAt m (FSComp.SR.parsEofInTripleQuoteStringInComment ()) diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fsi b/src/Compiler/SyntaxTree/ParseHelpers.fsi index 8e27dea500..8d158862f4 100644 --- a/src/Compiler/SyntaxTree/ParseHelpers.fsi +++ b/src/Compiler/SyntaxTree/ParseHelpers.fsi @@ -102,6 +102,7 @@ type LexerStringStyle = | Verbatim | TripleQuote | SingleQuote + | ExtendedInterpolated [] type LexerStringKind = diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl index 2fdd4037ad..11be21e813 100644 --- a/src/Compiler/lex.fsl +++ b/src/Compiler/lex.fsl @@ -600,6 +600,7 @@ rule token args skip = parse // Single quote in triple quote ok, others disallowed match args.stringNest with + | (_, LexerStringStyle.ExtendedInterpolated, _, _) :: _ | (_, LexerStringStyle.TripleQuote, _, _) :: _ -> () | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | [] -> () @@ -607,6 +608,18 @@ rule token args skip = parse if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.String, args.delimLength, m)) else singleQuoteString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } + | '$' '"' '"' '"' + { let buf, fin, m = startString args lexbuf + + // Single quote in triple quote ok, others disallowed + match args.stringNest with + | _ :: _ -> errorR(Error(FSComp.SR.lexTripleQuoteInTripleQuote(), m)) + | [] -> () + + args.delimLength <- 1 + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.InterpolatedStringFirst, 1, m)) + else tripleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } + | ('$'+) '"' '"' '"' { let buf, fin, m = startString args lexbuf @@ -616,15 +629,15 @@ rule token args skip = parse | [] -> () args.delimLength <- lexeme lexbuf |> Seq.takeWhile (fun c -> c = '$') |> Seq.length - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.InterpolatedStringFirst, args.delimLength, m)) - else tripleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, LexerStringKind.InterpolatedStringFirst, args.delimLength, m)) + else extendedInterpolatedString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } | '$' '"' { let buf,fin,m = startString args lexbuf // Single quote in triple quote ok, others disallowed match args.stringNest with - | (_, LexerStringStyle.TripleQuote, _, _) :: _ -> () + | (_, style, _, _) :: _ when style = LexerStringStyle.ExtendedInterpolated || style = LexerStringStyle.TripleQuote -> () | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () @@ -649,6 +662,7 @@ rule token args skip = parse // Single quote in triple quote ok, others disallowed match args.stringNest with + | (_, LexerStringStyle.ExtendedInterpolated, _, _) :: _ | (_, LexerStringStyle.TripleQuote, _, _) :: _ -> () | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () @@ -661,7 +675,7 @@ rule token args skip = parse // Single quote in triple quote ok, others disallowed match args.stringNest with - | (_, LexerStringStyle.TripleQuote, _, _) :: _ -> () + | (_, style, _, _) :: _ when style = LexerStringStyle.ExtendedInterpolated || style = LexerStringStyle.TripleQuote -> () | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () @@ -880,8 +894,8 @@ rule token args skip = parse // We encounter a '}' in the expression token stream. First check if we're in an interpolated string expression // and continue the string if necessary match args.stringNest with - | (1, LexerStringStyle.TripleQuote, delimLength, r) :: rest when delimLength > 1 -> - args.stringNest <- (1, LexerStringStyle.TripleQuote, delimLength - 1, r) :: rest + | (1, LexerStringStyle.ExtendedInterpolated, delimLength, r) :: rest when delimLength > 1 -> + args.stringNest <- (1, LexerStringStyle.ExtendedInterpolated, delimLength - 1, r) :: rest token args skip lexbuf | (1, style, _, _) :: rest -> args.stringNest <- rest @@ -893,15 +907,16 @@ rule token args skip = parse | LexerStringStyle.Verbatim -> verbatimString (buf, fin, m, LexerStringKind.InterpolatedStringPart, args) skip lexbuf | LexerStringStyle.SingleQuote -> singleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringPart, args) skip lexbuf | LexerStringStyle.TripleQuote -> tripleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringPart, args) skip lexbuf + | LexerStringStyle.ExtendedInterpolated -> extendedInterpolatedString (buf, fin, m, LexerStringKind.InterpolatedStringPart, args) skip lexbuf - | (counter, style, d, m) :: rest -> + | (counter, style, d, m) :: rest -> // Note, we do not update the 'm', any incomplete-interpolation error // will be reported w.r.t. the first '{' args.stringNest <- (counter - 1, style, d, m) :: rest let cont = LexCont.Token(args.ifdefStack, args.stringNest) RBRACE cont - | _ -> + | _ -> let cont = LexCont.Token(args.ifdefStack, args.stringNest) RBRACE cont } @@ -1235,8 +1250,7 @@ and singleQuoteString sargs skip = parse fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) else addUnicodeString buf (lexeme lexbuf) - (result()) - } + (result()) } | newline { let (buf, _fin, m, kind, args) = sargs @@ -1399,116 +1413,34 @@ and tripleQuoteString sargs skip = parse if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf } - | "%" + + | ("{{" | "}}") { let (buf, _fin, m, kind, args) = sargs - let numPercents = lexeme lexbuf |> String.length - let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) - else tripleQuoteString sargs skip lexbuf - if kind.IsInterpolated && args.delimLength > 1 then - // delimLength is number of $ chars prepended to opening quotes - // If number of consecutive % chars in content is equal to delimLength, - // then that sequence is treated as a format specifier, - // as in $"""%3d{42}""" or (equivalent) $$"""%%3d{{42}}""". - // Any extra % chars up to delimLength, are treated simply as regular string content. - // 2x delimLength or more % chars in a sequence will result in an error. - let maxPercents = 2 * args.delimLength - 1 - if numPercents > maxPercents then - let m2 = lexbuf.LexemeRange - let rest = result() - args.diagnosticsLogger.ErrorR(Error(FSComp.SR.lexTooManyPercentsInTripleQuote(), m2)) - rest - else - // Add two % chars for each % that is supposed to be treated as regular string content - // + 1 for a format specifier. - let percentsToEmit = - if numPercents < args.delimLength then 2 * numPercents - else 2 * (numPercents - args.delimLength) + 1 - let s = String.replicate percentsToEmit "%" - addUnicodeString buf s - result() - else - // For a non-interpolated string or an interpolated string with only single leading $ - // we can leave it as is. For interpolated strings, % chars are dealt with in later stages, - // single % is treated as format specifier and %% as escaped % to be put in content. - addUnicodeString buf (lexeme lexbuf) - result() } + let s = lexeme lexbuf + addUnicodeString buf (if kind.IsInterpolated then s.[0..0] else s) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) + else tripleQuoteString sargs skip lexbuf } - | "{" + + | "{" { let (buf, fin, m, kind, args) = sargs - let numBraces = String.length (lexeme lexbuf) - // Regular interpolated string: $"""...""" - if kind.IsInterpolated && args.delimLength < 2 then - // For every 2 '{' emit 1. Then, if there's an odd '{', start an interpolation expression. - if numBraces > 1 then - String.replicate (numBraces/2) "{" |> addUnicodeString buf - if numBraces % 2 = 0 then - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) - else tripleQuoteString sargs skip lexbuf - else - let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.TripleQuote, args.delimLength, m2) :: args.stringNest - let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont - // Interpolated string with 2+ `$`: $$"""....""" - elif kind.IsInterpolated then - // We are in an interpolated string with 2+ $ characters before the opening quotes. - // delimLength is total number of leading $s - // which is also the number of '{' needed to open interpolation expression. - let maxBraces = 2 * args.delimLength - 1 - if numBraces > maxBraces then - // 2 * delimLength '{' in a row is dissallowed - let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.TripleQuote, args.delimLength, m2) :: args.stringNest - let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fail args lexbuf - (FSComp.SR.lexTooManyLBracesInTripleQuote()) - (fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont) - elif numBraces < args.delimLength then - // Less than delimLength means we treat '{' as normal content - addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) - else tripleQuoteString sargs skip lexbuf - // numBraces in [delimLength; maxBraces) - else - // A sequence of delimLength * '{' starts interpolation expression. - // Any extra '{' are treated as normal string content. - if numBraces > args.delimLength then - String.replicate (numBraces - args.delimLength) "{" |> addUnicodeString buf - // get a new range for where the fill starts - let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.TripleQuote, args.delimLength, m2) :: args.stringNest - let cont = LexCont.Token(args.ifdefStack, args.stringNest) - fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont - // Not an interpolated string: """...""" + if kind.IsInterpolated then + // get a new range for where the fill starts + let m2 = lexbuf.LexemeRange + args.stringNest <- (1, LexerStringStyle.TripleQuote, args.delimLength, m2) :: args.stringNest + let cont = LexCont.Token(args.ifdefStack, args.stringNest) + fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont else addUnicodeString buf (lexeme lexbuf) if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf } - | "}" + + | "}" { let (buf, _fin, m, kind, args) = sargs - let numBraces = lexeme lexbuf |> String.length let result() = if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf - // Regular interpolated string: $"""...""" - if kind.IsInterpolated && args.delimLength < 2 then - if numBraces % 2 = 1 then - fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) - else - if numBraces > 1 then - String.replicate (numBraces/2) "}" |> addUnicodeString buf - (result()) - // Interpolated string with 2+ `$`: $$"""....""" - elif kind.IsInterpolated then - if args.delimLength > numBraces then - lexeme lexbuf |> addUnicodeString buf - (result()) - else // numBraces >= args.delimLength - fail args lexbuf (FSComp.SR.lexUnmatchedRBracesInTripleQuote()) (result()) - // Not an interpolated string: """...""" + if kind.IsInterpolated then + fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) else addUnicodeString buf (lexeme lexbuf) (result()) @@ -1525,6 +1457,115 @@ and tripleQuoteString sargs skip = parse if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) else tripleQuoteString sargs skip lexbuf } +and extendedInterpolatedString sargs skip = parse + | '"' '"' '"' + { let (buf, fin, _m, kind, args) = sargs + args.delimLength <- 0 + let cont = LexCont.Token(args.ifdefStack, args.stringNest) + fin.Finish buf kind LexerStringFinisherContext.TripleQuote cont } + + | newline + { let (buf, _fin, m, kind, args) = sargs + newline lexbuf + addUnicodeString buf (lexeme lexbuf) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.delimLength, m)) + else extendedInterpolatedString sargs skip lexbuf } + +// The rest is to break into pieces to allow double-click-on-word and other such things + | ident + | integer + | xinteger + | anywhite + + { let (buf, _fin, m, kind, args) = sargs + addUnicodeString buf (lexeme lexbuf) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.delimLength, m)) + else extendedInterpolatedString sargs skip lexbuf } + + | "%" + + { let (buf, _fin, m, kind, args) = sargs + let numPercents = lexeme lexbuf |> String.length + let result() = + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.delimLength, m)) + else extendedInterpolatedString sargs skip lexbuf + // delimLength is number of $ chars prepended to opening quotes + // If number of consecutive % chars in content is equal to delimLength, + // then that sequence is treated as a format specifier, + // as in $"""%3d{42}""" or (equivalent) $$"""%%3d{{42}}""". + // Any extra % chars up to delimLength, are treated simply as regular string content. + // 2x delimLength or more % chars in a sequence will result in an error. + let maxPercents = 2 * args.delimLength - 1 + if numPercents > maxPercents then + let m2 = lexbuf.LexemeRange + let rest = result() + args.diagnosticsLogger.ErrorR(Error(FSComp.SR.lexTooManyPercentsInTripleQuote(), m2)) + rest + else + // Add two % chars for each % that is supposed to be treated as regular string content + // + 1 for a format specifier. + let percentsToEmit = + if numPercents < args.delimLength then 2 * numPercents + else 2 * (numPercents - args.delimLength) + 1 + let s = String.replicate percentsToEmit "%" + addUnicodeString buf s + result() } + + | "{" + + { let (buf, fin, m, kind, args) = sargs + let numBraces = String.length (lexeme lexbuf) + // We are in an interpolated string with 2+ $ characters before the opening quotes. + // delimLength is total number of leading $s + // which is also the number of '{' needed to open interpolation expression. + let maxBraces = 2 * args.delimLength - 1 + if numBraces > maxBraces then + // 2 * delimLength '{' in a row is dissallowed + let m2 = lexbuf.LexemeRange + args.stringNest <- (1, LexerStringStyle.ExtendedInterpolated, args.delimLength, m2) :: args.stringNest + let cont = LexCont.Token(args.ifdefStack, args.stringNest) + fail args lexbuf + (FSComp.SR.lexTooManyLBracesInTripleQuote()) + (fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont) + elif numBraces < args.delimLength then + // Less than delimLength means we treat '{' as normal content + addUnicodeString buf (lexeme lexbuf) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.delimLength, m)) + else extendedInterpolatedString sargs skip lexbuf + // numBraces in [delimLength; maxBraces) + else + // A sequence of delimLength * '{' starts interpolation expression. + // Any extra '{' are treated as normal string content. + if numBraces > args.delimLength then + String.replicate (numBraces - args.delimLength) "{" |> addUnicodeString buf + // get a new range for where the fill starts + let m2 = lexbuf.LexemeRange + args.stringNest <- (1, LexerStringStyle.ExtendedInterpolated, args.delimLength, m2) :: args.stringNest + let cont = LexCont.Token(args.ifdefStack, args.stringNest) + fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont + } + + | "}" + + { let (buf, _fin, m, kind, args) = sargs + let numBraces = lexeme lexbuf |> String.length + let result() = + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.delimLength, m)) + else extendedInterpolatedString sargs skip lexbuf + if args.delimLength > numBraces then + lexeme lexbuf |> addUnicodeString buf + (result()) + else // numBraces >= args.delimLength + fail args lexbuf (FSComp.SR.lexUnmatchedRBracesInTripleQuote()) (result()) + } + + | eof + { let (_buf, _fin, m, kind, args) = sargs + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.delimLength, m)) } + + | surrogateChar surrogateChar // surrogate code points always come in pairs + | _ + { let (buf, _fin, m, kind, args) = sargs + addUnicodeString buf (lexeme lexbuf) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.delimLength, m)) + else extendedInterpolatedString sargs skip lexbuf } + // Parsing single-line comment - we need to split it into words for Visual Studio IDE and singleLineComment cargs skip = parse | newline diff --git a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs index f29bfcdbe9..5d1489354c 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs @@ -79,10 +79,28 @@ printfn \"%s\" s" Assert.Equal("% 42", $"%%%3d{42}") [] - let ``Percent sign characters in triple quote interpolated strings`` () = - Assert.Equal("%%", $$$"""%%""") - Assert.Equal("42%", $$"""{{42}}%""") - Assert.Equal("% 42", $$"""%%%3d{{42}}""") + let ``Double percent sign characters in triple quote interpolated strings`` () = + Fsx "printfn \"%s\" $$$\"\"\"%%\"\"\"" + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains "%%" + + [] + let ``Percent sign after interpolation hole in triple quote strings`` () = + Fsx "printfn \"%s\" $$\"\"\"{{42}}%\"\"\"" + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains "42%" + + [] + let ``Percent sign before format specifier in triple quote interpolated strings`` () = + Fsx "printfn \"%s\" $$\"\"\"%%%3d{{42}}\"\"\"" + |> withLangVersionPreview + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains "% 42" [] let ``Percent signs separated by format specifier's flags`` () = diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl index 964ddbf6e5..2b40a5eebb 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl @@ -11302,6 +11302,7 @@ FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokeniza FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokenization.FSharpTokenizerColorState Comment FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokenization.FSharpTokenizerColorState EndLineThenSkip FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokenization.FSharpTokenizerColorState EndLineThenToken +FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokenization.FSharpTokenizerColorState ExtendedInterpolatedString FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokenization.FSharpTokenizerColorState IfDefSkip FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokenization.FSharpTokenizerColorState InitialState FSharp.Compiler.Tokenization.FSharpTokenizerColorState: FSharp.Compiler.Tokenization.FSharpTokenizerColorState SingleLineComment From a6826f6e634d0eb1dc1dfdd054283e52076bbc49 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Tue, 18 Apr 2023 17:22:57 +0200 Subject: [PATCH 19/26] Update comments in lex.fsl, refactor slightly --- src/Compiler/lex.fsl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl index 11be21e813..e8efa4fd49 100644 --- a/src/Compiler/lex.fsl +++ b/src/Compiler/lex.fsl @@ -1512,12 +1512,11 @@ and extendedInterpolatedString sargs skip = parse | "{" + { let (buf, fin, m, kind, args) = sargs let numBraces = String.length (lexeme lexbuf) - // We are in an interpolated string with 2+ $ characters before the opening quotes. - // delimLength is total number of leading $s - // which is also the number of '{' needed to open interpolation expression. + // Extended interpolated strings starts with at least 2 $ + // Number of leading $s is the number of '{' needed to open interpolation expression (delimLength) + // 2x delimLength (or more) of '{' in a row would be unambiguous, so it's disallowed let maxBraces = 2 * args.delimLength - 1 if numBraces > maxBraces then - // 2 * delimLength '{' in a row is dissallowed let m2 = lexbuf.LexemeRange args.stringNest <- (1, LexerStringStyle.ExtendedInterpolated, args.delimLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) @@ -1533,8 +1532,9 @@ and extendedInterpolatedString sargs skip = parse else // A sequence of delimLength * '{' starts interpolation expression. // Any extra '{' are treated as normal string content. - if numBraces > args.delimLength then - String.replicate (numBraces - args.delimLength) "{" |> addUnicodeString buf + let extraBraces = numBraces - args.delimLength + if extraBraces > 0 then + String.replicate extraBraces "{" |> addUnicodeString buf // get a new range for where the fill starts let m2 = lexbuf.LexemeRange args.stringNest <- (1, LexerStringStyle.ExtendedInterpolated, args.delimLength, m2) :: args.stringNest From 63390c357c30bb7a0c3e23557c934176a7d2ae25 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Wed, 19 Apr 2023 11:58:02 +0200 Subject: [PATCH 20/26] Put lexer change under feature flag --- src/Compiler/FSComp.txt | 2 ++ src/Compiler/Facilities/LanguageFeatures.fs | 3 +++ src/Compiler/Facilities/LanguageFeatures.fsi | 1 + src/Compiler/lex.fsl | 23 +++++++++++++------- src/Compiler/xlf/FSComp.txt.cs.xlf | 10 +++++++++ src/Compiler/xlf/FSComp.txt.de.xlf | 10 +++++++++ src/Compiler/xlf/FSComp.txt.es.xlf | 10 +++++++++ src/Compiler/xlf/FSComp.txt.fr.xlf | 10 +++++++++ src/Compiler/xlf/FSComp.txt.it.xlf | 10 +++++++++ src/Compiler/xlf/FSComp.txt.ja.xlf | 10 +++++++++ src/Compiler/xlf/FSComp.txt.ko.xlf | 10 +++++++++ src/Compiler/xlf/FSComp.txt.pl.xlf | 10 +++++++++ src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 10 +++++++++ src/Compiler/xlf/FSComp.txt.ru.xlf | 10 +++++++++ src/Compiler/xlf/FSComp.txt.tr.xlf | 10 +++++++++ src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 10 +++++++++ src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 10 +++++++++ 17 files changed, 151 insertions(+), 8 deletions(-) diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 8acdad3822..a9bc95b74e 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1125,6 +1125,7 @@ lexIfOCaml,"IF-FSHARP/IF-CAML regions are no longer supported" 1248,lexTooManyLBracesInTripleQuote,"The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive opening braces as content." 1249,lexUnmatchedRBracesInTripleQuote,"The interpolated string contains unmatched closing braces." 1250,lexTooManyPercentsInTripleQuote,"The interpolated triple quoted string literal does not start with enough '$' characters to allow this many consecutive '%%' characters." +1251,lexExtendedStringInterpolationNotSupported,"Extended string interpolation is not supported in this version of F#." # reshapedmsbuild.fs 1300,toolLocationHelperUnsupportedFrameworkVersion,"The specified .NET Framework version '%s' is not supported. Please specify a value from the enumeration Microsoft.Build.Utilities.TargetDotNetFrameworkVersion." # ----------------------------------------------------------------------------- @@ -1570,6 +1571,7 @@ featureWarningWhenCopyAndUpdateRecordChangesAllFields,"Raises warnings when an c featureStaticMembersInInterfaces,"Static members in interfaces" featureNonInlineLiteralsAsPrintfFormat,"String values marked as literals and IL constants as printf format" featureNestedCopyAndUpdate,"Nested record field copy-and-update" +featureExtendedStringInterpolation,"Extended string interpolation similar to C# raw strings." 3353,fsiInvalidDirective,"Invalid directive '#%s %s'" 3354,tcNotAFunctionButIndexerNamedIndexingNotYetEnabled,"This value supports indexing, e.g. '%s.[index]'. The syntax '%s[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation." 3354,tcNotAFunctionButIndexerIndexingNotYetEnabled,"This expression supports indexing, e.g. 'expr.[index]'. The syntax 'expr[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation." diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 3e4b2465fc..f46b0dbeee 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -66,6 +66,7 @@ type LanguageFeature = | StaticMembersInInterfaces | NonInlineLiteralsAsPrintfFormat | NestedCopyAndUpdate + | ExtendedStringInterpolation /// LanguageVersion management type LanguageVersion(versionText) = @@ -155,6 +156,7 @@ type LanguageVersion(versionText) = LanguageFeature.StaticMembersInInterfaces, previewVersion LanguageFeature.NonInlineLiteralsAsPrintfFormat, previewVersion LanguageFeature.NestedCopyAndUpdate, previewVersion + LanguageFeature.ExtendedStringInterpolation, previewVersion ] @@ -276,6 +278,7 @@ type LanguageVersion(versionText) = | LanguageFeature.StaticMembersInInterfaces -> FSComp.SR.featureStaticMembersInInterfaces () | LanguageFeature.NonInlineLiteralsAsPrintfFormat -> FSComp.SR.featureNonInlineLiteralsAsPrintfFormat () | LanguageFeature.NestedCopyAndUpdate -> FSComp.SR.featureNestedCopyAndUpdate () + | LanguageFeature.ExtendedStringInterpolation -> FSComp.SR.featureExtendedStringInterpolation () /// Get a version string associated with the given feature. static member GetFeatureVersionString feature = diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index c5c407e80b..d12c77fdcb 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -56,6 +56,7 @@ type LanguageFeature = | StaticMembersInInterfaces | NonInlineLiteralsAsPrintfFormat | NestedCopyAndUpdate + | ExtendedStringInterpolation /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl index e8efa4fd49..2d45b0f9b1 100644 --- a/src/Compiler/lex.fsl +++ b/src/Compiler/lex.fsl @@ -623,14 +623,21 @@ rule token args skip = parse | ('$'+) '"' '"' '"' { let buf, fin, m = startString args lexbuf - // Single quote in triple quote ok, others disallowed - match args.stringNest with - | _ :: _ -> errorR(Error(FSComp.SR.lexTripleQuoteInTripleQuote(), m)) - | [] -> () - - args.delimLength <- lexeme lexbuf |> Seq.takeWhile (fun c -> c = '$') |> Seq.length - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, LexerStringKind.InterpolatedStringFirst, args.delimLength, m)) - else extendedInterpolatedString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } + if lexbuf.SupportsFeature LanguageFeature.ExtendedStringInterpolation then + // Single quote in triple quote ok, others disallowed + match args.stringNest with + | _ :: _ -> errorR(Error(FSComp.SR.lexTripleQuoteInTripleQuote(), m)) + | [] -> () + + args.delimLength <- lexeme lexbuf |> Seq.takeWhile (fun c -> c = '$') |> Seq.length + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, LexerStringKind.InterpolatedStringFirst, args.delimLength, m)) + else extendedInterpolatedString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf + else + let result() = + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.InterpolatedStringFirst, args.delimLength, m)) + else tripleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf + fail args lexbuf (FSComp.SR.lexExtendedStringInterpolationNotSupported()) (result()) + } | '$' '"' { let buf,fin,m = startString args lexbuf diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 1032649574..023a1f637a 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -252,6 +252,11 @@ více typů podporuje měrné jednotky + + Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw strings. + + fixed-index slice 3d/4d řez 3d/4d s pevným indexem @@ -522,6 +527,11 @@ Bajtový řetězec se nedá interpolovat. + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported Oblasti IF-FSHARP/IF-CAML už nejsou podporovány. diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 9a2d9f5964..657ef6cc05 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -252,6 +252,11 @@ Maßeinheitenunterstützung durch weitere Typen + + Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw strings. + + fixed-index slice 3d/4d Segment 3D/4D mit feststehendem Index @@ -522,6 +527,11 @@ Eine Bytezeichenfolge darf nicht interpoliert werden. + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported IF-FSHARP-/IF-CAML-Regionen werden nicht mehr unterstützt diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 7b3401c341..d282062aad 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -252,6 +252,11 @@ más tipos admiten las unidades de medida + + Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw strings. + + fixed-index slice 3d/4d segmento de índice fijo 3d/4d @@ -522,6 +527,11 @@ no se puede interpolar una cadena de bytes + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported Ya no se admiten las regiones IF-FSHARP/IF-CAML diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index c84b06fcc8..9f94242373 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -252,6 +252,11 @@ d'autres types prennent en charge les unités de mesure + + Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw strings. + + fixed-index slice 3d/4d section à index fixe 3D/4D @@ -522,6 +527,11 @@ une chaîne d'octets ne peut pas être interpolée + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported Les régions IF-FSHARP/IF-CAML ne sont plus prises en charge. diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 6540be7c08..72c7d6a158 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -252,6 +252,11 @@ più tipi supportano le unità di misura + + Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw strings. + + fixed-index slice 3d/4d sezione a indice fisso 3D/4D @@ -522,6 +527,11 @@ non è possibile interpolare una stringa di byte + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported Le aree IF-FSHARP/IF-CAML non sono più supportate diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index cba4119e81..bf95ea6e93 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -252,6 +252,11 @@ 単位をサポートするその他の型 + + Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw strings. + + fixed-index slice 3d/4d 固定インデックス スライス 3d/4d @@ -522,6 +527,11 @@ バイト文字列は補間されていない可能性があります + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported IF-FSHARP/IF-CAML リージョンは現在サポートされていません diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 29ee74a759..929e460109 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -252,6 +252,11 @@ 더 많은 형식이 측정 단위를 지원함 + + Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw strings. + + fixed-index slice 3d/4d 고정 인덱스 슬라이스 3d/4d @@ -522,6 +527,11 @@ 바이트 문자열을 보간하지 못할 수 있습니다. + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported IF-FSHARP/IF-CAML 영역은 더 이상 지원되지 않습니다. diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index adee72a150..22d00d2d44 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -252,6 +252,11 @@ więcej typów obsługuje jednostki miary + + Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw strings. + + fixed-index slice 3d/4d część o stałym indeksie 3d/4d @@ -522,6 +527,11 @@ ciąg bajtowy nie może być interpolowany + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported Regiony IF-FSHARP/IF-CAML nie są już obsługiwane diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 6d01558c4f..8bd70a821f 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -252,6 +252,11 @@ mais tipos dão suporte para unidades de medida + + Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw strings. + + fixed-index slice 3d/4d fatia de índice fixo 3d/4d @@ -522,6 +527,11 @@ uma cadeia de caracteres de byte não pode ser interpolada + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported As regiões IF-FSHARP/IF-CAML não são mais suportadas diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 6c06bb91f5..dc2358c163 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -252,6 +252,11 @@ другие типы поддерживают единицы измерения + + Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw strings. + + fixed-index slice 3d/4d срез с фиксированным индексом 3d/4d @@ -522,6 +527,11 @@ невозможно выполнить интерполяцию для строки байтов + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported Регионы IF-FSHARP/IF-CAML больше не поддерживаются diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 57aca3025d..a1879ae52d 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -252,6 +252,11 @@ tür daha ölçü birimlerini destekler + + Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw strings. + + fixed-index slice 3d/4d sabit dizinli dilim 3d/4d @@ -522,6 +527,11 @@ bir bayt dizesi, düz metin arasına kod eklenerek kullanılamaz + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported IF-FSHARP/IF-CAML bölgeleri artık desteklenmiyor diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 26d35e5caa..28a707c6b0 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -252,6 +252,11 @@ 更多类型支持度量单位 + + Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw strings. + + fixed-index slice 3d/4d 固定索引切片 3d/4d @@ -522,6 +527,11 @@ 不能内插字节字符串 + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported 不再支持 IF-FSHARP/IF-CAML 区域 diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index f853f1477f..abb3c6319b 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -252,6 +252,11 @@ 更多支援測量單位的類型 + + Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw strings. + + fixed-index slice 3d/4d 固定索引切割 3d/4d @@ -522,6 +527,11 @@ 位元組字串不能是插補字串 + + Extended string interpolation is not supported in this version of F#. + Extended string interpolation is not supported in this version of F#. + + IF-FSHARP/IF-CAML regions are no longer supported 不再支援 IF-FSHARP/IF-CAML 區域 From 1bef0cda2c7f0c826d44455cb3004cbec73cdaa7 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Fri, 21 Apr 2023 16:19:07 +0200 Subject: [PATCH 21/26] Pass lang version to FSharpSourceTokenizer --- src/Compiler/Service/ServiceLexing.fs | 8 +++- src/Compiler/Service/ServiceLexing.fsi | 3 +- src/Compiler/Service/service.fs | 2 +- ...vice.SurfaceArea.netstandard20.release.bsl | 2 +- tests/service/SyntaxTreeTests.fs | 3 +- tests/service/TokenizerTests.fs | 2 +- .../BraceCompletionSessionProvider.fs | 1 + .../Classification/ClassificationService.fs | 3 +- .../CodeFix/AddMissingFunKeyword.fs | 5 ++- .../AddMissingRecToMutuallyRecFunctions.fs | 7 +++- .../CodeFix/AddOpenCodeFixProvider.fs | 5 ++- .../ImplementInterfaceCodeFixProvider.fs | 3 ++ .../Commands/HelpContextService.fs | 12 +++++- .../Completion/CompletionProvider.fs | 21 ++++++---- .../Completion/CompletionService.fs | 7 +++- .../Completion/CompletionUtils.fs | 23 +++++++++-- .../HashDirectiveCompletionProvider.fs | 16 +++++++- .../FSharp.Editor/Completion/SignatureHelp.fs | 8 +++- .../Debugging/LanguageDebugInfoService.fs | 12 +++++- .../Formatting/EditorFormattingService.fs | 10 ++++- .../Formatting/IndentationService.fs | 10 ++++- .../FSharpProjectOptionsManager.fs | 6 +-- .../LanguageService/SymbolHelpers.fs | 3 +- .../LanguageService/Tokenizer.fs | 13 +++--- .../LanguageService/WorkspaceExtensions.fs | 19 +++++++-- .../FSharp.Editor/TaskList/TaskListService.fs | 13 +++--- .../ProjectSitesAndFiles.fs | 12 +++--- .../BraceMatchingServiceTests.fs | 22 +++++++--- .../CompletionProviderTests.fs | 41 ++++++++----------- .../GoToDefinitionServiceTests.fs | 12 +++++- .../HelpContextServiceTests.fs | 10 ++++- .../LanguageDebugInfoServiceTests.fs | 1 + .../SignatureHelpProviderTests.fs | 2 + .../SyntacticColorizationServiceTests.fs | 24 ++++++++--- .../TaskListServiceTests.fs | 5 ++- .../Salsa/FSharpLanguageServiceTestable.fs | 2 +- .../Tests.LanguageService.General.fs | 2 +- 37 files changed, 249 insertions(+), 101 deletions(-) diff --git a/src/Compiler/Service/ServiceLexing.fs b/src/Compiler/Service/ServiceLexing.fs index e5bc9ad6b2..e684b7f702 100644 --- a/src/Compiler/Service/ServiceLexing.fs +++ b/src/Compiler/Service/ServiceLexing.fs @@ -1218,9 +1218,13 @@ type FSharpLineTokenizer(lexbuf: UnicodeLexing.Lexbuf, maxLength: int option, fi } [] -type FSharpSourceTokenizer(conditionalDefines: string list, fileName: string option) = +type FSharpSourceTokenizer(conditionalDefines: string list, fileName: string option, langVersion: string option) = + + let langVersion = + langVersion + |> Option.map LanguageVersion + |> Option.defaultValue LanguageVersion.Default - let langVersion = LanguageVersion.Default let reportLibraryOnlyFeatures = true let lexResourceManager = LexResourceManager() diff --git a/src/Compiler/Service/ServiceLexing.fsi b/src/Compiler/Service/ServiceLexing.fsi index 39b2febf31..5abca96922 100755 --- a/src/Compiler/Service/ServiceLexing.fsi +++ b/src/Compiler/Service/ServiceLexing.fsi @@ -6,6 +6,7 @@ open System open System.Threading open FSharp.Compiler open FSharp.Compiler.Text +open FSharp.Compiler.Features #nowarn "57" @@ -325,7 +326,7 @@ type FSharpLineTokenizer = type FSharpSourceTokenizer = /// Create a tokenizer for a source file. - new: conditionalDefines: string list * fileName: string option -> FSharpSourceTokenizer + new: conditionalDefines: string list * fileName: string option * langVersion: string option -> FSharpSourceTokenizer /// Create a tokenizer for a line of this source file member CreateLineTokenizer: lineText: string -> FSharpLineTokenizer diff --git a/src/Compiler/Service/service.fs b/src/Compiler/Service/service.fs index cf5487e25d..2cfd7a53c6 100644 --- a/src/Compiler/Service/service.fs +++ b/src/Compiler/Service/service.fs @@ -1699,7 +1699,7 @@ type FSharpChecker /// Tokenize a single line, returning token information and a tokenization state represented by an integer member _.TokenizeLine(line: string, state: FSharpTokenizerLexState) = - let tokenizer = FSharpSourceTokenizer([], None) + let tokenizer = FSharpSourceTokenizer([], None, None) let lineTokenizer = tokenizer.CreateLineTokenizer line let mutable state = (None, state) diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl index 2b40a5eebb..95b6226047 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl @@ -10133,7 +10133,7 @@ FSharp.Compiler.Tokenization.FSharpLineTokenizer: FSharp.Compiler.Tokenization.F FSharp.Compiler.Tokenization.FSharpLineTokenizer: System.Tuple`2[Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Tokenization.FSharpTokenInfo],FSharp.Compiler.Tokenization.FSharpTokenizerLexState] ScanToken(FSharp.Compiler.Tokenization.FSharpTokenizerLexState) FSharp.Compiler.Tokenization.FSharpSourceTokenizer: FSharp.Compiler.Tokenization.FSharpLineTokenizer CreateBufferTokenizer(Microsoft.FSharp.Core.FSharpFunc`2[System.Tuple`3[System.Char[],System.Int32,System.Int32],System.Int32]) FSharp.Compiler.Tokenization.FSharpSourceTokenizer: FSharp.Compiler.Tokenization.FSharpLineTokenizer CreateLineTokenizer(System.String) -FSharp.Compiler.Tokenization.FSharpSourceTokenizer: Void .ctor(Microsoft.FSharp.Collections.FSharpList`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.String]) +FSharp.Compiler.Tokenization.FSharpSourceTokenizer: Void .ctor(Microsoft.FSharp.Collections.FSharpList`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.String], Microsoft.FSharp.Core.FSharpOption`1[System.String]) FSharp.Compiler.Tokenization.FSharpToken: Boolean IsCommentTrivia FSharp.Compiler.Tokenization.FSharpToken: Boolean IsIdentifier FSharp.Compiler.Tokenization.FSharpToken: Boolean IsKeyword diff --git a/tests/service/SyntaxTreeTests.fs b/tests/service/SyntaxTreeTests.fs index 2650ad1720..fdd82ce56a 100644 --- a/tests/service/SyntaxTreeTests.fs +++ b/tests/service/SyntaxTreeTests.fs @@ -133,7 +133,8 @@ let parseSourceCode (name: string, code: string) = SourceText.ofString code, { FSharpParsingOptions.Default with SourceFiles = [| location |] - IsExe = true } + IsExe = true + LangVersionText = "preview" } ) |> Async.RunImmediate diff --git a/tests/service/TokenizerTests.fs b/tests/service/TokenizerTests.fs index 8b0fc2c899..7be5312b32 100644 --- a/tests/service/TokenizerTests.fs +++ b/tests/service/TokenizerTests.fs @@ -12,7 +12,7 @@ open FSharp.Compiler.Tokenization open NUnit.Framework -let sourceTok = FSharpSourceTokenizer([], Some "C:\\test.fsx") +let sourceTok = FSharpSourceTokenizer([], Some "C:\\test.fsx", None) let rec parseLine(line: string, state: FSharpTokenizerLexState ref, tokenizer: FSharpLineTokenizer) = seq { match tokenizer.ScanToken(state.Value) with diff --git a/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs b/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs index bed47fcb67..9f00d6a0b0 100644 --- a/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs +++ b/vsintegration/src/FSharp.Editor/AutomaticCompletion/BraceCompletionSessionProvider.fs @@ -506,6 +506,7 @@ type EditorBraceCompletionSessionFactory() = TextSpan(position - 1, 1), Some(document.FilePath), [], + None, cancellationToken ) diff --git a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs index be79f40569..43673355ab 100644 --- a/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs +++ b/vsintegration/src/FSharp.Editor/Classification/ClassificationService.fs @@ -177,7 +177,7 @@ type internal FSharpClassificationService [] () = async { use _logBlock = Logger.LogBlock(LogEditorFunctionId.Classification_Syntactic) - let defines = document.GetFSharpQuickDefines() + let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask // For closed documents, only get classification for the text within the span. @@ -193,6 +193,7 @@ type internal FSharpClassificationService [] () = textSpan, Some(document.FilePath), defines, + Some langVersion, cancellationToken ) ) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs index 3f337cfbee..5c07cb6e79 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingFunKeyword.fs @@ -28,8 +28,8 @@ type internal FSharpAddMissingFunKeywordCodeFixProvider [] // Only trigger when failing to parse `->`, which arises when `fun` is missing do! Option.guard (textOfError = "->") - let! defines = - document.GetFSharpCompilationDefinesAsync(nameof (FSharpAddMissingFunKeywordCodeFixProvider)) + let! defines, langVersion = + document.GetFSharpCompilationDefinesAndLangVersionAsync(nameof (FSharpAddMissingFunKeywordCodeFixProvider)) |> liftAsync let adjustedPosition = @@ -51,6 +51,7 @@ type internal FSharpAddMissingFunKeywordCodeFixProvider [] SymbolLookupKind.Greedy, false, false, + Some langVersion, context.CancellationToken ) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs index 2739fde263..ec9c7dad66 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddMissingRecToMutuallyRecFunctions.fs @@ -26,8 +26,10 @@ type internal FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider [ liftAsync let! sourceText = context.Document.GetTextAsync(context.CancellationToken) @@ -51,6 +53,7 @@ type internal FSharpAddMissingRecToMutuallyRecFunctionsCodeFixProvider [] (assemblyCon let line = sourceText.Lines.GetLineFromPosition(context.Span.End) let linePos = sourceText.Lines.GetLinePosition(context.Span.End) - let! defines = - document.GetFSharpCompilationDefinesAsync(nameof (FSharpAddOpenCodeFixProvider)) + let! defines, langVersion = + document.GetFSharpCompilationDefinesAndLangVersionAsync(nameof (FSharpAddOpenCodeFixProvider)) |> liftAsync let! symbol = @@ -95,6 +95,7 @@ type internal FSharpAddOpenCodeFixProvider [] (assemblyCon SymbolLookupKind.Greedy, false, false, + Some langVersion, context.CancellationToken ) diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs index f0d23c45ae..d0630bbba0 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ImplementInterfaceCodeFixProvider.fs @@ -202,6 +202,7 @@ type internal FSharpImplementInterfaceCodeFixProvider [] ( |> liftAsync let defines = CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions + let langVersionOpt = Some parsingOptions.LangVersionText // Notice that context.Span doesn't return reliable ranges to find tokens at exact positions. // That's why we tokenize the line and try to find the last successive identifier token let tokens = @@ -211,6 +212,7 @@ type internal FSharpImplementInterfaceCodeFixProvider [] ( context.Span.Start, context.Document.FilePath, defines, + langVersionOpt, context.CancellationToken ) @@ -249,6 +251,7 @@ type internal FSharpImplementInterfaceCodeFixProvider [] ( SymbolLookupKind.Greedy, false, false, + langVersionOpt, context.CancellationToken ) diff --git a/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs b/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs index 2bbec8d2c4..88f5e5ae66 100644 --- a/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs +++ b/vsintegration/src/FSharp.Editor/Commands/HelpContextService.fs @@ -105,11 +105,19 @@ type internal FSharpHelpContextService [] () = member this.GetHelpTermAsync(document, textSpan, cancellationToken) = asyncMaybe { let! sourceText = document.GetTextAsync(cancellationToken) - let defines = document.GetFSharpQuickDefines() + let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() let textLine = sourceText.Lines.GetLineFromPosition(textSpan.Start) let classifiedSpans = - Tokenizer.getClassifiedSpans (document.Id, sourceText, textLine.Span, Some document.Name, defines, cancellationToken) + Tokenizer.getClassifiedSpans ( + document.Id, + sourceText, + textLine.Span, + Some document.Name, + defines, + Some langVersion, + cancellationToken + ) return! FSharpHelpContextService.GetHelpTerm(document, textSpan, classifiedSpans) } diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index 935a18b3bb..963997eb80 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -102,7 +102,7 @@ type internal FSharpCompletionProvider sourceText: SourceText, caretPosition: int, trigger: CompletionTriggerKind, - getInfo: (unit -> DocumentId * string * string list), + getInfo: (unit -> DocumentId * string * string list * string option), intelliSenseOptions: IntelliSenseOptions ) = if caretPosition = 0 then @@ -127,9 +127,9 @@ type internal FSharpCompletionProvider then false else - let documentId, filePath, defines = getInfo () + let documentId, filePath, defines, langVersion = getInfo () - CompletionUtils.shouldProvideCompletion (documentId, filePath, defines, sourceText, triggerPosition) + CompletionUtils.shouldProvideCompletion (documentId, filePath, defines, langVersion, sourceText, triggerPosition) && (triggerChar = '.' || (intelliSenseOptions.ShowAfterCharIsTyped && CompletionUtils.isStartingNewWord (sourceText, triggerPosition))) @@ -287,8 +287,8 @@ type internal FSharpCompletionProvider let getInfo () = let documentId = workspace.GetDocumentIdInCurrentContext(sourceText.Container) let document = workspace.CurrentSolution.GetDocument(documentId) - let defines = document.GetFSharpQuickDefines() - (documentId, document.FilePath, defines) + let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() + (documentId, document.FilePath, defines, Some langVersion) FSharpCompletionProvider.ShouldTriggerCompletionAux(sourceText, caretPosition, trigger.Kind, getInfo, settings.IntelliSense) @@ -299,11 +299,18 @@ type internal FSharpCompletionProvider let document = context.Document let! sourceText = context.Document.GetTextAsync(context.CancellationToken) - let defines = document.GetFSharpQuickDefines() + let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() do! Option.guard ( - CompletionUtils.shouldProvideCompletion (document.Id, document.FilePath, defines, sourceText, context.Position) + CompletionUtils.shouldProvideCompletion ( + document.Id, + document.FilePath, + defines, + Some langVersion, + sourceText, + context.Position + ) ) let getAllSymbols (fileCheckResults: FSharpCheckFileResults) = diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs index bcb4e3ea94..4311a1e552 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionService.fs @@ -54,8 +54,11 @@ type internal FSharpCompletionService override _.GetDefaultCompletionListSpan(sourceText, caretIndex) = let documentId = workspace.GetDocumentIdInCurrentContext(sourceText.Container) let document = workspace.CurrentSolution.GetDocument(documentId) - let defines = projectInfoManager.GetCompilationDefinesForEditingDocument(document) - CompletionUtils.getDefaultCompletionListSpan (sourceText, caretIndex, documentId, document.FilePath, defines) + + let defines, langVersion = + projectInfoManager.GetCompilationDefinesAndLangVersionForEditingDocument(document) + + CompletionUtils.getDefaultCompletionListSpan (sourceText, caretIndex, documentId, document.FilePath, defines, Some langVersion) [] [, FSharpConstants.FSharpLanguageName)>] diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs index 0257d2425b..c261cea6cc 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs @@ -95,6 +95,7 @@ module internal CompletionUtils = documentId: DocumentId, filePath: string, defines: string list, + langVersion: string option, sourceText: SourceText, triggerPosition: int ) : bool = @@ -102,7 +103,15 @@ module internal CompletionUtils = let triggerLine = textLines.GetLineFromPosition triggerPosition let classifiedSpans = - Tokenizer.getClassifiedSpans (documentId, sourceText, triggerLine.Span, Some filePath, defines, CancellationToken.None) + Tokenizer.getClassifiedSpans ( + documentId, + sourceText, + triggerLine.Span, + Some filePath, + defines, + langVersion, + CancellationToken.None + ) classifiedSpans.Count = 0 || // we should provide completion at the start of empty line, where there are no tokens at all @@ -131,7 +140,7 @@ module internal CompletionUtils = | CompletionItemKind.Method(isExtension = true) -> 7 /// Indicates the text span to be replaced by a committed completion list item. - let getDefaultCompletionListSpan (sourceText: SourceText, caretIndex, documentId, filePath, defines) = + let getDefaultCompletionListSpan (sourceText: SourceText, caretIndex, documentId, filePath, defines, langVersion) = // Gets connected identifier-part characters backward and forward from caret. let getIdentifierChars () = @@ -167,7 +176,15 @@ module internal CompletionUtils = // the majority of common cases. let classifiedSpans = - Tokenizer.getClassifiedSpans (documentId, sourceText, line.Span, Some filePath, defines, CancellationToken.None) + Tokenizer.getClassifiedSpans ( + documentId, + sourceText, + line.Span, + Some filePath, + defines, + langVersion, + CancellationToken.None + ) let isBacktickIdentifier (classifiedSpan: ClassifiedSpan) = classifiedSpan.ClassificationType = ClassificationTypeNames.Identifier diff --git a/vsintegration/src/FSharp.Editor/Completion/HashDirectiveCompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/HashDirectiveCompletionProvider.fs index 417daf2acc..539df9b6a7 100644 --- a/vsintegration/src/FSharp.Editor/Completion/HashDirectiveCompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/HashDirectiveCompletionProvider.fs @@ -67,10 +67,22 @@ type internal HashDirectiveCompletionProvider let getClassifiedSpans (text: SourceText, position: int) : ResizeArray = let documentId = workspace.GetDocumentIdInCurrentContext(text.Container) let document = workspace.CurrentSolution.GetDocument(documentId) - let defines = projectInfoManager.GetCompilationDefinesForEditingDocument(document) + + let defines, langVersion = + projectInfoManager.GetCompilationDefinesAndLangVersionForEditingDocument(document) + let textLines = text.Lines let triggerLine = textLines.GetLineFromPosition(position) - Tokenizer.getClassifiedSpans (documentId, text, triggerLine.Span, Some document.FilePath, defines, CancellationToken.None) + + Tokenizer.getClassifiedSpans ( + documentId, + text, + triggerLine.Span, + Some document.FilePath, + defines, + Some langVersion, + CancellationToken.None + ) let isInStringLiteral (text: SourceText, position: int) : bool = getClassifiedSpans (text, position) diff --git a/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs b/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs index f08aaa568f..c8e408d432 100644 --- a/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs +++ b/vsintegration/src/FSharp.Editor/Completion/SignatureHelp.fs @@ -284,6 +284,7 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi checkFileResults: FSharpCheckFileResults, documentId: DocumentId, defines: string list, + langVersion: string option, documentationBuilder: IDocumentationBuilder, sourceText: SourceText, caretPosition: int, @@ -320,6 +321,7 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi SymbolLookupKind.Greedy, false, false, + langVersion, ct ) @@ -595,6 +597,7 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi ( document: Document, defines: string list, + langVersion: string option, documentationBuilder: IDocumentationBuilder, caretPosition: int, triggerTypedChar: char option, @@ -636,6 +639,7 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi checkFileResults, document.Id, defines, + langVersion, documentationBuilder, sourceText, caretPosition, @@ -653,6 +657,7 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi checkFileResults, document.Id, defines, + langVersion, documentationBuilder, sourceText, caretPosition, @@ -683,7 +688,7 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi member _.GetItemsAsync(document, position, triggerInfo, cancellationToken) = asyncMaybe { - let defines = document.GetFSharpQuickDefines() + let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() let triggerTypedChar = if @@ -700,6 +705,7 @@ type internal FSharpSignatureHelpProvider [] (serviceProvi FSharpSignatureHelpProvider.ProvideSignatureHelp( document, defines, + Some langVersion, documentationBuilder, position, triggerTypedChar, diff --git a/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs b/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs index 919be73c7f..30a3d8a148 100644 --- a/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs +++ b/vsintegration/src/FSharp.Editor/Debugging/LanguageDebugInfoService.fs @@ -55,13 +55,21 @@ type internal FSharpLanguageDebugInfoService [] () = cancellationToken: CancellationToken ) : Task = async { - let defines = document.GetFSharpQuickDefines() + let defines, langVersion = document.GetFSharpQuickDefinesAndLangVersion() let! cancellationToken = Async.CancellationToken let! sourceText = document.GetTextAsync(cancellationToken) |> Async.AwaitTask let textSpan = TextSpan.FromBounds(0, sourceText.Length) let classifiedSpans = - Tokenizer.getClassifiedSpans (document.Id, sourceText, textSpan, Some(document.Name), defines, cancellationToken) + Tokenizer.getClassifiedSpans ( + document.Id, + sourceText, + textSpan, + Some(document.Name), + defines, + Some langVersion, + cancellationToken + ) let result = match FSharpLanguageDebugInfoService.GetDataTipInformation(sourceText, position, classifiedSpans) with diff --git a/vsintegration/src/FSharp.Editor/Formatting/EditorFormattingService.fs b/vsintegration/src/FSharp.Editor/Formatting/EditorFormattingService.fs index baa5b0a1f0..cb7e2c5a0a 100644 --- a/vsintegration/src/FSharp.Editor/Formatting/EditorFormattingService.fs +++ b/vsintegration/src/FSharp.Editor/Formatting/EditorFormattingService.fs @@ -50,7 +50,15 @@ type internal FSharpEditorFormattingService [] (settings: let defines = CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions let tokens = - Tokenizer.tokenizeLine (documentId, sourceText, line.Start, filePath, defines, cancellationToken) + Tokenizer.tokenizeLine ( + documentId, + sourceText, + line.Start, + filePath, + defines, + Some parsingOptions.LangVersionText, + cancellationToken + ) let! firstMeaningfulToken = tokens diff --git a/vsintegration/src/FSharp.Editor/Formatting/IndentationService.fs b/vsintegration/src/FSharp.Editor/Formatting/IndentationService.fs index 1e5b5110d0..c2897774a7 100644 --- a/vsintegration/src/FSharp.Editor/Formatting/IndentationService.fs +++ b/vsintegration/src/FSharp.Editor/Formatting/IndentationService.fs @@ -34,7 +34,15 @@ type internal FSharpIndentationService [] () = let defines = CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions let tokens = - Tokenizer.tokenizeLine (documentId, sourceText, position, filePath, defines, CancellationToken.None) + Tokenizer.tokenizeLine ( + documentId, + sourceText, + position, + filePath, + defines, + Some parsingOptions.LangVersionText, + CancellationToken.None + ) tokens |> Array.rev diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs index 0cf3c14676..fa4cef1bd4 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs @@ -526,10 +526,10 @@ type internal FSharpProjectOptionsManager(checker: FSharpChecker, workspace: Wor member _.ClearSingleFileOptionsCache(documentId) = reactor.ClearSingleFileOptionsCache(documentId) - /// Get compilation defines relevant for syntax processing. + /// Get compilation defines and language version relevant for syntax processing. /// Quicker then TryGetOptionsForDocumentOrProject as it doesn't need to recompute the exact project /// options for a script. - member _.GetCompilationDefinesForEditingDocument(document: Document) = + member _.GetCompilationDefinesAndLangVersionForEditingDocument(document: Document) = let parsingOptions = match reactor.TryGetCachedOptionsByProjectId(document.Project.Id) with | Some (_, parsingOptions, _) -> parsingOptions @@ -539,7 +539,7 @@ type internal FSharpProjectOptionsManager(checker: FSharpChecker, workspace: Wor IsInteractive = CompilerEnvironment.IsScriptFile document.Name } - CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions + CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions, parsingOptions.LangVersionText member _.TryGetOptionsByProject(project) = reactor.TryGetOptionsByProjectAsync(project) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs index a96a5979ba..1fcf662bdc 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs @@ -22,7 +22,7 @@ module internal SymbolHelpers = asyncMaybe { let userOpName = "getSymbolUsesOfSymbolAtLocationInDocument" let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync - let! defines = document.GetFSharpCompilationDefinesAsync(userOpName) |> liftAsync + let! defines, langVersion = document.GetFSharpCompilationDefinesAndLangVersionAsync(userOpName) |> liftAsync let! cancellationToken = Async.CancellationToken |> liftAsync let! sourceText = document.GetTextAsync(cancellationToken) @@ -40,6 +40,7 @@ module internal SymbolHelpers = SymbolLookupKind.Greedy, false, false, + Some langVersion, cancellationToken ) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs b/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs index 0f173a29e3..00c8554029 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/Tokenizer.fs @@ -695,12 +695,13 @@ module internal Tokenizer = textSpan: TextSpan, fileName: string option, defines: string list, + langVersion, cancellationToken: CancellationToken ) : ResizeArray = let result = new ResizeArray() try - let sourceTokenizer = FSharpSourceTokenizer(defines, fileName) + let sourceTokenizer = FSharpSourceTokenizer(defines, fileName, langVersion) let lines = sourceText.Lines let sourceTextData = getSourceTextData (documentKey, defines, lines.Count) @@ -896,10 +897,11 @@ module internal Tokenizer = position: int, fileName: string, defines: string list, + langVersion, cancellationToken ) = let textLinePos = sourceText.Lines.GetLinePosition(position) - let sourceTokenizer = FSharpSourceTokenizer(defines, Some fileName) + let sourceTokenizer = FSharpSourceTokenizer(defines, Some fileName, langVersion) // We keep incremental data per-document. When text changes we correlate text line-by-line (by hash codes of lines) let sourceTextData = getSourceTextData (documentKey, defines, sourceText.Lines.Count) @@ -912,10 +914,10 @@ module internal Tokenizer = lineData, textLinePos, contents - let tokenizeLine (documentKey, sourceText, position, fileName, defines, cancellationToken) = + let tokenizeLine (documentKey, sourceText, position, fileName, defines, langVersion, cancellationToken) = try let lineData, _, _ = - getCachedSourceLineData (documentKey, sourceText, position, fileName, defines, cancellationToken) + getCachedSourceLineData (documentKey, sourceText, position, fileName, defines, langVersion, cancellationToken) lineData.SavedTokens with ex -> @@ -932,12 +934,13 @@ module internal Tokenizer = lookupKind: SymbolLookupKind, wholeActivePatterns: bool, allowStringToken: bool, + langVersion, cancellationToken ) : LexerSymbol option = try let lineData, textLinePos, lineContents = - getCachedSourceLineData (documentKey, sourceText, position, fileName, defines, cancellationToken) + getCachedSourceLineData (documentKey, sourceText, position, fileName, defines, langVersion, cancellationToken) getSymbolFromSavedTokens ( fileName, diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index 3538e8bbd6..ebc208baae 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -168,6 +168,13 @@ type Document with return CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions } + /// Get the compilation defines and language version from F# project that is associated with the given F# document. + member this.GetFSharpCompilationDefinesAndLangVersionAsync(userOpName) = + async { + let! _, _, parsingOptions, _ = this.GetFSharpCompilationOptionsAsync(userOpName) + return CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions, parsingOptions.LangVersionText + } + /// Get the instance of the FSharpChecker from the workspace by the given F# document. member this.GetFSharpChecker() = let workspaceService = this.Project.Solution.GetFSharpWorkspaceService() @@ -184,11 +191,16 @@ type Document with let workspaceService = this.Project.Solution.GetFSharpWorkspaceService() workspaceService.FSharpProjectOptionsManager.TryGetQuickParsingOptionsForEditingDocumentOrProject(this.Id, this.FilePath) + /// A non-async call that quickly gets the defines and F# language version of the given F# document. + /// This tries to get the data by looking at an internal cache; if it doesn't exist in the cache it will create an inaccurate but usable form of the defines and the language version. + member this.GetFSharpQuickDefinesAndLangVersion() = + let workspaceService = this.Project.Solution.GetFSharpWorkspaceService() + workspaceService.FSharpProjectOptionsManager.GetCompilationDefinesAndLangVersionForEditingDocument(this) + /// A non-async call that quickly gets the defines of the given F# document. /// This tries to get the defines by looking at an internal cache; if it doesn't exist in the cache it will create an inaccurate but usable form of the defines. member this.GetFSharpQuickDefines() = - let workspaceService = this.Project.Solution.GetFSharpWorkspaceService() - workspaceService.FSharpProjectOptionsManager.GetCompilationDefinesForEditingDocument(this) + this.GetFSharpQuickDefinesAndLangVersion() |> fst /// Parses the given F# document. member this.GetFSharpParseResultsAsync(userOpName) = @@ -238,7 +250,7 @@ type Document with /// Try to find a F# lexer/token symbol of the given F# document and position. member this.TryFindFSharpLexerSymbolAsync(position, lookupKind, wholeActivePattern, allowStringToken, userOpName) = async { - let! defines = this.GetFSharpCompilationDefinesAsync(userOpName) + let! defines, langVersion = this.GetFSharpCompilationDefinesAndLangVersionAsync(userOpName) let! ct = Async.CancellationToken let! sourceText = this.GetTextAsync(ct) |> Async.AwaitTask @@ -252,6 +264,7 @@ type Document with lookupKind, wholeActivePattern, allowStringToken, + Some langVersion, ct ) } diff --git a/vsintegration/src/FSharp.Editor/TaskList/TaskListService.fs b/vsintegration/src/FSharp.Editor/TaskList/TaskListService.fs index 5097c01ffc..f3edadb774 100644 --- a/vsintegration/src/FSharp.Editor/TaskList/TaskListService.fs +++ b/vsintegration/src/FSharp.Editor/TaskList/TaskListService.fs @@ -17,15 +17,15 @@ open System.Diagnostics [)>] type internal FSharpTaskListService [] () as this = - let getDefines (doc: Microsoft.CodeAnalysis.Document) = + let getDefinesAndLangVersion (doc: Microsoft.CodeAnalysis.Document) = asyncMaybe { let! _, _, parsingOptions, _ = doc.GetFSharpCompilationOptionsAsync(nameof (FSharpTaskListService)) |> liftAsync - return CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions + return CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions, Some parsingOptions.LangVersionText } - |> Async.map (Option.defaultValue []) + |> Async.map (Option.defaultValue ([], None)) let extractContractedComments (tokens: Tokenizer.SavedTokenInfo[]) = let granularTokens = @@ -52,6 +52,7 @@ type internal FSharpTaskListService [] () as this = doc: Microsoft.CodeAnalysis.Document, sourceText: SourceText, defines: string list, + langVersion: string option, descriptors: (string * FSharpTaskListDescriptor)[], cancellationToken ) = @@ -61,7 +62,7 @@ type internal FSharpTaskListService [] () as this = for line in sourceText.Lines do let contractedTokens = - Tokenizer.tokenizeLine (doc.Id, sourceText, line.Span.Start, doc.FilePath, defines, cancellationToken) + Tokenizer.tokenizeLine (doc.Id, sourceText, line.Span.Start, doc.FilePath, defines, langVersion, cancellationToken) |> extractContractedComments for ct in contractedTokens do @@ -89,6 +90,6 @@ type internal FSharpTaskListService [] () as this = backgroundTask { let descriptors = desc |> Seq.map (fun d -> d.Text, d) |> Array.ofSeq let! sourceText = doc.GetTextAsync(cancellationToken) - let! defines = doc |> getDefines - return this.GetTaskListItems(doc, sourceText, defines, descriptors, cancellationToken) + let! defines, langVersion = doc |> getDefinesAndLangVersion + return this.GetTaskListItems(doc, sourceText, defines, langVersion, descriptors, cancellationToken) } diff --git a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs index 986f3fddf4..a4415b3a1a 100644 --- a/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs +++ b/vsintegration/src/FSharp.LanguageService/ProjectSitesAndFiles.fs @@ -250,17 +250,17 @@ type internal ProjectSitesAndFiles() = member art.GetDefinesForFile_DEPRECATED(rdt:IVsRunningDocumentTable, fileName : string, checker:FSharpChecker) = - // The only caller of this function calls it each time it needs to colorize a line, so this call must execute very fast. + // The only caller of this function calls it each time it needs to colorize a line, so this call must execute very fast. if CompilerEnvironment.MustBeSingleFileProject(fileName) then let parsingOptions = { FSharpParsingOptions.Default with IsInteractive = true} CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions - else - let siteOpt = - match VsRunningDocumentTable.FindDocumentWithoutLocking(rdt,fileName) with - | Some(hier,_) -> tryGetProjectSite(hier) + else + let siteOpt = + match VsRunningDocumentTable.FindDocumentWithoutLocking(rdt,fileName) with + | Some(hier,_) -> tryGetProjectSite(hier) | None -> None - let site = + let site = match siteOpt with | Some site -> site | None -> ProjectSitesAndFiles.ProjectSiteOfSingleFile(fileName) diff --git a/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs index f5a84873dc..55d8de9865 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs @@ -14,13 +14,18 @@ type BraceMatchingServiceTests() = let fileName = "C:\\test.fs" - member private this.VerifyNoBraceMatch(fileContents: string, marker: string) = + member private this.VerifyNoBraceMatch(fileContents: string, marker: string, ?langVersion: string) = let sourceText = SourceText.From(fileContents) let position = fileContents.IndexOf(marker) Assert.True(position >= 0, $"Cannot find marker '{marker}' in file contents") - let parsingOptions, _ = - checker.GetParsingOptionsFromProjectOptions RoslynTestHelpers.DefaultProjectOptions + let parsingOptions = + let parsingOptions, _ = + checker.GetParsingOptionsFromProjectOptions RoslynTestHelpers.DefaultProjectOptions + + { parsingOptions with + LangVersionText = langVersion |> Option.defaultValue "preview" + } match FSharpBraceMatchingService.GetBraceMatchingResult(checker, sourceText, fileName, parsingOptions, position, "UnitTest") @@ -29,7 +34,7 @@ type BraceMatchingServiceTests() = | None -> () | Some (left, right) -> failwith $"Found match for brace '{marker}'" - member private this.VerifyBraceMatch(fileContents: string, startMarker: string, endMarker: string) = + member private this.VerifyBraceMatch(fileContents: string, startMarker: string, endMarker: string, ?langVersion: string) = let sourceText = SourceText.From(fileContents) let startMarkerPosition = fileContents.IndexOf(startMarker) let endMarkerPosition = fileContents.IndexOf(endMarker) @@ -37,8 +42,13 @@ type BraceMatchingServiceTests() = Assert.True(startMarkerPosition >= 0, $"Cannot find start marker '{startMarkerPosition}' in file contents") Assert.True(endMarkerPosition >= 0, $"Cannot find end marker '{endMarkerPosition}' in file contents") - let parsingOptions, _ = - checker.GetParsingOptionsFromProjectOptions RoslynTestHelpers.DefaultProjectOptions + let parsingOptions = + let parsingOptions, _ = + checker.GetParsingOptionsFromProjectOptions RoslynTestHelpers.DefaultProjectOptions + + { parsingOptions with + LangVersionText = langVersion |> Option.defaultValue "preview" + } match FSharpBraceMatchingService.GetBraceMatchingResult( diff --git a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs index de8d75bd64..8bcf316837 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs @@ -16,6 +16,9 @@ module CompletionProviderTests = let filePath = "C:\\test.fs" + let mkGetInfo documentId = + fun () -> documentId, filePath, [], (Some "preview") + let formatCompletions (completions: string seq) = "\n\t" + String.Join("\n\t", completions) @@ -102,7 +105,7 @@ module CompletionProviderTests = let sourceText = SourceText.From(fileContents) let resultSpan = - CompletionUtils.getDefaultCompletionListSpan (sourceText, caretPosition, documentId, filePath, []) + CompletionUtils.getDefaultCompletionListSpan (sourceText, caretPosition, documentId, filePath, [], None) Assert.Equal(expected, sourceText.ToString(resultSpan)) @@ -130,14 +133,13 @@ System.Console.WriteLine(x + y) let caretPosition = fileContents.IndexOf(marker) + marker.Length let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -152,14 +154,13 @@ System.Console.WriteLine(x + y) let fileContents = "System.Console.WriteLine(123)" let caretPosition = fileContents.IndexOf("rite") let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, triggerKind, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -170,14 +171,13 @@ System.Console.WriteLine(x + y) let fileContents = "let literal = \"System.Console.WriteLine()\"" let caretPosition = fileContents.IndexOf("System.") let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -195,14 +195,13 @@ System.Console.WriteLine() let caretPosition = fileContents.IndexOf("System.") let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -233,14 +232,13 @@ let z = $"abc {System.Console.WriteLine(x + y)} def" for (marker, shouldBeTriggered) in testCases do let caretPosition = fileContents.IndexOf(marker) + marker.Length let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -260,14 +258,13 @@ System.Console.WriteLine() let caretPosition = fileContents.IndexOf("System.") let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -284,14 +281,13 @@ let f() = let caretPosition = fileContents.IndexOf("|.") let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -308,14 +304,13 @@ module Foo = module end let marker = "A" let caretPosition = fileContents.IndexOf(marker) + marker.Length let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -332,14 +327,13 @@ printfn "%d" !f let marker = "!f" let caretPosition = fileContents.IndexOf(marker) + marker.Length let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -357,14 +351,13 @@ use ptr = fixed &p let marker = "&p" let caretPosition = fileContents.IndexOf(marker) + marker.Length let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -391,14 +384,13 @@ xVal**y for marker in markers do let caretPosition = fileContents.IndexOf(marker) + marker.Length let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) @@ -413,14 +405,13 @@ l""" let marker = "l" let caretPosition = fileContents.IndexOf(marker) + marker.Length let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) - let getInfo () = documentId, filePath, [] let triggered = FSharpCompletionProvider.ShouldTriggerCompletionAux( SourceText.From(fileContents), caretPosition, CompletionTriggerKind.Insertion, - getInfo, + mkGetInfo documentId, IntelliSenseOptions.Default ) diff --git a/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs index f34c0640c5..face43ab81 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/GoToDefinitionServiceTests.fs @@ -14,7 +14,14 @@ module GoToDefinitionServiceTests = let userOpName = "GoToDefinitionServiceTests" - let private findDefinition (document: Document, sourceText: SourceText, position: int, defines: string list) : range option = + let private findDefinition + ( + document: Document, + sourceText: SourceText, + position: int, + defines: string list, + langVersion: string option + ) : range option = maybe { let textLine = sourceText.Lines.GetLineFromPosition position let textLinePos = sourceText.Lines.GetLinePosition position @@ -30,6 +37,7 @@ module GoToDefinitionServiceTests = SymbolLookupKind.Greedy, false, false, + langVersion, System.Threading.CancellationToken.None ) @@ -62,7 +70,7 @@ module GoToDefinitionServiceTests = |> RoslynTestHelpers.GetSingleDocument let actual = - findDefinition (document, sourceText, caretPosition, []) + findDefinition (document, sourceText, caretPosition, [], None) |> Option.map (fun range -> (range.StartLine, range.EndLine, range.StartColumn, range.EndColumn)) if actual <> expected then diff --git a/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs index c48506c78b..6088e79e9e 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/HelpContextServiceTests.fs @@ -42,7 +42,15 @@ type HelpContextServiceTests() = let documentId = DocumentId.CreateNewId(ProjectId.CreateNewId()) let classifiedSpans = - Tokenizer.getClassifiedSpans (documentId, sourceText, textLine.Span, Some "test.fs", [], CancellationToken.None) + Tokenizer.getClassifiedSpans ( + documentId, + sourceText, + textLine.Span, + Some "test.fs", + [], + None, + CancellationToken.None + ) FSharpHelpContextService.GetHelpTerm(document, span, classifiedSpans) |> Async.RunSynchronously diff --git a/vsintegration/tests/FSharp.Editor.Tests/LanguageDebugInfoServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/LanguageDebugInfoServiceTests.fs index e24c4e93d3..2f34a78f38 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/LanguageDebugInfoServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/LanguageDebugInfoServiceTests.fs @@ -59,6 +59,7 @@ let main argv = TextSpan.FromBounds(0, sourceText.Length), Some(fileName), defines, + None, CancellationToken.None ) diff --git a/vsintegration/tests/FSharp.Editor.Tests/SignatureHelpProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/SignatureHelpProviderTests.fs index a96c237433..af6f3f3016 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/SignatureHelpProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/SignatureHelpProviderTests.fs @@ -169,6 +169,7 @@ module SignatureHelpProvider = checkFileResults, document.Id, [], + None, DefaultDocumentationProvider, sourceText, caretPosition, @@ -510,6 +511,7 @@ M.f checkFileResults, document.Id, [], + None, DefaultDocumentationProvider, sourceText, caretPosition, diff --git a/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs index 1245859207..5120cd71d8 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/SyntacticColorizationServiceTests.fs @@ -12,7 +12,14 @@ open FSharp.Test type SyntacticClassificationServiceTests() = - member private this.ExtractMarkerData(fileContents: string, marker: string, defines: string list, isScriptFile: Option) = + member private this.ExtractMarkerData + ( + fileContents: string, + marker: string, + defines: string list, + langVersion: string option, + isScriptFile: Option + ) = let textSpan = TextSpan(0, fileContents.Length) let fileName = @@ -30,6 +37,7 @@ type SyntacticClassificationServiceTests() = textSpan, Some(fileName), defines, + langVersion, CancellationToken.None ) @@ -43,10 +51,13 @@ type SyntacticClassificationServiceTests() = marker: string, defines: string list, classificationType: string, - ?isScriptFile: bool + ?isScriptFile: bool, + ?langVersion: string ) = + let langVersion = langVersion |> Option.orElse (Some "preview") + let (tokens, markerPosition) = - this.ExtractMarkerData(fileContents, marker, defines, isScriptFile) + this.ExtractMarkerData(fileContents, marker, defines, langVersion, isScriptFile) match tokens |> Seq.tryFind (fun token -> token.TextSpan.Contains(markerPosition)) with | None -> failwith "Cannot find colorization data for start of marker" @@ -60,10 +71,13 @@ type SyntacticClassificationServiceTests() = marker: string, defines: string list, classificationType: string, - ?isScriptFile: bool + ?isScriptFile: bool, + ?langVersion: string ) = + let langVersion = langVersion |> Option.orElse (Some "preview") + let (tokens, markerPosition) = - this.ExtractMarkerData(fileContents, marker, defines, isScriptFile) + this.ExtractMarkerData(fileContents, marker, defines, langVersion, isScriptFile) match tokens diff --git a/vsintegration/tests/FSharp.Editor.Tests/TaskListServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/TaskListServiceTests.fs index 0abfccccec..f342b0e92a 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/TaskListServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/TaskListServiceTests.fs @@ -24,7 +24,10 @@ let private descriptors = let assertTasks expectedTasks fileContents = let doc = createDocument fileContents let sourceText = doc.GetTextAsync().Result - let t = service.GetTaskListItems(doc, sourceText, [], descriptors, ct) + + let t = + service.GetTaskListItems(doc, sourceText, [], (Some "preview"), descriptors, ct) + let tasks = t |> Seq.map (fun t -> t.Message) |> List.ofSeq Assert.Equal(expectedTasks |> List.sort, tasks |> List.sort) diff --git a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs index feb4d14303..f0189d75b4 100644 --- a/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs +++ b/vsintegration/tests/Salsa/FSharpLanguageServiceTestable.fs @@ -212,7 +212,7 @@ type internal FSharpLanguageServiceTestable() as this = let fileName = VsTextLines.GetFilename buffer let rdt = this.ServiceProvider.RunningDocumentTable let defines = this.ProjectSitesAndFiles.GetDefinesForFile_DEPRECATED(rdt, fileName, this.FSharpChecker) - let sourceTokenizer = FSharpSourceTokenizer(defines,Some(fileName)) + let sourceTokenizer = FSharpSourceTokenizer(defines,Some(fileName), None) sourceTokenizer.CreateLineTokenizer(source)) let colorizer = new FSharpColorizer_DEPRECATED(this.CloseColorizer, buffer, scanner) diff --git a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.General.fs b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.General.fs index 11a6b23143..214d6ddf05 100644 --- a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.General.fs +++ b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.General.fs @@ -133,7 +133,7 @@ type UsingMSBuild() = let fileName = "test.fs" let defines = [ "COMPILED"; "EDITING" ] - FSharpSourceTokenizer(defines,Some(fileName)).CreateLineTokenizer(source)) + FSharpSourceTokenizer(defines,Some(fileName),None).CreateLineTokenizer(source)) let cm = Microsoft.VisualStudio.FSharp.LanguageService.TokenColor.Comment let kw = Microsoft.VisualStudio.FSharp.LanguageService.TokenColor.Keyword From 7749a8648d66b61f03fd540bc02a8d697de0bf44 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Tue, 25 Apr 2023 16:26:22 +0200 Subject: [PATCH 22/26] Rename delimLength to interpolationDelimiterLength --- src/Compiler/Service/ServiceLexing.fs | 2 +- src/Compiler/SyntaxTree/LexHelpers.fs | 4 +- src/Compiler/SyntaxTree/LexHelpers.fsi | 2 +- src/Compiler/lex.fsl | 328 ++++++++++++++++--------- 4 files changed, 211 insertions(+), 125 deletions(-) diff --git a/src/Compiler/Service/ServiceLexing.fs b/src/Compiler/Service/ServiceLexing.fs index e684b7f702..0996275c5a 100644 --- a/src/Compiler/Service/ServiceLexing.fs +++ b/src/Compiler/Service/ServiceLexing.fs @@ -969,7 +969,7 @@ type FSharpLineTokenizer(lexbuf: UnicodeLexing.Lexbuf, maxLength: int option, fi | LexCont.String (ifdefs, stringNest, style, kind, delimLen, m) -> lexargs.ifdefStack <- ifdefs lexargs.stringNest <- stringNest - lexargs.delimLength <- delimLen + lexargs.interpolationDelimiterLength <- delimLen use buf = ByteBuffer.Create Lexer.StringCapacity let args = (buf, LexerStringFinisher.Default, m, kind, lexargs) diff --git a/src/Compiler/SyntaxTree/LexHelpers.fs b/src/Compiler/SyntaxTree/LexHelpers.fs index e1185786a3..704526f544 100644 --- a/src/Compiler/SyntaxTree/LexHelpers.fs +++ b/src/Compiler/SyntaxTree/LexHelpers.fs @@ -65,7 +65,7 @@ type LexArgs = mutable ifdefStack: LexerIfdefStack mutable indentationSyntaxStatus: IndentationAwareSyntaxStatus mutable stringNest: LexerInterpolatedStringNesting - mutable delimLength: int + mutable interpolationDelimiterLength: int } /// possible results of lexing a long Unicode escape sequence in a string literal, e.g. "\U0001F47D", @@ -94,7 +94,7 @@ let mkLexargs applyLineDirectives = applyLineDirectives stringNest = [] pathMap = pathMap - delimLength = 0 + interpolationDelimiterLength = 0 } /// Register the lexbuf and call the given function diff --git a/src/Compiler/SyntaxTree/LexHelpers.fsi b/src/Compiler/SyntaxTree/LexHelpers.fsi index 45df24b19d..292adaf5be 100644 --- a/src/Compiler/SyntaxTree/LexHelpers.fsi +++ b/src/Compiler/SyntaxTree/LexHelpers.fsi @@ -38,7 +38,7 @@ type LexArgs = mutable ifdefStack: LexerIfdefStack mutable indentationSyntaxStatus: IndentationAwareSyntaxStatus mutable stringNest: LexerInterpolatedStringNesting - mutable delimLength: int } + mutable interpolationDelimiterLength: int } type LongUnicodeLexResult = | SurrogatePair of uint16 * uint16 diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl index 2d45b0f9b1..269094bda3 100644 --- a/src/Compiler/lex.fsl +++ b/src/Compiler/lex.fsl @@ -605,7 +605,7 @@ rule token args skip = parse | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | [] -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.String, args.delimLength, m)) + if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.String, args.interpolationDelimiterLength, m)) else singleQuoteString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } | '$' '"' '"' '"' @@ -616,7 +616,7 @@ rule token args skip = parse | _ :: _ -> errorR(Error(FSComp.SR.lexTripleQuoteInTripleQuote(), m)) | [] -> () - args.delimLength <- 1 + args.interpolationDelimiterLength <- 1 if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.InterpolatedStringFirst, 1, m)) else tripleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } @@ -629,14 +629,18 @@ rule token args skip = parse | _ :: _ -> errorR(Error(FSComp.SR.lexTripleQuoteInTripleQuote(), m)) | [] -> () - args.delimLength <- lexeme lexbuf |> Seq.takeWhile (fun c -> c = '$') |> Seq.length - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, LexerStringKind.InterpolatedStringFirst, args.delimLength, m)) - else extendedInterpolatedString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf + args.interpolationDelimiterLength <- lexeme lexbuf |> Seq.takeWhile (fun c -> c = '$') |> Seq.length + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, LexerStringKind.InterpolatedStringFirst, args.interpolationDelimiterLength, m)) + else + extendedInterpolatedString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf else - let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.InterpolatedStringFirst, args.delimLength, m)) - else tripleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf - fail args lexbuf (FSComp.SR.lexExtendedStringInterpolationNotSupported()) (result()) + let result = + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.InterpolatedStringFirst, args.interpolationDelimiterLength, m)) + else + tripleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf + fail args lexbuf (FSComp.SR.lexExtendedStringInterpolationNotSupported()) result } | '$' '"' @@ -648,21 +652,25 @@ rule token args skip = parse | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.InterpolatedStringFirst, args.delimLength, m)) - else singleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, LexerStringKind.InterpolatedStringFirst, args.interpolationDelimiterLength, m)) + else + singleQuoteString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } | '"' '"' '"' { let buf, fin, m = startString args lexbuf - args.delimLength <- 0 + args.interpolationDelimiterLength <- 0 // Single quote in triple quote ok, others disallowed match args.stringNest with | _ :: _ -> errorR(Error(FSComp.SR.lexTripleQuoteInTripleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.String, args.delimLength, m)) - else tripleQuoteString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, LexerStringKind.String, args.interpolationDelimiterLength, m)) + else + tripleQuoteString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } | '@' '"' { let buf, fin, m = startString args lexbuf @@ -674,8 +682,10 @@ rule token args skip = parse | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.String, args.delimLength, m)) - else verbatimString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.String, args.interpolationDelimiterLength, m)) + else + verbatimString (buf, fin, m, LexerStringKind.String, args) skip lexbuf } | ("$@" | "@$") '"' { let buf, fin, m = startString args lexbuf @@ -686,8 +696,10 @@ rule token args skip = parse | _ :: _ -> errorR(Error(FSComp.SR.lexSingleQuoteInSingleQuote(), m)) | _ -> () - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.InterpolatedStringFirst, args.delimLength, m)) - else verbatimString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, LexerStringKind.InterpolatedStringFirst, args.interpolationDelimiterLength, m)) + else + verbatimString (buf, fin, m, LexerStringKind.InterpolatedStringFirst, args) skip lexbuf } | truewhite+ { if skip then token args skip lexbuf @@ -908,7 +920,7 @@ rule token args skip = parse args.stringNest <- rest let buf, fin, m = startString args lexbuf if not skip then - STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, style, LexerStringKind.InterpolatedStringPart, args.delimLength, m)) + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, style, LexerStringKind.InterpolatedStringPart, args.interpolationDelimiterLength, m)) else match style with | LexerStringStyle.Verbatim -> verbatimString (buf, fin, m, LexerStringKind.InterpolatedStringPart, args) skip lexbuf @@ -1170,40 +1182,52 @@ and singleQuoteString sargs skip = parse let text = lexeme lexbuf let text2 = text |> String.filter (fun c -> c <> ' ' && c <> '\t') advanceColumnBy lexbuf (text.Length - text2.Length) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | escape_char { let (buf, _fin, m, kind, args) = sargs addByteChar buf (escape (lexeme lexbuf).[1]) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | trigraph { let (buf, _fin, m, kind, args) = sargs let s = lexeme lexbuf addByteChar buf (trigraph s.[1] s.[2] s.[3]) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | hexGraphShort { let (buf, _fin, m, kind, args) = sargs addUnicodeChar buf (int (hexGraphShort (lexemeTrimLeft lexbuf 2))) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | unicodeGraphShort { let (buf, _fin, m, kind, args) = sargs addUnicodeChar buf (int (unicodeGraphShort (lexemeTrimLeft lexbuf 2))) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | unicodeGraphLong { let (buf, _fin, m, kind, args) = sargs let hexChars = lexemeTrimLeft lexbuf 2 let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) - else singleQuoteString sargs skip lexbuf + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf match unicodeGraphLong hexChars with | Invalid -> fail args lexbuf (FSComp.SR.lexInvalidUnicodeLiteral hexChars) (result()) @@ -1231,28 +1255,34 @@ and singleQuoteString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs let s = lexeme lexbuf addUnicodeString buf (if kind.IsInterpolated then s.[0..0] else s) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | "{" { let (buf, fin, m, kind, args) = sargs if kind.IsInterpolated then // get a new range for where the fill starts let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.SingleQuote, args.delimLength, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.SingleQuote, args.interpolationDelimiterLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind LexerStringFinisherContext.InterpolatedPart cont else addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) - else singleQuoteString sargs skip lexbuf + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | "}" { let (buf, _fin, m, kind, args) = sargs let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) - else singleQuoteString sargs skip lexbuf + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf if kind.IsInterpolated then fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) else @@ -1263,46 +1293,58 @@ and singleQuoteString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs newline lexbuf addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | ident { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | integer | xinteger { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | anywhite + { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } | eof { let (_buf, _fin, m, kind, args) = sargs - EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) } + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) } | surrogateChar surrogateChar // surrogate code points always come in pairs | _ { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.delimLength, m)) - else singleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.SingleQuote, kind, args.interpolationDelimiterLength, m)) + else + singleQuoteString sargs skip lexbuf } and verbatimString sargs skip = parse | '"' '"' { let (buf, _fin, m, kind, args) = sargs addByteChar buf '\"' - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) - else verbatimString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } | '"' { let (buf, fin, _m, kind, args) = sargs @@ -1320,35 +1362,43 @@ and verbatimString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs newline lexbuf addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) - else verbatimString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } | ("{{" | "}}") { let (buf, _fin, m, kind, args) = sargs let s = lexeme lexbuf addUnicodeString buf (if kind.IsInterpolated then s.[0..0] else s) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) - else verbatimString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } | "{" { let (buf, fin, m, kind, args) = sargs if kind.IsInterpolated then // get a new range for where the fill starts let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.Verbatim, args.delimLength, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.Verbatim, args.interpolationDelimiterLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.Verbatim) cont else addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) - else verbatimString sargs skip lexbuf + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } | "}" { let (buf, _fin, m, kind, args) = sargs let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) - else verbatimString sargs skip lexbuf + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf if kind.IsInterpolated then fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) else @@ -1359,37 +1409,45 @@ and verbatimString sargs skip = parse | ident { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) - else verbatimString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } | integer | xinteger { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) - else verbatimString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } | anywhite + { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) - else verbatimString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } | eof { let (_buf, _fin, m, kind, args) = sargs - EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) } + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) } | surrogateChar surrogateChar // surrogate code points always come in pairs | _ { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.delimLength, m)) - else verbatimString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.Verbatim, kind, args.interpolationDelimiterLength, m)) + else + verbatimString sargs skip lexbuf } and tripleQuoteString sargs skip = parse | '"' '"' '"' { let (buf, fin, _m, kind, args) = sargs - args.delimLength <- 0 + args.interpolationDelimiterLength <- 0 let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind LexerStringFinisherContext.TripleQuote cont } @@ -1397,55 +1455,69 @@ and tripleQuoteString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs newline lexbuf addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) - else tripleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf } // The rest is to break into pieces to allow double-click-on-word and other such things | ident { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) - else tripleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf } | integer | xinteger { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) - else tripleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf } | anywhite + { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) - else tripleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf } | ("{{" | "}}") { let (buf, _fin, m, kind, args) = sargs let s = lexeme lexbuf addUnicodeString buf (if kind.IsInterpolated then s.[0..0] else s) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) - else tripleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf } | "{" { let (buf, fin, m, kind, args) = sargs if kind.IsInterpolated then // get a new range for where the fill starts let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.TripleQuote, args.delimLength, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.TripleQuote, args.interpolationDelimiterLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont else addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) - else tripleQuoteString sargs skip lexbuf + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf } | "}" { let (buf, _fin, m, kind, args) = sargs let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) - else tripleQuoteString sargs skip lexbuf + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf if kind.IsInterpolated then fail args lexbuf (FSComp.SR.lexRBraceInInterpolatedString()) (result()) else @@ -1455,19 +1527,21 @@ and tripleQuoteString sargs skip = parse | eof { let (_buf, _fin, m, kind, args) = sargs - EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) } + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) } | surrogateChar surrogateChar // surrogate code points always come in pairs | _ { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.delimLength, m)) - else tripleQuoteString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.TripleQuote, kind, args.interpolationDelimiterLength, m)) + else + tripleQuoteString sargs skip lexbuf } and extendedInterpolatedString sargs skip = parse | '"' '"' '"' { let (buf, fin, _m, kind, args) = sargs - args.delimLength <- 0 + args.interpolationDelimiterLength <- 0 let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind LexerStringFinisherContext.TripleQuote cont } @@ -1475,8 +1549,10 @@ and extendedInterpolatedString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs newline lexbuf addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.delimLength, m)) - else extendedInterpolatedString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.interpolationDelimiterLength, m)) + else + extendedInterpolatedString sargs skip lexbuf } // The rest is to break into pieces to allow double-click-on-word and other such things | ident @@ -1485,22 +1561,26 @@ and extendedInterpolatedString sargs skip = parse | anywhite + { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.delimLength, m)) - else extendedInterpolatedString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.interpolationDelimiterLength, m)) + else + extendedInterpolatedString sargs skip lexbuf } | "%" + { let (buf, _fin, m, kind, args) = sargs let numPercents = lexeme lexbuf |> String.length let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.delimLength, m)) - else extendedInterpolatedString sargs skip lexbuf - // delimLength is number of $ chars prepended to opening quotes - // If number of consecutive % chars in content is equal to delimLength, + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.interpolationDelimiterLength, m)) + else + extendedInterpolatedString sargs skip lexbuf + // interpolationDelimiterLength is number of $ chars prepended to opening quotes + // If number of consecutive % chars in content is equal to interpolationDelimiterLength, // then that sequence is treated as a format specifier, // as in $"""%3d{42}""" or (equivalent) $$"""%%3d{{42}}""". - // Any extra % chars up to delimLength, are treated simply as regular string content. - // 2x delimLength or more % chars in a sequence will result in an error. - let maxPercents = 2 * args.delimLength - 1 + // Any extra % chars up to interpolationDelimiterLength, are treated simply as regular string content. + // 2x interpolationDelimiterLength or more % chars in a sequence will result in an error. + let maxPercents = 2 * args.interpolationDelimiterLength - 1 if numPercents > maxPercents then let m2 = lexbuf.LexemeRange let rest = result() @@ -1510,8 +1590,8 @@ and extendedInterpolatedString sargs skip = parse // Add two % chars for each % that is supposed to be treated as regular string content // + 1 for a format specifier. let percentsToEmit = - if numPercents < args.delimLength then 2 * numPercents - else 2 * (numPercents - args.delimLength) + 1 + if numPercents < args.interpolationDelimiterLength then 2 * numPercents + else 2 * (numPercents - args.interpolationDelimiterLength) + 1 let s = String.replicate percentsToEmit "%" addUnicodeString buf s result() } @@ -1520,31 +1600,33 @@ and extendedInterpolatedString sargs skip = parse { let (buf, fin, m, kind, args) = sargs let numBraces = String.length (lexeme lexbuf) // Extended interpolated strings starts with at least 2 $ - // Number of leading $s is the number of '{' needed to open interpolation expression (delimLength) - // 2x delimLength (or more) of '{' in a row would be unambiguous, so it's disallowed - let maxBraces = 2 * args.delimLength - 1 + // Number of leading $s is the number of '{' needed to open interpolation expression (interpolationDelimiterLength) + // 2x interpolationDelimiterLength (or more) of '{' in a row would be unambiguous, so it's disallowed + let maxBraces = 2 * args.interpolationDelimiterLength - 1 if numBraces > maxBraces then let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.ExtendedInterpolated, args.delimLength, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.ExtendedInterpolated, args.interpolationDelimiterLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) fail args lexbuf (FSComp.SR.lexTooManyLBracesInTripleQuote()) (fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont) - elif numBraces < args.delimLength then - // Less than delimLength means we treat '{' as normal content + elif numBraces < args.interpolationDelimiterLength then + // Less than interpolationDelimiterLength means we treat '{' as normal content addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.delimLength, m)) - else extendedInterpolatedString sargs skip lexbuf - // numBraces in [delimLength; maxBraces) + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.interpolationDelimiterLength, m)) + else + extendedInterpolatedString sargs skip lexbuf + // numBraces in [interpolationDelimiterLength; maxBraces) else - // A sequence of delimLength * '{' starts interpolation expression. + // A sequence of interpolationDelimiterLength * '{' starts interpolation expression. // Any extra '{' are treated as normal string content. - let extraBraces = numBraces - args.delimLength + let extraBraces = numBraces - args.interpolationDelimiterLength if extraBraces > 0 then String.replicate extraBraces "{" |> addUnicodeString buf // get a new range for where the fill starts let m2 = lexbuf.LexemeRange - args.stringNest <- (1, LexerStringStyle.ExtendedInterpolated, args.delimLength, m2) :: args.stringNest + args.stringNest <- (1, LexerStringStyle.ExtendedInterpolated, args.interpolationDelimiterLength, m2) :: args.stringNest let cont = LexCont.Token(args.ifdefStack, args.stringNest) fin.Finish buf kind (LexerStringFinisherContext.InterpolatedPart ||| LexerStringFinisherContext.TripleQuote) cont } @@ -1553,25 +1635,29 @@ and extendedInterpolatedString sargs skip = parse { let (buf, _fin, m, kind, args) = sargs let numBraces = lexeme lexbuf |> String.length let result() = - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.delimLength, m)) - else extendedInterpolatedString sargs skip lexbuf - if args.delimLength > numBraces then + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.interpolationDelimiterLength, m)) + else + extendedInterpolatedString sargs skip lexbuf + if args.interpolationDelimiterLength > numBraces then lexeme lexbuf |> addUnicodeString buf (result()) - else // numBraces >= args.delimLength + else // numBraces >= args.interpolationDelimiterLength fail args lexbuf (FSComp.SR.lexUnmatchedRBracesInTripleQuote()) (result()) } | eof { let (_buf, _fin, m, kind, args) = sargs - EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.delimLength, m)) } + EOF (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.interpolationDelimiterLength, m)) } | surrogateChar surrogateChar // surrogate code points always come in pairs | _ { let (buf, _fin, m, kind, args) = sargs addUnicodeString buf (lexeme lexbuf) - if not skip then STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.delimLength, m)) - else extendedInterpolatedString sargs skip lexbuf } + if not skip then + STRING_TEXT (LexCont.String(args.ifdefStack, args.stringNest, LexerStringStyle.ExtendedInterpolated, kind, args.interpolationDelimiterLength, m)) + else + extendedInterpolatedString sargs skip lexbuf } // Parsing single-line comment - we need to split it into words for Visual Studio IDE and singleLineComment cargs skip = parse From ef8caeeeff512c2d44c5f2b39619384a907f7999 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Tue, 25 Apr 2023 17:19:30 +0200 Subject: [PATCH 23/26] Remove an unnecessary check --- src/Compiler/Checking/CheckFormatStrings.fs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Compiler/Checking/CheckFormatStrings.fs b/src/Compiler/Checking/CheckFormatStrings.fs index 5f2a0f12b6..ea15e196fb 100644 --- a/src/Compiler/Checking/CheckFormatStrings.fs +++ b/src/Compiler/Checking/CheckFormatStrings.fs @@ -89,13 +89,11 @@ let makeFmts (context: FormatStringCheckContext) (fragRanges: range list) (fmt: fullRangeText |> Seq.takeWhile (fun c -> c = '$') |> Seq.length - |> max delimLen - let interpolatedTripleQuotePrefix = + let tripleQuotePrefix = [String.replicate delimLen "$"; "\"\"\""] |> String.concat "" match fullRangeText with - | PrefixedBy interpolatedTripleQuotePrefix len - | PrefixedBy "\"\"\"" len -> + | PrefixedBy tripleQuotePrefix len -> nQuotes <- 3 len | PrefixedBy "$@\"" len From 503e5e2a9323cc1614d34a7e5efc354f810308b5 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Wed, 26 Apr 2023 14:37:09 +0200 Subject: [PATCH 24/26] Use errorR rather than diagnosticsLogger.ErrorR Replace call to args.diagnosticsLogger.ErrorR in lex.fsl --- src/Compiler/lex.fsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl index 269094bda3..0e4ceecc6d 100644 --- a/src/Compiler/lex.fsl +++ b/src/Compiler/lex.fsl @@ -1584,7 +1584,7 @@ and extendedInterpolatedString sargs skip = parse if numPercents > maxPercents then let m2 = lexbuf.LexemeRange let rest = result() - args.diagnosticsLogger.ErrorR(Error(FSComp.SR.lexTooManyPercentsInTripleQuote(), m2)) + errorR(Error(FSComp.SR.lexTooManyPercentsInTripleQuote(), m2)) rest else // Add two % chars for each % that is supposed to be treated as regular string content From d33883b5e0139eaec8bf28ee76ce883e8a278b9e Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Thu, 27 Apr 2023 14:11:53 +0200 Subject: [PATCH 25/26] Apply code review comments Fix some names and remove unnecessary code --- src/Compiler/FSComp.txt | 2 +- src/Compiler/Service/FSharpCheckerResults.fs | 2 +- src/Compiler/Service/ServiceLexing.fs | 14 +++++++------- src/Compiler/lex.fsl | 2 +- src/Compiler/xlf/FSComp.txt.cs.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.de.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.es.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.fr.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.it.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.ja.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.ko.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.pl.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.ru.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.tr.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 4 ++-- src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 4 ++-- 17 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index a9bc95b74e..83062836f1 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1571,7 +1571,7 @@ featureWarningWhenCopyAndUpdateRecordChangesAllFields,"Raises warnings when an c featureStaticMembersInInterfaces,"Static members in interfaces" featureNonInlineLiteralsAsPrintfFormat,"String values marked as literals and IL constants as printf format" featureNestedCopyAndUpdate,"Nested record field copy-and-update" -featureExtendedStringInterpolation,"Extended string interpolation similar to C# raw strings." +featureExtendedStringInterpolation,"Extended string interpolation similar to C# raw string literals." 3353,fsiInvalidDirective,"Invalid directive '#%s %s'" 3354,tcNotAFunctionButIndexerNamedIndexingNotYetEnabled,"This value supports indexing, e.g. '%s.[index]'. The syntax '%s[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation." 3354,tcNotAFunctionButIndexerIndexingNotYetEnabled,"This expression supports indexing, e.g. 'expr.[index]'. The syntax 'expr[index]' requires /langversion:preview. See https://aka.ms/fsharp-index-notation." diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index 8480305a43..fabb4d8f49 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -2396,7 +2396,7 @@ module internal ParseAndCheckFile = let braceOffset = match tok with | INTERP_STRING_BEGIN_PART (_, SynStringKind.TripleQuote, (LexerContinuation.Token (_, (_, _, dl, _) :: _))) -> - max 0 (dl - 1) + dl - 1 | _ -> 0 let m = lexbuf.LexemeRange diff --git a/src/Compiler/Service/ServiceLexing.fs b/src/Compiler/Service/ServiceLexing.fs index 0996275c5a..b14b0cb309 100644 --- a/src/Compiler/Service/ServiceLexing.fs +++ b/src/Compiler/Service/ServiceLexing.fs @@ -513,7 +513,7 @@ module internal LexerStateEncoding = let ifdefstackNumBits = 24 // 0 means if, 1 means else let stringKindBits = 3 let nestingBits = 12 - let dlenBits = 3 + let delimLenBits = 3 let _ = assert @@ -524,7 +524,7 @@ module internal LexerStateEncoding = + ifdefstackNumBits + stringKindBits + nestingBits - + dlenBits + + delimLenBits <= 64) let lexstateStart = 0 @@ -550,7 +550,7 @@ module internal LexerStateEncoding = + ifdefstackNumBits + stringKindBits - let dlenStart = + let delimLenStart = lexstateNumBits + ncommentsNumBits + hardwhiteNumBits @@ -566,7 +566,7 @@ module internal LexerStateEncoding = let ifdefstackMask = Bits.mask64 ifdefstackStart ifdefstackNumBits let stringKindMask = Bits.mask64 stringKindStart stringKindBits let nestingMask = Bits.mask64 nestingStart nestingBits - let dlenMask = Bits.mask64 dlenStart dlenBits + let delimLenMask = Bits.mask64 delimLenStart delimLenBits let bitOfBool b = if b then 1 else 0 let boolOfBit n = (n = 1L) @@ -638,7 +638,7 @@ module internal LexerStateEncoding = ||| ((kind1 <<< 2) &&& 0b000000001100) ||| ((kind2 <<< 0) &&& 0b000000000011) - let delimLen = min delimLen (Bits.pown32 dlenBits) + let delimLen = min delimLen (Bits.pown32 delimLenBits) let bits = lexStateOfColorState colorState @@ -648,7 +648,7 @@ module internal LexerStateEncoding = ||| ((int64 ifdefStackBits <<< ifdefstackStart) &&& ifdefstackMask) ||| ((int64 stringKindValue <<< stringKindStart) &&& stringKindMask) ||| ((int64 nestingValue <<< nestingStart) &&& nestingMask) - ||| ((int64 delimLen <<< dlenStart) &&& dlenMask) + ||| ((int64 delimLen <<< delimLenStart) &&& delimLenMask) { PosBits = b.Encoding @@ -706,7 +706,7 @@ module internal LexerStateEncoding = nest - let delimLen = int32 ((bits &&& dlenMask) >>> dlenStart) + let delimLen = int32 ((bits &&& delimLenMask) >>> delimLenStart) (colorState, ncomments, pos, ifDefs, hardwhite, stringKind, stringNest, delimLen) diff --git a/src/Compiler/lex.fsl b/src/Compiler/lex.fsl index 0e4ceecc6d..9a803d63a3 100644 --- a/src/Compiler/lex.fsl +++ b/src/Compiler/lex.fsl @@ -1642,7 +1642,7 @@ and extendedInterpolatedString sargs skip = parse if args.interpolationDelimiterLength > numBraces then lexeme lexbuf |> addUnicodeString buf (result()) - else // numBraces >= args.interpolationDelimiterLength + else fail args lexbuf (FSComp.SR.lexUnmatchedRBracesInTripleQuote()) (result()) } diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 023a1f637a..c7dc0284bb 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -253,8 +253,8 @@ - Extended string interpolation similar to C# raw strings. - Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index 657ef6cc05..c398aaf2ba 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -253,8 +253,8 @@ - Extended string interpolation similar to C# raw strings. - Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index d282062aad..60225d12eb 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -253,8 +253,8 @@ - Extended string interpolation similar to C# raw strings. - Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 9f94242373..64f9f7f963 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -253,8 +253,8 @@ - Extended string interpolation similar to C# raw strings. - Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 72c7d6a158..2a9adff342 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -253,8 +253,8 @@ - Extended string interpolation similar to C# raw strings. - Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index bf95ea6e93..207e80955d 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -253,8 +253,8 @@ - Extended string interpolation similar to C# raw strings. - Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 929e460109..9d83ebe684 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -253,8 +253,8 @@ - Extended string interpolation similar to C# raw strings. - Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 22d00d2d44..ce2756532c 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -253,8 +253,8 @@ - Extended string interpolation similar to C# raw strings. - Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 8bd70a821f..bd67aca995 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -253,8 +253,8 @@ - Extended string interpolation similar to C# raw strings. - Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index dc2358c163..70aca08735 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -253,8 +253,8 @@ - Extended string interpolation similar to C# raw strings. - Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index a1879ae52d..583e55f7bc 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -253,8 +253,8 @@ - Extended string interpolation similar to C# raw strings. - Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index 28a707c6b0..6fedbd6a23 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -253,8 +253,8 @@ - Extended string interpolation similar to C# raw strings. - Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index abb3c6319b..a00f23792e 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -253,8 +253,8 @@ - Extended string interpolation similar to C# raw strings. - Extended string interpolation similar to C# raw strings. + Extended string interpolation similar to C# raw string literals. + Extended string interpolation similar to C# raw string literals. From 0e5434aa7341bf71ee32dba2523e1af350e41e39 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Thu, 27 Apr 2023 14:59:26 +0200 Subject: [PATCH 26/26] Add more brace matching tests --- .../BraceMatchingServiceTests.fs | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs index 55d8de9865..8699e6fd42 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs @@ -102,6 +102,10 @@ type BraceMatchingServiceTests() = member this.BraceInInterpolatedStringSimple() = this.VerifyBraceMatch("let x = $\"abc{1}def\"", "{1", "}def") + [] + member this.BraceInInterpolatedStringWith2Dollars() = + this.VerifyBraceMatch("let x = $$\"\"\"abc{{1}}}def\"\"\"", "{{", "}}") + [] member this.BraceInInterpolatedStringWith3Dollars() = this.VerifyBraceMatch("let x = $$$\"\"\"abc{{{1}}}def\"\"\"", "{{{", "}}}") @@ -111,10 +115,23 @@ type BraceMatchingServiceTests() = [] [] [] - member this.BraceNoMatchInNestedInterpolatedStrings(marker) = + member this.BraceNoMatchInNestedInterpolatedStrings3Dollars(marker) = let source = "let x = $$$\"\"\"{{not a }}match e{{{4$\"f{56}g\"}}}h +\"\"\"" + + this.VerifyNoBraceMatch(source, marker) + + [] + [] + [] + [] + [] + member this.BraceNoMatchInNestedInterpolatedStrings2Dollars(marker) = + let source = + "let x = $$\"\"\"{not a }match +e{{{4$\"f{56}g\"}}}h \"\"\"" this.VerifyNoBraceMatch(source, marker) @@ -123,10 +140,22 @@ e{{{4$\"f{56}g\"}}}h [] [] [] - member this.BraceMatchInNestedInterpolatedStrings(startMark, endMark) = + member this.BraceMatchInNestedInterpolatedStrings3Dollars(startMark, endMark) = let source = "let x = $$$\"\"\"a{{{01}}}b --- c{{{23}}}d e{{{4$\"f{56}g\"}}}h +\"\"\"" + + this.VerifyBraceMatch(source, startMark, endMark) + + [] + [] + [] + [] + member this.BraceMatchInNestedInterpolatedStrings2Dollars(startMark, endMark) = + let source = + "let x = $$\"\"\"a{{{01}}}b --- c{{{23}}}d +e{{{4$\"f{56}g\"}}}h \"\"\"" this.VerifyBraceMatch(source, startMark, endMark)