<div dir="ltr"><div class="gmail_default" style="font-family:trebuchet ms,sans-serif;font-size:small"> Hello!</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">I'm trying to implement <a href="https://gitlab.haskell.org/ghc/ghc/-/issues/24390" target="_blank">#24390</a>, which implements the <a href="https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0569-multiline-strings.rst" target="_blank">multiline string literals proposal</a> (existing work done in <a href="https://gitlab.haskell.org/ghc/ghc/-/compare/master...wip%2Fmultiline-strings?from_project_id=1&straight=false">wip/multiline-strings</a>). I originally suggested adding HsMultilineString to HsLit and translating it to HsString in renaming, then Matthew Pickering suggested I translate it in desugaring instead. I tried going down this approach, but I'm running into two main issues: Escaped characters and Overloaded strings.</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">Apologies in advance for a long email. <b>TL;DR</b> - The best implementation I could think of involves a complete rewrite of how strings are lexed and modifying HsString instead of adding a new HsMultilineString constructor. If this is absolutely crazy talk, please dissuade me from this :)</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">===== Problem 1: Escaped characters =====</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">Currently, Lexer.x resolves escaped characters for string literals. In the Note [Literal source text], we see that this is intentional; HsString should contain a normalized internal representation. However, multiline string literals have a post-processing step that requires distinguishing between the user typing a newline vs the user typing literally a backslash + an `N` (and other things like knowing if a user typed in `\&`, which currently goes away in lexing as well).</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">Fundamentally, the current logic to resolve escaped characters is specific to the Lexer monad and operates on a per-character basis. But the multiline string literals proposal requires post-processing the whole string, then resolving escaped characters all at once.</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">Possible solutions:</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">(1.1) Duplicate the logic for resolving escaped characters</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Pro: Leaves normal string lexing untouched</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Con: Two sources of truth, possibly divergent behaviors between multiline and normal strings</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">(1.2) Stick the post-processed string back into P, then rerun normal string lexing to resolve escaped characters</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Pro: Leaves normal string lexing untouched</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Con: Seems roundabout, inefficient, and hacky</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">(1.3) Refactor the resolve-escaped-characters logic to work in both the P monad and as a pure function `String -> String`</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Pro: Reuses same escaped-characters logic for both normal + multiline strings</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Con: Different overall behavior between the two string types: Normal string still lexed per-character, Multiline strings would lex everything</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Con: Small refactor of lexing normal strings, which could introduce regressions</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">(1.4) Read entire string (both normal + multiline) with no preprocessing (including string gaps or anything, except escaping quote delimiters), and define all post-processing steps as pure `String -> String` functions</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Pro: Gets out of monadic code quickly, turn bulk of string logic into pure code</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Pro: Processes normal + multiline strings exactly the same</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Pro: Opens the door for future string behaviors, e.g. raw string could do the same "read entire string" logic, and just not do any post-processing.</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Con: Could be less performant</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Con: Major refactor of lexing normal strings, which could introduce regressions</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">I like solution 1.4 the best, as it generalizes string processing behavior the best and is more pipeline-style vs the currently more imperative style. But I recognize possible performance or behavior regressions are a real thing, so if anyone has any thoughts here, I'd love to hear them.</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">===== Problem 2: Overloaded strings =====</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">Currently, `HsString s` is converted into `HsOverLit (HsIsString s)` in the renaming phase. Following Matthew's suggestion of resolving multiline string literals in the desugar step, this would mean that multiline string literals are post-processed after OverloadedStrings has already been applied.</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">I don't like any of the solutions this approach brings up:</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">* Do post processing both when Desugaring HsMultilineString AND when Renaming HsMultilineString to HsOverLit - seems wrong to process multiline strings in two different phases</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">* Add HsIsStringMultiline and post process when desugaring both HsMultilineString and HsIsStringMultiline - would ideally like to avoid adding a variant of HsIsStringMultiline</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">Instead, I propose we throw away the HsMultilineString idea and reuse HsString. The multiline syntax would still be preserved in the SourceText, and this also leaves the door open for future string features. For example, if we went with HsMultilineString, then adding raw strings would require adding both HsRawString and HsMultilineRawString.</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">Here are two possible solutions for reusing HsString:</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">(2.1) Add a HsStringType parameter to HsString</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * HsStringType would define the format of the FastString stored in HsString: Normal => processed, Multiline => stores raw string, needs post-processing</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Post processing could occur in desugaring, with or without OverloadedStrings</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Pro: Shows the parsed multiline string before processing in -ddump-parsed</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Con: HsString containing Multiline strings would not contain the normalized representation mentioned in Note [Literal source text]</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Con: Breaking change in the GHC API</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">(2.2) Post-process multiline strings in lexer</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Lexer would do all the post processing (for example, in conjunction with solution 1.4) and just return a normal HsString</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Pro: Multiline string is immediately desugared and behaves as expected for OverloadedStrings (and any other behaviors of string literals, existing or future) for free</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Pro: HsString would still always contain the normalized representation</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">    * Con: No way of inspecting the raw multiline parse output before processing, e.g. via -ddump-parsed</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">I'm leaning towards solution 2.1, but curious what people's thoughts are.</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">===== Closing remarks =====</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">Again, sorry for the long email. My head is spinning trying to figure out this feature. Any help would be greatly appreciated.</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">As an aside, I last worked on GHC back in 2020 or 2021, and my goodness. The Hadrian build is so much smoother (and faster!? Not sure if it's just my new laptop though) than what it was last time I touched the codebase. Huge thanks to the maintainers, both for the tooling and the docs in the wiki. This is a much more enjoyable experience.</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif"><br></div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">Thanks,</div><div class="gmail_default" style="font-family:"trebuchet ms",sans-serif">Brandon</div></div>