packages feed

PyF 0.6.1.1 → 0.11.5.0

raw patch · 107 files changed

Files

ChangeLog.md view
@@ -1,5 +1,170 @@-# Revision history for FormatStringLiteral+# Revision history for PyF +## 0.11.5.0 -- 2025-12-01++- Release with support for GHC 9.14 (tested with -rc1)++## 0.11.4.0 -- 2025-01-03++- Fix indentation in `fmtTrim` when line break was escaped (bug https://github.com/guibou/PyF/issues/141).+- Support for GHC 9.12.+- Fix for tests in GHC 9.10.+- No more "python" reference check in the test phase. I'm removing complexity,+  and if it does not match the python implementation, we can just introduce a+  new test case. Note that python checking can be reimplemented easilly by+  parsing the AST.++## 0.11.3.0 -- 2024-05-15++- Support for GHC 9.10.++## 0.11.2.1 -- 2023-10-25++- Final version for GHC 9.8++## 0.11.2.0++- Fix for the neovim treesitter syntax highlighter for `fmt` and `fmtTrim` quasiquotes+- Initial support for GHC 9.8+- Version bump for new MTL++## 0.11.1.1 -- 2023-03-15++- Support for GHC 9.6. Thank you @Kleidukos for initiating the port.++## 0.11.1.0 -- 2022-09-24++- Support for OverloadedRecordsDot syntax in Meta. Thank you @Profpatsch for the report.+- In some context, the error reporting for variable not found in the quasi quote expression was incorrectly reporting existing variables as not found. See https://github.com/guibou/PyF/issues/115 for details. This is now fixed by not abusing GHC api. Thank you @michaelpj for reporting this really weird problem.++## 0.11.0.0 -- 2022-08-10++- Support for GHC 9.4. (Written with a pre-release of GHC 9.4, hopefully it won't change too much before the release).+- Error reporting now uses the native GHC API. In summary, it means that+  haskell-language-server will point to the correct location of the error, not+  the beginning of the quasi quotes.+- PyF will now correctly locate the error for variable not found in expression, even if the expression is complicated. The support for complex expression is limited, and PyF may return a false positive if you try to format a complex lambda / case expression. Please open a ticket if you need that.+- Add support for literal `[]` and `()` in haskell expression.+- Add support for overloaded labels, thank you Shimuuar.+- Support for `::` in haskell expression. Such as `[fmt| 10 :: Int:d}|]`, as a suggestion from julm (close #87).+- `Integral` padding width and precision also for formatter without type specifier.+- Extra care was used to catch all `type-defaults` warning message. PyF should+  not generate code with this kind of warning, unless the embedded Haskell+  expression are ambiguous (e.g. `[fmt|{10}|]`). You can use `::` to+  disambiguate, e.g. `[fmt|{10 :: Int}|]`.++## 0.10.1.0 -- 2021-12-05++- Padding width can now be any arbitrary Haskell expression, such as `[fmt|hello pi = {pi:<{5 * 10}}|]`.+- Precision (and now padding width) arbitrary expression can now be any `Integral` and it is not limited to `Int` anymore.+- (Meta): type expression are now parsed and hence allowed inside arbitrary Haskell expression for padding width and precision. For example, `[fmt|Hello {pi:.{3 :: Int}}|]`.++## 0.10.0.1 -- 2021-10-30++- Due to the dependencies refactor, `PyF` no have no dependencies other than the one packaged with GHC. The direct result is that `PyF` build time is reduced to 6s versus 4 minutes and 20s before.++- Remove the dependency to `megaparsec` and replaces it by `parsec`. This should have minor impact on the error messages.++- *Huge Change*. The parsing of embeded expression does not depend anymore on `haskell-src-ext` and `haskell-src-meta` and instead depends on the built-in `ghc` lib.++- Added instances for `(Lazy)ByteString` to `PyFClassify` and `PyFToString`. `ByteString` can now be integrated into format string, and will be decoded as ascii.++- Relax the constraint for floating point formatting from `RealFrac` to `Real`. As a result, a few new type can be formatted as floating point number. One drawback is that some `Integral` are `Real` too and hence it is not an error anymore to format an integral as floating point, but you still need to explicitly select a floating point formatter.++- Added instance for `(Nominal)DiffTime` to `PyFClassify`, so you can now format them without conversion.++- Introducing of the new typeclass `PyfFormatIntegral` and `PyfFormatFractional` in order to customize the formatting for numbers. An instance is derived for respectively any `Integral` and `Real` types.++- Support for `Char` formatting, as string (showing the `Char` value) or as integral, showing the `ord`.++- `Data.Ratio`.++- Introducing `fmtTrim` module. It offers the same behavior as `fmt`, but trims common indentation. Se `PyF.trimIndent` for documentation.++- Introducing `raw` for convenience. It is a multiline string without any escaping, formatting neither leading whitespace handling.++- Introducing `str` and `strTrim`. They are similar to `fmt` and `fmtTrim` but without formatting. You can see them as multiline haskell string, with special character escaping, but without formatting. For convenience, the `strTrim` version also removes indentation.++- `fmtWithDelimiters` is gone and replaced by `mkFormatter` in `PyF` which is "more" generic.++## 0.9.0.3 -- 2021-02-06++- Test phase do not depend anymore on python (unless cabal flag `python_test` is+  set). This ease the deployment / packaging process.++## 0.9.0.2 -- 2020-09-11++- Version bump for megaparsec 9.0++## 0.9.0.1 -- 2020-03-25++- Fixs for GHC 8.10++## 0.9.0.0 -- 2019-12-29++- Any type with `Show` instance can be formatted using `:s` formatter. For example, `[fmt|hello {(True, 10):s}|]`. This breaks compatibility because previous version of PyF was generating an error when try to format to string anything which was not a string, now it accepts roughly anything (with a `Show` instance).++## 0.8.1.2 -- 2019-11-08++- Bump megaparsec bounds++## 0.8.1.1 -- 2019-10-13++- Compatibility with GHC 8.8++## 0.8.1.0 -- 2019-09-03++- Precision can now be any arbitrary haskell expression, such as `[fmt|hello pi = {pi:.{1 + 3}}|]`.++## 0.8.0.2 -- 2019-08-27++- (minor bugfix in tests): Use python3 instead of "python" to help build on environment with both python2 and python3++## 0.8.0.1 -- 2019-08-27++- Stack support++## 0.8.0.0 -- 2019-08-06++- `f` (and `fWithDelimiters`) were renamed `fmt` (`fmtWithDelimiters`). `f` was causing too much shadowing in any codebase.+- PyF now exposes the typeclass `PyFToString` and `PyFClassify` which can be extended to support any type as input for the formatters.+- PyF now uses `Data.String.IsString t` as its output type if `OverloadedString` is enabled. It means that it behaves as a real haskell string literal.+- A caveat of the previous change is that PyF does not have instances for `IO` anymore.++### bugfixes and general improvements++- An important amount of bugfixs+- Error reporting for generic formatting (i.e. formatting without a specified type) is now more robust+- Template haskell splices are simpler. This leads to more efficient / small generated code and in the event of this code appears in a GHC error message, it is more readable.+- PyF now longer emit unnecessary default typing.++## 0.7.3.0 -- 2019-02-28++- Tests: fix non reproducible tests++## 0.7.2.0 -- 2019-02-27++- Fixed: PyF now uses the same haskell extensions as the one used by the current haskell file to parse sub expressions.++## 0.7.1.0 -- 2019-02-11++- Fixed: PyF was wrongly ignoring everything located after a non-doubled closing delimiter.+- New Feature: line break can be escaped with , thus allowing string to start on a new line ignoring the initial backspace++## 0.7.0.0 -- 2019-02-04++- Bump dependencies to megaparsec 7+- Error message are now tested+- Name in template haskell splices are stable. This increases readability of error messages+- Better error message for badly formated expression++### Formatting removal++- All monomorphic quasiquoters (`f`, `fString`, `fText`, `fIO`, `fLazyText`) are removed+- Polymophic quasiquoter `f'` is renamed `f` and is the only entry point. Monomorphic users are encouraged to use the polymorphic quasiquoter with type annotation.+- `Formatting` dependency is removed.+- Previously named `f` quasiquoters which was exporting to `Formatting.Format` is removed. User of this behavior should use `Formatting.now` instead.+ ## 0.6.1.0 -- 2018-08-03  - Custom delimiters, you can use whatever delimiters you want in place of `{` and `}`.@@ -8,7 +173,6 @@  - Fix the espace parsing of `{{` and `}}` as `{` and `}` - ## 0.5.0.0 -- 2018-04-16  - Support for negative zero@@ -27,15 +191,14 @@ - Introduce `PyF.Formatters`, type safe generic number formatter solution - Remove dependency to `scientific` - ## 0.3.0.0 -- 2018-04-01 -* Support for haskell subexpression+- Support for haskell subexpression  ## 0.1.1.0  -- 2018-01-07 -* Add support for the `sign` field.+- Add support for the `sign` field.  ## 0.1.0.0  -- 2018-01-03 -* First version. Released on an unsuspecting world.+- First version. Released on an unsuspecting world.
PyF.cabal view
@@ -1,49 +1,107 @@-name:                PyF-version:             0.6.1.1-synopsis: Quasiquotations for a python like interpolated string formater-description: Quasiquotations for a python like interpolated string formater.-license:             BSD3-license-file:        LICENSE-author:              Guillaume Bouchard-maintainer:          guillaum.bouchard@gmail.com-category:            Text-build-type:          Simple-extra-source-files:  ChangeLog.md Readme.md-cabal-version:       >=1.10+cabal-version:      2.4+name:               PyF+version:            0.11.5.0+synopsis:+  Quasiquotations for a python like interpolated string formatter +description:+  Quasiquotations for a python like interpolated string formatter.++license:            BSD-3-Clause+license-file:       LICENSE+author:             Guillaume Bouchard+maintainer:         guillaum.bouchard@gmail.com+category:           Text+build-type:         Simple+extra-source-files:+  ChangeLog.md+  Readme.md+  test/golden/*.golden+  test/golden96/*.golden+ library   exposed-modules:-                  PyF-                  PyF.Internal.PythonSyntax-                  PyF.Internal.QQ-                  PyF.Formatters--  build-depends:       base >= 4.9 && < 5.0-                     , template-haskell >= 2.11 && < 2.14+    PyF+    PyF.Class+    PyF.Formatters+    PyF.Internal.Meta+    PyF.Internal.Parser+    PyF.Internal.ParserEx+    PyF.Internal.PythonSyntax+    PyF.Internal.QQ -                     -- Parsec and some transitive deps-                     , megaparsec >= 6.0 && < 6.6-                     , text >= 0.11 && < 1.3-                     , containers >= 0.5 && < 0.6+  build-depends:+    , base              >=4.12   && <4.23+    , bytestring        >=0.10.8 && <0.13+    , ghc               >=8.6.1+    , mtl               >=2.2.2  && <2.4+    , parsec            >=3.1.13 && <3.2+    , template-haskell  >=2.14.0 && <2.25+    , text              >=1.2.3  && <2.2+    , time              >=1.8.0  && <1.16 -                     -- Formatting and some transitive deps-                     , formatting >= 6.2 && < 6.4+  if impl(ghc <9.2.1)+    build-depends: ghc-boot >=8.6.1 && <9.7 -                     ---                     , haskell-src-meta-  hs-source-dirs: src-  ghc-options: -Wall-  default-language:    Haskell2010+  hs-source-dirs:     src+  ghc-options:        -Wall -Wunused-packages -Wincomplete-uni-patterns+  default-language:   Haskell2010+  default-extensions: QuasiQuotes  test-suite pyf-test-  type:                exitcode-stdio-1.0-  hs-source-dirs:      test-  main-is:             Spec.hs-  other-modules: SpecUtils SpecCustomDelimiters-  build-tools:  python3-  build-depends:       base, PyF, hspec, text, template-haskell, formatting, process-  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N-  default-language:    Haskell2010+  type:             exitcode-stdio-1.0+  hs-source-dirs:   test+  main-is:          Spec.hs+  other-modules:    SpecCustomDelimiters+  build-depends:+    , base+    , bytestring+    , hspec+    , PyF+    , template-haskell+    , text+    , time++  ghc-options:+    -Wall -threaded -rtsopts -with-rtsopts=-N -Wunused-packages++  default-language: Haskell2010++test-suite pyf-overloaded+  type:             exitcode-stdio-1.0+  hs-source-dirs:   test+  main-is:          SpecOverloaded.hs+  build-depends:+    , base+    , bytestring+    , hspec+    , PyF+    , text++  ghc-options:+    -Wall -threaded -rtsopts -with-rtsopts=-N -Wunused-packages++  default-language: Haskell2010++test-suite pyf-failure+  type:             exitcode-stdio-1.0+  hs-source-dirs:   test+  main-is:          SpecFail.hs+  build-depends:+    , base+    , deepseq+    , filepath+    , hspec+    , HUnit+    , process+    , PyF+    , temporary+    , text++  ghc-options:+    -Wall -threaded -rtsopts -with-rtsopts=-N -Wunused-packages++  default-language: Haskell2010  source-repository head   type:     git
Readme.md view
@@ -1,32 +1,20 @@+# PyF+ *PyF* is a Haskell library for string interpolation and formatting. -*PyF* exposes a quasiquoter `f` for the [Formatting](https://hackage.haskell.org/package/formatting) library. The quasiquotation introduces string interpolation and formatting with a mini language inspired from printf and Python.+*PyF* exposes a quasiquoter `fmt` which introduces string interpolation and formatting with a mini language inspired by printf and Python.  # Quick Start -The following *Formatting* example:- ```haskell->>> import Formatting-->>> name = "Dave"->>> age = 54-->>> format ("Person's name is " % text % ", age is " % hex) name age-"Person's name is Dave, age is 36"-```--can be written as:--```haskell->>> import Formatting+>>> :set -XQuasiQuotes >>> import PyF  >>> name = "Dave" >>> age = 54 ->>> format [f|Person's name is {name}, age is {age:x}|]-"Person's name is Dave, age is 36"+>>> [fmt|Person's name is {name}, age is {age}|]+"Person's name is Dave, age is 54" ```  The formatting mini language can represent:@@ -36,11 +24,11 @@ - Sign handling, to display or not the `+` for positive number - Number grouping - Floating point representation-- The interpolated value can be any Haskell expression+- The interpolated value can be any Haskell expression. -You will need the extension `QuasiQuotes`, enable it with `{-# LANGUAGE QuasiQuotes #-}` in top of your source file or with `:set -XQuasiQuotes` in your `ghci` session.+You will need the extension `QuasiQuotes`, enable it with `{-# LANGUAGE QuasiQuotes #-}` in top of your source file or with `:set -XQuasiQuotes` in your `ghci` session. `ExtendedDefaultRules` and `OverloadedStrings` may be more convenient. -Expression to be formatted are referenced by `{expression:formatingOptions}` where `formatingOptions` follows the [Python format mini-language](https://docs.python.org/3/library/string.html#formatspec). It is recommended to read the python documentation, but the [Test file](https://github.com/guibou/PyF/blob/master/test/Spec.hs) as well as this readme contain many examples.+Expression to be formatted are referenced by `{expression:formattingOptions}` where `formattingOptions` follows the [Python format mini-language](https://docs.python.org/3/library/string.html#formatspec). It is recommended to read the Python documentation, but the [Test file](https://github.com/guibou/PyF/blob/main/test/Spec.hs) as well as this readme contain many examples.  # More Examples @@ -50,38 +38,40 @@  ```haskell >>> name = "Guillaume"->>> format [f|{name:<11}|]+>>> [fmt|{name:<11}|] "Guillaume  "->>> format [f|{name:>11}|]+>>> [fmt|{name:>11}|] "  Guillaume"->>> format [f|{name:|^13}|]+>>> [fmt|{name:|^13}|] "||Guillaume||" ``` -Padding inside `=` the sign:+Padding inside `=` the sign  ```haskell->>> [fString|{-pi:=10.3}|]-"-    3.142"+>>> [fmt|{-3:=6}|]+"-    3" ```  ## Float rounding  ```haskell->>> format [f|{pi:.2}|]+>>> [fmt|{pi:.2}|] "3.14" ``` -## Binary / Octal / Hex representation (with or without prefix)+## Binary / Octal / Hex representation (with or without prefix using `#`)  ```haskell >>> v = 31->>> format [f|Binary: {v:#b}|]+>>> [fmt|Binary: {v:#b}|] "Binary: 0b11111"->>> format [f|Octal (no prefix): {age:o}|]+>>> [fmt|Octal: {v:#o}|]+"Octal: 0o37"+>>> [fmt|Octal (no prefix): {v:o}|] "Octal (no prefix): 37"->>> format [f|Hexa (caps and prefix): {age:#X}|]-"Hexa (caps and prefix): 0x1F"+>>> [fmt|Hexa (caps and prefix): {v:#X}|]+"Hexa (caps and prefix): 0X1F" ```  ## Grouping@@ -89,9 +79,9 @@ Using `,` or `_`.  ```haskell->>> [fString|{10 ^ 9 - 1:,}|]+>>> [fmt|{10 ^ 9 - 1:,}|] "999,999,999"->>> [fString|{2 ^ 32  -1:_b}|]+>>> [fmt|{2 ^ 32  -1:_b}|] "1111_1111_1111_1111_1111_1111_1111_1111" ``` @@ -100,10 +90,14 @@ Using `+` to display the positive sign (if any) or ` ` to display a space instead:  ```haskell->>> [fString|{pi:+.3}|]+>>> [fmt|{pi:+.3}|] "+3.142"->>> [fString|{pi: .3}|]+>>> [fmt|{-pi:+.3} (Negative number)|]+"-3.142 (Negative number)"+>>> [fmt|{pi: .3}|] " 3.142"+>>> [fmt|{-pi: .3} (Negative number)|]+"-3.142 (Negative number)" ```  ## 0@@ -111,7 +105,9 @@ Preceding the width with a `0` enables sign-aware zero-padding, this is equivalent to inside `=` padding with a fill char of `0`.  ```haskell->>> [fString{-10:010}|]+>>> [fmt|{10:010}|]+0000000010+>>> [fmt|{-10:010}|] -000000010 ``` @@ -120,9 +116,9 @@ First argument inside the curly braces can be a valid Haskell expression, for example:  ```haskell->>> format [f|2pi = {2* pi:.2}|]-6.28->>> format [f|tail "hello" = {tail "hello":->6}|]+>>> [fmt|2pi = {2* pi:.2}|]+2pi = 6.28+>>> [fmt|tail "hello" = {tail "hello":->6}|] "tail \"hello\" = --ello" ``` @@ -133,28 +129,58 @@ Most options can be combined. This generally leads to totally unreadable format string ;)  ```haskell->>> format [f|{pi:~>5.2}|]+>>> [fmt|{pi:~>5.2}|] "~~3.14" ``` -# Other quasiquoters+## Multi-line strings -*PyF* main entry point is `f` but for convenience some other quasiquoters are provided:+You can ignore a line break with `\` if needed. For example: -- `f(StrictText|LazyText|String|Builder|IO)` directly call the underlying `Formatting` runner and produce the specified type.-- `f'` use type inference to deduce the type.+```haskell+[fmt|\+- a+- b\+|]+``` -`PyF` reexport most of `Formatting` runners, such as `format`, `sformat`, `formatToString`, ...+Will returns `"- a\n- b"`. Note how the first and last line breaks are ignored. -For example:+## Arbitrary value for precision and padding +The precision and padding width fields can be any Haskell expression (including variables) instead of a fixed number:+ ```haskell->>> [f'|hello {pi.2}|] :: String+>>> [fmt|{pi:.{1+2}}|]+3.142+```++```haskell+>>> [fmt|{1986:^{2 * 10}d}|]+"        1986        "+```++# Output type++*PyF* aims at extending the string literal syntax. As such, it default to `String` type. However, if the `OverloadedString` is enabled, PyF will happilly generate `IsString t => t` instead. This means that you can use PyF to generate `String`, but also `Text` and why not `ByteString`, with all the caveats known to this extension.++```haskell+>>> [fmt|hello {pi.2}|] :: String "hello 3.14"->>> :type [fString|hello|]-[Char] ``` +# Custom types++PyF can format three categories of input types:++- Floating. Using the `f`, `g`, `e`, ... type specifiers. Any type instance of `RealFloat` can be formated as such.+- Integral. Using the `d`, `b`, `x`, `o`, ... type specifiers. Any type instance of `Integral` can be formated as such.+- String. Using the `s` type specifier. Any type instance of `PyFToString` can be formated as such.++See `PyF.Class` if you want to create new instances for the `PyFToString` class.++By default, if you do not provide any type specifier, PyF uses the `PyFClassify` type class to decide if your type must be formated as a Floating, Integral or String.+ # Caveats  ## Type inference@@ -163,14 +189,14 @@  ```haskell >>> v = 10 :: Double->>> [f|A float: {v}|]+>>> [fmt|A float: {v}|] A float: 10 ```  ## Error reporting -Template haskell is generally known to give developers a lot of-frustration when it comes to error message, dumping an unreadable+Template Haskell is generally known to give developers a lot of+frustration when it comes to error messages, dumping an unreadable piece of generated code.  However, in PyF, we took great care to provide clear error reporting, this means that:@@ -178,28 +204,37 @@ - Any parsing error on the mini language results in a clear indication of the error, for example:  ```haskell->>> [f|{age:.3d}|]+foo = [fmt|{age:.3d}|]+``` -<interactive>:77:4: error:-    • <interactive>:1:8:+```+File.hs:77:19: error:   | 1 | {age:.3d}   |        ^ Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field. ``` +Note: error reporting uses the native GHC error infrastructure, so they will correctly appear in your editor (using [HLS](https://github.com/haskell/haskell-language-server)), for example:++![Error reported in editor](error_example.png)+ - Error in variable name are also readable:  ```haskell->>> [f|{toto}|]-<interactive>:78:4: error: Variable not in scope: toto+test/SpecUtils.hs:81:33: error:+    • Variable not found: chien+    • In the quasi-quotation: [fmt|A missing variable: {chien}|]+   |+81 | fiz = [fmt|A missing variable: {chien}|]+   |                                 ^^^^^ ```  - However, if the interpolated name is not of a compatible type (or   too polymorphic), you will get an awful error:  ```haskell->>*> [fString|{True:d}|]+>>*> [fmt|{True:d}|]  <interactive>:80:10: error:     • No instance for (Integral Bool)@@ -207,34 +242,20 @@ ... ``` -- There is also one class of error related to alignement which can be triggered, when using alignement inside sign (i.e. `=`) with string. This can fail in two flavors:--```haskell->>> [fString|{"hello":=10s}|]--<interactive>:88:1: error:-    • Exception when trying to run compile-time code:-        String Cannot be aligned with the inside `=` mode-CallStack (from HasCallStack):-  error, called at src/PyF/Internal/QQ.hs:143:18 in PyF-0.4.0.0-inplace:PyF.Internal.QQ-      Code: quoteExp fString "{\"hello\":=10s}"-    • In the quasi-quotation: [fString|{"hello":=10s}|]-```--And+- There is also one class of error related to alignement which can be triggered, when using alignement inside sign (i.e. `=`) with string:  ```haskell-*PyF PyF.Internal.QQ> [fString|{"hello":=10}|]+*PyF PyF.Internal.QQ> [fmt|{"hello":=10}|]  <interactive>:89:10: error:     • String Cannot be aligned with the inside `=` mode ... ``` -- Finally, if you make any type error inside the expression field, you are on your own:+- Finally, if you make any type error inside the expression field, you are on your own, you'll get an awful error in the middle of the generated Template Haskell splice.  ```haskell->>> [fString|{3 + pi + "hello":10}|]+>>> [fmt|{3 + pi + "hello":10}|]  <interactive>:99:10: error:     • No instance for (Floating [Char]) arising from a use of ‘pi’@@ -243,7 +264,7 @@  ## Custom Delimiters -If `{` and `}` does not fit your needs, for example if you are formatting a lot of json, you can use custom delimiters. All quasi quoters have a parametric form which accepts custom delimiters. Due to template haskell stage restriction, you must define your custom quasi quoter in an other module.+If `{` and `}` do not fit your needs, for example if you are formatting a lot of JSON, you can use custom delimiters. All quasi quoters have a parametric form which accepts custom delimiters. Due to the Template Haskell stage restriction, you must define your custom quasi quoter in another module.  For example, in `MyCustomDelimiter.hs`: @@ -255,7 +276,9 @@ import PyF  myCustomFormatter :: QuasiQuoter-myCustomFormatter = fStringWithDelimiters ('@','!')+myCustomFormatter = mkFormatter "fmtWithDelimiters" (fmtConfig {+  delimiters = ('@','!')+  }) ```  Later, in another module:@@ -270,21 +293,23 @@  Escaping still works by doubling the delimiters, `@@!!@@!!` will be formatted as `@!@!`. -## Difference with the Python Syntax+Have a look at `PyF.mkFormatter` for all the details about customization. -The implementation is unit-tested against the reference python implementation (python 3.6.4) and should match its result. However some formatters are not supported or some (minor) differences can be observed.+## Differences with the Python Syntax +The implementation *was* unit-tested against the reference Python implementation (Python 3.6.4) and should match its result. Since 2025, the standalone test suite is not cross checked with the standard python implementation. However some formatters are not supported or some (minor) differences can be observed.+ ### Not supported -- Number `n` formatter is not supported. In python this formatter can format a number and use current locale information for decimal part and thousand separator. There is no plan to support that because of the impure interface needed to read the locale.-- Python support sub variables in the formatting options, such as `{varname:.{precision}}`, we should too. However should we accept `String` parameter (such as `<`), with a possible runtime error, or should we use the `ADT` such as `AlignRight`?-- Python literal integers accepts binary/octal/hexa/decimal literals, PyF only accept decimal ones, hdece in to plan to support that, if you really need to format a float with a number of digit provided as a binary constant, open an issue.-- Python support adding custom formatters for new types, such as date. This may be really cool, for example `[f|{today:%Y-%M-%D}`. I don't know how to support that now.+- Number `n` formatter is not supported. In Python this formatter can format a number and use current locale information for decimal part and thousand separator. There is no plan to support that because of the impure interface needed to read the locale.+- Python supports sub variables in the formatting options in all places, such as `{expression:.{precision}}`. We only support it for `precision` and `width`. This is more complexe to setup for others fields.+- Python literal integers accept binary/octal/hexa/decimal literals, PyF only accept decimal ones, I don't have a plan to support that, if you really need to format a float with a number of digit provided as a binary constant, open an issue.+- Python supports adding custom formatters for new types, such as date. This may be really cool, for example `[fmt|{today:%Y-%M-%D}`. I don't know how to support that now. -### Difference+### Differences  - General formatters *g* and *G* behaves a bit differently. Precision influence the number of significant digits instead of the number of the magnitude at which the representation changes between fixed and exponential.-- Grouping options allows grouping with an `_` for floating point, python only allows `,`.+- Grouping options allows grouping with an `_` for floating point, Python only allows `,`. - Custom delimiters  # Build / test@@ -293,19 +318,21 @@  ```shell nix-shell # Optional, if you use nix-cabal new-build-cabal new-test+cabal build+cabal test ``` -# TODO+There are a few available shells for you. -- Improve the error reporting with more Parsec annotation-- Improve the parser for sub-expression (handle the `:` and `}` cases if possible).-- Allow extension to others type / custom formatters (for date for example)-- Improve code quality. This code is really ugly, but there is a really strong test suite so, well.-- Work on performance, do we really care? For now, everything is internally done with `String`.-- Directly expose the formatter to be used as a template haskell splice+- `nix-shell` is the default, current GHC version with language server available.+- `nix-shell ./. -A pyf_xx.shell` is another GHC version (change `xx`) without language server.+- `nix-shell ./. -A pyf_xx.shell_hls` is another GHC version (change `xx`) with language server. +We also provide a few utility functions:++- `nix-build ./ -A hlint` will check hlint.+- `nix-shell ./ -A ormolu-fix` will format the codebase.+ # Library note  `PyF.Formatters` exposes two functions to format numbers. They are type-safe (as much as possible) and comes with a combination of formatting options not seen in other formatting libraries:@@ -315,7 +342,43 @@ "+~~~~~~~~~~1111,1111" ``` +# GHC compatibility++This library is tested in CI with ghc 8.6 to 9.12.+ # Conclusion  Don't hesitate to make any suggestion, I'll be more than happy to work on it. +# Hacking++Everything works with nix and flakes. But you can also try with manual cabal / stack if you wish.++- `nix develop` will open a shell with everything you need to work on PyF, including haskell-language-server. It may be a bit too much, so you can instead:+- `nix develop .#pyf_XY` opens a shell with a specific GHC version and without haskell-language-server. That's mostly to test compatibility with different GHC version or open a shell without HLS if you are in a hurry. Replace `pyf_XY` by a GHC version, e.g. `pyf_912`.++Once in the shell, use `cabal build`, `cabal test`, `cabal repl`.++There is a cachix available, used by CI, and already configured in flakes. You can manually run `cachix use guibou` if you want.++You can locally build and test everything using:++- `nix flake check`++Don't hesitate to submit a PR not tested on all GHC versions.++## Formatting++Please run:++- `nix run fmt`++Before submitting.++## Treesitter support++Have a look in the [./tree-sitter-pyf/](./tree-sitter-pyf) directory for a+parser of PyF which can be integrated in your treesitter compatible editor to+get syntax highlighting for PyF.++![](./tree-sitter-pyf/nvim_ts_highlight.png)
Setup.hs view
@@ -1,2 +1,3 @@ import Distribution.Simple+ main = defaultMain
src/PyF.hs view
@@ -1,119 +1,127 @@-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE FlexibleInstances #-}-{-# LANGUAGE TypeApplications #-}-{- | A lot of quasiquoters to format and interpolate string expression--}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE ViewPatterns #-}++-- | A lot of quasiquoters to format and interpolate string expressions. module PyF-  (f,-   f',-   fIO,-   fString,-   fBuilder,-   fLazyText,-   fStrictText,+  ( fmt,+    fmtTrim,+    str,+    strTrim,+    raw,+    module PyF.Class, -   -- * With custom delimiters-   fWithDelimiters,-   f'WithDelimiters,-   fIOWithDelimiters,-   fStringWithDelimiters,-   fBuilderWithDelimiters,-   fLazyTextWithDelimiters,-   fStrictTextWithDelimiters,+    -- * Whitespace utilities+    trimIndent, -   -- * Formatting re-export-   runFormat,-   format,-   sformat,-   bprint,-   fprint,-   hprint)+    -- * Configuration+    mkFormatter,+    defaultConfig,+    fmtConfig,+    strConfig,+    addTrim,+    addFormatting,+  ) where -import           Language.Haskell.TH.Quote (QuasiQuoter(..))-import qualified PyF.Internal.QQ as QQ--import Formatting (runFormat, format, sformat, bprint, fprint, hprint)-import qualified Formatting as F-import           Language.Haskell.TH--import qualified Data.Text.Lazy as LText-import qualified Data.Text as SText-import qualified Data.Text.Lazy.Builder as Builder--templateF :: (Char, Char) -> String -> QuasiQuoter-templateF delimiters fName = QuasiQuoter {-    quoteExp = QQ.toExp delimiters-  , quotePat = err "pattern"-  , quoteType = err "type"-  , quoteDec = err "declaration"-  }-  where-    err name = error (fName ++ ": This QuasiQuoter can not be used as a " ++ name ++ "!")---- | Returns an expression usable with Formatting.format (and similar functions)-f :: QuasiQuoter-f = templateF pythonDelimiters "f"--fWithDelimiters :: (Char, Char) -> QuasiQuoter-fWithDelimiters delimiters = templateF delimiters "fWithDelimiters"---- | Generic formatter, can format an expression to (lazy) Text, String, Builder and IO () depending on type inference-f' :: QuasiQuoter-f' = wrapQQ (templateF pythonDelimiters "f'") (VarE 'magicFormat)--f'WithDelimiters :: (Char, Char) -> QuasiQuoter-f'WithDelimiters delimiters = templateF delimiters "f'WithDelimiters"+import Data.Char (isSpace)+import Data.List (intercalate)+import Language.Haskell.TH.Quote (QuasiQuoter (..))+import PyF.Class+import PyF.Internal.QQ (Config (..), expQQ, toExp, wrapFromString) -wrapQQ :: QuasiQuoter -> Exp -> QuasiQuoter-wrapQQ qq wrap = qq {-  quoteExp = \s -> do-      e <- quoteExp qq s-      pure (AppE wrap e)-  }+-- | Generic formatter, can format an expression to any @t@ as long as+--   @t@ is an instance of 'IsString'.+fmt :: QuasiQuoter+fmt = mkFormatter "fmt" fmtConfig -class MagicFormat t where-  magicFormat :: F.Format t t -> t+-- | Format with whitespace trimming.+fmtTrim :: QuasiQuoter+fmtTrim = let+  qq = mkFormatter "fmtTrim" fmtConfig+  in qq { quoteExp = \s -> quoteExp qq (trimIndent  s) } -instance MagicFormat (IO ()) where-  magicFormat = F.fprint+-- | Multiline string, no interpolation.+str :: QuasiQuoter+str = mkFormatter "str" strConfig -instance MagicFormat [Char] where-  magicFormat = F.formatToString+-- | Multiline string, no interpolation, but does indentation trimming.+strTrim :: QuasiQuoter+strTrim = let qq = mkFormatter "strTrim" strConfig+  in qq { quoteExp = \s -> quoteExp qq (trimIndent  s) } -instance MagicFormat SText.Text where-  magicFormat = F.sformat+-- | Raw string, neither interpolation nor escaping is performed.+raw :: QuasiQuoter+raw = expQQ "raw" (\s -> [|s|]) -instance MagicFormat LText.Text where-  magicFormat = F.format+-- | Removes the trailing whitespace of a string.+--+-- - First line is ignored if it only contains whitespaces+-- - All other line common indentation is removed, ignoring lines with only whitespaces.+--+-- >>> trimIndent "\n   hello\n   - a\n   - b\n   "+-- "hello\n- a\n- b\n"+--+-- See 'fmtTrim' for a quasiquoter with this behavior.+trimIndent :: String -> String+trimIndent s =+  case lines s of+    [] -> ""+    [_] -> s+    (firstLine : others) ->+      let -- Discard the first line if needed+          usedLines+            | all isSpace firstLine = others ++ trail+            | otherwise = firstLine : others ++ trail -instance MagicFormat Builder.Builder where-  magicFormat = F.bprint+          -- If the string ends with a newline, `lines` will discard it. We restore it.+          trail+            | last s == '\n' = [""]+            | otherwise = []+          -- Find the minimum indent common to all lines+          biggestLines = map (length . takeWhile isSpace) (filter (not . all isSpace) usedLines) --- Monomorphic formatters-fIO, fString, fStrictText, fLazyText, fBuilder :: QuasiQuoter-fIOWithDelimiters, fStringWithDelimiters, fStrictTextWithDelimiters, fLazyTextWithDelimiters, fBuilderWithDelimiters :: (Char, Char) -> QuasiQuoter+          stripLen = case biggestLines of+            [] -> 0+            _ -> minimum biggestLines -fIO = fIOWithDelimiters pythonDelimiters-fString = fStringWithDelimiters pythonDelimiters-fStrictText = fStrictTextWithDelimiters pythonDelimiters-fLazyText = fLazyTextWithDelimiters pythonDelimiters-fBuilder = fBuilderWithDelimiters pythonDelimiters+          -- drop them+          trimmedLines = map (drop stripLen) usedLines+       in intercalate "\n" trimmedLines -pythonDelimiters :: (Char, Char)-pythonDelimiters = ('{', '}')+-- | This is an empty configuration. No formatting, no post processing+defaultConfig :: Config+defaultConfig =+  Config+    { delimiters = Nothing,+      postProcess = id+    } --- | Format the format string and directly print it to stdout-fIOWithDelimiters delimiters = wrapQQ (templateF delimiters "fIO") (VarE 'F.fprint)+-- | Configuration for 'str'. It just wraps the multiline string with 'fromString'.+strConfig :: Config+strConfig =+  Config+    { delimiters = Nothing,+      postProcess = wrapFromString+    } --- | Format the format string as a 'String'-fStringWithDelimiters delimiters = wrapQQ (templateF delimiters "fString") (VarE 'F.formatToString)+-- | The config for 'fmt'.+fmtConfig :: Config+fmtConfig = addFormatting ('{', '}') strConfig --- | Format the format string as a strict 'SText.Text'-fStrictTextWithDelimiters delimiters = wrapQQ (templateF delimiters "fStrictTeext") (VarE 'F.sformat)+-- | Add indentation trimming to a configuration.+addTrim :: Config -> Config+addTrim config =+  config+    { postProcess = \q -> postProcess config [|PyF.trimIndent $(q)|]+    } --- | Format the format string as a Lazy 'LText.Text'-fLazyTextWithDelimiters delimiters = wrapQQ (templateF delimiters "fLazy") (VarE 'F.sformat)+-- | Enable formatting.+addFormatting :: (Char, Char) -> Config -> Config+addFormatting delims c = c {delimiters = Just delims} --- | Format the format string as a 'Builder.Builder'-fBuilderWithDelimiters delimiters = wrapQQ (templateF delimiters "fBuilder") (VarE 'F.bprint)+-- | Build a formatter. See the 'Config' type for details, as well as+-- 'fmtConfig' and 'strConfig' for examples.+mkFormatter :: String -> Config -> QuasiQuoter+mkFormatter name config = expQQ name (toExp config)
+ src/PyF/Class.hs view
@@ -0,0 +1,205 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE UndecidableInstances #-}++-- | You want to add formatting support for your custom type. This is the right module.+--+-- In PyF, formatters are in three categories:+--+-- - Integral numbers, which are numbers without fractional part+-- - Fractional numbers, which are numbers with a fractional part+-- - String, which represents text.+--+-- The formatting can be either explicit or implicit. For example:+--+-- >>> let x = 10 in [fmt|{x}|]+-- 10+--+-- Is an implicit formatting to number, but:+--+-- >>> let x = 10 in [fmt|{x:d}|]+--+-- Is an explicit formatting to Integral numbers, using @d@.+--+-- Implicit formatting will only format to either Integral, Fractional or text,+-- and this choice is done by the (open) type family `PyFCategory'.+--+-- This modules also provides 3 type class for formatting.+--+-- - 'PyfFormatFractional' and 'PyfFormatIntegral' are responsible for+-- formatting integral and fractional numbers. Default instances are provided+-- respectively for 'Real' and 'Integral'. 'PyFToString' is in charge of+-- formatting text.+module PyF.Class where++import qualified Data.ByteString+import qualified Data.ByteString.Lazy+import Data.Char (ord)+import Data.Int+import qualified Data.Ratio+import qualified Data.Text as SText+import qualified Data.Text.Encoding as E+import qualified Data.Text.Lazy as LText+import qualified Data.Time+import Data.Word+import Numeric.Natural+import PyF.Formatters++-- * Default formatting classification++-- | The three categories of formatting in 'PyF'+data PyFCategory+  = -- | Format as an integral, no fractional part, precise value+    PyFIntegral+  | -- | Format as a fractional, approximate value with a fractional part+    PyFFractional+  | -- | Format as a string+    PyFString++-- | Classify a type to a 'PyFCategory'+--   This classification will be used to decide which formatting to+--   use when no type specifier in provided.+type family PyFClassify t :: PyFCategory++type instance PyFClassify Integer = 'PyFIntegral++type instance PyFClassify Int = 'PyFIntegral++type instance PyFClassify Int8 = 'PyFIntegral++type instance PyFClassify Int16 = 'PyFIntegral++type instance PyFClassify Int32 = 'PyFIntegral++type instance PyFClassify Int64 = 'PyFIntegral++type instance PyFClassify Natural = 'PyFIntegral++type instance PyFClassify Word = 'PyFIntegral++type instance PyFClassify Word8 = 'PyFIntegral++type instance PyFClassify Word16 = 'PyFIntegral++type instance PyFClassify Word32 = 'PyFIntegral++type instance PyFClassify Word64 = 'PyFIntegral++-- Float numbers++type instance PyFClassify Float = 'PyFFractional++type instance PyFClassify Double = 'PyFFractional++type instance PyFClassify Data.Time.DiffTime = 'PyFFractional++type instance PyFClassify Data.Time.NominalDiffTime = 'PyFFractional++type instance PyFClassify (Data.Ratio.Ratio i) = 'PyFFractional++-- String++type instance PyFClassify String = 'PyFString++type instance PyFClassify LText.Text = 'PyFString++type instance PyFClassify SText.Text = 'PyFString++type instance PyFClassify Data.ByteString.ByteString = 'PyFString++type instance PyFClassify Data.ByteString.Lazy.ByteString = 'PyFString++type instance PyFClassify Char = 'PyFString++-- * String formatting++-- | Convert a type to string+-- This is used for the string formatting.+class PyFToString t where+  pyfToString :: t -> String++instance PyFToString String where pyfToString = id++instance PyFToString LText.Text where pyfToString = LText.unpack++instance PyFToString SText.Text where pyfToString = SText.unpack++instance PyFToString Data.ByteString.ByteString where pyfToString = SText.unpack . E.decodeUtf8++instance PyFToString Data.ByteString.Lazy.ByteString where pyfToString = pyfToString . Data.ByteString.Lazy.toStrict++instance PyFToString Char where pyfToString c = [c]++-- | Default instance. Convert any type with a 'Show instance.+instance {-# OVERLAPPABLE #-} (Show t) => PyFToString t where pyfToString = show++-- * Real formatting (with optional fractional part)++-- | Apply a fractional formatting to any type.+--+-- A default instance for any 'Real' is provided which internally converts to+-- 'Double', which may not be efficient or results in rounding errors.+--+-- You can provide your own instance and internally use 'formatFractional'+-- which does have the same signatures as 'pyfFormatFractional' but with a+-- 'RealFrac' constraint.+class PyfFormatFractional a where+  pyfFormatFractional ::+    (Integral paddingWidth, Integral precision) =>+    Format t t' 'Fractional ->+    -- | Sign formatting+    SignMode ->+    -- | Padding+    Maybe (paddingWidth, AlignMode k, Char) ->+    -- | Grouping+    Maybe (Int, Char) ->+    -- | Precision+    Maybe precision ->+    a ->+    String++-- | Default instance working for any 'Real'. Internally it converts the type to 'Double'.+instance {-# OVERLAPPABLE #-} (Real t) => PyfFormatFractional t where+  pyfFormatFractional f s p g prec v = formatFractional f s p g prec (realToFrac @t @Double v)++-- | This instance does not do any conversion.+instance PyfFormatFractional Double where pyfFormatFractional = formatFractional++-- | This instance does not do any conversion.+instance PyfFormatFractional Float where pyfFormatFractional = formatFractional++-- * Integral formatting++-- | Apply an integral formatting to any type.+--+-- A default instance for any 'Integral' is provided.+--+-- You can provide your own instance and internally use 'formatIntegral'+-- which does have the same signatures as 'pyfFormatIntegral' but with an+-- 'Integral' constraint.+class PyfFormatIntegral i where+  pyfFormatIntegral ::+    (Integral paddingWidth) =>+    Format t t' 'Integral ->+    -- | Sign formatting+    SignMode ->+    -- | Padding+    Maybe (paddingWidth, AlignMode k, Char) ->+    -- | Grouping+    Maybe (Int, Char) ->+    i ->+    String++-- | Default instance for any 'Integral'.+instance {-# OVERLAPPABLE #-} (Integral t) => PyfFormatIntegral t where+  pyfFormatIntegral f s p g v = formatIntegral f s p g v++-- | Returns the numerical value of a 'Char'+-- >>> [fmt|{'a':d}|]+-- 97+instance PyfFormatIntegral Char where+  pyfFormatIntegral f s p g v = formatIntegral f s p g (ord v)
src/PyF/Formatters.hs view
@@ -1,60 +1,74 @@-{-# LANGUAGE DataKinds, KindSignatures, GADTs, ViewPatterns, OverloadedStrings, StandaloneDeriving, LambdaCase #-}-{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE DeriveLift #-}-{- |--Formatters for integral / fractional and strings.--Is support:--For all types:--  * Grouping of the integral part (i.e: adding a custom char to separate groups of digits)-  * Padding (left, right, around, and between the sign and the number)-  * Sign handling (i.e: display the positive sign or not)--For floating:--  * Precision-  * Fixed / Exponential / Generic formatting--For integrals:--   * Binary / Hexa / Octal / Character representation--}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE ViewPatterns #-} +-- |+--+-- Formatters for integral / fractional and strings.+--+-- The following is supported:+--+-- For all types:+--+--   * Grouping of the integral part (i.e. adding a custom char to separate groups of digits)+--   * Padding (left, right, around, and between the sign and the number)+--   * Sign handling (i.e. display the positive sign or not)+--+-- For floating:+--+--   * Precision+--   * Fixed / Exponential / Generic formatting+--+-- For integrals:+--+--    * Binary / Hexadecimal / Octal / Character representation module PyF.Formatters-  (-    -- * Generic formating function-    formatString-  , formatIntegral-  , formatFractional+  ( -- * Generic formatting function+    formatString,+    formatIntegral,+    formatFractional,+     -- * Formatter details-  , AltStatus(..)-  , UpperStatus(..)-  , FormatType (..)-  , Format(..)-  , SignMode(..)-  , AnyAlign(..)+    AltStatus (..),+    UpperStatus (..),+    FormatType (..),+    Format (..),+    SignMode (..),+    AnyAlign (..),+     -- * Internal usage only-  , AlignMode(..)-  , getAlignForString-  , AlignForString(..)-)+    AlignMode (..),+    getAlignForString,+    AlignForString (..),+  ) where -import Data.Monoid ((<>))+import Data.Char (chr, toUpper)+import Data.Data (Data) import Data.List (intercalate)-import Data.Char (toUpper, chr)-import qualified Numeric import Language.Haskell.TH.Syntax+import qualified Numeric  -- ADT for API+ -- | Sign handling-data SignMode = Plus -- ^ Display '-' sign and '+' sign-              | Minus -- ^ Only display '-' sign-              | Space -- ^ Display '-' sign and a space for positive numbers-  deriving (Show)+data SignMode+  = -- | Display '-' sign and '+' sign+    Plus+  | -- | Only display '-' sign+    Minus+  | -- | Display '-' sign and a space for positive numbers+    Space+  deriving (Show, Data)  data AlignForString = AlignAll | AlignNumber   deriving (Show)@@ -67,17 +81,19 @@   AlignRight :: AlignMode 'AlignAll   -- | Padding will be added between the sign and the number   AlignInside :: AlignMode 'AlignNumber-  -- | Padding will be added around the valueber+  -- | Padding will be added around the value   AlignCenter :: AlignMode 'AlignAll  deriving instance Show (AlignMode k)  -- The generic version+ -- | Existential version of 'AlignMode' data AnyAlign where   AnyAlign :: AlignMode (k :: AlignForString) -> AnyAlign  deriving instance Show AnyAlign+ deriving instance Lift AnyAlign  -- I hate how a must list all cases, any solution ?@@ -88,16 +104,16 @@ getAlignForString AlignCenter = Just AlignCenter getAlignForString AlignLeft = Just AlignLeft --- | This formatter support alternate version+-- | This formatter supports an alternate version. data AltStatus = CanAlt | NoAlt --- | This formatter support Upper case version+-- | This formatter support an uppercase version. data UpperStatus = CanUpper | NoUpper --- | This formatter formats an integral or a fractional+-- | This formatter formats an integral or a fractional. data FormatType = Fractional | Integral --- | All the Formatters+-- | All the Formatters. data Format (k :: AltStatus) (k' :: UpperStatus) (k'' :: FormatType) where   -- Integrals   Decimal :: Format 'NoAlt 'NoUpper 'Integral@@ -105,34 +121,40 @@   Binary :: Format 'CanAlt 'NoUpper 'Integral   Hexa :: Format 'CanAlt 'CanUpper 'Integral   Octal :: Format 'CanAlt 'NoUpper 'Integral-   -- Fractionals   Fixed :: Format 'CanAlt 'CanUpper 'Fractional   Exponent :: Format 'CanAlt 'CanUpper 'Fractional   Generic :: Format 'CanAlt 'CanUpper 'Fractional   Percent :: Format 'CanAlt 'NoUpper 'Fractional-   -- Meta formats   Alternate :: Format 'CanAlt u f -> Format 'NoAlt u f   -- Upper should come AFTER Alt, so this disallow any future alt   Upper :: Format alt 'CanUpper f -> Format 'NoAlt 'NoUpper f +newtype ShowIntegral i = ShowIntegral i+  deriving (Real, Enum, Ord, Eq, Num, Integral)++-- | Stupid instance in order to use 'Numeric.showIntAtBase' which needs a+-- 'Show' constraint for error reporting when number are negative.+-- However, in 'reprIntegral', there is no negative number, so the case is+-- impossible, but it allows the removal of the 'Show' constraint.+instance Show (ShowIntegral i) where+  show _ = error "show should not be called on ShowIntegral"+ -- Internal Integral--- Todo: remove the Show constraint ? -- Needed for debug in Numeric function, this is painful-reprIntegral :: (Show i, Integral i) => Format t t' 'Integral -> i -> Repr+reprIntegral :: (Integral i) => Format t t' 'Integral -> i -> Repr reprIntegral fmt i = IntegralRepr sign $ format fmt   where     format :: Format t t' 'Integral -> String     format = \case       Decimal -> Numeric.showInt iAbs ""-      Octal -> Numeric.showOct iAbs ""-      Binary -> Numeric.showIntAtBase 2 (\digit -> if digit == 0 then '0' else '1') iAbs ""-      Hexa -> Numeric.showHex iAbs ""+      Octal -> Numeric.showOct (ShowIntegral iAbs) ""+      Binary -> Numeric.showIntAtBase 2 (\digit -> if digit == 0 then '0' else '1') (ShowIntegral iAbs) ""+      Hexa -> Numeric.showHex (ShowIntegral iAbs) ""       Upper fmt' -> map toUpper $ format fmt'       Character -> [chr (fromIntegral i)]       Alternate fmt' -> format fmt'-     (sign, iAbs) = splitSign i  prefixIntegral :: Format t t' 'Integral -> String@@ -150,47 +172,54 @@ reprFractional fmt precision f   | isInfinite f = Infinite sign (upperIt "inf")   | isNaN f = NaN (upperIt "nan")-  | isNegativeZero f = let (FractionalRepr Positive aa bb cc) = reprFractional fmt precision (abs f)-                       in FractionalRepr Negative aa bb cc+  | isNegativeZero f = case reprFractional fmt precision (abs f) of+      FractionalRepr Positive aa bb cc -> FractionalRepr Negative aa bb cc+      other -> error $ "reprFractional (isNegativeZero f): The impossible happened : " ++ show other ++ ". Please open an issue at https://github.com/guibou/PyF/issues/"   | otherwise = FractionalRepr sign decimalPart fractionalPart suffixPart   where     upperIt s = case fmt of       Upper _ -> toUpper <$> s       _ -> s-     (sign, iAbs) = splitSign f     (decimalPart, fractionalPart, suffixPart) = format fmt-     format :: Format t t' 'Fractional -> (String, String, String)     format = \case       Fixed -> splitFractional (Numeric.showFFloatAlt precision iAbs "")       Exponent -> overrideExponent precision $ splitFractionalExp (Numeric.showEFloat precision iAbs "")       Generic -> splitFractionalExp (Numeric.showGFloatAlt precision iAbs "")-      Percent -> let (a, b, "") = splitFractional (Numeric.showFFloatAlt precision (iAbs * 100) "") in (a, b, "%")+      Percent -> case splitFractional (Numeric.showFFloatAlt precision (iAbs * 100) "") of+        (a, b, "") -> (a, b, "%")+        other -> error $ "reprFractional (format): The impossible happened : " ++ show other ++ ". Please open an issue at https://github.com/guibou/PyF/issues/"       Alternate fmt' -> format fmt'-      Upper fmt' -> let (a, b, c) = format fmt'-                        in (a, b, map toUpper c)-+      Upper fmt' ->+        let (a, b, c) = format fmt'+         in (a, b, map toUpper c)     splitFractional :: String -> (String, String, String)-    splitFractional s = let (a, b) = break (=='.') s-                        in (a, drop 1 b, "")+    splitFractional s =+      let (a, b) = break (== '.') s+       in (a, drop 1 b, "")  overrideExponent :: Maybe Int -> (String, String, String) -> (String, String, String) overrideExponent (Just 0) (a, "0", c) = (a, "", c) overrideExponent _ o = o  splitFractionalExp :: String -> (String, String, String)-splitFractionalExp s = let (a, b') = break (\c -> c == '.' || c == 'e' ) s-                           b = drop 1 b'-                           (fpart, e) = case b' of-                             'e':_ -> ("", b')-                             _ -> break (=='e') b-                       in (a, fpart, case e of-                            'e':'-':n -> "e-" ++ pad n-                            'e':n -> "e+" ++ pad n-                            leftover -> leftover)-                         where pad n@[_] = '0':n-                               pad n = n+splitFractionalExp s =+  let (a, b') = break (\c -> c == '.' || c == 'e') s+      b = drop 1 b'+      (fpart, e) = case b' of+        'e' : _ -> ("", b')+        _ -> break (== 'e') b+   in ( a,+        fpart,+        case e of+          'e' : '-' : n -> "e-" ++ pad n+          'e' : n -> "e+" ++ pad n+          leftover -> leftover+      )+  where+    pad n@[_] = '0' : n+    pad n = n  -- Cases Integral / Fractional @@ -199,7 +228,7 @@ group (FractionalRepr s a b d) (Just (size, c)) = FractionalRepr s (groupIntercalate c size a) b d group i _ = i -padAndSign :: Format t t' t'' -> String -> SignMode -> Maybe (Int, AlignMode k, Char) -> Repr -> String+padAndSign :: (Integral paddingWidth) => Format t t' t'' -> String -> SignMode -> Maybe (paddingWidth, AlignMode k, Char) -> Repr -> String padAndSign format prefix sign padding repr = leftAlignMode <> prefixStr <> middleAlignMode <> content <> rightAlignMode   where     (signStr, content) = case repr of@@ -208,17 +237,16 @@       Infinite s str -> (formatSign s sign, str)       NaN str -> ("", str)     prefixStr = signStr <> prefix-     len = length prefixStr + length content     (leftAlignMode, rightAlignMode, middleAlignMode) = case padding of       Nothing -> ("", "", "")-      Just (pad, padMode, padC) -> let-        padNeeded = max 0 (pad - len)-        in case padMode of-             AlignLeft -> ("", replicate padNeeded padC, "")-             AlignRight -> (replicate padNeeded padC, "", "")-             AlignCenter -> (replicate (padNeeded `div` 2) padC, replicate (padNeeded - padNeeded `div` 2) padC, "")-             AlignInside -> ("", "", replicate padNeeded padC)+      Just (fromIntegral -> pad, padMode, padC) ->+        let padNeeded = max 0 (pad - len)+         in case padMode of+              AlignLeft -> ("", replicate padNeeded padC, "")+              AlignRight -> (replicate padNeeded padC, "", "")+              AlignCenter -> (replicate (padNeeded `div` 2) padC, replicate (padNeeded - padNeeded `div` 2) padC, "")+              AlignInside -> ("", "", replicate padNeeded padC)  joinPoint :: Format t t' t'' -> String -> String -> String joinPoint (Upper f) a b = joinPoint f a b@@ -251,50 +279,63 @@  -- Final formatters --- | Format an integral number-formatIntegral :: (Show i, Integral i)-               => Format t t' 'Integral-               -> SignMode-               -> Maybe (Int, AlignMode k, Char) -- ^ Padding-               -> Maybe (Int, Char) -- ^ Grouping-               -> i-               -> String+-- | Format an integral number.+formatIntegral ::+  (Integral paddingWidth) =>+  (Integral i) =>+  Format t t' 'Integral ->+  SignMode ->+  -- | Padding+  Maybe (paddingWidth, AlignMode k, Char) ->+  -- | Grouping+  Maybe (Int, Char) ->+  i ->+  String formatIntegral f sign padding grouping i = padAndSign f (prefixIntegral f) sign padding (group (reprIntegral f i) grouping) --- | Format a fractional number-formatFractional-  :: (RealFloat f)-  => Format t t' 'Fractional-  -> SignMode-  -> Maybe (Int, AlignMode k, Char) -- ^ Padding-  -> Maybe (Int, Char) -- ^ Grouping-  -> Maybe Int -- ^ Precision-  -> f-  -> String-formatFractional f sign padding grouping precision i = padAndSign f "" sign padding (group (reprFractional f precision i) grouping)+-- | Format a fractional number.+formatFractional ::+  (RealFloat f, Integral paddingWidth, Integral precision) =>+  Format t t' 'Fractional ->+  SignMode ->+  -- | Padding+  Maybe (paddingWidth, AlignMode k, Char) ->+  -- | Grouping+  Maybe (Int, Char) ->+  -- | Precision+  Maybe precision ->+  f ->+  String+formatFractional f sign padding grouping precision i = padAndSign f "" sign padding (group (reprFractional f (fmap fromIntegral precision) i) grouping) --- | Format a string-formatString-  :: Maybe (Int, AlignMode 'AlignAll, Char) -- ^ Padding-  -> Maybe Int -- ^ Precision (will truncate before padding)-  -> String-  -> String+-- | Format a string.+formatString ::+  forall paddingWidth precision.+  (Integral paddingWidth, Integral precision) =>+  -- | Padding+  Maybe (paddingWidth, AlignMode 'AlignAll, Char) ->+  -- | Precision (will truncate before padding)+  Maybe precision ->+  String ->+  String formatString Nothing Nothing s = s-formatString Nothing (Just i) s = take i s-formatString (Just (padSize, padMode, padC)) size s = padLeft <> str <> padRight+formatString Nothing (Just i) s = take (fromIntegral i) s+formatString (Just (fromIntegral -> padSize, padMode, padC)) size s = padLeft <> str <> padRight   where-    str = formatString Nothing size s-+    str = formatString @paddingWidth Nothing size s     paddingLength = max 0 (padSize - length str)     (padLeft, padRight) = case padMode of-         AlignLeft -> ("", replicate paddingLength padC)-         AlignRight -> (replicate paddingLength padC, "")-         AlignCenter -> (replicate (paddingLength `div` 2) padC, replicate (paddingLength - paddingLength `div` 2) padC)+      AlignLeft -> ("", replicate paddingLength padC)+      AlignRight -> (replicate paddingLength padC, "")+      AlignCenter -> (replicate (paddingLength `div` 2) padC, replicate (paddingLength - paddingLength `div` 2) padC)+ -- TODO {- the . -}  deriving instance Lift (AlignMode k)+ deriving instance Lift SignMode+ deriving instance Lift (Format k k' k'')
+ src/PyF/Internal/Meta.hs view
@@ -0,0 +1,316 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE TemplateHaskellQuotes #-}+{-# LANGUAGE ViewPatterns #-}++module PyF.Internal.Meta (toExp, baseDynFlags, toName) where++import qualified Data.List.NonEmpty as NE++#if MIN_VERSION_ghc(9,2,0)+import GHC.Hs.Type (HsWildCardBndrs (..), HsType (..), HsSigType(HsSig), sig_body)+#elif MIN_VERSION_ghc(9,0,0)+import GHC.Hs.Type (HsWildCardBndrs (..), HsType (..), HsImplicitBndrs(HsIB), hsib_body)+#elif MIN_VERSION_ghc(8,10,0)+import GHC.Hs.Types (HsWildCardBndrs (..), HsType (..), HsImplicitBndrs (HsIB, hsib_body))+#else+import HsTypes (HsWildCardBndrs (..), HsType (..), HsImplicitBndrs (HsIB), hsib_body)+#endif++#if MIN_VERSION_ghc(9,6,0)+import Language.Haskell.Syntax.Basic (field_label)+#endif++#if MIN_VERSION_ghc(8,10,0)+import GHC.Hs.Expr as Expr+import GHC.Hs.Extension as Ext+import GHC.Hs.Pat as Pat+import GHC.Hs.Lit+#else+import HsExpr as Expr+import HsExtension as Ext+import HsPat as Pat+import HsLit+#endif++import qualified Data.ByteString as B+import qualified Language.Haskell.TH.Syntax as GhcTH+import qualified Language.Haskell.TH.Syntax as TH++#if MIN_VERSION_ghc(9,6,0)+import PyF.Internal.ParserEx (fakeSettings)+#else+import PyF.Internal.ParserEx (fakeLlvmConfig, fakeSettings)+#endif++#if MIN_VERSION_ghc(9,6,0)+import GHC.Types.SourceText (il_value, rationalFromFractionalLit,SourceText(..))+#endif++#if MIN_VERSION_ghc(9,0,0)+import GHC.Types.SrcLoc+import GHC.Types.Name+import GHC.Types.Name.Reader+import GHC.Data.FastString+#if MIN_VERSION_ghc(9,2,0)+import GHC.Utils.Outputable (ppr)+import GHC.Types.Basic (Boxity(..))+#if MIN_VERSION_ghc(9,6,0)+import GHC.Types.SourceText (FractionalLit)+#else+import GHC.Types.SourceText (il_value, rationalFromFractionalLit, FractionalLit)+#endif+import GHC.Driver.Ppr (showSDoc)+#else+import GHC.Utils.Outputable (ppr, showSDoc)+import GHC.Types.Basic (il_value, fl_value, Boxity(..))+#endif+import GHC.Driver.Session (DynFlags, xopt_set, defaultDynFlags)+import qualified GHC.Unit.Module as Module+#else+import SrcLoc+import Name+import RdrName+import FastString+import Outputable (ppr, showSDoc)+import BasicTypes (il_value, fl_value, Boxity(..))+import DynFlags (DynFlags, xopt_set, defaultDynFlags)+import qualified Module+#endif++import GHC.Stack++#if MIN_VERSION_ghc(9,2,0)+-- TODO: why this disapears in GHC >= 9.2?+fl_value :: FractionalLit -> Rational+fl_value = rationalFromFractionalLit+#endif++toLit :: HsLit GhcPs -> TH.Lit+toLit (HsChar _ c) = TH.CharL c+toLit (HsCharPrim _ c) = TH.CharPrimL c+toLit (HsString _ s) = TH.StringL (unpackFS s)+toLit (HsStringPrim _ s) = TH.StringPrimL (B.unpack s)+toLit (HsInt _ i) = TH.IntegerL (il_value i)+toLit (HsIntPrim _ i) = TH.IntPrimL i+toLit (HsWordPrim _ i) = TH.WordPrimL i+toLit (HsInt64Prim _ i) = TH.IntegerL i+toLit (HsWord64Prim _ i) = TH.WordPrimL i+#if MIN_VERSION_ghc(9,13,0)+-- toLit (HsRat _ f _) = TH.FloatPrimL (fl_value f)+-- toLit (HsInteger _ i _) = TH.IntegerL i+#else+toLit (HsRat _ f _) = TH.FloatPrimL (fl_value f)+toLit (HsInteger _ i _) = TH.IntegerL i+#endif+toLit (HsFloatPrim _ f) = TH.FloatPrimL (fl_value f)+toLit (HsDoublePrim _ f) = TH.DoublePrimL (fl_value f)+#if MIN_VERSION_ghc(9,7,0)+toLit (HsInt8Prim _ i) = TH.IntPrimL i+toLit (HsInt16Prim _ i) = TH.IntPrimL i+toLit (HsInt32Prim _ i) = TH.IntPrimL i+toLit (HsWord8Prim _ i) = TH.WordPrimL i+toLit (HsWord16Prim _ i) = TH.WordPrimL i+toLit (HsWord32Prim _ i) = TH.WordPrimL i+#endif++#if !MIN_VERSION_ghc(9,0,0)+toLit (XLit _) = noTH "toLit" "XLit"+#endif++toLit' :: OverLitVal -> TH.Lit+toLit' (HsIntegral i) = TH.IntegerL (il_value i)+toLit' (HsFractional f) = TH.RationalL (fl_value f)+toLit' (HsIsString _ fs) = TH.StringL (unpackFS fs)++toType :: HsType GhcPs -> TH.Type+toType (HsWildCardTy _) = TH.WildCardT+toType (HsTyVar _ _ n) =+  let n' = unLoc n+   in if isRdrTyVar n'+        then TH.VarT (toName n')+        else TH.ConT (toName n')+toType t = todo "toType" (showSDoc (baseDynFlags []) . ppr $ t)++toName :: RdrName -> TH.Name+toName n = case n of+  (Unqual o) -> TH.mkName (occNameString o)+  (Qual m o) -> TH.mkName (Module.moduleNameString m <> "." <> occNameString o)+  (Orig _m _o) -> error "PyFMeta: not supported toName (Orig _)"+  (Exact nm) -> case getOccString nm of+    "[]" -> '[]+    "()" -> '()+    _ -> error "toName: exact name encountered"++toFieldExp :: a+toFieldExp = undefined++toPat :: DynFlags -> Pat.Pat GhcPs -> TH.Pat+toPat _dynFlags (Pat.VarPat _ (unLoc -> name)) = TH.VarP (toName name)+toPat dynFlags p = todo "Advanced pattern match are not supported in PyF. See https://github.com/guibou/PyF/issues/107 if that's a problem for you." (showSDoc dynFlags . ppr $ p)++{- ORMOLU_DISABLE -}+toExp :: DynFlags -> Expr.HsExpr GhcPs -> TH.Exp+toExp _ (Expr.HsVar _ n) =+  let n' = unLoc n+   in if isRdrDataCon n'+        then TH.ConE (toName n')+        else TH.VarE (toName n')+#if MIN_VERSION_ghc(9, 13, 0)++#elif MIN_VERSION_ghc(9,6,0)+toExp _ (Expr.HsUnboundVar _ n)              = TH.UnboundVarE (TH.mkName . occNameString . rdrNameOcc $ n)+#elif MIN_VERSION_ghc(9,0,0)+toExp _ (Expr.HsUnboundVar _ n)              = TH.UnboundVarE (TH.mkName . occNameString $ n)+#else+toExp _ (Expr.HsUnboundVar _ n)              = TH.UnboundVarE (TH.mkName . occNameString . Expr.unboundVarOcc $ n)+#endif++toExp _ Expr.HsIPVar {} = noTH "toExp" "HsIPVar"+toExp _ (Expr.HsLit _ l) = TH.LitE (toLit l)+toExp _ (Expr.HsOverLit _ OverLit {ol_val}) = TH.LitE (toLit' ol_val)+toExp d (Expr.HsApp _ e1 e2) = TH.AppE (toExp d . unLoc $ e1) (toExp d . unLoc $ e2)+#if MIN_VERSION_ghc(9,10,0)+toExp d (Expr.HsAppType _ e HsWC{hswc_body}) = TH.AppTypeE (toExp d . unLoc $ e) (toType . unLoc $ hswc_body)+toExp d (Expr.ExprWithTySig _ e HsWC{hswc_body=unLoc -> HsSig{sig_body}}) = TH.SigE (toExp d . unLoc $ e) (toType . unLoc $ sig_body)+#elif MIN_VERSION_ghc(9,6,0)+toExp d (Expr.HsAppType _ e _ HsWC{hswc_body}) = TH.AppTypeE (toExp d . unLoc $ e) (toType . unLoc $ hswc_body)+toExp d (Expr.ExprWithTySig _ e HsWC{hswc_body=unLoc -> HsSig{sig_body}}) = TH.SigE (toExp d . unLoc $ e) (toType . unLoc $ sig_body)+#elif MIN_VERSION_ghc(9,2,0)+toExp d (Expr.HsAppType _ e HsWC {hswc_body}) = TH.AppTypeE (toExp d . unLoc $ e) (toType . unLoc $ hswc_body)+toExp d (Expr.ExprWithTySig _ e HsWC{hswc_body=unLoc -> HsSig{sig_body}}) = TH.SigE (toExp d . unLoc $ e) (toType . unLoc $ sig_body)+#elif MIN_VERSION_ghc(8,8,0)+toExp d (Expr.HsAppType _ e HsWC {hswc_body}) = TH.AppTypeE (toExp d . unLoc $ e) (toType . unLoc $ hswc_body)+toExp d (Expr.ExprWithTySig _ e HsWC{hswc_body=HsIB{hsib_body}}) = TH.SigE (toExp d . unLoc $ e) (toType . unLoc $ hsib_body)+#else+toExp d (Expr.HsAppType HsWC {hswc_body} e) = TH.AppTypeE (toExp d . unLoc $ e) (toType . unLoc $ hswc_body)+toExp d (Expr.ExprWithTySig HsWC{hswc_body=HsIB{hsib_body}} e) = TH.SigE (toExp d . unLoc $ e) (toType . unLoc $ hsib_body)+#endif+toExp d (Expr.OpApp _ e1 o e2) = TH.UInfixE (toExp d . unLoc $ e1) (toExp d . unLoc $ o) (toExp d . unLoc $ e2)+toExp d (Expr.NegApp _ e _) = TH.AppE (TH.VarE 'negate) (toExp d . unLoc $ e)+#if MIN_VERSION_ghc(9,13,0)+toExp d (Expr.HsLam _ _ (Expr.MG _ (unLoc -> (map unLoc -> [Expr.Match _ _ (unLoc -> map unLoc -> ps) (Expr.GRHSs _ (NE.toList -> [unLoc -> Expr.GRHS _ _ (unLoc -> e)]) _)])))) = TH.LamE (fmap (toPat d) ps) (toExp d e)+#elif MIN_VERSION_ghc(9,12,0)+-- NOTE: for lambda, there is only one match+toExp d (Expr.HsLam _ _ (Expr.MG _ (unLoc -> (map unLoc -> [Expr.Match _ _ (unLoc -> map unLoc -> ps) (Expr.GRHSs _ [unLoc -> Expr.GRHS _ _ (unLoc -> e)] _)])))) = TH.LamE (fmap (toPat d) ps) (toExp d e)+#elif MIN_VERSION_ghc(9,10,0)+toExp d (Expr.HsLam _ _ (Expr.MG _ (unLoc -> (map unLoc -> [Expr.Match _ _ (map unLoc -> ps) (Expr.GRHSs _ [unLoc -> Expr.GRHS _ _ (unLoc -> e)] _)])))) = TH.LamE (fmap (toPat d) ps) (toExp d e)+#elif MIN_VERSION_ghc(9,6,0)+toExp d (Expr.HsLam _ (Expr.MG _ (unLoc -> (map unLoc -> [Expr.Match _ _ (map unLoc -> ps) (Expr.GRHSs _ [unLoc -> Expr.GRHS _ _ (unLoc -> e)] _)])))) = TH.LamE (fmap (toPat d) ps) (toExp d e)+#else+toExp d (Expr.HsLam _ (Expr.MG _ (unLoc -> (map unLoc -> [Expr.Match _ _ (map unLoc -> ps) (Expr.GRHSs _ [unLoc -> Expr.GRHS _ _ (unLoc -> e)] _)])) _)) = TH.LamE (fmap (toPat d) ps) (toExp d e)+#endif+-- toExp (Expr.Let _ bs e)                       = TH.LetE (toDecs bs) (toExp e)+-- toExp (Expr.If _ a b c)                       = TH.CondE (toExp a) (toExp b) (toExp c)+-- toExp (Expr.MultiIf _ ifs)                    = TH.MultiIfE (map toGuard ifs)+-- toExp (Expr.Case _ e alts)                    = TH.CaseE (toExp e) (map toMatch alts)+-- toExp (Expr.Do _ ss)                          = TH.DoE (map toStmt ss)+-- toExp e@Expr.MDo{}                            = noTH "toExp" e+#if MIN_VERSION_ghc(9, 2, 0)+toExp d (Expr.ExplicitTuple _ args boxity) = ctor tupArgs+#else+toExp d (Expr.ExplicitTuple _ (map unLoc -> args) boxity) = ctor tupArgs+#endif+  where+    toTupArg (Expr.Present _ e) = Just $ unLoc e+    toTupArg (Expr.Missing _) = Nothing+    toTupArg _ = error "impossible case"++    ctor = case boxity of+      Boxed -> TH.TupE+      Unboxed -> TH.UnboxedTupE++#if MIN_VERSION_ghc(8,10,0)+    tupArgs = fmap ((fmap (toExp d)) . toTupArg) args+#else+    tupArgs = case traverse toTupArg args of+      Nothing -> error "Tuple section are not supported by template haskell < 8.10"+      Just args' -> fmap (toExp d) args'+#endif++{- ORMOLU_ENABLE -}++-- toExp (Expr.List _ xs)                        = TH.ListE (fmap toExp xs)+#if MIN_VERSION_ghc(9,10,0)+toExp d (Expr.HsPar _ e) = TH.ParensE (toExp d . unLoc $ e)+#elif MIN_VERSION_ghc(9,3,0)+toExp d (Expr.HsPar _ _ e _) = TH.ParensE (toExp d . unLoc $ e)+#else+toExp d (Expr.HsPar _ e) = TH.ParensE (toExp d . unLoc $ e)+#endif+toExp d (Expr.SectionL _ (unLoc -> a) (unLoc -> b)) = TH.InfixE (Just . toExp d $ a) (toExp d b) Nothing+toExp d (Expr.SectionR _ (unLoc -> a) (unLoc -> b)) = TH.InfixE Nothing (toExp d a) (Just . toExp d $ b)+toExp _ (Expr.RecordCon _ name HsRecFields {rec_flds}) =+  TH.RecConE (toName . unLoc $ name) (fmap toFieldExp rec_flds)++-- toExp (Expr.RecUpdate _ e xs)                 = TH.RecUpdE (toExp e) (fmap toFieldExp xs)+-- toExp (Expr.ListComp _ e ss)                  = TH.CompE $ map convert ss ++ [TH.NoBindS (toExp e)]+--  where+--   convert (Expr.QualStmt _ st)                = toStmt st+--   convert s                                   = noTH "toExp ListComp" s+-- toExp (Expr.ExpTypeSig _ e t)                 = TH.SigE (toExp e) (toType t)+#if MIN_VERSION_ghc(9, 2, 0)+toExp d (Expr.ExplicitList _ (map unLoc -> args)) = TH.ListE (map (toExp d) args)+#else+toExp d (Expr.ExplicitList _ _ (map unLoc -> args)) = TH.ListE (map (toExp d) args)+#endif+toExp d (Expr.ArithSeq _ _ e) = TH.ArithSeqE $ case e of+  (From a) -> TH.FromR (toExp d $ unLoc a)+  (FromThen a b) -> TH.FromThenR (toExp d $ unLoc a) (toExp d $ unLoc b)+  (FromTo a b) -> TH.FromToR (toExp d $ unLoc a) (toExp d $ unLoc b)+  (FromThenTo a b c) -> TH.FromThenToR (toExp d $ unLoc a) (toExp d $ unLoc b) (toExp d $ unLoc c)+#if MIN_VERSION_ghc(9,12,0)+toExp _ (HsOverLabel _ fs) = TH.LabelE (unpackFS fs)+#elif MIN_VERSION_ghc(9,7,0)+toExp _ (HsOverLabel _ lbl _) = TH.LabelE (fromSourceText lbl)+   where+     fromSourceText :: SourceText -> String+     fromSourceText (SourceText s) = unpackFS s+     fromSourceText NoSourceText = ""+#elif MIN_VERSION_ghc(9,6,0)+toExp _ (HsOverLabel _ lbl _) = TH.LabelE (fromSourceText lbl)+   where+     fromSourceText :: SourceText -> String+     fromSourceText (SourceText s) = s+     fromSourceText NoSourceText = ""+#elif MIN_VERSION_ghc(9, 2, 0)+toExp _ (HsOverLabel _ lbl) = TH.LabelE (unpackFS lbl)+#else+-- It's not quite clear what to do in case when overloaded syntax is+-- enabled thus match on Nothing+toExp _ (HsOverLabel _ Nothing lbl) = TH.LabelE (unpackFS lbl)+#endif+#if MIN_VERSION_ghc(9,12,0)+toExp dynFlags (HsGetField _ expr field) = TH.GetFieldE (toExp dynFlags (unLoc expr)) (unpackFS . field_label . unLoc . dfoLabel . unLoc $ field)+toExp _ (HsProjection _ fields) = TH.ProjectionE (fmap (unpackFS . unLoc . fmap field_label . dfoLabel) fields)+#elif MIN_VERSION_ghc(9,6,0)+toExp dynFlags (HsGetField _ expr field) = TH.GetFieldE (toExp dynFlags (unLoc expr)) (unpackFS . field_label . unLoc . dfoLabel . unLoc $ field)+toExp _ (HsProjection _ fields) = TH.ProjectionE (fmap (unpackFS . unLoc . fmap field_label . dfoLabel . unLoc) fields)+#elif MIN_VERSION_ghc(9, 4, 0)+toExp dynFlags (HsGetField _ expr field) = TH.GetFieldE (toExp dynFlags (unLoc expr)) (unpackFS . unLoc . dfoLabel . unLoc $ field)+toExp _ (HsProjection _ fields) = TH.ProjectionE (fmap (unpackFS . unLoc . dfoLabel . unLoc) fields)+#elif MIN_VERSION_ghc(9, 2, 0)+toExp dynFlags (HsGetField _ expr field) = TH.GetFieldE (toExp dynFlags (unLoc expr)) (unpackFS . unLoc . hflLabel . unLoc $ field)+toExp _ (HsProjection _ fields) = TH.ProjectionE (fmap (unpackFS . unLoc . hflLabel . unLoc) fields)+#endif+toExp dynFlags e = todo "toExp" (showSDoc dynFlags . ppr $ e)++todo :: (HasCallStack, Show e) => String -> e -> a+todo fun thing = error . concat $ [moduleName, ".", fun, ": not implemented: ", show thing, "Please open an issue at https://github.com/guibou/PyF/issues"]++noTH :: (HasCallStack, Show e) => String -> e -> a+noTH fun thing = error . concat $ [moduleName, ".", fun, ": no TemplateHaskell for: ", show thing, "Please open an issue at https://github.com/guibou/PyF/issues"]++moduleName :: String+moduleName = "PyF.Internal.Meta"++baseDynFlags :: [GhcTH.Extension] -> DynFlags+baseDynFlags exts = foldl xopt_set dynFlags enable+  where+    enable = GhcTH.TemplateHaskellQuotes : exts+#if MIN_VERSION_ghc(9,6,0)+    dynFlags = defaultDynFlags fakeSettings+#else+    dynFlags = defaultDynFlags fakeSettings fakeLlvmConfig+#endif
+ src/PyF/Internal/Parser.hs view
@@ -0,0 +1,137 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE ViewPatterns #-}++-- | This module is here to parse Haskell expression using the GHC Api+module PyF.Internal.Parser (parseExpression) where++#if MIN_VERSION_ghc(9,6,0)+import GHC.Parser.Errors.Types (PsMessage)+#endif++#if MIN_VERSION_ghc(9,0,0)+import GHC.Parser.Lexer (ParseResult (..), PState (..))+#elif MIN_VERSION_ghc(8,10,0)+import Lexer (ParseResult (..), PState (..))+#else+import Lexer (ParseResult (..))+#endif++#if MIN_VERSION_ghc(9,3,0)+import GHC.Types.Error+import GHC.Utils.Outputable+import GHC.Utils.Error+#endif++#if MIN_VERSION_ghc(9,4,0)+#elif MIN_VERSION_ghc(9,2,0)+import qualified GHC.Parser.Errors.Ppr as ParserErrorPpr+#endif++#if MIN_VERSION_ghc(9,0,0)+import qualified GHC.Types.SrcLoc as SrcLoc+#endif++#if MIN_VERSION_ghc(9,0,0)+import GHC.Driver.Session (DynFlags)+import GHC.Types.SrcLoc+#else+import DynFlags (DynFlags)+import SrcLoc+#endif++#if MIN_VERSION_ghc(8,10,0)+import GHC.Hs.Expr as Expr+import GHC.Hs.Extension as Ext+#else+import HsExpr as Expr+import HsExtension as Ext+import Outputable (showSDoc)+#endif++import qualified PyF.Internal.ParserEx as ParseExp++parseExpression :: RealSrcLoc -> String -> DynFlags -> Either (Int, Int, String) (HsExpr GhcPs)+parseExpression initLoc s dynFlags =+  case ParseExp.parseExpression initLoc s dynFlags of+    POk _ locatedExpr ->+      let expr = SrcLoc.unLoc locatedExpr+       in Right+            expr++{- ORMOLU_DISABLE #-}+#if MIN_VERSION_ghc(9,2,0)+    -- TODO messages?+    PFailed PState{loc=SrcLoc.psRealLoc -> srcLoc, errors=errorMessages} ->+#elif MIN_VERSION_ghc(9,0,0)+    PFailed PState{loc=SrcLoc.psRealLoc -> srcLoc, messages=msgs} ->+#elif MIN_VERSION_ghc(8,10,0)+    PFailed PState{loc=srcLoc, messages=msgs} ->+#else+    PFailed _ (SrcLoc.srcSpanEnd -> SrcLoc.RealSrcLoc srcLoc) doc ->+#endif++#if MIN_VERSION_ghc(9,7,0)+            let+                err = renderWithContext defaultSDocContext+                    $ vcat+                    $ map formatBulleted+                    $ map (\psMessage -> diagnosticMessage (defaultDiagnosticOpts @PsMessage) psMessage)+                    $ map errMsgDiagnostic+                    $ sortMsgBag Nothing+                    $ getMessages $ errorMessages+                line' = SrcLoc.srcLocLine srcLoc+                col = SrcLoc.srcLocCol srcLoc+            in Left (line', col, err)+#elif MIN_VERSION_ghc(9,6,0)+            let+                err = renderWithContext defaultSDocContext+                    $ vcat+                    $ map (formatBulleted defaultSDocContext)+                    $ map (\psMessage -> diagnosticMessage (defaultDiagnosticOpts @PsMessage) psMessage)+                    $ map errMsgDiagnostic+                    $ sortMsgBag Nothing+                    $ getMessages $ errorMessages+                line' = SrcLoc.srcLocLine srcLoc+                col = SrcLoc.srcLocCol srcLoc+            in Left (line', col, err)+#elif MIN_VERSION_ghc(9,3,0)+            let+                err = renderWithContext defaultSDocContext+                    $ vcat+                    $ map (formatBulleted defaultSDocContext)+                    $ map diagnosticMessage+                    $ map errMsgDiagnostic+                    $ sortMsgBag Nothing+                    $ getMessages $ errorMessages+                line = SrcLoc.srcLocLine srcLoc+                col = SrcLoc.srcLocCol srcLoc+            in Left (line, col, err)+#elif MIN_VERSION_ghc(9,2,0)+            let+                psErrToString e = show $ ParserErrorPpr.pprError e+                err = concatMap psErrToString errorMessages+                line = SrcLoc.srcLocLine srcLoc+                col = SrcLoc.srcLocCol srcLoc+            in Left (line, col, err)+#elif MIN_VERSION_ghc(8,10,0)+            let -- TODO: do not ignore "warnMessages"+                -- I have no idea what they can be+                (_warnMessages, errorMessages) = msgs dynFlags+                err = concatMap show errorMessages+                line = SrcLoc.srcLocLine srcLoc+                col = SrcLoc.srcLocCol srcLoc+            in Left (line, col, err)+#else+            let err = showSDoc dynFlags doc+                line = SrcLoc.srcLocLine srcLoc+                col = SrcLoc.srcLocCol srcLoc+            in Left (line, col, err)+#endif++#if MIN_VERSION_ghc(8,10,0)+#elif MIN_VERSION_ghc(8,6,0)+    -- Only here to satisfy GHC checker which was not able to consider this as total with GHC <8.10+    PFailed _ _ _ -> error "The impossible happen: this case is not possible"+#endif
+ src/PyF/Internal/ParserEx.hs view
@@ -0,0 +1,229 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ViewPatterns #-}+{-# OPTIONS_GHC -Wno-missing-fields -Wno-name-shadowing -Wno-unused-imports #-}++module PyF.Internal.ParserEx (fakeSettings, fakeLlvmConfig, parseExpression) where++{- ORMOLU_DISABLE -}++#if MIN_VERSION_ghc(9,6,0)+import GHC.Settings.Config+import GHC.CmmToLlvm.Config (LlvmConfig(..))+import GHC.Utils.Fingerprint+import GHC.Platform+import GHC.Settings+#elif MIN_VERSION_ghc(9,0,0)+import GHC.Settings.Config+import GHC.Driver.Session+import GHC.Utils.Fingerprint+import GHC.Platform+import GHC.Settings+#elif MIN_VERSION_ghc(8, 10, 0)+import Config+import DynFlags+import Fingerprint+import GHC.Platform+import ToolSettings+#else+import Config+import DynFlags+import Fingerprint+import Platform+#endif++#if MIN_VERSION_ghc(8,10,0)+import GHC.Hs+#else+import HsSyn+#endif++#if MIN_VERSION_ghc(9, 2, 0)+import GHC.Driver.Config+#endif++#if MIN_VERSION_ghc(9,0,0)+import GHC.Parser.PostProcess+import GHC.Driver.Session+import GHC.Data.StringBuffer+import GHC.Parser.Lexer+import qualified GHC.Parser.Lexer as Lexer+import qualified GHC.Parser as Parser+import GHC.Data.FastString+import GHC.Types.SrcLoc+import GHC.Driver.Backpack.Syntax+import GHC.Unit.Info+import GHC.Types.Name.Reader+#else+import StringBuffer+import Lexer+import qualified Parser+import FastString+import SrcLoc+import RdrName+#endif++#if MIN_VERSION_ghc(9, 0, 0)+#else+import RdrHsSyn+#endif++import Data.Data hiding (Fixity)++#if MIN_VERSION_ghc(9,0,0)+import GHC.Hs++#if MIN_VERSION_ghc(9, 2, 0)+import GHC.Types.Fixity+import GHC.Types.SourceText+#else+import GHC.Types.Basic+#endif++import GHC.Types.Name.Reader+import GHC.Types.Name+import GHC.Types.SrcLoc+#elif MIN_VERSION_ghc(8, 10, 0)+import BasicTypes+import OccName+#else+import BasicTypes+import OccName+#endif++#if MIN_VERSION_ghc(9,3,0)+import GHC.Driver.Config.Parser (initParserOpts)+#endif++#if MIN_VERSION_ghc(9,13,0)+import GHC.Unit.Types (UnitId(..))+#endif++import Data.Maybe++fakeSettings :: Settings+fakeSettings = Settings+#if MIN_VERSION_ghc(9, 13, 0)+  { sGhcNameVersion=ghcNameVersion+  , sFileSettings=fileSettings+  , sTargetPlatform=platform+  , sToolSettings=toolSettings+  , sPlatformMisc=platformMisc+  , sUnitSettings = UnitSettings (UnitId $ fsLit "pyf-preprocessor")+  }+#elif MIN_VERSION_ghc(9, 2, 0)+  { sGhcNameVersion=ghcNameVersion+  , sFileSettings=fileSettings+  , sTargetPlatform=platform+  , sToolSettings=toolSettings+  , sPlatformMisc=platformMisc+  }+#elif MIN_VERSION_ghc(8, 10, 0)+  { sGhcNameVersion=ghcNameVersion+  , sFileSettings=fileSettings+  , sTargetPlatform=platform+  , sPlatformMisc=platformMisc+  , sPlatformConstants=platformConstants+  , sToolSettings=toolSettings+  }+#else+  { sTargetPlatform=platform+  , sPlatformConstants=platformConstants+  , sProjectVersion=cProjectVersion+  , sProgramName="ghc"+  , sOpt_P_fingerprint=fingerprint0+  }+#endif+  where+#if MIN_VERSION_ghc(8, 10, 0)+    toolSettings = ToolSettings {+      toolSettings_opt_P_fingerprint=fingerprint0+      }+    fileSettings = FileSettings {}+    platformMisc = PlatformMisc {}+    ghcNameVersion =+      GhcNameVersion{ghcNameVersion_programName="ghc"+                    ,ghcNameVersion_projectVersion=cProjectVersion+                    }+#endif+    platform =+      Platform{+#if MIN_VERSION_ghc(9, 0, 0)+    -- It doesn't matter what values we write here as these fields are+    -- not referenced for our purposes. However the fields are strict+    -- so we must say something.+        platformByteOrder=LittleEndian+      , platformHasGnuNonexecStack=True+      , platformHasIdentDirective=False+      , platformHasSubsectionsViaSymbols=False+      , platformIsCrossCompiling=False+      , platformLeadingUnderscore=False+      , platformTablesNextToCode=False+#if MIN_VERSION_ghc(9, 2, 0)+      , platform_constants=platformConstants+#endif+#if MIN_VERSION_ghc(9, 3, 0)+      , platformHasLibm=True+#endif++      ,+#endif++#if MIN_VERSION_ghc(9, 2, 0)+        platformWordSize=PW8+      , platformArchOS=ArchOS {archOS_arch=ArchUnknown, archOS_OS=OSUnknown}+#elif MIN_VERSION_ghc(8, 10, 0)+        platformWordSize=PW8+      , platformMini=PlatformMini {platformMini_arch=ArchUnknown, platformMini_os=OSUnknown}+#else+        platformWordSize=8+      , platformOS=OSUnknown+#endif+      , platformUnregisterised=True+      }+#if MIN_VERSION_ghc(9, 2, 0)+    platformConstants = Nothing+#else+    platformConstants =+      PlatformConstants{pc_DYNAMIC_BY_DEFAULT=False,pc_WORD_SIZE=8}+#endif++#if MIN_VERSION_ghc(8, 10, 0)+fakeLlvmConfig :: LlvmConfig+fakeLlvmConfig = LlvmConfig [] []+#else+fakeLlvmConfig :: (LlvmTargets, LlvmPasses)+fakeLlvmConfig = ([], [])+#endif++-- From Language.Haskell.GhclibParserEx.GHC.Parser++parse :: RealSrcLoc -> P a -> String -> DynFlags -> ParseResult a+parse initLoc p str flags =+  Lexer.unP p parseState+  where+    buffer = stringToStringBuffer str+    parseState =+#if MIN_VERSION_ghc(9, 2, 0)+      initParserState (initParserOpts flags) buffer initLoc+#else+      mkPState flags buffer initLoc+#endif++#if MIN_VERSION_ghc(9, 2, 0)+parseExpression :: RealSrcLoc -> String -> DynFlags -> ParseResult (LocatedA (HsExpr GhcPs))+parseExpression initLoc s flags =+  case parse initLoc Parser.parseExpression s flags of+    POk s e -> unP (runPV (unECP e)) s+    PFailed ps -> PFailed ps+#elif MIN_VERSION_ghc(8, 10, 0)+parseExpression :: RealSrcLoc -> String -> DynFlags -> ParseResult (Located (HsExpr GhcPs))+parseExpression initLoc s flags =+  case parse initLoc Parser.parseExpression s flags of+    POk s e -> unP (runECP_P e) s+    PFailed ps -> PFailed ps+#else+parseExpression :: RealSrcLoc -> String -> DynFlags -> ParseResult (LHsExpr GhcPs)+parseExpression initLoc = parse initLoc Parser.parseExpression+#endif
src/PyF/Internal/PythonSyntax.hs view
@@ -1,41 +1,66 @@-{-# LANGUAGE PatternSynonyms #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE StandaloneDeriving #-}-{-# LANGUAGE DeriveLift #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE KindSignatures #-}-{-# LANGUAGE DataKinds #-}-{- |-This module provides a parser for <https://docs.python.org/3.4/library/string.html#formatspec python format string mini language>.--}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms #-}++-- |+-- This module provides a parser for <https://docs.python.org/3.4/library/string.html#formatspec python format string mini language>. module PyF.Internal.PythonSyntax-  ( parsePythonFormatString-  , parseGenericFormatString-  , Item(..)-  , FormatMode(..)-  , Padding(..)-  , Precision(..)-  , TypeFormat(..)-  , AlternateForm(..)-  , pattern DefaultFormatMode+  ( parseGenericFormatString,+    Item (..),+    FormatMode (..),+    Padding (..),+    Precision (..),+    TypeFormat (..),+    AlternateForm (..),+    pattern DefaultFormatMode,+    Parser,+    ParsingContext (..),+    ExprOrValue (..),   ) where -import Language.Haskell.TH.Syntax+import Control.Applicative (some)+import Control.Monad (replicateM_, void)+import Control.Monad.Reader (Reader, asks)+import qualified Data.Char+import Data.Data (Data)+import Data.Maybe (fromMaybe)+import GHC (GhcPs, HsExpr)+import Language.Haskell.TH.LanguageExtensions (Extension (..))+import Language.Haskell.TH.Syntax (Exp)+import PyF.Formatters+import PyF.Internal.Meta+import qualified PyF.Internal.Parser as ParseExp+import Text.Parsec -import Text.Megaparsec-import qualified Text.Megaparsec.Char.Lexer as L-import Text.Megaparsec.Char-import Data.Void (Void)+#if MIN_VERSION_ghc(9,7,0) -import qualified Data.Char+#elif MIN_VERSION_ghc(9,6,0)+-- For some reasons, theses function are not exported anymore by some others+import Data.Functor (void)+import Control.Monad (replicateM_)+#endif -import Data.Maybe (fromMaybe)+#if MIN_VERSION_ghc(9,0,0)+import GHC.Types.SrcLoc+import GHC.Data.FastString+#else+import SrcLoc+import FastString+#endif -import qualified Data.Set as Set -- For fancyFailure-import PyF.Formatters+type Parser t = ParsecT String () (Reader ParsingContext) t -type Parser t = Parsec Void String t+data ParsingContext = ParsingContext+  { delimiters :: Maybe (Char, Char),+    enabledExtensions :: [Extension]+  }+  deriving (Show)  {- -- TODO:@@ -46,7 +71,6 @@      - types: n -} - {- f_string          ::=  (literal_char | "{{" | "}}" | replacement_field)* replacement_field ::=  "{" f_expression ["!" conversion] [":" format_spec] "}"@@ -59,75 +83,121 @@ -}  -- | A format string is composed of many chunks of raw string or replacement-data Item = Raw String -- ^ A raw string-           | Replacement String (Maybe FormatMode) -- ^ A replacement string, composed of an arbitrary Haskell expression followed by an optional formatter-           deriving (Show)+data Item+  = -- | A raw string+    Raw String+  | -- | A replacement string, composed of an arbitrary Haskell expression followed by an optional formatter+    Replacement (HsExpr GhcPs, Exp) (Maybe FormatMode) -{- |-Parse a string, returns a list of raw string or replacement fields+-- |+-- Parse a string, returns a list of raw string or replacement fields+--+-- >>> import Text.Megaparsec+-- >>> parse parsePythonFormatString "" "hello {1+1:>10.2f}"+-- Right [+--        Raw "hello ",+--        Replacement "1+1"+--        (+--        Just (FormatMode+--                       (Padding 10 (Just (Nothing,AnyAlign AlignRight)))+--                       (FixedF (Precision 2) NormalForm Minus)+--                        Nothing))]+parseGenericFormatString :: Parser [Item]+parseGenericFormatString = do+  delimitersM <- asks delimiters ->>> import Text.Megaparsec->>> parse parsePythonFormatString "" "hello {1+1:>10.2f}"-Right [-       Raw "hello ",-       Replacement "1+1"-       (-       Just (FormatMode-                      (Padding 10 (Just (Nothing,AnyAlign AlignRight)))-                      (FixedF (Precision 2) NormalForm Minus)-                       Nothing))]--}-parsePythonFormatString :: Parser [Item]-parsePythonFormatString = parseGenericFormatString ('{', '}')+  case delimitersM of+    Nothing -> many (rawString Nothing)+    Just _ -> many (rawString delimitersM <|> escapedParenthesis <|> replacementField) <* eof -parseGenericFormatString :: (Char, Char) -> Parser [Item]-parseGenericFormatString delimiters = many (rawString delimiters <|> escapedParenthesis delimiters <|> replacementField delimiters)+rawString :: Maybe (Char, Char) -> Parser Item+rawString delimsM = do+  let delims = case delimsM of+        Nothing -> []+        Just (openingChar, closingChar) -> [openingChar, closingChar] -rawString :: (Char, Char) -> Parser Item-rawString (openingChar,closingChar) = Raw . escapeChars <$> some (noneOf ([openingChar, closingChar]))+  -- lookahead+  let p = some (noneOf delims)+  chars <- lookAhead p -escapedParenthesis :: (Char, Char) -> Parser Item-escapedParenthesis (openingChar, closingChar) = Raw <$> (parseRaw openingChar <|> parseRaw closingChar)-  where parseRaw c = c:[] <$ string (replicate 2 c)+  case escapeChars chars of+    Left remaining -> do+      -- Consume up to the error location+      void $ count (length chars - length remaining) anyChar+      fail "Lexical error in literal section"+    Right escaped -> do+      -- Consumne everything+      void p+      return (Raw escaped) -{- | Replace escape chars with their value->>> escapeChars "hello \\n"-"hello \n"--}-escapeChars :: String -> String-escapeChars "" = ""+escapedParenthesis :: Parser Item+escapedParenthesis = do+  Just (openingChar, closingChar) <- asks delimiters+  Raw <$> (parseRaw openingChar <|> parseRaw closingChar)+  where+    parseRaw c = [c] <$ try (string (replicate 2 c))++-- | Replace escape chars with their value. Results in a Left with the+-- remainder of the string on encountering a lexical error (such as a bad escape+-- sequence).+-- >>> escapeChars "hello \\n"+-- Right "hello \n"+-- >>> escapeChars "hello \\x"+-- Left "\\x"+escapeChars :: String -> Either String String+escapeChars "" = Right ""+escapeChars ('\\' : '\n' : xs) = escapeChars xs+escapeChars ('\\' : '\\' : xs) = ('\\' :) <$> escapeChars xs escapeChars s = case Data.Char.readLitChar s of-                  [] -> ""-                  ((c, xs):_) -> c : escapeChars xs+  ((c, xs) : _) -> (c :) <$> escapeChars xs+  _ -> Left s -replacementField :: (Char, Char) -> Parser Item-replacementField (charOpening, charClosing) = do+-- | Parses the expression field (i.e. what's appear before the format field)+parseExpressionString :: Parser String+parseExpressionString = do+  Just (_charOpening, charClosing) <- asks delimiters+  -- Special case for "::", we want to parse it as part of an expression,+  -- unless it may be the end of the format field (':'), followed by a padding+  -- char (':') followed by a padding specifier.+  res <- some (try (string "::" <* notFollowedBy (oneOf "<>=^")) <|> (pure <$> noneOf (charClosing : ":" :: String)))+  pure $ concat res++replacementField :: Parser Item+replacementField = do+  exts <- asks enabledExtensions+  Just (charOpening, charClosing) <- asks delimiters   _ <- char charOpening-  expr <- many (noneOf (charClosing:":"))-  fmt <- optional $ do+  expr <- evalExpr exts (parseExpressionString <?> "an haskell expression")+  fmt <- optionMaybe $ do     _ <- char ':'-    format_spec+    formatSpec   _ <- char charClosing-   pure (Replacement expr fmt) --- | Default formating mode, no padding, default precision, no grouping, no sign handling+-- | Default formatting mode, no padding, default precision, no grouping, no sign handling pattern DefaultFormatMode :: FormatMode pattern DefaultFormatMode = FormatMode PaddingDefault (DefaultF PrecisionDefault Minus) Nothing  -- | A Formatter, listing padding, format and and grouping char data FormatMode = FormatMode Padding TypeFormat (Maybe Char)-                deriving (Show)  -- | Padding, containing the padding width, the padding char and the alignement mode-data Padding = PaddingDefault-             | Padding Integer (Maybe (Maybe Char, AnyAlign))-             deriving (Show)+data Padding+  = PaddingDefault+  | Padding (ExprOrValue Int) (Maybe (Maybe Char, AnyAlign)) +-- | Represents a value of type @t@ or an Haskell expression supposed to represents that value+data ExprOrValue t+  = Value t+  | HaskellExpr (HsExpr GhcPs, Exp)+  deriving (Data)+ -- | Floating point precision-data Precision = PrecisionDefault-               | Precision Integer-               deriving (Show)+data Precision+  = PrecisionDefault+  | Precision (ExprOrValue Int)+  deriving (Data)+ {-  Python format mini language@@ -145,98 +215,165 @@ data TypeFlag = Flagb | Flagc | Flagd | Flage | FlagE | Flagf | FlagF | Flagg | FlagG | Flagn | Flago | Flags | Flagx | FlagX | FlagPercent   deriving (Show) --- | All formating type-data TypeFormat =-    DefaultF Precision SignMode -- ^ Default, depends on the infered type of the expression-  | BinaryF AlternateForm SignMode -- ^ Binary, such as `0b0121`-  | CharacterF -- ^ Character, will convert an integer to its character representation-  | DecimalF SignMode -- ^ Decimal, base 10 integer formatting-  | ExponentialF Precision AlternateForm SignMode -- ^ Exponential notation for floatting points-  | ExponentialCapsF Precision AlternateForm SignMode -- ^ Exponential notation with capitalised 'e'-  | FixedF Precision AlternateForm SignMode -- ^ Fixed number of digits floating point-  | FixedCapsF Precision AlternateForm SignMode -- ^ Capitalized version of the previous-  | GeneralF Precision AlternateForm SignMode -- ^ General formatting: `FixedF` or `ExponentialF` depending on the number magnitude-  | GeneralCapsF Precision AlternateForm SignMode -- ^ Same as `GeneralF` but with upper case 'E' and infinite / NaN-  | OctalF AlternateForm SignMode -- ^ Octal, such as 00245-  | StringF Precision -- ^ Simple string-  | HexF AlternateForm SignMode -- ^ Hexadecimal, such as 0xaf3e-  | HexCapsF AlternateForm SignMode -- ^ Hexadecimal with capitalized letters, such as 0XAF3E-  | PercentF Precision AlternateForm SignMode -- ^ Percent representation-  deriving (Show)+-- | All formatting type+data TypeFormat+  = -- | Default, depends on the infered type of the expression+    DefaultF Precision SignMode+  | -- | Binary, such as `0b0121`+    BinaryF AlternateForm SignMode+  | -- | Character, will convert an integer to its character representation+    CharacterF+  | -- | Decimal, base 10 integer formatting+    DecimalF SignMode+  | -- | Exponential notation for floatting points+    ExponentialF Precision AlternateForm SignMode+  | -- | Exponential notation with capitalised @e@+    ExponentialCapsF Precision AlternateForm SignMode+  | -- | Fixed number of digits floating point+    FixedF Precision AlternateForm SignMode+  | -- | Capitalized version of the previous+    FixedCapsF Precision AlternateForm SignMode+  | -- | General formatting: `FixedF` or `ExponentialF` depending on the number magnitude+    GeneralF Precision AlternateForm SignMode+  | -- | Same as `GeneralF` but with upper case @E@ and infinite / NaN+    GeneralCapsF Precision AlternateForm SignMode+  | -- | Octal, such as 00245+    OctalF AlternateForm SignMode+  | -- | Simple string+    StringF Precision+  | -- | Hexadecimal, such as 0xaf3e+    HexF AlternateForm SignMode+  | -- | Hexadecimal with capitalized letters, such as 0XAF3E+    HexCapsF AlternateForm SignMode+  | -- | Percent representation+    PercentF Precision AlternateForm SignMode+  deriving (Data)  -- | If the formatter use its alternate form data AlternateForm = AlternateForm | NormalForm-  deriving (Show)--lastCharFailed :: String -> Parser t-lastCharFailed err = do-  (SourcePos name line col) <- getPosition+  deriving (Show, Data) -  -- This is right as long as there is not line break in the string-  setPosition (SourcePos name line (mkPos (unPos col - 1)))-  fancyFailure (Set.singleton (ErrorFail err))+evalExpr :: [Extension] -> Parser String -> Parser (HsExpr GhcPs, Exp)+evalExpr exts exprParser = do+  exprPos <- getPosition+  -- Inject the correct source location in the GHC parser, so it already match+  -- the input source file.+  let initLoc = mkRealSrcLoc (mkFastString (sourceName exprPos)) (sourceLine exprPos) (sourceColumn exprPos)+  s <- lookAhead exprParser+  -- Setup the dyn flags using the provided list of extensions+  let dynFlags = baseDynFlags exts+  case ParseExp.parseExpression initLoc s dynFlags of+    Right expr -> do+      -- Consume the expression+      void exprParser+      pure (expr, toExp dynFlags expr)+    Left (lineError, colError, err) -> do+      -- In case of error, we just advance the parser to the error location.+      -- Note: we have to remove what was introduced in `initLoc`+      -- Skip lines+      replicateM_ (lineError - sourceLine exprPos) (manyTill anyChar newline)+      -- Skip columns+      -- This is a bit more counter intuitive. If we have skipped not lines, we+      -- must remove the introduced column offset, otherwise no.+      let columnSkip+            | lineError - sourceLine exprPos == 0 = colError - 1 - sourceColumn exprPos+            | otherwise = colError - 2+      void $ count columnSkip anyChar+      fail $ err <> " in haskell expression"  overrideAlignmentIfZero :: Bool -> Maybe (Maybe Char, AnyAlign) -> Maybe (Maybe Char, AnyAlign) overrideAlignmentIfZero True Nothing = Just (Just '0', AnyAlign AlignInside) overrideAlignmentIfZero True (Just (Nothing, al)) = Just (Just '0', al) overrideAlignmentIfZero _ v = v -format_spec :: Parser FormatMode-format_spec = do-  al' <- optional alignment-  s <- optional sign+formatSpec :: Parser FormatMode+formatSpec = do+  al' <- optionMaybe alignment+  s <- optionMaybe sign   alternateForm <- option NormalForm (AlternateForm <$ char '#')-   hasZero <- option False (True <$ char '0')-   let al = overrideAlignmentIfZero hasZero al'--  w <- optional width--  grouping <- optional grouping_option--  prec <- option PrecisionDefault (char '.' *> (Precision <$> precision))-  t <- optional type_+  w <- optionMaybe parseWidth+  grouping <- optionMaybe groupingOption+  prec <- option PrecisionDefault parsePrecision +  t <- optionMaybe $ lookAhead type_   let padding = case w of         Just p -> Padding p al         Nothing -> PaddingDefault-   case t of     Nothing -> pure (FormatMode padding (DefaultF prec (fromMaybe Minus s)) grouping)-    Just flag -> case evalFlag flag prec alternateForm s of-      Right fmt -> pure (FormatMode padding fmt grouping)-      Left typeError -> do-        lastCharFailed typeError+    Just flag -> case evalFlag flag padding grouping prec alternateForm s of+      Right fmt -> do+        -- Consumne the parser+        void type_+        pure (FormatMode padding fmt grouping)+      Left typeError ->+        fail typeError -evalFlag :: TypeFlag -> Precision -> AlternateForm -> Maybe SignMode -> Either String TypeFormat-evalFlag Flagb prec alt s = failIfPrec prec (BinaryF alt (defSign s))-evalFlag Flagc prec alt s = failIfS s =<< failIfPrec prec =<< failIfAlt alt CharacterF-evalFlag Flagd prec alt s = failIfPrec prec =<< failIfAlt alt (DecimalF (defSign s))-evalFlag Flage prec alt s = pure $ExponentialF prec alt (defSign s)-evalFlag FlagE prec alt s = pure $ ExponentialCapsF prec alt (defSign s)-evalFlag Flagf prec alt s = pure $ FixedF prec alt (defSign s)-evalFlag FlagF prec alt s = pure $ FixedCapsF prec alt (defSign s)-evalFlag Flagg prec alt s = pure $ GeneralF prec alt (defSign s)-evalFlag FlagG prec alt s = pure $ GeneralCapsF prec alt (defSign s)-evalFlag Flagn _prec _alt _s = Left ("Type 'n' not handled (yet). " ++ errgGn)-evalFlag Flago prec alt s = failIfPrec prec $ OctalF alt (defSign s)-evalFlag Flags prec alt s = failIfS s =<< (failIfAlt alt $ StringF prec)-evalFlag Flagx prec alt s = failIfPrec prec $ HexF alt (defSign s)-evalFlag FlagX prec alt s = failIfPrec prec $ HexCapsF alt (defSign s)-evalFlag FlagPercent prec alt s = pure $ PercentF prec alt (defSign s)+parseWidth :: Parser (ExprOrValue Int)+parseWidth = do+  exts <- asks enabledExtensions+  Just (charOpening, charClosing) <- asks delimiters+  choice+    [ Value <$> width,+      char charOpening *> (HaskellExpr <$> evalExpr exts (someTill (satisfy (/= charClosing)) (char charClosing) <?> "an haskell expression"))+    ] +parsePrecision :: Parser Precision+parsePrecision = do+  exts <- asks enabledExtensions+  Just (charOpening, charClosing) <- asks delimiters+  _ <- char '.'+  choice+    [ Precision . Value <$> precision,+      char charOpening *> (Precision . HaskellExpr <$> evalExpr exts (someTill (satisfy (/= charClosing)) (char charClosing) <?> "an haskell expression"))+    ]++-- | Similar to 'manyTill' but always parse one element.+-- Be careful, @someTill p e@ may parse @e@ as first element if @e@ is a subset of @p@.+someTill :: (Stream s m t) => ParsecT s u m a -> ParsecT s u m end -> ParsecT s u m [a]+someTill p e = (:) <$> p <*> manyTill p e++evalFlag :: TypeFlag -> Padding -> Maybe Char -> Precision -> AlternateForm -> Maybe SignMode -> Either String TypeFormat+evalFlag Flagb _pad _grouping prec alt s = failIfPrec prec (BinaryF alt (defSign s))+evalFlag Flagc _pad _grouping prec alt s = failIfS s =<< failIfPrec prec =<< failIfAlt alt CharacterF+evalFlag Flagd _pad _grouping prec alt s = failIfPrec prec =<< failIfAlt alt (DecimalF (defSign s))+evalFlag Flage _pad _grouping prec alt s = pure $ ExponentialF prec alt (defSign s)+evalFlag FlagE _pad _grouping prec alt s = pure $ ExponentialCapsF prec alt (defSign s)+evalFlag Flagf _pad _grouping prec alt s = pure $ FixedF prec alt (defSign s)+evalFlag FlagF _pad _grouping prec alt s = pure $ FixedCapsF prec alt (defSign s)+evalFlag Flagg _pad _grouping prec alt s = pure $ GeneralF prec alt (defSign s)+evalFlag FlagG _pad _grouping prec alt s = pure $ GeneralCapsF prec alt (defSign s)+evalFlag Flagn _pad _grouping _prec _alt _s = Left ("Type 'n' not handled (yet). " ++ errgGn)+evalFlag Flago _pad _grouping prec alt s = failIfPrec prec $ OctalF alt (defSign s)+evalFlag Flags pad grouping prec alt s = failIfGrouping grouping =<< failIfInsidePadding pad =<< failIfS s =<< failIfAlt alt (StringF prec)+evalFlag Flagx _pad _grouping prec alt s = failIfPrec prec $ HexF alt (defSign s)+evalFlag FlagX _pad _grouping prec alt s = failIfPrec prec $ HexCapsF alt (defSign s)+evalFlag FlagPercent _pad _grouping prec alt s = pure $ PercentF prec alt (defSign s)+ defSign :: Maybe SignMode -> SignMode defSign Nothing = Minus defSign (Just s) = s +failIfGrouping :: Maybe Char -> TypeFormat -> Either String TypeFormat+failIfGrouping (Just _) _t = Left "String type is incompatible with grouping (_ or ,)."+failIfGrouping Nothing t = Right t +failIfInsidePadding :: Padding -> TypeFormat -> Either String TypeFormat+failIfInsidePadding (Padding _ (Just (_, AnyAlign AlignInside))) _t = Left "String type is incompatible with inside padding (=)."+failIfInsidePadding _ t = Right t+ errgGn :: String errgGn = "Use one of {'b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 's', 'x', 'X', '%'}."  failIfPrec :: Precision -> TypeFormat -> Either String TypeFormat failIfPrec PrecisionDefault i = Right i-failIfPrec (Precision i) _ = Left ("Type incompatible with precision (." ++ show i ++ "), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field.")+failIfPrec (Precision e) _ = Left ("Type incompatible with precision (." ++ showExpr ++ "), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field.")+  where+    showExpr = case e of+      Value v -> show v+      HaskellExpr (_, expr) -> show expr  failIfAlt :: AlternateForm -> TypeFormat -> Either String TypeFormat failIfAlt NormalForm i = Right i@@ -252,12 +389,13 @@ toSignMode Space = ' '  alignment :: Parser (Maybe Char, AnyAlign)-alignment = choice [-    try $ do+alignment =+  choice+    [ try $ do         c <- fill         mode <- align-        pure (Just c, mode)-    , do+        pure (Just c, mode),+      do         mode <- align         pure (Nothing, mode)     ]@@ -266,52 +404,50 @@ fill = anyChar  align :: Parser AnyAlign-align = choice [-  AnyAlign AlignLeft <$ char '<',-  AnyAlign AlignRight <$ char '>',-  AnyAlign AlignCenter <$ char '^',-  AnyAlign AlignInside <$ char '='-  ]+align =+  choice+    [ AnyAlign AlignLeft <$ char '<',+      AnyAlign AlignRight <$ char '>',+      AnyAlign AlignCenter <$ char '^',+      AnyAlign AlignInside <$ char '='+    ]  sign :: Parser SignMode-sign = choice-  [Plus <$ char '+',-   Minus <$ char '-',-   Space <$ char ' '-  ]+sign =+  choice+    [ Plus <$ char '+',+      Minus <$ char '-',+      Space <$ char ' '+    ] -width :: Parser Integer+width :: Parser Int width = integer -integer :: Parser Integer-integer = L.decimal -- incomplete: see: https://docs.python.org/3/reference/lexical_analysis.html#grammar-token-integer+integer :: Parser Int+integer = read <$> some (oneOf ['0' .. '9']) -- incomplete: see: https://docs.python.org/3/reference/lexical_analysis.html#grammar-token-integer -grouping_option :: Parser Char-grouping_option = oneOf ("_," :: [Char])+groupingOption :: Parser Char+groupingOption = oneOf ("_," :: String) -precision :: Parser Integer+precision :: Parser Int precision = integer  type_ :: Parser TypeFlag-type_ = choice [-  Flagb <$ char 'b',-  Flagc <$ char 'c',-  Flagd <$ char 'd',-  Flage <$ char 'e',-  FlagE <$ char 'E',-  Flagf <$ char 'f',-  FlagF <$ char 'F',-  Flagg <$ char 'g',-  FlagG <$ char 'G',-  Flagn <$ char 'n',-  Flago <$ char 'o',-  Flags <$ char 's',-  Flagx <$ char 'x',-  FlagX <$ char 'X',-  FlagPercent <$ char '%'-  ]---  -- TODO: remove !-deriving instance Lift Precision-deriving instance Lift Padding+type_ =+  choice+    [ Flagb <$ char 'b',+      Flagc <$ char 'c',+      Flagd <$ char 'd',+      Flage <$ char 'e',+      FlagE <$ char 'E',+      Flagf <$ char 'f',+      FlagF <$ char 'F',+      Flagg <$ char 'g',+      FlagG <$ char 'G',+      Flagn <$ char 'n',+      Flago <$ char 'o',+      Flags <$ char 's',+      Flagx <$ char 'x',+      FlagX <$ char 'X',+      FlagPercent <$ char '%'+    ]
src/PyF/Internal/QQ.hs view
@@ -1,208 +1,457 @@-{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DisambiguateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TupleSections #-}-{-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-}--{- | This module uses the python mini language detailed in 'PyF.Internal.PythonSyntax' to build an template haskell expression which represents a 'Formatting.Format'.+{-# LANGUAGE ViewPatterns #-}+{-# OPTIONS_GHC -Wno-name-shadowing #-} --}-module PyF.Internal.QQ (-  toExp,-  toExpPython)+-- | This module uses the python mini language detailed in+-- 'PyF.Internal.PythonSyntax' to build an template haskell expression+-- representing a formatted string.+module PyF.Internal.QQ+  ( toExp,+    Config (..),+    wrapFromString,+    expQQ,+  ) where -import Text.Megaparsec+import qualified Data.List.NonEmpty as NE -import qualified Formatting as F+import Control.Monad.Reader+import Data.Data (Data (gmapQ), Typeable, cast)+import Data.Kind+import Data.List (intercalate)+import Data.Maybe (catMaybes, fromMaybe, isJust)+import Data.Proxy+import Data.String (fromString) -import           Language.Haskell.TH+#if MIN_VERSION_ghc(9,0,0)+import GHC.Tc.Utils.Monad (addErrAt)+import GHC.Tc.Types (TcM)+import GHC.Types.Name (occNameString)+#else+import OccName+import TcRnTypes (TcM)+import TcRnMonad (addErrAt)+#endif -import Data.Maybe (fromMaybe)+#if MIN_VERSION_ghc(9,6,0)+#else+import GHC (moduleNameString)+#endif -import qualified Data.Text.Lazy.Builder as Builder+#if MIN_VERSION_ghc(9,3,0)+import GHC.Tc.Errors.Types+import GHC.Types.Error+import GHC.Utils.Outputable (text) -import qualified Data.Text.Lazy as LText-import qualified Data.Text as SText-import qualified Data.List.NonEmpty as NonEmpty+#if MIN_VERSION_ghc(9,6,0)+#else+import GHC.Driver.Errors.Types+import GHC.Parser.Errors.Types+#endif+#endif -import qualified Data.Word as Word-import qualified Data.Int as Int-import Numeric.Natural+#if MIN_VERSION_ghc(9,0,0)+import GHC.Types.Name.Reader+#else+import RdrName+#endif -import Language.Haskell.Meta.Parse (parseExp)+#if MIN_VERSION_ghc(8,10,0)+import GHC.Hs.Expr as Expr+import GHC.Hs.Extension as Ext+import GHC.Hs.Pat as Pat+#else+import HsExpr as Expr+import HsExtension as Ext+import HsPat as Pat+#endif -import PyF.Internal.PythonSyntax-import qualified PyF.Formatters as Formatters-import PyF.Formatters (AnyAlign(..))-import Data.Proxy+#if MIN_VERSION_ghc(9,0,0)+import GHC.Types.SrcLoc+#else+import SrcLoc+#endif++#if MIN_VERSION_ghc(9,2,0)+import GHC.Hs+#endif+ import GHC.TypeLits+import Language.Haskell.TH hiding (Type)+import Language.Haskell.TH.Quote+import Language.Haskell.TH.Syntax (Q (Q))+import PyF.Class+import PyF.Formatters (AnyAlign (..))+import qualified PyF.Formatters as Formatters+import PyF.Internal.Meta (toName)+import PyF.Internal.PythonSyntax+import Text.Parsec+import Text.Parsec.Error+  ( errorMessages,+    messageString,+    showErrorMessages,+  )+import Text.Parsec.Pos (initialPos)+import Text.ParserCombinators.Parsec.Error (Message (..))+import Unsafe.Coerce (unsafeCoerce) --- Be Careful: empty format string--- | Parse a string and return a formatter for it-toExp:: (Char, Char) -> String -> Q Exp-toExp delimiters s = do-  filename <- loc_filename <$> location-  (line, col) <- loc_start <$> location+-- | Configuration for the quasiquoter+data Config = Config+  { -- | What are the delimiters for interpolation. 'Nothing' means no+    -- interpolation / formatting.+    delimiters :: Maybe (Char, Char),+    -- | Post processing. The input 'Exp' represents a 'String'. Common use+    -- case includes using 'wrapFromString' to add 'fromString' in the context+    -- of 'OverloadedStrings'.+    postProcess :: Q Exp -> Q Exp+  } -  let change_log "<interactive>" currentState = currentState-      change_log _ currentState = let-        (SourcePos sName _ _) NonEmpty.:| xs = statePos currentState-        in currentState {statePos = (SourcePos sName (mkPos line) (mkPos col)) NonEmpty.:| xs}+-- | Build a quasiquoter for expression+expQQ :: String -> (String -> Q Exp) -> QuasiQuoter+expQQ fName qExp =+  QuasiQuoter+    { quoteExp = qExp,+      quotePat = err "pattern",+      quoteType = err "type",+      quoteDec = err "declaration"+    }+  where+    err :: String -> t+    err name = error (fName ++ ": This QuasiQuoter can not be used as a " ++ name ++ "!") -  case parse (updateParserState (change_log filename) >> parseGenericFormatString delimiters) filename s of+-- | If 'OverloadedStrings' is enabled, from the input expression with+-- 'fromString'.+wrapFromString :: ExpQ -> Q Exp+wrapFromString e = do+  exts <- extsEnabled+  if OverloadedStrings `elem` exts+    then [|fromString $(e)|]+    else e++-- | Parse a string and return a formatter for it+toExp :: Config -> String -> Q Exp+toExp Config {delimiters = expressionDelimiters, postProcess} s = do+  loc <- location+  exts <- extsEnabled+  let context = ParsingContext expressionDelimiters exts++  -- Setup the parser so it matchs the real original position in the source+  -- code.+  let filename = loc_filename loc+  let initPos = setSourceColumn (setSourceLine (initialPos filename) (fst $ loc_start loc)) (snd $ loc_start loc)+  case runReader (runParserT (setPosition initPos >> parseGenericFormatString) () filename s) context of     Left err -> do+      reportParserErrorAt err+      -- returns a dummy exp, so TH continues its life. This TH code won't be+      -- executed anyway, there is an error+      [|()|]+    Right items -> do+      checkResult <- checkVariables items+      case checkResult of+        Nothing -> postProcess (goFormat items)+        Just (srcSpan, msg) -> do+          reportErrorAt srcSpan msg+          [|()|] -      if filename == "<interactive>"-        then do-          fail (parseErrorPretty' s err)-        else do-          fileContent <- runIO (readFile filename)-          fail (parseErrorPretty' fileContent err)-    Right items -> goFormat items+findFreeVariablesInFormatMode :: Maybe FormatMode -> [(SrcSpan, RdrName)]+findFreeVariablesInFormatMode Nothing = []+findFreeVariablesInFormatMode (Just (FormatMode padding tf _)) =+  findFreeVariables tf <> case padding of+    PaddingDefault -> []+    Padding eoi _ -> findFreeVariables eoi -toExpPython :: String -> Q Exp-toExpPython = toExp ('{', '}')+checkOneItem :: Item -> Q (Maybe (SrcSpan, String))+checkOneItem (Raw _) = pure Nothing+checkOneItem (Replacement (hsExpr, _) formatMode) = do+  let allNames = findFreeVariables hsExpr <> findFreeVariablesInFormatMode formatMode+  res <- mapM doesExists allNames+  let resFinal = catMaybes res +  case resFinal of+    [] -> pure Nothing+    ((err, span) : _) -> pure $ Just (span, err)++{- ORMOLU_DISABLE -}+findFreeVariables :: Data a => a -> [(SrcSpan, RdrName)]+findFreeVariables item = allNames+  where+    -- Find all free Variables in an HsExpr+    f :: forall a. (Data a, Typeable a) => a -> [Located RdrName]+    f e = case cast @_ @(HsExpr GhcPs) e of+#if MIN_VERSION_ghc(9,2,0)+      Just (HsVar _ l@(L a _)) -> [L (locA a) (unLoc l)]+#else+      Just (HsVar _ l) -> [l]+#endif++#if MIN_VERSION_ghc(9,13,0)+      Just (HsLam _ _ (MG _ (unLoc -> (map unLoc -> [Expr.Match _ _ (unLoc -> map unLoc -> ps) (GRHSs _ (NE.toList -> [unLoc -> GRHS _ _ (unLoc -> e)]) _)])))) -> filter keepVar subVars+        where+          keepVar (L _ n) = n `notElem` subPats+          subVars = concat $ gmapQ f [e]+          subPats = concat $ gmapQ findPats ps+#elif MIN_VERSION_ghc(9,12,0)+      Just (HsLam _ _ (MG _ (unLoc -> (map unLoc -> [Expr.Match _ _ (unLoc -> map unLoc -> ps) (GRHSs _ [unLoc -> GRHS _ _ (unLoc -> e)] _)])))) -> filter keepVar subVars+        where+          keepVar (L _ n) = n `notElem` subPats+          subVars = concat $ gmapQ f [e]+          subPats = concat $ gmapQ findPats ps+#elif MIN_VERSION_ghc(9,10,0)+      Just (HsLam _ _ (MG _ (unLoc -> (map unLoc -> [Expr.Match _ _ (map unLoc -> ps) (GRHSs _ [unLoc -> GRHS _ _ (unLoc -> e)] _)])))) -> filter keepVar subVars+        where+          keepVar (L _ n) = n `notElem` subPats+          subVars = concat $ gmapQ f [e]+          subPats = concat $ gmapQ findPats ps+#elif MIN_VERSION_ghc(9,6,0)+      Just (HsLam _ (MG _ (unLoc -> (map unLoc -> [Expr.Match _ _ (map unLoc -> ps) (GRHSs _ [unLoc -> GRHS _ _ (unLoc -> e)] _)])))) -> filter keepVar subVars+        where+          keepVar (L _ n) = n `notElem` subPats+          subVars = concat $ gmapQ f [e]+          subPats = concat $ gmapQ findPats ps+#else+      Just (HsLam _ (MG _ (unLoc -> (map unLoc -> [Expr.Match _ _ (map unLoc -> ps) (GRHSs _ [unLoc -> GRHS _ _ (unLoc -> e)] _)])) _)) -> filter keepVar subVars+        where+          keepVar (L _ n) = n `notElem` subPats+          subVars = concat $ gmapQ f [e]+          subPats = concat $ gmapQ findPats ps+#endif+      _ -> concat $ gmapQ f e++    -- Find all Variables bindings (i.e. patterns) in an HsExpr+    findPats :: forall a. (Data a, Typeable a) => a -> [RdrName]+    findPats p = case cast @_ @(Pat.Pat GhcPs) p of+      Just (VarPat _ (unLoc -> name)) -> [name]+      _ -> concat $ gmapQ findPats p+    -- Be careful, we wrap hsExpr in a list, so the toplevel hsExpr will be+    -- seen by gmapQ. Otherwise it will miss variables if they are the top+    -- level expression: gmapQ only checks sub constructors.+    allVars = concat $ gmapQ f [item]+    allNames = map (\(L l e) -> (l, e)) allVars+{- ORMOLU_ENABLE -}++lookupName :: RdrName -> Q Bool+lookupName n = case n of+  (Unqual o) -> isJust <$> lookupValueName (occNameString o)+  (Qual m o) -> isJust <$> lookupValueName (moduleNameString m <> "." <> occNameString o)+  -- No idea how to lookup for theses names, so consider that they exists+  (Orig _m _o) -> pure True+  (Exact _) -> pure True++doesExists :: (b, RdrName) -> Q (Maybe (String, b))+doesExists (loc, name) = do+  res <- lookupName name+  if res+    then pure Nothing+    else pure (Just ("Variable not in scope: " <> show (toName name), loc))++-- | Check that all variables used in 'Item' exists, otherwise, fail.+checkVariables :: [Item] -> Q (Maybe (SrcSpan, String))+checkVariables [] = pure Nothing+checkVariables (x : xs) = do+  r <- checkOneItem x+  case r of+    Nothing -> checkVariables xs+    Just err -> pure $ Just err++-- Stolen from: https://www.tweag.io/blog/2021-01-07-haskell-dark-arts-part-i/+-- This allows to hack inside the the GHC api and use function not exported by template haskell.+-- This may not be always safe, see https://github.com/guibou/PyF/issues/115,+-- hence keep that for "failing path" (i.e. error reporting), but not on+-- codepath which are executed otherwise.+unsafeRunTcM :: TcM a -> Q a+unsafeRunTcM m = Q (unsafeCoerce m)++-- | This function is similar to TH reportError, however it also provide+-- correct SrcSpan, so error are localised at the correct position in the TH+-- splice instead of being at the beginning.+reportErrorAt :: SrcSpan -> String -> Q ()+reportErrorAt loc msg = unsafeRunTcM $ addErrAt loc msg'+  where+#if MIN_VERSION_ghc(9,13,0)+    -- TODO: maybe leverage the "hint" logic to add additionnal hints here?+    msg' = TcRnUnknownMessage (UnknownDiagnostic (const NoDiagnosticOpts) (\x -> x) (mkPlainError noHints (text msg)))+#elif MIN_VERSION_ghc(9,7,0)+    msg' = TcRnUnknownMessage (UnknownDiagnostic (const NoDiagnosticOpts) (mkPlainError noHints (text msg)))+#elif MIN_VERSION_ghc(9,6,0)+    msg' = TcRnUnknownMessage (UnknownDiagnostic $ mkPlainError noHints $+                         text msg)+#elif MIN_VERSION_ghc(9,3,0)+    msg' = TcRnUnknownMessage (GhcPsMessage $ PsUnknownMessage $ mkPlainError noHints $+                         text msg)+#else+    msg' = fromString msg+#endif++reportParserErrorAt :: ParseError -> Q ()+reportParserErrorAt err = reportErrorAt span msg+  where+    msg = intercalate "\n" $ formatErrorMessages err++    span :: SrcSpan+    span = mkSrcSpan loc loc'++    loc = srcLocFromParserError (errorPos err)+    loc' = srcLocFromParserError (incSourceColumn (errorPos err) 1)++srcLocFromParserError :: SourcePos -> SrcLoc+srcLocFromParserError sourceLoc = srcLoc+  where+    line = sourceLine sourceLoc+    column = sourceColumn sourceLoc+    name = sourceName sourceLoc++    srcLoc = mkSrcLoc (fromString name) line column++formatErrorMessages :: ParseError -> [String]+formatErrorMessages err+  -- If there is an explicit error message from parsec, use only that+  | not $ null messages = map messageString messages+  -- Otherwise, uses parsec formatting+  | otherwise = [showErrorMessages "or" "unknown parse error" "expecting" "unexpected" "end of input" (errorMessages err)]+  where+    (_sysUnExpect, msgs1) = span (SysUnExpect "" ==) (errorMessages err)+    (_unExpect, msgs2) = span (UnExpect "" ==) msgs1+    (_expect, messages) = span (Expect "" ==) msgs2++{-+Note: Empty String Lifting++Empty string are lifted as [] instead of "", so I'm using LitE (String L) instead+-}+ goFormat :: [Item] -> Q Exp-goFormat items = foldl1 fofo <$> (mapM toFormat items)+-- We special case on empty list in order to generate an empty string+goFormat [] = pure $ LitE (StringL "") -- see [Empty String Lifting]+goFormat items = foldl1 sappendQ <$> mapM toFormat items -fofo :: Exp -> Exp -> Exp-fofo s0 s1 = InfixE (Just s0) (VarE '(F.%)) (Just s1)+-- | call `<>` between two 'Exp'+sappendQ :: Exp -> Exp -> Exp+sappendQ s0 s1 = InfixE (Just s0) (VarE '(<>)) (Just s1)  -- Real formatting is here  toFormat :: Item -> Q Exp-toFormat (Raw x) = [| F.now (Builder.fromString x) |]-toFormat (Replacement x y) = do+toFormat (Raw x) = pure $ LitE (StringL x) -- see [Empty String Lifting]+toFormat (Replacement (_, expr) y) = do   formatExpr <- padAndFormat (fromMaybe DefaultFormatMode y)--  case parseExp x of-    Right expr -> pure (AppE (VarE 'F.now) (VarE 'Builder.fromString `AppE` (formatExpr `AppE` expr)))-    Left err -> fail err+  pure (formatExpr `AppE` expr) -changePrec :: Precision -> Maybe Int-changePrec PrecisionDefault = Just 6-changePrec (Precision n) = Just (fromIntegral n)+-- | Default precision for floating point+defaultFloatPrecision :: Maybe Int+defaultFloatPrecision = Just 6 -changePrec' :: Precision -> Maybe Int-changePrec' PrecisionDefault = Nothing-changePrec' (Precision n) = Just (fromIntegral n)+-- | Precision to maybe+splicePrecision :: Maybe Int -> Precision -> Q Exp+splicePrecision def PrecisionDefault = [|def :: Maybe Int|]+splicePrecision _ (Precision p) = [|Just $(exprToInt p)|] -toGrp :: Maybe b -> a -> Maybe (a, b)-toGrp mb a = (a,) <$> mb+toGrp :: Maybe Char -> Int -> Q Exp+toGrp mb a = [|grp|]+  where+    grp = (a,) <$> mb  withAlt :: AlternateForm -> Formatters.Format t t' t'' -> Q Exp-withAlt NormalForm e = [| e |]-withAlt AlternateForm e = [| Formatters.Alternate e |]+withAlt NormalForm e = [|e|]+withAlt AlternateForm e = [|Formatters.Alternate e|] --- Todo: Alternates for floating padAndFormat :: FormatMode -> Q Exp padAndFormat (FormatMode padding tf grouping) = case tf of   -- Integrals-  BinaryF alt s -> [| formatAnyIntegral $(withAlt alt Formatters.Binary) s (newPadding padding) (toGrp grouping 4) |]-  CharacterF -> [| formatAnyIntegral Formatters.Character Formatters.Minus (newPadding padding) Nothing |]-  DecimalF s -> [| formatAnyIntegral Formatters.Decimal s (newPadding padding) (toGrp grouping 3) |]-  HexF alt s -> [| formatAnyIntegral $(withAlt alt Formatters.Hexa) s (newPadding padding) (toGrp grouping 4) |]-  OctalF alt s -> [| formatAnyIntegral $(withAlt alt Formatters.Octal) s (newPadding padding) (toGrp grouping 4) |]-  HexCapsF alt s -> [| formatAnyIntegral (Formatters.Upper $(withAlt alt Formatters.Hexa)) s (newPadding padding) (toGrp grouping 4) |]-+  BinaryF alt s -> [|formatAnyIntegral $(withAlt alt Formatters.Binary) s $(newPaddingQ padding) $(toGrp grouping 4)|]+  CharacterF -> [|formatAnyIntegral Formatters.Character Formatters.Minus $(newPaddingQ padding) Nothing|]+  DecimalF s -> [|formatAnyIntegral Formatters.Decimal s $(newPaddingQ padding) $(toGrp grouping 3)|]+  HexF alt s -> [|formatAnyIntegral $(withAlt alt Formatters.Hexa) s $(newPaddingQ padding) $(toGrp grouping 4)|]+  OctalF alt s -> [|formatAnyIntegral $(withAlt alt Formatters.Octal) s $(newPaddingQ padding) $(toGrp grouping 4)|]+  HexCapsF alt s -> [|formatAnyIntegral (Formatters.Upper $(withAlt alt Formatters.Hexa)) s $(newPaddingQ padding) $(toGrp grouping 4)|]   -- Floating-  ExponentialF prec alt s -> [| formatAnyFractional $(withAlt alt Formatters.Exponent) s (newPadding padding) (toGrp grouping 3) (changePrec prec) |]-  ExponentialCapsF prec alt s -> [| formatAnyFractional (Formatters.Upper $(withAlt alt Formatters.Exponent)) s (newPadding padding) (toGrp grouping 3) (changePrec prec) |]-  GeneralF prec alt s -> [| formatAnyFractional $(withAlt alt Formatters.Generic) s (newPadding padding) (toGrp grouping 3) (changePrec prec) |]-  GeneralCapsF prec alt s -> [| formatAnyFractional (Formatters.Upper $(withAlt alt Formatters.Generic)) s (newPadding padding) (toGrp grouping 3) (changePrec prec) |]-  FixedF prec alt s -> [| formatAnyFractional $(withAlt alt Formatters.Fixed) s (newPadding padding) (toGrp grouping 3) (changePrec prec) |]-  FixedCapsF prec alt s -> [| formatAnyFractional (Formatters.Upper $(withAlt alt Formatters.Fixed)) s (newPadding padding) (toGrp grouping 3) (changePrec prec) |]-  PercentF prec alt s -> [| formatAnyFractional $(withAlt alt Formatters.Percent) s (newPadding padding) (toGrp grouping 3) (changePrec prec) |]-+  ExponentialF prec alt s -> [|formatAnyFractional $(withAlt alt Formatters.Exponent) s $(newPaddingQ padding) $(toGrp grouping 3) $(splicePrecision defaultFloatPrecision prec)|]+  ExponentialCapsF prec alt s -> [|formatAnyFractional (Formatters.Upper $(withAlt alt Formatters.Exponent)) s $(newPaddingQ padding) $(toGrp grouping 3) $(splicePrecision defaultFloatPrecision prec)|]+  GeneralF prec alt s -> [|formatAnyFractional $(withAlt alt Formatters.Generic) s $(newPaddingQ padding) $(toGrp grouping 3) $(splicePrecision defaultFloatPrecision prec)|]+  GeneralCapsF prec alt s -> [|formatAnyFractional (Formatters.Upper $(withAlt alt Formatters.Generic)) s $(newPaddingQ padding) $(toGrp grouping 3) $(splicePrecision defaultFloatPrecision prec)|]+  FixedF prec alt s -> [|formatAnyFractional $(withAlt alt Formatters.Fixed) s $(newPaddingQ padding) $(toGrp grouping 3) $(splicePrecision defaultFloatPrecision prec)|]+  FixedCapsF prec alt s -> [|formatAnyFractional (Formatters.Upper $(withAlt alt Formatters.Fixed)) s $(newPaddingQ padding) $(toGrp grouping 3) $(splicePrecision defaultFloatPrecision prec)|]+  PercentF prec alt s -> [|formatAnyFractional $(withAlt alt Formatters.Percent) s $(newPaddingQ padding) $(toGrp grouping 3) $(splicePrecision defaultFloatPrecision prec)|]   -- Default / String-  DefaultF prec s -> [| \v ->-      case categorise (Proxy :: Proxy $(typeAllowed)) v of-        Integral i -> formatAnyIntegral Formatters.Decimal s (newPadding padding) (toGrp grouping 3) i-        Fractional f -> formatAnyFractional Formatters.Generic s (newPadding padding) (toGrp grouping 3) (changePrec' prec) f-        StringType f -> Formatters.formatString (newPaddingForString padding) (changePrec' prec) f-                         |]-   where-     typeAllowed :: Q Type-     typeAllowed = case padding of-       PaddingDefault -> [t| EnableForString |]-       Padding _ Nothing -> [t| EnableForString |]-       Padding _ (Just (_, AnyAlign a)) -> case Formatters.getAlignForString a of-         Nothing -> [t| DisableForString |]-         Just _ -> [t| EnableForString |]+  DefaultF prec s -> [|formatAny s $(paddingToPaddingK padding) $(toGrp grouping 3) $(splicePrecision Nothing prec)|]+  StringF prec -> [|Formatters.formatString (newPaddingKForString $(paddingToPaddingK padding)) $(splicePrecision Nothing prec) . pyfToString|] -  StringF prec -> [| Formatters.formatString pad (changePrec' prec) |]-    where pad = newPaddingForString padding+newPaddingQ :: Padding -> Q Exp+newPaddingQ padding = case padding of+  PaddingDefault -> [|Nothing :: Maybe (Int, AnyAlign, Char)|]+  (Padding i al) -> case al of+    Nothing -> [|Just ($(exprToInt i), AnyAlign Formatters.AlignRight, ' ')|] -- Right align and space is default for any object, except string+    Just (Nothing, a) -> [|Just ($(exprToInt i), a, ' ')|]+    Just (Just c, a) -> [|Just ($(exprToInt i), a, c)|] -newPaddingForString :: Padding -> Maybe (Int, Formatters.AlignMode 'Formatters.AlignAll, Char)-newPaddingForString padding = case padding of-    PaddingDefault -> Nothing-    Padding i Nothing -> Just (fromIntegral i, Formatters.AlignLeft, ' ') -- default align left and fill with space for string-    Padding i (Just (mc, AnyAlign a)) -> case Formatters.getAlignForString a of-      Nothing -> error alignErrorMsg-      Just al -> pure (fromIntegral i, al, fromMaybe ' ' mc)+exprToInt :: ExprOrValue Int -> Q Exp+-- Note: this is a literal provided integral. We use explicit case to ::Int so it won't warn about defaulting+exprToInt (Value i) = [|$(pure $ LitE (IntegerL (fromIntegral i))) :: Int|]+exprToInt (HaskellExpr (_, e)) = [|$(pure e)|] -newPadding :: Padding -> Maybe (Integer, AnyAlign, Char)-newPadding padding = case padding of-    PaddingDefault -> Nothing-    (Padding i al) -> case al of-      Nothing -> Just (i, AnyAlign Formatters.AlignRight, ' ') -- Right align and space is default for any object, except string-      Just (Nothing, a) -> Just (i, a, ' ')-      Just (Just c, a) -> Just (i, a, c)+data PaddingK k i where+  PaddingDefaultK :: PaddingK 'Formatters.AlignAll Int+  PaddingK :: i -> Maybe (Maybe Char, Formatters.AlignMode k) -> PaddingK k i -formatAnyIntegral :: (Show i, Integral i) => Formatters.Format t t' 'Formatters.Integral -> Formatters.SignMode -> Maybe (Integer, AnyAlign, Char) -> Maybe (Int, Char) -> i -> String-formatAnyIntegral f s Nothing grouping i = Formatters.formatIntegral f s Nothing grouping i-formatAnyIntegral f s (Just (padSize, AnyAlign alignMode, c)) grouping i = Formatters.formatIntegral f s (Just (fromIntegral padSize, alignMode, c)) grouping i+paddingToPaddingK :: Padding -> Q Exp+paddingToPaddingK p = case p of+  PaddingDefault -> [|PaddingDefaultK|]+  Padding i Nothing -> [|PaddingK ($(exprToInt i)) Nothing :: PaddingK 'Formatters.AlignAll Int|]+  Padding i (Just (c, AnyAlign a)) -> [|PaddingK $(exprToInt i) (Just (c, a))|] -formatAnyFractional :: (RealFloat i) => Formatters.Format t t' 'Formatters.Fractional -> Formatters.SignMode -> Maybe (Integer, AnyAlign, Char) -> Maybe (Int, Char) -> Maybe Int -> i -> String-formatAnyFractional f s Nothing grouping p i = Formatters.formatFractional f s Nothing grouping p i-formatAnyFractional f s (Just (padSize, AnyAlign alignMode, c)) grouping p i = Formatters.formatFractional f s (Just (fromIntegral padSize, alignMode, c)) grouping p i+paddingKToPadding :: PaddingK k i -> Maybe (i, AnyAlign, Char)+paddingKToPadding p = case p of+  PaddingDefaultK -> Nothing+  (PaddingK i al) -> case al of+    Nothing -> Just (i, AnyAlign Formatters.AlignRight, ' ') -- Right align and space is default for any object, except string+    Just (Nothing, a) -> Just (i, AnyAlign a, ' ')+    Just (Just c, a) -> Just (i, AnyAlign a, c) -data FormattingType where-  StringType :: String -> FormattingType-  Fractional :: RealFloat t => t -> FormattingType-  Integral :: (Show t, Integral t) => t -> FormattingType+formatAnyIntegral :: forall i paddingWidth t t'. (Integral paddingWidth) => (PyfFormatIntegral i) => Formatters.Format t t' 'Formatters.Integral -> Formatters.SignMode -> Maybe (paddingWidth, AnyAlign, Char) -> Maybe (Int, Char) -> i -> String+formatAnyIntegral f s Nothing grouping i = pyfFormatIntegral @i @paddingWidth f s Nothing grouping i+formatAnyIntegral f s (Just (padSize, AnyAlign alignMode, c)) grouping i = pyfFormatIntegral f s (Just (padSize, alignMode, c)) grouping i -class Categorise k t where-  categorise :: Proxy k -> t -> FormattingType+formatAnyFractional :: forall paddingWidth precision i t t'. (Integral paddingWidth, Integral precision, PyfFormatFractional i) => Formatters.Format t t' 'Formatters.Fractional -> Formatters.SignMode -> Maybe (paddingWidth, AnyAlign, Char) -> Maybe (Int, Char) -> Maybe precision -> i -> String+formatAnyFractional f s Nothing grouping p i = pyfFormatFractional @i @paddingWidth @precision f s Nothing grouping p i+formatAnyFractional f s (Just (padSize, AnyAlign alignMode, c)) grouping p i = pyfFormatFractional f s (Just (padSize, alignMode, c)) grouping p i -instance Categorise k Integer where categorise _  i = Integral i-instance Categorise k Int where categorise _  i = Integral i-instance Categorise k Int.Int8 where categorise _  i = Integral i-instance Categorise k Int.Int16 where categorise _  i = Integral i-instance Categorise k Int.Int32 where categorise _  i = Integral i-instance Categorise k Int.Int64 where categorise _  i = Integral i+class FormatAny i k where+  formatAny :: forall paddingWidth precision. (Integral paddingWidth, Integral precision) => Formatters.SignMode -> PaddingK k paddingWidth -> Maybe (Int, Char) -> Maybe precision -> i -> String -instance Categorise k Natural where categorise _  i = Integral i-instance Categorise k Word where categorise _  i = Integral i-instance Categorise k Word.Word8 where categorise _  i = Integral i-instance Categorise k Word.Word16 where categorise _  i = Integral i-instance Categorise k Word.Word32 where categorise _  i = Integral i-instance Categorise k Word.Word64 where categorise _  i = Integral i+instance (FormatAny2 (PyFClassify t) t k) => FormatAny t k where+  formatAny = formatAny2 (Proxy :: Proxy (PyFClassify t)) -instance Categorise k Float where categorise _  f = Fractional f-instance Categorise k Double where categorise _  f = Fractional f+class FormatAny2 (c :: PyFCategory) (i :: Type) (k :: Formatters.AlignForString) where+  formatAny2 :: forall paddingWidth precision. (Integral paddingWidth, Integral precision) => Proxy c -> Formatters.SignMode -> PaddingK k paddingWidth -> Maybe (Int, Char) -> Maybe precision -> i -> String --- This may use DataKinds extension, however the need for the--- extension will leak inside the code calling the template haskell--- quasi quotes.-data EnableForString-data DisableForString+instance (Show t, Integral t) => FormatAny2 'PyFIntegral t k where+  formatAny2 _ s a p _precision = formatAnyIntegral Formatters.Decimal s (paddingKToPadding a) p -instance Categorise EnableForString LText.Text where categorise _  t = StringType (LText.unpack t)-instance Categorise EnableForString SText.Text where categorise _  t = StringType (SText.unpack t)-instance Categorise EnableForString String where categorise _  t = StringType t+instance (PyfFormatFractional t) => FormatAny2 'PyFFractional t k where+  formatAny2 _ s a = formatAnyFractional Formatters.Generic s (paddingKToPadding a) -alignErrorMsg :: String-alignErrorMsg = "String Cannot be aligned with the inside `=` mode"+newPaddingKForString :: (Integral i) => PaddingK 'Formatters.AlignAll i -> Maybe (Int, Formatters.AlignMode 'Formatters.AlignAll, Char)+newPaddingKForString padding = case padding of+  PaddingDefaultK -> Nothing+  PaddingK i Nothing -> Just (fromIntegral i, Formatters.AlignLeft, ' ') -- default align left and fill with space for string+  PaddingK i (Just (mc, a)) -> Just (fromIntegral i, a, fromMaybe ' ' mc) -instance TypeError ('Text "String Cannot be aligned with the inside `=` mode") => Categorise DisableForString LText.Text where categorise _ _ = error "unreachable"-instance TypeError ('Text "String Cannot be aligned with the inside `=` mode") => Categorise DisableForString SText.Text where categorise _ _ = error "unreachable"-instance TypeError ('Text "String Cannot be aligned with the inside `=` mode") => Categorise DisableForString String where categorise _ _ = error "unreachable"+-- TODO: _s(ign) and _grouping should trigger errors+instance (PyFToString t) => FormatAny2 'PyFString t 'Formatters.AlignAll where+  formatAny2 _ _s a _grouping precision t = Formatters.formatString (newPaddingKForString a) precision (pyfToString t)++instance (TypeError ('Text "String type is incompatible with inside padding (=).")) => FormatAny2 'PyFString t 'Formatters.AlignNumber where+  formatAny2 = error "Unreachable"
test/Spec.hs view
@@ -1,196 +1,546 @@-{-# OPTIONS -Wno-type-defaults #-}+{-# LANGUAGE BinaryLiterals #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE ExtendedDefaultRules #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}-{-# LANGUAGE ExtendedDefaultRules #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-} -import Test.Hspec+-- This warning is disabled because any expression with literal leads to it.+{-# OPTIONS -Wno-type-defaults #-} +import qualified Data.ByteString+import qualified Data.ByteString.Char8+import qualified Data.ByteString.Lazy+import qualified Data.ByteString.Lazy.Char8+import qualified Data.List as List+import Data.Proxy (Proxy (..))+import qualified Data.Ratio+import qualified Data.Text+import qualified Data.Text as Text+import qualified Data.Text.Lazy+import qualified Data.Time+import GHC.OverloadedLabels+import GHC.TypeLits (KnownSymbol, Symbol, symbolVal) import PyF-import SpecUtils import SpecCustomDelimiters--{--   - Normal tests are done using the recommanded API: [fString|.....|]-   - Test with $(checkExample formatString result) are checked against the python reference implementation. Result is provided as documentation.-   - Test with $(checkExampleDiff formatString result) are not checked against the python reference implementation. This is known (and documented) differences.-   - Test with $(check formatString) are only tested against the python reference implementation.--}+import Test.Hspec  main :: IO ()-main = hspec spec+main = hspec $ parallel spec +newtype FooFloating t = FooFloating t+  deriving newtype (Show, RealFloat, RealFrac, Floating, Fractional, Real, Enum, Num, Ord, Eq, PyfFormatFractional)++newtype FooIntegral = FooIntegral Integer+  deriving newtype (Show, Integral, Real, Enum, Num, Ord, Eq)++data Foo = Foo++data FooDefault = FooDefault+  deriving (Show)++instance PyFToString Foo where+  pyfToString Foo = "I'm a Foo"++type instance PyFClassify Foo = 'PyFString++type instance PyFClassify (FooFloating t) = 'PyFFractional++type instance PyFClassify FooIntegral = 'PyFIntegral++type instance PyFClassify FooDefault = 'PyFString++-- Data type to test overloaded labels+data V (a :: Symbol) = V++instance (KnownSymbol a) => Show (V a) where+  show V = "V=" ++ symbolVal (Proxy @a)++instance (a ~ a') => IsLabel a (V a') where+  fromLabel = V++showV :: (KnownSymbol a) => V a -> String+showV = show++globalName :: String+globalName = "Valérian"+ spec :: Spec spec = do   describe "simple with external variable" $ do-    let-      anInt = 123-      aFloat = 0.234-      aString = "hello"-    it "int" $ [fString|{anInt}|] `shouldBe` "123"-    it "float" $ [fString|{aFloat}|] `shouldBe` "0.234"-    it "string" $ [fString|{aString}|] `shouldBe` "hello"+    let anInt = 123+        aFloat = 0.234+        aString = "hello"+    it "int" $ [fmt|{anInt}|] `shouldBe` "123"+    it "float" $ [fmt|{aFloat}|] `shouldBe` "0.234"+    it "string" $ [fmt|{aString}|] `shouldBe` "hello"   describe "only expression" $ do     describe "default" $ do-      it "int" $(checkExample "{123}" "123")-      it "float" $(checkExample "{0.234}" "0.234")-      it "string" $(checkExample "{\"hello\"}" "hello")-      it "float precision" $(checkExample "{0.234:.1}" "0.2")-      it "string precision" $(checkExample "{\"hello\":.1}" "h")-      it "sign +" $(checkExample "{0.234:+}" "+0.234")-      it "sign space" $(checkExample "{0.234: }" " 0.234")-      it "sign neg" $(checkExample "{-123:+}" "-123")+      it "int" $ [fmt|{123}|] `shouldBe` "123"+      it "float" $ [fmt|{0.234}|] `shouldBe` "0.234"+      it "string" $ [fmt|{"hello"}|] `shouldBe` "hello"+      it "float precision" $ [fmt|{0.234:.1}|] `shouldBe` "0.2"+      it "string precision" $ [fmt|{"hello":.1}|] `shouldBe` "h"+      it "sign +" $ [fmt|{0.234:+}|] `shouldBe` "+0.234"+      it "sign space" $ [fmt|{0.234: }|] `shouldBe` " 0.234"+      it "sign neg" $ [fmt|{-123:+}|] `shouldBe` "-123"     describe "binary" $ do-      it "simple" $(checkExample "{123:b}" "1111011")-      it "alt" $(checkExample "{123:#b}" "0b1111011")-      it "sign" $(checkExample "{123:+#b}" "+0b1111011")-    describe "character" $ do-      it "simple" $(checkExample "{123:c}" "{")+      it "simple" $ [fmt|{123:b}|] `shouldBe` "1111011"+      it "alt" $ [fmt|{123:#b}|] `shouldBe` "0b1111011"+      it "sign" $ [fmt|{123:+#b}|] `shouldBe` "+0b1111011"+    describe "character" $+      it "simple" $+        [fmt|{123:c}|] `shouldBe` "{"     describe "decimal" $ do-      it "simple" $(checkExample "{123:d}" "123")-      it "sign" $(checkExample "{123:+d}" "+123")+      it "simple" $ [fmt|{123:d}|] `shouldBe` "123"+      it "sign" $ [fmt|{123:+d}|] `shouldBe` "+123"     describe "exponentiel" $ do-      it "simple > 1" $(checkExample "{234.0:e}" "2.340000e+02")-      it "precision > 1" $(checkExample "{234.0:.1e}" "2.3e+02")-      it "simple < 1" $(checkExample "{0.234:e}" "2.340000e-01")-      it "precision < 1 " $(checkExample "{0.234:.1e}" "2.3e-01")+      it "simple > 1" $ [fmt|{234.0:e}|] `shouldBe` "2.340000e+02"+      it "precision > 1" $ [fmt|{234.0:.1e}|] `shouldBe` "2.3e+02"+      it "simple < 1" $ [fmt|{0.234:e}|] `shouldBe` "2.340000e-01"+      it "precision < 1 " $ [fmt|{0.234:.1e}|] `shouldBe` "2.3e-01"     describe "exponentiel caps" $ do-      it "simple > 1" $(checkExample "{234.0:E}" "2.340000E+02")-      it "precision > 1" $(checkExample "{234.0:.1E}" "2.3E+02")-      it "simple < 1" $(checkExample "{0.234:E}" "2.340000E-01")-      it "precision < 1 " $(checkExample "{0.234:.1E}" "2.3E-01")+      it "simple > 1" $ [fmt|{234.0:E}|] `shouldBe` "2.340000E+02"+      it "precision > 1" $ [fmt|{234.0:.1E}|] `shouldBe` "2.3E+02"+      it "simple < 1" $ [fmt|{0.234:E}|] `shouldBe` "2.340000E-01"+      it "precision < 1 " $ [fmt|{0.234:.1E}|] `shouldBe` "2.3E-01"     describe "general" $ do-      it "simple small" $(checkExampleDiff "{123.02:g}" "123.020000")-      it "precision small" $(checkExampleDiff "{123.02:.1g}" "123.0")-      it "simple big" $(checkExampleDiff "{1234567890.23:g}" "1.234568e+09")-      it "precision big" $(checkExampleDiff "{1234567890.23:.1g}" "1.2e+09")+      it "simple small" $ [fmt|{123.02:g}|] `shouldBe` "123.020000"+      it "precision small" $ [fmt|{123.02:.1g}|] `shouldBe` "123.0"+      it "simple big" $ [fmt|{1234567890.23:g}|] `shouldBe` "1.234568e+09"+      it "precision big" $ [fmt|{1234567890.23:.1g}|] `shouldBe` "1.2e+09"     describe "general caps" $ do-      it "simple small" $(checkExampleDiff "{123.02:G}" "123.020000")-      it "precision small" $(checkExampleDiff "{123.02:.1G}" "123.0")-      it "simple big" $(checkExampleDiff "{1234567890.23:G}" "1.234568E+09")-      it "precision big" $(checkExampleDiff "{1234567890.23:.1G}" "1.2E+09")+      it "simple small" $ [fmt|{123.02:G}|] `shouldBe` "123.020000"+      it "precision small" $ [fmt|{123.02:.1G}|] `shouldBe` "123.0"+      it "simple big" $ [fmt|{1234567890.23:G}|] `shouldBe` "1.234568E+09"+      it "precision big" $ [fmt|{1234567890.23:.1G}|] `shouldBe` "1.2E+09"     describe "fixed" $ do-      it "simple" $(checkExample "{0.234:f}" "0.234000")-      it "precision" $(checkExample "{0.234:.1f}" "0.2")+      it "simple" $ [fmt|{0.234:f}|] `shouldBe` "0.234000"+      it "precision" $ [fmt|{0.234:.1f}|] `shouldBe` "0.2"     describe "fixed caps" $ do-      it "simple" $(checkExample "{0.234:F}" "0.234000")-      it "precision" $(checkExample "{0.234:.1F}" "0.2")+      it "simple" $ [fmt|{0.234:F}|] `shouldBe` "0.234000"+      it "precision" $ [fmt|{0.234:.1F}|] `shouldBe` "0.2"     describe "octal" $ do-      it "simple" $(checkExample "{123:o}" "173")-      it "alt" $(checkExample "{123:#o}" "0o173")+      it "simple" $ [fmt|{123:o}|] `shouldBe` "173"+      it "alt" $ [fmt|{123:#o}|] `shouldBe` "0o173"     describe "string" $ do-      it "string" $(checkExample "{\"hello\":s}" "hello")-      it "precision" $(checkExample "{\"hello\":.2s}" "he")+      it "string" $ [fmt|{"hello":s}|] `shouldBe` "hello"+      it "precision" $ [fmt|{"hello":.2s}|] `shouldBe` "he"     describe "hex" $ do-      it "simple" $(checkExample "{123:x}" "7b")-      it "alt" $(checkExample "{123:#x}" "0x7b")+      it "simple" $ [fmt|{123:x}|] `shouldBe` "7b"+      it "alt" $ [fmt|{123:#x}|] `shouldBe` "0x7b"     describe "hex caps" $ do-      it "simple" $(checkExample "{123:X}" "7B")-      it "alt" $(checkExample "{123:#X}" "0X7B")+      it "simple" $ [fmt|{123:X}|] `shouldBe` "7B"+      it "alt" $ [fmt|{123:#X}|] `shouldBe` "0X7B"     describe "percent" $ do-      it "simple" $(checkExample "{0.234:%}" "23.400000%")-      it "precision" $(checkExample "{0.234:.2%}" "23.40%")+      it "simple" $ [fmt|{0.234:%}|] `shouldBe` "23.400000%"+      it "precision" $ [fmt|{0.234:.2%}|] `shouldBe` "23.40%"+    describe "string truncating" $+      it "works" $+        [fmt|{"hello":.3}|] `shouldBe` "hel"     describe "padding" $ do       describe "default char" $ do-        it "left" $(checkExample "{\"hello\":<10}" "hello     ")-        it "right" $(checkExample "{\"hello\":>10}" "     hello")-        it "center" $(checkExample "{\"hello\":^10}" "  hello   ")+        it "left" $ [fmt|{"hello":<10}|] `shouldBe` "hello     "+        it "right" $ [fmt|{"hello":>10}|] `shouldBe` "     hello"+        it "center" $ [fmt|{"hello":^10}|] `shouldBe` "  hello   "       describe "a char" $ do-        it "left" $(checkExample "{\"hello\":-<10}" "hello-----")-        it "right" $(checkExample "{\"hello\":->10}" "-----hello")-        it "center" $(checkExample "{\"hello\":-^10}" "--hello---")+        it "left" $ [fmt|{"hello":-<10}|] `shouldBe` "hello-----"+        it "right" $ [fmt|{"hello":->10}|] `shouldBe` "-----hello"+        it "center" $ [fmt|{"hello":-^10}|] `shouldBe` "--hello---"       describe "inside" $ do-        it "inside" $(checkExample "{123:=+10}" "+      123")-        it "inside" $(checkExample "{123:=10}" "       123")-        it "inside" $(checkExample "{- 123:=10}" "-      123")-        it "inside" $(checkExample "{- 123:|= 10}" "-||||||123")-        it "inside" $(checkExample "{123:|= 10}" " ||||||123")+        it "inside" $ [fmt|{123:=+10}|] `shouldBe` "+      123"+        it "inside" $ [fmt|{123:=10}|] `shouldBe` "       123"+        it "inside" $ [fmt|{- 123:=10}|] `shouldBe` "-      123"+        it "inside" $ [fmt|{- 123:|= 10}|] `shouldBe` "-||||||123"+        it "inside" $ [fmt|{123:|= 10}|] `shouldBe` " ||||||123"       describe "default padding" $ do-        it "floating" $(checkExample "{1:10f}" "  1.000000")-        it "integral" $(checkExample "{1:10d}" "         1")-        it "string" $(checkExample "{\"h\":10s}" "h         ")-        it "default" $(checkExample "{1:10}" "         1")-        it "default" $(checkExample "{1.0:10}" "       1.0")-        it "default" $(checkExample "{\"h\":10}" "h         ")+        it "floating" $ [fmt|{1:10f}|] `shouldBe` "  1.000000"+        it "integral" $ [fmt|{1:10d}|] `shouldBe` "         1"+        it "string" $ [fmt|{"h":10s}|] `shouldBe` "h         "+        it "default" $ [fmt|{1:10}|] `shouldBe` "         1"+        it "default" $ [fmt|{1.0:10}|] `shouldBe` "       1.0"+        it "default" $ [fmt|{"h":10}|] `shouldBe` "h         "     describe "NaN" $ do-        let nan = 0.0 / 0-        it "nan" $(checkExample "{nan}" "nan")-        it "nan f" $(checkExample "{nan:f}" "nan")-        it "nan e" $(checkExample "{nan:e}" "nan")-        it "nan g" $(checkExample "{nan:g}" "nan")-        it "nan F" $(checkExample "{nan:F}" "NAN")-        it "nan G" $(checkExample "{nan:G}" "NAN")-        it "nan E" $(checkExample "{nan:E}" "NAN")+      describe "float" $ do+        let nan = 0.0 / 0 :: Float+        it "nan" $ [fmt|{nan}|] `shouldBe` "nan"+        it "nan f" $ [fmt|{nan:f}|] `shouldBe` "nan"+        it "nan e" $ [fmt|{nan:e}|] `shouldBe` "nan"+        it "nan g" $ [fmt|{nan:g}|] `shouldBe` "nan"+        it "nan F" $ [fmt|{nan:F}|] `shouldBe` "NAN"+        it "nan G" $ [fmt|{nan:G}|] `shouldBe` "NAN"+        it "nan E" $ [fmt|{nan:E}|] `shouldBe` "NAN"+      describe "double" $ do+        let nan = 0.0 / 0 :: Double+        it "nan" $ [fmt|{nan}|] `shouldBe` "nan"+        it "nan f" $ [fmt|{nan:f}|] `shouldBe` "nan"+        it "nan e" $ [fmt|{nan:e}|] `shouldBe` "nan"+        it "nan g" $ [fmt|{nan:g}|] `shouldBe` "nan"+        it "nan F" $ [fmt|{nan:F}|] `shouldBe` "NAN"+        it "nan G" $ [fmt|{nan:G}|] `shouldBe` "NAN"+        it "nan E" $ [fmt|{nan:E}|] `shouldBe` "NAN"     describe "Infinite" $ do-        let inf = 1.0 / 0-        it "infinite" $(checkExample "{inf}" "inf")-        it "infinite f" $(checkExample "{inf:f}" "inf")-        it "infinite e" $(checkExample "{inf:e}" "inf")-        it "infinite g" $(checkExample "{inf:g}" "inf")-        it "infinite F" $(checkExample "{inf:F}" "INF")-        it "infinite G" $(checkExample "{inf:G}" "INF")-        it "infinite E" $(checkExample "{inf:E}" "INF")+      describe "float" $ do+        let inf = 1.0 / 0 :: Float+        it "infinite" $ [fmt|{inf}|] `shouldBe` "inf"+        it "infinite f" $ [fmt|{inf:f}|] `shouldBe` "inf"+        it "infinite e" $ [fmt|{inf:e}|] `shouldBe` "inf"+        it "infinite g" $ [fmt|{inf:g}|] `shouldBe` "inf"+        it "infinite F" $ [fmt|{inf:F}|] `shouldBe` "INF"+        it "infinite G" $ [fmt|{inf:G}|] `shouldBe` "INF"+        it "infinite E" $ [fmt|{inf:E}|] `shouldBe` "INF"+      describe "double" $ do+        let inf = 1.0 / 0 :: Double+        it "infinite" $ [fmt|{inf}|] `shouldBe` "inf"+        it "infinite f" $ [fmt|{inf:f}|] `shouldBe` "inf"+        it "infinite e" $ [fmt|{inf:e}|] `shouldBe` "inf"+        it "infinite g" $ [fmt|{inf:g}|] `shouldBe` "inf"+        it "infinite F" $ [fmt|{inf:F}|] `shouldBe` "INF"+        it "infinite G" $ [fmt|{inf:G}|] `shouldBe` "INF"+        it "infinite E" $ [fmt|{inf:E}|] `shouldBe` "INF"     describe "Grouping" $ do-        it "groups int" $(checkExample "{123456789:,d}" "123,456,789")-        it "groups int with _" $(checkExample "{123456789:_d}" "123_456_789")-        it "groups float" $(checkExample "{123456789.234:,f}" "123,456,789.234000")-        it "groups bin" $(checkExample "{123456789:_b}" "111_0101_1011_1100_1101_0001_0101")-        it "groups hex" $(checkExample "{123456789:_x}" "75b_cd15")-        it "groups oct" $(checkExample "{123456789:_o}" "7_2674_6425")+      it "groups int" $ [fmt|{123456789:,d}|] `shouldBe` "123,456,789"+      it "groups int with _" $ [fmt|{123456789:_d}|] `shouldBe` "123_456_789"+      it "groups float" $ [fmt|{123456789.234:,f}|] `shouldBe` "123,456,789.234000"+      it "groups bin" $ [fmt|{123456789:_b}|] `shouldBe` "111_0101_1011_1100_1101_0001_0101"+      it "groups hex" $ [fmt|{123456789:_x}|] `shouldBe` "75b_cd15"+      it "groups oct" $ [fmt|{123456789:_o}|] `shouldBe` "7_2674_6425"     describe "negative zero" $ do-        it "f" $(checkExample "{-0.0:f}" "-0.000000")-        it "e" $(checkExample "{-0.0:e}" "-0.000000e+00")-        it "g" $(checkExampleDiff "{-0.0:g}" "-0.000000")-        it "F" $(checkExample "{-0.0:F}" "-0.000000")-        it "G" $(checkExampleDiff "{-0.0:G}" "-0.000000")-        it "E" $(checkExample "{-0.0:E}" "-0.000000E+00")+      it "f" $ [fmt|{-0.0:f}|] `shouldBe` "-0.000000"+      it "e" $ [fmt|{-0.0:e}|] `shouldBe` "-0.000000e+00"+      it "g" $ [fmt|{-0.0:g}|] `shouldBe` "-0.000000"+      it "F" $ [fmt|{-0.0:F}|] `shouldBe` "-0.000000"+      it "G" $ [fmt|{-0.0:G}|] `shouldBe` "-0.000000"+      it "E" $ [fmt|{-0.0:E}|] `shouldBe` "-0.000000E+00"     describe "0" $ do-        it "works" $(checkExample "{123:010}" "0000000123")-        it "works with sign" $(checkExample "{-123:010}" "-000000123")-        it "accept mode override" $(checkExample "{-123:<010}" "-123000000")-        it "accept mode and char override" $(checkExample "{-123:.<010}" "-123......")-+      it "works" $ [fmt|{123:010}|] `shouldBe` "0000000123"+      it "works with sign" $ [fmt|{-123:010}|] `shouldBe` "-000000123"+      it "accept mode override" $ [fmt|{-123:<010}|] `shouldBe` "-123000000"+      it "accept mode and char override" $ [fmt|{-123:.<010}|] `shouldBe` "-123......"     describe "no digit no dot" $ do-      it "f" $(checkExample "{1.0:.0f}" "1")-      it "e" $(checkExample "{1.0:.0e}" "1e+00")-      it "g" $(checkExample "{1.0:.0g}" "1")-      it "E" $(checkExample "{1.0:.0E}" "1E+00")-      it "G" $(checkExample "{1.0:.0G}" "1")-      it "percent" $(checkExample "{1.0:.0%}" "100%")+      it "f" $ [fmt|{1.0:.0f}|] `shouldBe` "1"+      it "e" $ [fmt|{1.0:.0e}|] `shouldBe` "1e+00"+      it "g" $ [fmt|{1.0:.0g}|] `shouldBe` "1"+      it "E" $ [fmt|{1.0:.0E}|] `shouldBe` "1E+00"+      it "G" $ [fmt|{1.0:.0G}|] `shouldBe` "1"+      it "percent" $ [fmt|{1.0:.0%}|] `shouldBe` "100%"     describe "no digit alt -> dot" $ do-      it "f" $(checkExample "{1.0:#.0f}" "1.")-      it "e" $(checkExample "{1.0:#.0e}" "1.e+00")-      it "g" $(checkExample "{1.0:#.0g}" "1.")-      it "E" $(checkExample "{1.0:#.0E}" "1.E+00")-      it "G" $(checkExample "{1.0:#.0G}" "1.")-      it "percent" $(checkExample "{1.0:#.0%}" "100.%")--  describe "complex" $ do+      it "f" $ [fmt|{1.0:#.0f}|] `shouldBe` "1."+      it "e" $ [fmt|{1.0:#.0e}|] `shouldBe` "1.e+00"+      it "g" $ [fmt|{1.0:#.0g}|] `shouldBe` "1."+      it "E" $ [fmt|{1.0:#.0E}|] `shouldBe` "1.E+00"+      it "G" $ [fmt|{1.0:#.0G}|] `shouldBe` "1."+      it "percent" $ [fmt|{1.0:#.0%}|] `shouldBe` "100.%"+  describe "complex" $     it "works with many things at once" $-      let-        name = "Guillaume"-        age = 31-        euroToFrancs = 6.55957-      in-        [fString|hello {name} you are {age} years old and the conversion rate of euro is {euroToFrancs:.2}|] `shouldBe` ("hello Guillaume you are 31 years old and the conversion rate of euro is 6.56")+      let name = "Guillaume"+          age = 31+          euroToFrancs = 6.55957+       in [fmt|hello {name} you are {age} years old and the conversion rate of euro is {euroToFrancs:.2}|] `shouldBe` "hello Guillaume you are 31 years old and the conversion rate of euro is 6.56"+  describe "error reporting" $+    pure () -- TODO: find a way to test error reporting+  describe "sub expressions" $+    it "works" $+      [fmt|2pi = {2 * pi:.2}|] `shouldBe` "2pi = 6.28"+  describe "escape strings" $+    it "works" $+      [fmt|hello \n\b|] `shouldBe` "hello \n\b"+  describe "variable precision" $+    it "works" $+      do+        let n = 3 :: Int+        [fmt|{pi:.{n}}|] `shouldBe` "3.142"+  describe "variable padding" $+    it "works" $+      do+        let n = 5 :: Integer+        [fmt|Bonjour {'a':>{n}}|] `shouldBe` "Bonjour     a"+  it "escape chars" $+    [fmt|}}{{}}{{|] `shouldBe` "}{}{"+  describe "custom delimiters" $ do+    it "works" $+      [myCustomFormatter|2 * pi = @2*pi:.2f!|] `shouldBe` "2 * pi = 6.28"+    it "escape chars" $+      [myCustomFormatter|@@!!@@!!|] `shouldBe` "@!@!"+    it "works for custom precision" $+      [myCustomFormatter|@pi:.@2!!|] `shouldBe` "3.14"+  describe "empty line" $+    it "works" $+      [fmt||] `shouldBe` ""+  describe "multi line escape" $ do+    it "works" $+      [fmt|\+- a+- b+\+|]+        `shouldBe` "- a\n- b\n"+    it "escapes in middle of line" $+      [fmt|Example goes \+here!|]+        `shouldBe` "Example goes here!"+    it "escapes a lot of things" $+      [fmt|\+I'm a line with \n and \\ and a correct line+ending, but that one is escaped\+And I'm escaping before and after: \\{pi:.3f}\\+yeah\+|]+        `shouldBe` "I'm a line with \n and \\ and a correct line\nending, but that one is escapedAnd I'm escaping before and after: \\3.142\\\nyeah"+    it "escapes" $+      [fmt|\\+- a+- b+\+|]+        `shouldBe` "\\\n- a\n- b\n"+  describe "empty trailing value" $+    it "String" $+      ( [fmt|\+{pi:.0}+|] ::+          String+      )+        `shouldBe` "3\n"+  describe "language extensions" $ do+    it "parses @Int" $+      [fmt|hello {show @Int 10}|] `shouldBe` "hello 10"+    it "parses @_" $+      [fmt|hello {show @_ 10}|] `shouldBe` "hello 10"+    it "parses BinaryLiterals" $+      [fmt|hello {0b1111}|] `shouldBe` "hello 15"+    it "OverloadedLabels works" $+      [fmt|{showV #abc}|] `shouldBe` "V=abc"+  describe "custom types" $ do+    it "works with integral" $+      [fmt|{FooIntegral 10:d}|] `shouldBe` "10"+    it "works with floating" $+      [fmt|{FooFloating 25.123:f}|] `shouldBe` "25.123000"+    it "works with string" $ do+      [fmt|{Foo:s}|] `shouldBe` "I'm a Foo"+      [fmt|{FooDefault:s}|] `shouldBe` "FooDefault"+    it "works with classify" $ do+      [fmt|{Foo}|] `shouldBe` "I'm a Foo"+      [fmt|{FooIntegral 100}|] `shouldBe` "100"+      let fooDouble = FooFloating (100.123 :: Double) in [fmt|{fooDouble}|] `shouldBe` "100.123"+      let fooFloat = FooFloating (100.123 :: Float) in [fmt|{fooFloat}|] `shouldBe` "100.123"+      [fmt|{FooDefault}|] `shouldBe` "FooDefault"+  describe "Special syntax " $ do+    it "[] is correct expression" $ do+      [fmt|{[] @Char}|] `shouldBe` ""+    it "() is correct expression" $ do+      [fmt|{const "STRING" ()}|] `shouldBe` "STRING" +  describe "instances" $ do+    describe "default" $ do+      it "bytestring" $ do+        let x = "hello" :: Data.ByteString.ByteString in [fmt|{x}|] `shouldBe` "hello"+      it "bytestring lazy" $ do+        let x = "hello" :: Data.ByteString.Lazy.ByteString in [fmt|{x}|] `shouldBe` "hello"+      it "bytestring char 8 lazy" $ do+        let x = "hello" :: Data.ByteString.Lazy.Char8.ByteString in [fmt|{x}|] `shouldBe` "hello"+      it "bytestring char 8" $ do+        let x = "hello" :: Data.ByteString.Char8.ByteString in [fmt|{x}|] `shouldBe` "hello"+      it "text" $ do+        let x = "hello" :: Data.Text.Text in [fmt|{x}|] `shouldBe` "hello"+      it "lazy text" $ do+        let x = "hello" :: Data.Text.Lazy.Text in [fmt|{x}|] `shouldBe` "hello"+      it "DiffTime" $ do+        let x = 3 :: Data.Time.DiffTime in [fmt|{x}|] `shouldBe` "3.0"+      it "NominalDiffTime" $ do+        let x = 3 :: Data.Time.NominalDiffTime in [fmt|{x}|] `shouldBe` "3.0"+      it "Ratio" $ do+        let x = 3 :: Data.Ratio.Ratio Int in [fmt|{x}|] `shouldBe` "3.0"+      it "Int" $ do+        let x = 3 :: Int in [fmt|{x}|] `shouldBe` "3"+      it "Char" $ do+        let x = 'a' in [fmt|{x}|] `shouldBe` "a"+    describe "forced" $ do+      it "bytestring" $ do+        let x = "hello" :: Data.ByteString.ByteString in [fmt|{x:s}|] `shouldBe` "hello"+      it "bytestring lazy" $ do+        let x = "hello" :: Data.ByteString.Lazy.ByteString in [fmt|{x:s}|] `shouldBe` "hello"+      it "bytestring char 8 lazy" $ do+        let x = "hello" :: Data.ByteString.Lazy.Char8.ByteString in [fmt|{x:s}|] `shouldBe` "hello"+      it "bytestring char 8" $ do+        let x = "hello" :: Data.ByteString.Char8.ByteString in [fmt|{x:s}|] `shouldBe` "hello"+      it "text" $ do+        let x = "hello" :: Data.Text.Text in [fmt|{x:s}|] `shouldBe` "hello"+      it "lazy text" $ do+        let x = "hello" :: Data.Text.Text in [fmt|{x:s}|] `shouldBe` "hello"+      it "DiffTime" $ do+        let x = 3 :: Data.Time.DiffTime in [fmt|{x:.2f}|] `shouldBe` "3.00"+      it "NominalDiffTime" $ do+        let x = 3 :: Data.Time.NominalDiffTime in [fmt|{x:.2f}|] `shouldBe` "3.00"+      it "Ratio" $ do+        let x = 3 :: Data.Ratio.Ratio Int in [fmt|{x:.2f}|] `shouldBe` "3.00"+      describe "int" $ do+        it "decimal" $ do+          let x = 3 :: Int in [fmt|{x:d}|] `shouldBe` "3"+        it "fractional" $ do+          let x = 3 :: Int in [fmt|{x:.2f}|] `shouldBe` "3.00"+      describe "Char" $ do+        it "string" $ do+          let x = 'a' in [fmt|{x:s}|] `shouldBe` "a"+        it "int" $ do+          let x = 'a' in [fmt|{x:d}|] `shouldBe` "97" -  describe "error reporting" $ do-    pure () -- TODO: find a way to test error reporting+  describe "syntax" $ do+    describe "name" $ do+      it "qualified" $ do+        [fmt|{List.sort [1,2,3]:s}|] `shouldBe` "[1,2,3]"+    describe "literals" $ do+      it "list" $ do+        [fmt|{[1,2,3]:s}|] `shouldBe` "[1,2,3]"+      describe "arith seq" $ do+        it "from" $ [fmt|{take 3 [1..]:s}|] `shouldBe` "[1,2,3]"+        it "fromthen" $ [fmt|{take 3 [1, 3..]:s}|] `shouldBe` "[1,3,5]"+        it "fromto" $ [fmt|{[1..3]:s}|] `shouldBe` "[1,2,3]"+        it "fromthento" $ [fmt|{[1,3..5]:s}|] `shouldBe` "[1,3,5]" -  describe "sub expressions" $ do-    it "works" $ do-      [fString|2pi = {2 * pi:.2}|] `shouldBe` "2pi = 6.28"+    it "lambda" $ do+      [fmt|{(\x -> 2 * x) 10}|] `shouldBe` "20"+    it "section" $ do+      [fmt|{(100/) 10:.0f}|] `shouldBe` "10"+      [fmt|{(/2) 10:.0f}|] `shouldBe` "5"+    it "tuples" $ do+      [fmt|{fst (1, 2)}|] `shouldBe` "1" -  describe "escape strings" $ do-    it "works" $ do-      [fString|hello \n\b|] `shouldBe` "hello \n\b"+  -- Disabled because it does not build with GHC < 8.10+  -- xit "tuples section" $ do+  -- [fmt|{fst ((,2) 1)}|] `shouldBe` "1" -  it "escape chars" $ do-     [fString|}}{{}}{{|] `shouldBe` "}{}{"+  describe "multiline trimming" $ do+    it "works with overloading" $ do+      [fmtTrim|hello|] `shouldBe` ("hello" :: Data.Text.Text)+    it "overloading in overloading" $ do+      let foo = [fmtTrim|hello {10}|]+      -- foo `shouldBe` "hello 10"+      [fmtTrim|biz {foo} goodbye|] `shouldBe` ("biz hello 10 goodbye" :: Data.Text.Text)+    it "do not fail on trailing ignore line return" $ do+      [fmtTrim|+      hello\ -  describe "custom delimiters" $ do+      |]+        `shouldBe` "hello\n"+    it "do not take too much indent in account" $ do+      [fmtTrim|+      hello+      - a+        - b+      - c+      |]+        `shouldBe` "hello\n- a\n  - b\n- c\n"+    it "works with empty lines" $ do+      [fmtTrim|+      hello+++      |]+        `shouldBe` "hello\n\n\n"+    it "works with empty last lines" $ do+      [fmtTrim|+      hello+++|]+        `shouldBe` "hello\n\n\n"     it "works" $ do-      [myCustomFormatter|2 * pi = @2*pi:.2f!|] `shouldBe` "2 * pi = 6.28"-    it "escape chars" $ do-       [myCustomFormatter|@@!!@@!!|] `shouldBe` "@!@!"+      [fmtTrim|+                  hello+                  - a+                   - b++                  - c+      |]+        `shouldBe` "hello\n- a\n - b\n\n- c\n"+    it "works with replacement" $ do+      [fmtTrim|+                  hello+                  - a+                   - {pi:.2}|]+        `shouldBe` "hello\n- a\n - 3.14"+    it "Do not ignore not indented lines" $ do+      [fmtTrim|  hello+- a+ - {pi:.2}|]+        `shouldBe` "  hello\n- a\n - 3.14"++    it "works with multiline" $ do+      [fmtTrim|+                  hello+                  - a+                   - {+                      2 + 2+                   :d}|]+        `shouldBe` "hello\n- a\n - 4"++    it "Do not touch single lines" $ do+      [fmtTrim|  hello|] `shouldBe` "  hello"+    it "trim when there is a linebreak" $ do+      -- https://github.com/guibou/PyF/issues/141+      [fmtTrim|+          Cannot convert formula 2.0 * exponent(unit=s, value=1.0) which has unit dimensionless to\+           unit dimensionless for they have different dimensions|]+          `shouldBe` "Cannot convert formula 2.0 * exponent(unit=s, value=1.0) which has unit dimensionless to unit dimensionless for they have different dimensions"+  describe "raw" $ do+    it "does not escape anything" $+      [raw|hello+  - a \n {\+  - b }+  |]+        `shouldBe` "hello\n  - a \\n {\\\n  - b }\n  "+  describe "str" $ do+    it "basic escaping but no indentation neither formatting" $+      [str|hello+  - a \n {\+  - b {pi}+  |]+        `shouldBe` "hello\n  - a \n {  - b {pi}\n  "+  describe "strTrim" $ do+    it "basic escaping neither formatting" $+      [strTrim|+  - a \b {+  - b {pi}+  |]+        `shouldBe` "- a \b {\n- b {pi}\n"++  describe "handle ::" $ do+    it "works in simple context" $ do+      [fmt|{-10 :: Int:d}|] `shouldBe` "-10"+    it "works in a padding = context" $ do+      [fmt|{-10 :: Int::=10d}|] `shouldBe` "-:::::::10"+    it "works in a padding < context" $ do+      [fmt|{-10 :: Int::<10d}|] `shouldBe` "-10:::::::"+    it "works in a padding > context" $ do+      [fmt|{-10 :: Int::>10d}|] `shouldBe` ":::::::-10"+    it "works in a padding ^ context" $ do+      [fmt|{-10 :: Int::^10d}|] `shouldBe` ":::-10::::"++  describe "variables" $ do+    it "local" $ do+      let var = "Guillaume" :: String+      [fmt|{var}|] `shouldBe` "Guillaume"+    it "global" $ do+      [fmt|{globalName}|] `shouldBe` "Valérian"+    it "multiple expressions with variables" $ do+      let padding = 10+      let precision = 3+      [fmt|{globalName:{padding}} and pi = {pi:.{precision}}|] `shouldBe` "Valérian   and pi = 3.142"+    it "an expression with multiples variables" $ do+      let padding = 10+      let precision = 3+      [fmt|pi = {pi:{padding}.{precision}}|] `shouldBe` "pi =      3.142"+    it "an expression with module.variable" $ do+      [fmt|{Text.intercalate " " ["a" :: Text.Text, "b" :: Text.Text]}|] `shouldBe` "a b"
test/SpecCustomDelimiters.hs view
@@ -1,8 +1,14 @@ module SpecCustomDelimiters where  import Language.Haskell.TH.Quote- import PyF+import PyF.Internal.QQ  myCustomFormatter :: QuasiQuoter-myCustomFormatter = fStringWithDelimiters ('@','!')+myCustomFormatter =+  mkFormatter+    "fmt"+    ( fmtConfig+        { delimiters = Just ('@', '!')+        }+    )
+ test/SpecFail.hs view
@@ -0,0 +1,245 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE ExtendedDefaultRules #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE ScopedTypeVariables #-}++import Control.DeepSeq+import Control.Exception+import Data.Bits (Bits (..))+import Data.Char (ord)+import qualified Data.Text as Text+import PyF+import System.Exit+import System.FilePath+import System.IO.Temp+import System.Process (readProcessWithExitCode)+import Test.HUnit.Lang+import Test.Hspec++-- * Check compilation with external GHC (this is usefull to test compilation failure)++data CompilationStatus+  = -- | Fails during compilation (with error)+    CompileError String+  | RuntimeError String+  | Ok String+  deriving (Show, Eq)++makeTemplate :: String -> String+makeTemplate s =+  [fmt|{{-# LANGUAGE QuasiQuotes, ExtendedDefaultRules, TypeApplications #-}}+import PyF+truncate' = truncate @Float @Int+hello = "hello"+number = 3.14 :: Float+main :: IO ()+main = putStrLn [fmt|{s}|]+    <> "|]\n"++-- | Compile a formatting string+--+-- >>> checkCompile fileContent+-- CompileError "Bla bla bla, Floating cannot be formatted as hexa (`x`)+checkCompile :: (HasCallStack) => String -> IO CompilationStatus+checkCompile content = withSystemTempDirectory "PyF" $ \dirPath -> do+  let path = dirPath </> "PyFTest.hs"+  writeFile path content+  (ecode, _stdout, stderr) <-+    readProcessWithExitCode+      "ghc"+      [ path,+        -- Include all PyF files+        "-isrc",+        -- Disable the usage of the annoying .ghc environment file+        "-package-env",+        "-",+        -- Move the ".o" in the temporary dir+        "-odir",+        dirPath,+        "-hidir",+        dirPath,+        -- Tests use a filename in a temporary directory which may have a long filename which triggers+        -- line wrapping, reducing the reproducibility of error message+        -- By setting the column size to a high value, we ensure reproducible error messages+        "-dppr-cols=10000000000000",+        -- Clean package environment+        "-hide-all-packages",+        "-package base",+        "-package bytestring",+        "-package parsec",+        "-package text",+        "-package template-haskell",+        "-package ghc-boot",+        "-package mtl",+        "-package ghc",+        "-package time",+        "-package containers"+      ]+      ""+  case ecode of+    ExitFailure _ -> pure (CompileError (sanitize path stderr))+    ExitSuccess -> do+      (ecode', stdout', stderr') <- readProcessWithExitCode (take (length path - 3) path) [] ""+      case ecode' of+        ExitFailure _ -> pure (RuntimeError stderr')+        ExitSuccess -> pure (Ok stdout')++-- sanitize a compilation result by removing variables strings such as+-- temporary files name+sanitize :: FilePath -> String -> String+sanitize path =+  Text.unpack+    -- Strip the filename+    . Text.replace (Text.pack path) (Text.pack "INITIALPATH")+    -- GHC 9.0 replaces [Char] by String everywhere+    . Text.replace (Text.pack "[Char]") (Text.pack "String")+    . Text.pack++{- ORMOLU_DISABLE -}+golden :: HasCallStack => String -> String -> IO ()+golden name output = do+  let+#if __GLASGOW_HASKELL__ >= 906+      goldenFile = "test/golden96" </> (name <> ".golden")+#else+      goldenFile = "test/golden" </> (name <> ".golden")+#endif+      actualFile = "test/golden" </> (name <> ".actual")+  -- It can fail if the golden file does not exists+  goldenContentE :: Either SomeException String <- try $ readFile goldenFile+  let -- if no golden file, the golden file is the content+      goldenContent = case goldenContentE of+        Right e -> e+        Left _ -> output+  -- Flush lazy IO+  _ <- evaluate (force goldenContent)+  -- Apparently the whitespaces in GHC 9.10 changed+  -- By stripping, we just keep the test working as expected, but we are+  -- compatible with different version of GHC.+  --+  -- TODO: use GHC API to build directly the example and gather errors in a+  -- more reproducible way.+  if Text.strip (Text.pack output) /= Text.strip (Text.pack goldenContent)+    then do+      writeFile actualFile output+      (_, diffOutput, _) <- readProcessWithExitCode "diff" ["-b", goldenFile, actualFile] ""+      putStrLn diffOutput+      -- Update golden file+      writeFile goldenFile (Text.unpack $ Text.strip (Text.pack output))+      assertFailure diffOutput+    else writeFile goldenFile (Text.unpack $ Text.strip (Text.pack output))+{- ORMOLU_ENABLE -}++failCompile :: (HasCallStack) => String -> Spec+failCompile s = failCompileContent s s (makeTemplate s)++failCompileContent :: (HasCallStack) => String -> String -> String -> Spec+failCompileContent h caption fileContent =+  before (checkCompile fileContent) $ do+    let goldenName = concatMap cleanSpecialChars h+        -- Add an unique identifier, so golden files won't conflict on case+        -- insensitive systems+        -- See: bug #97.+        goldenPath = goldenName ++ "." ++ show (stableHash goldenName)+    it (show caption) $ \res -> case res of+      CompileError output -> golden goldenPath output+      _ -> assertFailure (show $ ".golden/" <> goldenPath <> "\n" <> show res)++-- | A stable hash from string, based on+-- https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1_hash+stableHash :: String -> Word+stableHash [] = 14695981039346656037+stableHash (x : xs) = fromIntegral (ord x) * stableHash xs `xor` 1099511628211++-- Remove chars which are not accepted in a path name+-- The encoding is rather approximative, I'm trying to avoid too long names.+cleanSpecialChars :: Char -> [Char]+cleanSpecialChars '/' = "SL"+cleanSpecialChars '\\' = "BS"+cleanSpecialChars ':' = "CL"+cleanSpecialChars '\n' = "NL"+cleanSpecialChars e = pure e++main :: IO ()+main = hspec $ parallel spec++spec :: Spec+spec =+  describe "error reporting" $ do+    describe "string" $ do+      describe "integral / fractional qualifiers" $ do+        failCompile "{hello:f}"+        failCompile "{hello:d}"+        failCompile "{hello:e}"+        failCompile "{hello:b}"+        failCompile "{hello:E}"+        failCompile "{hello:G}"+        failCompile "{hello:g}"+        failCompile "{hello:%}"+        failCompile "{hello:x}"+        failCompile "{hello:X}"+        failCompile "{hello:o}"+      describe "padding center" $ do+        failCompile "{hello:=100s}"+        failCompile "{hello:=100}"+      describe "grouping" $ do+        failCompile "{hello:_s}"+        failCompile "{hello:,s}"+      describe "sign" $ do+        failCompile "{hello:+s}"+        failCompile "{hello: s}"+        failCompile "{hello:-s}"+    describe "number with precision" $ do+      failCompile "{truncate number:.3d}"+      failCompile "{truncate number:.3o}"+      failCompile "{truncate number:.3b}"+      failCompile "{truncate number:.3x}"+    describe "floats" $ do+      failCompile "{number:o}"+      failCompile "{number:b}"+      failCompile "{number:x}"+      failCompile "{number:X}"+      failCompile "{number:d}"+    -- XXX: this are not failing for now, it should be fixed+    xdescribe "not specified" $ do+      failCompile "{truncate number:.3}"+      failCompile "{hello:#}"+      failCompile "{hello:+}"+      failCompile "{hello: }"+      failCompile "{hello:-}"+      failCompile "{hello:_}"+      failCompile "{hello:,}"+    describe "multiples lines" $+      failCompile "hello\n\n\n{pi:l}"+    describe "on haskell expression parsing" $ do+      describe "single line" $+        failCompile "{1 + - / lalalal}"+      describe "empty expression" $+        failCompile "{}"+      describe "sub expression" $ do+        describe "simple failure" $+          failCompile "{pi:.{/}}"+        describe "empty failure" $+          failCompile "{pi:.{}}"+      describe "multiples lines" $+        failCompile "hello\n    {\nlet a = 5\n    b = 10\nin 1 + - / lalalal}"+    describe "non-doubled delimiters" $ do+      failCompile "hello } world"+      failCompile "hello { world"+    describe "lexical errors" $ do+      describe "single line" $+        failCompile "foo\\Pbar"+      describe "multiple line" $+        failCompile "foo\nbli\\Pbar"+    describe "Wrong type" $ do+      failCompile "{True}"+      failCompile "{True:f}"+      failCompile "{True:d}"+    describe "Missing variables" $ do+      failCompile "Hello {name}"+      failCompile "Hello {length name}"+      failCompile "Hello {pi:.{precision}}"+      failCompile "Hello {pi:.{truncate number + precision}}"+      failCompile "Hello {pi:.{precision}}"+      failCompile "Hello {pi:{width}}"
+ test/SpecOverloaded.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TemplateHaskell #-}++import Data.ByteString+import Data.Text+import PyF+import Test.Hspec++main :: IO ()+main = hspec spec++spec :: Spec+spec = do+  let ten = 10 :: Int+  describe "Test formatting with different types" $ do+    it "String" $+      [fmt|hello {ten:d}|] `shouldBe` ("hello 10" :: String)+    it "Text" $+      [fmt|hello {ten:d}|] `shouldBe` ("hello 10" :: Text)+    it "ByteString" $+      [fmt|hello {ten:d}|] `shouldBe` ("hello 10" :: ByteString)
− test/SpecUtils.hs
@@ -1,80 +0,0 @@-{-# LANGUAGE TemplateHaskell #-}-module SpecUtils-  ( checkExample-  , checkExampleDiff-  , check-)-where--import Test.Hspec-import PyF.Internal.QQ--import Language.Haskell.TH-import Language.Haskell.TH.Syntax--import Formatting-import System.Process-import System.Exit---- * Utils--{- | Runs a python formatter example--For conveniance, it exports a few python symbols, `inf`, `nan` and pi.-->>> runPythonExample "{3.14159:.1f}-"3.1--}-runPythonExample :: String -> IO (Maybe String)-runPythonExample s = do-  let-    pythonPath = "python"-    args = ["-c", "from math import pi;nan = float('NaN');inf = float('inf');print(f\'''" ++ s ++ "''', end='')"]-  (ecode, stdout, _stderr) <- readProcessWithExitCode pythonPath args ""-  pure $ case ecode of-    ExitSuccess -> Just stdout-    ExitFailure _ -> Nothing--{- | `pyCheck formatString reference` compares a format string against-a reference (if `Just`) and against the python implementation--This TH expression will return an expression compatible with `Hspec` `SpecM`.--This expression is a failure if python cannot format this formatString-or if the python result does not match the (provided) reference.--}--pyCheck :: String -> Maybe String -> Q Exp-pyCheck s exampleStr = do-  pythonRes <- Language.Haskell.TH.Syntax.runIO (runPythonExample s)--  case pythonRes of-    Nothing -> [| expectationFailure $ "Expression: `" ++ s ++ "` fails in python" |]-    Just res -> do-      let qexp = [| formatToString $(toExpPython s)  `shouldBe` res |]-      case exampleStr of-        Nothing -> qexp-        Just e -> if res == e-        then qexp-        else [| expectationFailure $ "Provided result `" ++ e ++ "` does not match the python result `" ++ res ++ "`" |]---- * Exported--{- | `checkExample formatString result` checks if, once formated,-     `formatString` is equal to result. It also checks that the result is-     the same as the one provided by python.--}-checkExample :: String -> String -> Q Exp-checkExample s res = pyCheck s (Just res)--{- | `checkExampleDiff formatString result` checks if, once formated,-     `formatString` is equal to result. It does not check the result-     against the python implementation--}-checkExampleDiff :: String -> String -> Q Exp-checkExampleDiff s res = [| formatToString $(toExpPython s) `shouldBe` res |]--{- | `check formatString` checks only with the python implementation--}-check :: String -> Q Exp-check s = pyCheck s Nothing
+ test/golden/Hello {length name}.16675806454852491955.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:36: error:+    • Variable not in scope: name+    • In the quasi-quotation: [fmt|Hello {length name}|]+  |+7 | main = putStrLn [fmt|Hello {length name}|]+  |                                    ^^^^
+ test/golden/Hello {name}.16618004304593959603.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:29: error:+    • Variable not in scope: name+    • In the quasi-quotation: [fmt|Hello {name}|]+  |+7 | main = putStrLn [fmt|Hello {name}|]+  |                             ^^^^
+ test/golden/Hello {piCL.{precision}}.9628757040831863475.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:34: error:+    • Variable not in scope: precision+    • In the quasi-quotation: [fmt|Hello {pi:.{precision}}|]+  |+7 | main = putStrLn [fmt|Hello {pi:.{precision}}|]+  |                                  ^^^^^^^^^
+ test/golden/Hello {piCL.{truncate number + precision}}.18094049741103110835.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:52: error:+    • Variable not in scope: precision+    • In the quasi-quotation: [fmt|Hello {pi:.{truncate number + precision}}|]+  |+7 | main = putStrLn [fmt|Hello {pi:.{truncate number + precision}}|]+  |                                                    ^^^^^^^^^
+ test/golden/Hello {piCL{width}}.15463239215289408179.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:33: error:+    • Variable not in scope: width+    • In the quasi-quotation: [fmt|Hello {pi:{width}}|]+  |+7 | main = putStrLn [fmt|Hello {pi:{width}}|]+  |                                 ^^^^^
+ test/golden/fooBSPbar.17645057532673886893.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:25: error:+    • Lexical error in literal section+    • In the quasi-quotation: [fmt|foo\Pbar|]+  |+7 | main = putStrLn [fmt|foo\Pbar|]+  |                         ^
+ test/golden/fooNLbliBSPbar.16759496276764189145.golden view
@@ -0,0 +1,8 @@++INITIALPATH:8:4: error:+    • Lexical error in literal section+    • In the quasi-quotation: [fmt|foo+bli\Pbar|]+  |+8 | bli\Pbar|]+  |    ^
+ test/golden/hello { world.10778336899993839283.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:35: error:+    • +unexpected end of input+expecting "::", ":" or "}"+    • In the quasi-quotation: [fmt|hello { world|]+  |+7 | main = putStrLn [fmt|hello { world|]+  |                                   ^
+ test/golden/hello } world.5295037443422799539.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:28: error:+    • +unexpected '}'+expecting "{{", "}}", "{" or end of input+    • In the quasi-quotation: [fmt|hello } world|]+  |+7 | main = putStrLn [fmt|hello } world|]+  |                            ^
+ test/golden/helloNL {NLlet a = 5NL b = 10NLin 1 + - SL lalalal}.3018767237106994099.golden view
@@ -0,0 +1,11 @@++INITIALPATH:11:10: error:+    • parse error on input `/' in haskell expression+    • In the quasi-quotation: [fmt|hello+    {+let a = 5+    b = 10+in 1 + - / lalalal}|]+   |+11 | in 1 + - / lalalal}|]+   |          ^
+ test/golden/helloNLNLNL{piCLl}.13123157148160021427.golden view
@@ -0,0 +1,12 @@++INITIALPATH:10:6: error:+    • +unexpected "}"+expecting "<", ">", "^" or "="+    • In the quasi-quotation: [fmt|hello+++{pi:l}|]+   |+10 | {pi:l}|]+   |      ^
+ test/golden/{1 + - SL lalalal}.14923086665437293731.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:29: error:+    • parse error on input `/' in haskell expression+    • In the quasi-quotation: [fmt|{1 + - / lalalal}|]+  |+7 | main = putStrLn [fmt|{1 + - / lalalal}|]+  |                             ^
+ test/golden/{TrueCLd}.12627313193367841398.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Integral Bool) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) True)’+      In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) True)+      In an equation for ‘main’: main = putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) True)+  |+7 | main = putStrLn [fmt|{True:d}|]+  |                      ^^^^^^^^^^
+ test/golden/{TrueCLf}.18281408089045870326.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Real Bool) arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’+    • In the first argument of ‘putStrLn’, namely ‘((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Fixed) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) True)’+      In the expression: putStrLn ((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Fixed) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) True)+      In an equation for ‘main’: main = putStrLn ((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Fixed) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) True)+  |+7 | main = putStrLn [fmt|{True:f}|]+  |                      ^^^^^^^^^^
+ test/golden/{True}.16254223077612353942.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (PyF.Internal.QQ.FormatAny2 (PyFClassify Bool) Bool 'PyF.Formatters.AlignAll) arising from a use of ‘PyF.Internal.QQ.formatAny’+    • In the first argument of ‘putStrLn’, namely ‘(((((PyF.Internal.QQ.formatAny PyF.Formatters.Minus) PyF.Internal.QQ.PaddingDefaultK) Nothing) (Nothing :: Maybe Int)) True)’+      In the expression: putStrLn (((((PyF.Internal.QQ.formatAny PyF.Formatters.Minus) PyF.Internal.QQ.PaddingDefaultK) Nothing) (Nothing :: Maybe Int)) True)+      In an equation for ‘main’: main = putStrLn (((((PyF.Internal.QQ.formatAny PyF.Formatters.Minus) PyF.Internal.QQ.PaddingDefaultK) Nothing) (Nothing :: Maybe Int)) True)+  |+7 | main = putStrLn [fmt|{True}|]+  |                      ^^^^^^^^
+ test/golden/{helloCL s}.13047921915648718386.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:30: error:+    • Type incompatible with sign field ( ), use any of {'b', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 'x', 'X', '%'} or remove the sign field.+    • In the quasi-quotation: [fmt|{hello: s}|]+  |+7 | main = putStrLn [fmt|{hello: s}|]+  |                              ^
+ test/golden/{helloCL%}.1257653362598537778.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Real String) arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’+    • In the first argument of ‘putStrLn’, namely ‘((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Percent) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)’+      In the expression: putStrLn ((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Percent) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)+      In an equation for ‘main’: main = putStrLn ((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Percent) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)+  |+7 | main = putStrLn [fmt|{hello:%}|]+  |                      ^^^^^^^^^^^
+ test/golden/{helloCL+s}.1657517030647448626.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:30: error:+    • Type incompatible with sign field (+), use any of {'b', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 'x', 'X', '%'} or remove the sign field.+    • In the quasi-quotation: [fmt|{hello:+s}|]+  |+7 | main = putStrLn [fmt|{hello:+s}|]+  |                              ^
+ test/golden/{helloCL,s}.14139635988852178482.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:30: error:+    • String type is incompatible with grouping (_ or ,).+    • In the quasi-quotation: [fmt|{hello:,s}|]+  |+7 | main = putStrLn [fmt|{hello:,s}|]+  |                              ^
+ test/golden/{helloCL-s}.12627805606214404146.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:30: error:+    • Type incompatible with sign field (-), use any of {'b', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 'x', 'X', '%'} or remove the sign field.+    • In the quasi-quotation: [fmt|{hello:-s}|]+  |+7 | main = putStrLn [fmt|{hello:-s}|]+  |                              ^
+ test/golden/{helloCL=100s}.14374776122070431282.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:33: error:+    • String type is incompatible with inside padding (=).+    • In the quasi-quotation: [fmt|{hello:=100s}|]+  |+7 | main = putStrLn [fmt|{hello:=100s}|]+  |                                 ^
+ test/golden/{helloCL=100}.9444838110946424370.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • String type is incompatible with inside padding (=).+    • In the first argument of ‘putStrLn’, namely ‘(((((PyF.Internal.QQ.formatAny PyF.Formatters.Minus) ((PyF.Internal.QQ.PaddingK (100 :: Int)) (Just (Nothing, PyF.Formatters.AlignInside)))) Nothing) (Nothing :: Maybe Int)) hello)’+      In the expression: putStrLn (((((PyF.Internal.QQ.formatAny PyF.Formatters.Minus) ((PyF.Internal.QQ.PaddingK (100 :: Int)) (Just (Nothing, PyF.Formatters.AlignInside)))) Nothing) (Nothing :: Maybe Int)) hello)+      In an equation for ‘main’: main = putStrLn (((((PyF.Internal.QQ.formatAny PyF.Formatters.Minus) ((PyF.Internal.QQ.PaddingK (100 :: Int)) (Just (Nothing, PyF.Formatters.AlignInside)))) Nothing) (Nothing :: Maybe Int)) hello)+  |+7 | main = putStrLn [fmt|{hello:=100}|]+  |                      ^^^^^^^^^^^^^^
+ test/golden/{helloCLE}.15676531368138664498.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Real String) arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’+    • In the first argument of ‘putStrLn’, namely ‘((((((PyF.Internal.QQ.formatAnyFractional (PyF.Formatters.Upper PyF.Formatters.Exponent)) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)’+      In the expression: putStrLn ((((((PyF.Internal.QQ.formatAnyFractional (PyF.Formatters.Upper PyF.Formatters.Exponent)) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)+      In an equation for ‘main’: main = putStrLn ((((((PyF.Internal.QQ.formatAnyFractional (PyF.Formatters.Upper PyF.Formatters.Exponent)) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)+  |+7 | main = putStrLn [fmt|{hello:E}|]+  |                      ^^^^^^^^^^^
+ test/golden/{helloCLG}.17442699390234010162.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Real String) arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’+    • In the first argument of ‘putStrLn’, namely ‘((((((PyF.Internal.QQ.formatAnyFractional (PyF.Formatters.Upper PyF.Formatters.Generic)) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)’+      In the expression: putStrLn ((((((PyF.Internal.QQ.formatAnyFractional (PyF.Formatters.Upper PyF.Formatters.Generic)) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)+      In an equation for ‘main’: main = putStrLn ((((((PyF.Internal.QQ.formatAnyFractional (PyF.Formatters.Upper PyF.Formatters.Generic)) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)+  |+7 | main = putStrLn [fmt|{hello:G}|]+  |                      ^^^^^^^^^^^
+ test/golden/{helloCLX}.8447528333473699378.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Integral String) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(((((PyF.Internal.QQ.formatAnyIntegral (PyF.Formatters.Upper PyF.Formatters.Hexa)) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)’+      In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral (PyF.Formatters.Upper PyF.Formatters.Hexa)) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)+      In an equation for ‘main’: main = putStrLn (((((PyF.Internal.QQ.formatAnyIntegral (PyF.Formatters.Upper PyF.Formatters.Hexa)) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)+  |+7 | main = putStrLn [fmt|{hello:X}|]+  |                      ^^^^^^^^^^^
+ test/golden/{helloCL_s}.1094067961907256370.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:30: error:+    • String type is incompatible with grouping (_ or ,).+    • In the quasi-quotation: [fmt|{hello:_s}|]+  |+7 | main = putStrLn [fmt|{hello:_s}|]+  |                              ^
+ test/golden/{helloCLb}.14869862508711808562.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Integral String) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)’+      In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)+      In an equation for ‘main’: main = putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)+  |+7 | main = putStrLn [fmt|{hello:b}|]+  |                      ^^^^^^^^^^^
+ test/golden/{helloCLd}.1892681375540151858.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Integral String) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)’+      In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)+      In an equation for ‘main’: main = putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)+  |+7 | main = putStrLn [fmt|{hello:d}|]+  |                      ^^^^^^^^^^^
+ test/golden/{helloCLe}.13933826712837941810.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Real String) arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’+    • In the first argument of ‘putStrLn’, namely ‘((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Exponent) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)’+      In the expression: putStrLn ((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Exponent) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)+      In an equation for ‘main’: main = putStrLn ((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Exponent) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)+  |+7 | main = putStrLn [fmt|{hello:e}|]+  |                      ^^^^^^^^^^^
+ test/golden/{helloCLf}.14332487603622862386.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Real String) arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’+    • In the first argument of ‘putStrLn’, namely ‘((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Fixed) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)’+      In the expression: putStrLn ((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Fixed) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)+      In an equation for ‘main’: main = putStrLn ((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Fixed) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)+  |+7 | main = putStrLn [fmt|{hello:f}|]+  |                      ^^^^^^^^^^^
+ test/golden/{helloCLg}.9607247906229690930.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Real String) arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’+    • In the first argument of ‘putStrLn’, namely ‘((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Generic) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)’+      In the expression: putStrLn ((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Generic) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)+      In an equation for ‘main’: main = putStrLn ((((((PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Generic) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) (Just 6 :: Maybe Int)) hello)+  |+7 | main = putStrLn [fmt|{hello:g}|]+  |                      ^^^^^^^^^^^
+ test/golden/{helloCLo}.9389880575827657266.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Integral String) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)’+      In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)+      In an equation for ‘main’: main = putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)+  |+7 | main = putStrLn [fmt|{hello:o}|]+  |                      ^^^^^^^^^^^
+ test/golden/{helloCLx}.14710080644372944434.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Integral String) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)’+      In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)+      In an equation for ‘main’: main = putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello)+  |+7 | main = putStrLn [fmt|{hello:x}|]+  |                      ^^^^^^^^^^^
+ test/golden/{numberCLX}.4609648040604121432.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Integral Float) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(((((PyF.Internal.QQ.formatAnyIntegral (PyF.Formatters.Upper PyF.Formatters.Hexa)) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)’+      In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral (PyF.Formatters.Upper PyF.Formatters.Hexa)) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)+      In an equation for ‘main’: main = putStrLn (((((PyF.Internal.QQ.formatAnyIntegral (PyF.Formatters.Upper PyF.Formatters.Hexa)) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)+  |+7 | main = putStrLn [fmt|{number:X}|]+  |                      ^^^^^^^^^^^^
+ test/golden/{numberCLb}.8801685868342243288.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Integral Float) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)’+      In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)+      In an equation for ‘main’: main = putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)+  |+7 | main = putStrLn [fmt|{number:b}|]+  |                      ^^^^^^^^^^^^
+ test/golden/{numberCLd}.13336740346716692056.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Integral Float) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)’+      In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)+      In an equation for ‘main’: main = putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)+  |+7 | main = putStrLn [fmt|{number:d}|]+  |                      ^^^^^^^^^^^^
+ test/golden/{numberCLo}.12467189151987896600.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Integral Float) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)’+      In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)+      In an equation for ‘main’: main = putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)+  |+7 | main = putStrLn [fmt|{number:o}|]+  |                      ^^^^^^^^^^^^
+ test/golden/{numberCLx}.14457861675063419224.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:22: error:+    • No instance for (Integral Float) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)’+      In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)+      In an equation for ‘main’: main = putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number)+  |+7 | main = putStrLn [fmt|{number:x}|]+  |                      ^^^^^^^^^^^^
+ test/golden/{piCL.{SL}}.6840925804160914882.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:28: error:+    • parse error on input `/' in haskell expression+    • In the quasi-quotation: [fmt|{pi:.{/}}|]+  |+7 | main = putStrLn [fmt|{pi:.{/}}|]+  |                            ^
+ test/golden/{piCL.{}}.9894464503607709506.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:28: error:+    • +unexpected "}"+expecting an haskell expression+    • In the quasi-quotation: [fmt|{pi:.{}}|]+  |+7 | main = putStrLn [fmt|{pi:.{}}|]+  |                            ^
+ test/golden/{truncate numberCL.3b}.11608798523190422838.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:41: error:+    • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field.+    • In the quasi-quotation: [fmt|{truncate number:.3b}|]+  |+7 | main = putStrLn [fmt|{truncate number:.3b}|]+  |                                         ^
+ test/golden/{truncate numberCL.3d}.9142976352287206710.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:41: error:+    • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field.+    • In the quasi-quotation: [fmt|{truncate number:.3d}|]+  |+7 | main = putStrLn [fmt|{truncate number:.3d}|]+  |                                         ^
+ test/golden/{truncate numberCL.3o}.1443712191031422262.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:41: error:+    • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field.+    • In the quasi-quotation: [fmt|{truncate number:.3o}|]+  |+7 | main = putStrLn [fmt|{truncate number:.3o}|]+  |                                         ^
+ test/golden/{truncate numberCL.3x}.12613302271643495734.golden view
@@ -0,0 +1,7 @@++INITIALPATH:7:41: error:+    • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field.+    • In the quasi-quotation: [fmt|{truncate number:.3x}|]+  |+7 | main = putStrLn [fmt|{truncate number:.3x}|]+  |                                         ^
+ test/golden/{}.14986928820806517861.golden view
@@ -0,0 +1,9 @@++INITIALPATH:7:23: error:+    • +unexpected "}"+expecting an haskell expression+    • In the quasi-quotation: [fmt|{}|]+  |+7 | main = putStrLn [fmt|{}|]+  |                       ^
+ test/golden96/Hello {length name}.16675806454852491955.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:36: error:+    • Variable not in scope: name+    • In the quasi-quotation: [fmt|Hello {length name}|]+  |+7 | main = putStrLn [fmt|Hello {length name}|]+  |                                    ^^^^
+ test/golden96/Hello {name}.16618004304593959603.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:29: error:+    • Variable not in scope: name+    • In the quasi-quotation: [fmt|Hello {name}|]+  |+7 | main = putStrLn [fmt|Hello {name}|]+  |                             ^^^^
+ test/golden96/Hello {piCL.{precision}}.9628757040831863475.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:34: error:+    • Variable not in scope: precision+    • In the quasi-quotation: [fmt|Hello {pi:.{precision}}|]+  |+7 | main = putStrLn [fmt|Hello {pi:.{precision}}|]+  |                                  ^^^^^^^^^
+ test/golden96/Hello {piCL.{truncate number + precision}}.18094049741103110835.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:52: error:+    • Variable not in scope: precision+    • In the quasi-quotation: [fmt|Hello {pi:.{truncate number + precision}}|]+  |+7 | main = putStrLn [fmt|Hello {pi:.{truncate number + precision}}|]+  |                                                    ^^^^^^^^^
+ test/golden96/Hello {piCL{width}}.15463239215289408179.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:33: error:+    • Variable not in scope: width+    • In the quasi-quotation: [fmt|Hello {pi:{width}}|]+  |+7 | main = putStrLn [fmt|Hello {pi:{width}}|]+  |                                 ^^^^^
+ test/golden96/fooBSPbar.17645057532673886893.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:25: error:+    • Lexical error in literal section+    • In the quasi-quotation: [fmt|foo\Pbar|]+  |+7 | main = putStrLn [fmt|foo\Pbar|]+  |                         ^
+ test/golden96/fooNLbliBSPbar.16759496276764189145.golden view
@@ -0,0 +1,7 @@+INITIALPATH:8:4: error:+    • Lexical error in literal section+    • In the quasi-quotation: [fmt|foo+bli\Pbar|]+  |+8 | bli\Pbar|]+  |    ^
+ test/golden96/hello { world.10778336899993839283.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:35: error:+    • +unexpected end of input+expecting "::", ":" or "}"+    • In the quasi-quotation: [fmt|hello { world|]+  |+7 | main = putStrLn [fmt|hello { world|]+  |                                   ^
+ test/golden96/hello } world.5295037443422799539.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:28: error:+    • +unexpected '}'+expecting "{{", "}}", "{" or end of input+    • In the quasi-quotation: [fmt|hello } world|]+  |+7 | main = putStrLn [fmt|hello } world|]+  |                            ^
+ test/golden96/helloNL {NLlet a = 5NL b = 10NLin 1 + - SL lalalal}.3018767237106994099.golden view
@@ -0,0 +1,10 @@+INITIALPATH:11:10: error:+    • parse error on input `/' in haskell expression+    • In the quasi-quotation: [fmt|hello+    {+let a = 5+    b = 10+in 1 + - / lalalal}|]+   |+11 | in 1 + - / lalalal}|]+   |          ^
+ test/golden96/helloNLNLNL{piCLl}.13123157148160021427.golden view
@@ -0,0 +1,11 @@+INITIALPATH:10:6: error:+    • +unexpected "}"+expecting "<", ">", "^" or "="+    • In the quasi-quotation: [fmt|hello+++{pi:l}|]+   |+10 | {pi:l}|]+   |      ^
+ test/golden96/{1 + - SL lalalal}.14923086665437293731.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:29: error:+    • parse error on input `/' in haskell expression+    • In the quasi-quotation: [fmt|{1 + - / lalalal}|]+  |+7 | main = putStrLn [fmt|{1 + - / lalalal}|]+  |                             ^
+ test/golden96/{TrueCLd}.12627313193367841398.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Integral Bool’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing True)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing True)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing True)+  |+7 | main = putStrLn [fmt|{True:d}|]+  |                      ^^^^^^^^^^
+ test/golden96/{TrueCLf}.18281408089045870326.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Real Bool’ arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Fixed PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) True)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Fixed PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) True)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Fixed PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) True)+  |+7 | main = putStrLn [fmt|{True:f}|]+  |                      ^^^^^^^^^^
+ test/golden96/{True}.16254223077612353942.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘PyF.Internal.QQ.FormatAny2 (PyFClassify Bool) Bool PyF.Formatters.AlignAll’ arising from a use of ‘PyF.Internal.QQ.formatAny’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAny PyF.Formatters.Minus PyF.Internal.QQ.PaddingDefaultK Nothing (Nothing :: Maybe Int) True)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAny PyF.Formatters.Minus PyF.Internal.QQ.PaddingDefaultK Nothing (Nothing :: Maybe Int) True)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAny PyF.Formatters.Minus PyF.Internal.QQ.PaddingDefaultK Nothing (Nothing :: Maybe Int) True)+  |+7 | main = putStrLn [fmt|{True}|]+  |                      ^^^^^^^^
+ test/golden96/{helloCL s}.13047921915648718386.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:30: error:+    • Type incompatible with sign field ( ), use any of {'b', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 'x', 'X', '%'} or remove the sign field.+    • In the quasi-quotation: [fmt|{hello: s}|]+  |+7 | main = putStrLn [fmt|{hello: s}|]+  |                              ^
+ test/golden96/{helloCL%}.1257653362598537778.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Real String’ arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Percent PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Percent PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Percent PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)+  |+7 | main = putStrLn [fmt|{hello:%}|]+  |                      ^^^^^^^^^^^
+ test/golden96/{helloCL+s}.1657517030647448626.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:30: error:+    • Type incompatible with sign field (+), use any of {'b', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 'x', 'X', '%'} or remove the sign field.+    • In the quasi-quotation: [fmt|{hello:+s}|]+  |+7 | main = putStrLn [fmt|{hello:+s}|]+  |                              ^
+ test/golden96/{helloCL,s}.14139635988852178482.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:30: error:+    • String type is incompatible with grouping (_ or ,).+    • In the quasi-quotation: [fmt|{hello:,s}|]+  |+7 | main = putStrLn [fmt|{hello:,s}|]+  |                              ^
+ test/golden96/{helloCL-s}.12627805606214404146.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:30: error:+    • Type incompatible with sign field (-), use any of {'b', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 'x', 'X', '%'} or remove the sign field.+    • In the quasi-quotation: [fmt|{hello:-s}|]+  |+7 | main = putStrLn [fmt|{hello:-s}|]+  |                              ^
+ test/golden96/{helloCL=100s}.14374776122070431282.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:33: error:+    • String type is incompatible with inside padding (=).+    • In the quasi-quotation: [fmt|{hello:=100s}|]+  |+7 | main = putStrLn [fmt|{hello:=100s}|]+  |                                 ^
+ test/golden96/{helloCL=100}.9444838110946424370.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-64725]+    • String type is incompatible with inside padding (=).+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAny PyF.Formatters.Minus (PyF.Internal.QQ.PaddingK (100 :: Int) (Just (Nothing, PyF.Formatters.AlignInside))) Nothing (Nothing :: Maybe Int) hello)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAny PyF.Formatters.Minus (PyF.Internal.QQ.PaddingK (100 :: Int) (Just (Nothing, PyF.Formatters.AlignInside))) Nothing (Nothing :: Maybe Int) hello)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAny PyF.Formatters.Minus (PyF.Internal.QQ.PaddingK (100 :: Int) (Just (Nothing, PyF.Formatters.AlignInside))) Nothing (Nothing :: Maybe Int) hello)+  |+7 | main = putStrLn [fmt|{hello:=100}|]+  |                      ^^^^^^^^^^^^^^
+ test/golden96/{helloCLE}.15676531368138664498.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Real String’ arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyFractional (PyF.Formatters.Upper PyF.Formatters.Exponent) PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyFractional (PyF.Formatters.Upper PyF.Formatters.Exponent) PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyFractional (PyF.Formatters.Upper PyF.Formatters.Exponent) PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)+  |+7 | main = putStrLn [fmt|{hello:E}|]+  |                      ^^^^^^^^^^^
+ test/golden96/{helloCLG}.17442699390234010162.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Real String’ arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyFractional (PyF.Formatters.Upper PyF.Formatters.Generic) PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyFractional (PyF.Formatters.Upper PyF.Formatters.Generic) PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyFractional (PyF.Formatters.Upper PyF.Formatters.Generic) PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)+  |+7 | main = putStrLn [fmt|{hello:G}|]+  |                      ^^^^^^^^^^^
+ test/golden96/{helloCLX}.8447528333473699378.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Integral String’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyIntegral (PyF.Formatters.Upper PyF.Formatters.Hexa) PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral (PyF.Formatters.Upper PyF.Formatters.Hexa) PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyIntegral (PyF.Formatters.Upper PyF.Formatters.Hexa) PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)+  |+7 | main = putStrLn [fmt|{hello:X}|]+  |                      ^^^^^^^^^^^
+ test/golden96/{helloCL_s}.1094067961907256370.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:30: error:+    • String type is incompatible with grouping (_ or ,).+    • In the quasi-quotation: [fmt|{hello:_s}|]+  |+7 | main = putStrLn [fmt|{hello:_s}|]+  |                              ^
+ test/golden96/{helloCLb}.14869862508711808562.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Integral String’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)+  |+7 | main = putStrLn [fmt|{hello:b}|]+  |                      ^^^^^^^^^^^
+ test/golden96/{helloCLd}.1892681375540151858.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Integral String’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)+  |+7 | main = putStrLn [fmt|{hello:d}|]+  |                      ^^^^^^^^^^^
+ test/golden96/{helloCLe}.13933826712837941810.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Real String’ arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Exponent PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Exponent PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Exponent PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)+  |+7 | main = putStrLn [fmt|{hello:e}|]+  |                      ^^^^^^^^^^^
+ test/golden96/{helloCLf}.14332487603622862386.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Real String’ arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Fixed PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Fixed PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Fixed PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)+  |+7 | main = putStrLn [fmt|{hello:f}|]+  |                      ^^^^^^^^^^^
+ test/golden96/{helloCLg}.9607247906229690930.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Real String’ arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Generic PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Generic PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyFractional PyF.Formatters.Generic PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing (Just 6 :: Maybe Int) hello)+  |+7 | main = putStrLn [fmt|{hello:g}|]+  |                      ^^^^^^^^^^^
+ test/golden96/{helloCLo}.9389880575827657266.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Integral String’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)+  |+7 | main = putStrLn [fmt|{hello:o}|]+  |                      ^^^^^^^^^^^
+ test/golden96/{helloCLx}.14710080644372944434.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Integral String’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello)+  |+7 | main = putStrLn [fmt|{hello:x}|]+  |                      ^^^^^^^^^^^
+ test/golden96/{numberCLX}.4609648040604121432.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Integral Float’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyIntegral (PyF.Formatters.Upper PyF.Formatters.Hexa) PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral (PyF.Formatters.Upper PyF.Formatters.Hexa) PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyIntegral (PyF.Formatters.Upper PyF.Formatters.Hexa) PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)+  |+7 | main = putStrLn [fmt|{number:X}|]+  |                      ^^^^^^^^^^^^
+ test/golden96/{numberCLb}.8801685868342243288.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Integral Float’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)+  |+7 | main = putStrLn [fmt|{number:b}|]+  |                      ^^^^^^^^^^^^
+ test/golden96/{numberCLd}.13336740346716692056.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Integral Float’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)+  |+7 | main = putStrLn [fmt|{number:d}|]+  |                      ^^^^^^^^^^^^
+ test/golden96/{numberCLo}.12467189151987896600.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Integral Float’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)+  |+7 | main = putStrLn [fmt|{number:o}|]+  |                      ^^^^^^^^^^^^
+ test/golden96/{numberCLx}.14457861675063419224.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:22: error: [GHC-39999]+    • No instance for ‘Integral Float’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’+    • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)’+      In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)+      In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number)+  |+7 | main = putStrLn [fmt|{number:x}|]+  |                      ^^^^^^^^^^^^
+ test/golden96/{piCL.{SL}}.6840925804160914882.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:28: error:+    • parse error on input `/' in haskell expression+    • In the quasi-quotation: [fmt|{pi:.{/}}|]+  |+7 | main = putStrLn [fmt|{pi:.{/}}|]+  |                            ^
+ test/golden96/{piCL.{}}.9894464503607709506.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:28: error:+    • +unexpected "}"+expecting an haskell expression+    • In the quasi-quotation: [fmt|{pi:.{}}|]+  |+7 | main = putStrLn [fmt|{pi:.{}}|]+  |                            ^
+ test/golden96/{truncate numberCL.3b}.11608798523190422838.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:41: error:+    • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field.+    • In the quasi-quotation: [fmt|{truncate number:.3b}|]+  |+7 | main = putStrLn [fmt|{truncate number:.3b}|]+  |                                         ^
+ test/golden96/{truncate numberCL.3d}.9142976352287206710.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:41: error:+    • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field.+    • In the quasi-quotation: [fmt|{truncate number:.3d}|]+  |+7 | main = putStrLn [fmt|{truncate number:.3d}|]+  |                                         ^
+ test/golden96/{truncate numberCL.3o}.1443712191031422262.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:41: error:+    • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field.+    • In the quasi-quotation: [fmt|{truncate number:.3o}|]+  |+7 | main = putStrLn [fmt|{truncate number:.3o}|]+  |                                         ^
+ test/golden96/{truncate numberCL.3x}.12613302271643495734.golden view
@@ -0,0 +1,6 @@+INITIALPATH:7:41: error:+    • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field.+    • In the quasi-quotation: [fmt|{truncate number:.3x}|]+  |+7 | main = putStrLn [fmt|{truncate number:.3x}|]+  |                                         ^
+ test/golden96/{}.14986928820806517861.golden view
@@ -0,0 +1,8 @@+INITIALPATH:7:23: error:+    • +unexpected "}"+expecting an haskell expression+    • In the quasi-quotation: [fmt|{}|]+  |+7 | main = putStrLn [fmt|{}|]+  |                       ^