├── .envrc ├── .github └── workflows │ ├── ci.yml │ └── master.yml ├── .gitignore ├── ChangeLog.md ├── LICENSE ├── PyF.cabal ├── Readme.md ├── Setup.hs ├── error_example.png ├── flake.lock ├── flake.nix ├── hie.yaml ├── nixpkgs.nix ├── src ├── PyF.hs └── PyF │ ├── Class.hs │ ├── Formatters.hs │ └── Internal │ ├── Meta.hs │ ├── Parser.hs │ ├── ParserEx.hs │ ├── PythonSyntax.hs │ └── QQ.hs ├── stack.yaml ├── test ├── Spec.hs ├── SpecCustomDelimiters.hs ├── SpecFail.hs ├── SpecOverloaded.hs ├── golden │ ├── Hello {length name}.16675806454852491955.golden │ ├── Hello {name}.16618004304593959603.golden │ ├── Hello {piCL.{precision}}.9628757040831863475.golden │ ├── Hello {piCL.{truncate number + precision}}.18094049741103110835.golden │ ├── Hello {piCL{width}}.15463239215289408179.golden │ ├── fooBSPbar.17645057532673886893.golden │ ├── fooNLbliBSPbar.16759496276764189145.golden │ ├── hello { world.10778336899993839283.golden │ ├── hello } world.5295037443422799539.golden │ ├── helloNL {NLlet a = 5NL b = 10NLin 1 + - SL lalalal}.3018767237106994099.golden │ ├── helloNLNLNL{piCLl}.13123157148160021427.golden │ ├── {1 + - SL lalalal}.14923086665437293731.golden │ ├── {TrueCLd}.12627313193367841398.golden │ ├── {TrueCLf}.18281408089045870326.golden │ ├── {True}.16254223077612353942.golden │ ├── {helloCL s}.13047921915648718386.golden │ ├── {helloCL%}.1257653362598537778.golden │ ├── {helloCL+s}.1657517030647448626.golden │ ├── {helloCL,s}.14139635988852178482.golden │ ├── {helloCL-s}.12627805606214404146.golden │ ├── {helloCL=100s}.14374776122070431282.golden │ ├── {helloCL=100}.9444838110946424370.golden │ ├── {helloCLE}.15676531368138664498.golden │ ├── {helloCLG}.17442699390234010162.golden │ ├── {helloCLX}.8447528333473699378.golden │ ├── {helloCL_s}.1094067961907256370.golden │ ├── {helloCLb}.14869862508711808562.golden │ ├── {helloCLd}.1892681375540151858.golden │ ├── {helloCLe}.13933826712837941810.golden │ ├── {helloCLf}.14332487603622862386.golden │ ├── {helloCLg}.9607247906229690930.golden │ ├── {helloCLo}.9389880575827657266.golden │ ├── {helloCLx}.14710080644372944434.golden │ ├── {numberCLX}.4609648040604121432.golden │ ├── {numberCLb}.8801685868342243288.golden │ ├── {numberCLd}.13336740346716692056.golden │ ├── {numberCLo}.12467189151987896600.golden │ ├── {numberCLx}.14457861675063419224.golden │ ├── {piCL.{SL}}.6840925804160914882.golden │ ├── {piCL.{}}.9894464503607709506.golden │ ├── {truncate numberCL.3b}.11608798523190422838.golden │ ├── {truncate numberCL.3d}.9142976352287206710.golden │ ├── {truncate numberCL.3o}.1443712191031422262.golden │ ├── {truncate numberCL.3x}.12613302271643495734.golden │ └── {}.14986928820806517861.golden └── golden96 │ ├── Hello {length name}.16675806454852491955.golden │ ├── Hello {name}.16618004304593959603.golden │ ├── Hello {piCL.{precision}}.9628757040831863475.golden │ ├── Hello {piCL.{truncate number + precision}}.18094049741103110835.golden │ ├── Hello {piCL{width}}.15463239215289408179.golden │ ├── fooBSPbar.17645057532673886893.golden │ ├── fooNLbliBSPbar.16759496276764189145.golden │ ├── hello { world.10778336899993839283.golden │ ├── hello } world.5295037443422799539.golden │ ├── helloNL {NLlet a = 5NL b = 10NLin 1 + - SL lalalal}.3018767237106994099.golden │ ├── helloNLNLNL{piCLl}.13123157148160021427.golden │ ├── {1 + - SL lalalal}.14923086665437293731.golden │ ├── {TrueCLd}.12627313193367841398.golden │ ├── {TrueCLf}.18281408089045870326.golden │ ├── {True}.16254223077612353942.golden │ ├── {helloCL s}.13047921915648718386.golden │ ├── {helloCL%}.1257653362598537778.golden │ ├── {helloCL+s}.1657517030647448626.golden │ ├── {helloCL,s}.14139635988852178482.golden │ ├── {helloCL-s}.12627805606214404146.golden │ ├── {helloCL=100s}.14374776122070431282.golden │ ├── {helloCL=100}.9444838110946424370.golden │ ├── {helloCLE}.15676531368138664498.golden │ ├── {helloCLG}.17442699390234010162.golden │ ├── {helloCLX}.8447528333473699378.golden │ ├── {helloCL_s}.1094067961907256370.golden │ ├── {helloCLb}.14869862508711808562.golden │ ├── {helloCLd}.1892681375540151858.golden │ ├── {helloCLe}.13933826712837941810.golden │ ├── {helloCLf}.14332487603622862386.golden │ ├── {helloCLg}.9607247906229690930.golden │ ├── {helloCLo}.9389880575827657266.golden │ ├── {helloCLx}.14710080644372944434.golden │ ├── {numberCLX}.4609648040604121432.golden │ ├── {numberCLb}.8801685868342243288.golden │ ├── {numberCLd}.13336740346716692056.golden │ ├── {numberCLo}.12467189151987896600.golden │ ├── {numberCLx}.14457861675063419224.golden │ ├── {piCL.{SL}}.6840925804160914882.golden │ ├── {piCL.{}}.9894464503607709506.golden │ ├── {truncate numberCL.3b}.11608798523190422838.golden │ ├── {truncate numberCL.3d}.9142976352287206710.golden │ ├── {truncate numberCL.3o}.1443712191031422262.golden │ ├── {truncate numberCL.3x}.12613302271643495734.golden │ └── {}.14986928820806517861.golden ├── tree-sitter-pyf ├── .envrc ├── Readme.md ├── example-file ├── grammar.js ├── nvim_ts_highlight.png ├── package.json ├── tree-sitter.json └── vim-plugin │ └── after │ └── queries │ ├── haskell │ ├── highlights.scm │ └── injections.scm │ └── pyf │ ├── highlights.scm │ └── injections.scm └── treefmt.nix /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: (fast) CI 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: cachix/install-nix-action@v21 13 | - uses: cachix/cachix-action@v12 14 | with: 15 | name: guibou 16 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 17 | # Builds 18 | - name: Build current GHC 19 | run: nix build 20 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: Complete CI 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | jobs: 8 | nix_matrix: 9 | strategy: 10 | matrix: 11 | ghc: [90, 92, 94, 96, 98, 910, 912] 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: cachix/install-nix-action@v21 16 | - uses: cachix/cachix-action@v12 17 | with: 18 | name: guibou 19 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 20 | # Builds cabal (nix) 21 | - name: Build with GHC ${{ matrix.ghc }} 22 | run: nix build .#pyf_${{ matrix.ghc }} 23 | stack_build: 24 | strategy: 25 | matrix: 26 | resolver: [lts-14.27, # 8.6 27 | lts-16.31, # 8.8 28 | lts-17.1, # 8.10 29 | lts-19.1, # 9.0 30 | lts-20.26, # 9.2 31 | lts-21.17, # 9.4 32 | lts-22.22, # 9.6 33 | lts-23.2, # 9.8 34 | nightly-2025-01-03, # 9.10 nightly 35 | ] 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v2 39 | - name: Setup Stack 40 | run: sudo apt-get install haskell-stack 41 | - name: Stack resolver ${{ matrix.resolver }} 42 | run: stack --resolver ${{ matrix.resolver }} test 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | dist-newstyle 3 | .stack-work 4 | test/golden/*.actual 5 | result 6 | stack.yaml.lock 7 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # Revision history for PyF 2 | 3 | ## 0.11.4.0 -- 2025-01-03 4 | 5 | - Fix indentation in `fmtTrim` when line break was escaped (bug https://github.com/guibou/PyF/issues/141). 6 | - Support for GHC 9.12. 7 | - Fix for tests in GHC 9.10. 8 | - No more "python" reference check in the test phase. I'm removing complexity, 9 | and if it does not match the python implementation, we can just introduce a 10 | new test case. Note that python checking can be reimplemented easilly by 11 | parsing the AST. 12 | 13 | ## 0.11.3.0 -- 2024-05-15 14 | 15 | - Support for GHC 9.10. 16 | 17 | ## 0.11.2.1 -- 2023-10-25 18 | 19 | - Final version for GHC 9.8 20 | 21 | ## 0.11.2.0 22 | 23 | - Fix for the neovim treesitter syntax highlighter for `fmt` and `fmtTrim` quasiquotes 24 | - Initial support for GHC 9.8 25 | - Version bump for new MTL 26 | 27 | ## 0.11.1.1 -- 2023-03-15 28 | 29 | - Support for GHC 9.6. Thank you @Kleidukos for initiating the port. 30 | 31 | ## 0.11.1.0 -- 2022-09-24 32 | 33 | - Support for OverloadedRecordsDot syntax in Meta. Thank you @Profpatsch for the report. 34 | - 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. 35 | 36 | ## 0.11.0.0 -- 2022-08-10 37 | 38 | - Support for GHC 9.4. (Written with a pre-release of GHC 9.4, hopefully it won't change too much before the release). 39 | - Error reporting now uses the native GHC API. In summary, it means that 40 | haskell-language-server will point to the correct location of the error, not 41 | the beginning of the quasi quotes. 42 | - 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. 43 | - Add support for literal `[]` and `()` in haskell expression. 44 | - Add support for overloaded labels, thank you Shimuuar. 45 | - Support for `::` in haskell expression. Such as `[fmt| 10 :: Int:d}|]`, as a suggestion from julm (close #87). 46 | - `Integral` padding width and precision also for formatter without type specifier. 47 | - Extra care was used to catch all `type-defaults` warning message. PyF should 48 | not generate code with this kind of warning, unless the embedded Haskell 49 | expression are ambiguous (e.g. `[fmt|{10}|]`). You can use `::` to 50 | disambiguate, e.g. `[fmt|{10 :: Int}|]`. 51 | 52 | ## 0.10.1.0 -- 2021-12-05 53 | 54 | - Padding width can now be any arbitrary Haskell expression, such as `[fmt|hello pi = {pi:<{5 * 10}}|]`. 55 | - Precision (and now padding width) arbitrary expression can now be any `Integral` and it is not limited to `Int` anymore. 56 | - (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}}|]`. 57 | 58 | ## 0.10.0.1 -- 2021-10-30 59 | 60 | - 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. 61 | 62 | - Remove the dependency to `megaparsec` and replaces it by `parsec`. This should have minor impact on the error messages. 63 | 64 | - *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. 65 | 66 | - Added instances for `(Lazy)ByteString` to `PyFClassify` and `PyFToString`. `ByteString` can now be integrated into format string, and will be decoded as ascii. 67 | 68 | - 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. 69 | 70 | - Added instance for `(Nominal)DiffTime` to `PyFClassify`, so you can now format them without conversion. 71 | 72 | - 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. 73 | 74 | - Support for `Char` formatting, as string (showing the `Char` value) or as integral, showing the `ord`. 75 | 76 | - `Data.Ratio`. 77 | 78 | - Introducing `fmtTrim` module. It offers the same behavior as `fmt`, but trims common indentation. Se `PyF.trimIndent` for documentation. 79 | 80 | - Introducing `raw` for convenience. It is a multiline string without any escaping, formatting neither leading whitespace handling. 81 | 82 | - 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. 83 | 84 | - `fmtWithDelimiters` is gone and replaced by `mkFormatter` in `PyF` which is "more" generic. 85 | 86 | ## 0.9.0.3 -- 2021-02-06 87 | 88 | - Test phase do not depend anymore on python (unless cabal flag `python_test` is 89 | set). This ease the deployment / packaging process. 90 | 91 | ## 0.9.0.2 -- 2020-09-11 92 | 93 | - Version bump for megaparsec 9.0 94 | 95 | ## 0.9.0.1 -- 2020-03-25 96 | 97 | - Fixs for GHC 8.10 98 | 99 | ## 0.9.0.0 -- 2019-12-29 100 | 101 | - 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). 102 | 103 | ## 0.8.1.2 -- 2019-11-08 104 | 105 | - Bump megaparsec bounds 106 | 107 | ## 0.8.1.1 -- 2019-10-13 108 | 109 | - Compatibility with GHC 8.8 110 | 111 | ## 0.8.1.0 -- 2019-09-03 112 | 113 | - Precision can now be any arbitrary haskell expression, such as `[fmt|hello pi = {pi:.{1 + 3}}|]`. 114 | 115 | ## 0.8.0.2 -- 2019-08-27 116 | 117 | - (minor bugfix in tests): Use python3 instead of "python" to help build on environment with both python2 and python3 118 | 119 | ## 0.8.0.1 -- 2019-08-27 120 | 121 | - Stack support 122 | 123 | ## 0.8.0.0 -- 2019-08-06 124 | 125 | - `f` (and `fWithDelimiters`) were renamed `fmt` (`fmtWithDelimiters`). `f` was causing too much shadowing in any codebase. 126 | - PyF now exposes the typeclass `PyFToString` and `PyFClassify` which can be extended to support any type as input for the formatters. 127 | - 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. 128 | - A caveat of the previous change is that PyF does not have instances for `IO` anymore. 129 | 130 | ### bugfixes and general improvements 131 | 132 | - An important amount of bugfixs 133 | - Error reporting for generic formatting (i.e. formatting without a specified type) is now more robust 134 | - 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. 135 | - PyF now longer emit unnecessary default typing. 136 | 137 | ## 0.7.3.0 -- 2019-02-28 138 | 139 | - Tests: fix non reproducible tests 140 | 141 | ## 0.7.2.0 -- 2019-02-27 142 | 143 | - Fixed: PyF now uses the same haskell extensions as the one used by the current haskell file to parse sub expressions. 144 | 145 | ## 0.7.1.0 -- 2019-02-11 146 | 147 | - Fixed: PyF was wrongly ignoring everything located after a non-doubled closing delimiter. 148 | - New Feature: line break can be escaped with , thus allowing string to start on a new line ignoring the initial backspace 149 | 150 | ## 0.7.0.0 -- 2019-02-04 151 | 152 | - Bump dependencies to megaparsec 7 153 | - Error message are now tested 154 | - Name in template haskell splices are stable. This increases readability of error messages 155 | - Better error message for badly formated expression 156 | 157 | ### Formatting removal 158 | 159 | - All monomorphic quasiquoters (`f`, `fString`, `fText`, `fIO`, `fLazyText`) are removed 160 | - Polymophic quasiquoter `f'` is renamed `f` and is the only entry point. Monomorphic users are encouraged to use the polymorphic quasiquoter with type annotation. 161 | - `Formatting` dependency is removed. 162 | - Previously named `f` quasiquoters which was exporting to `Formatting.Format` is removed. User of this behavior should use `Formatting.now` instead. 163 | 164 | ## 0.6.1.0 -- 2018-08-03 165 | 166 | - Custom delimiters, you can use whatever delimiters you want in place of `{` and `}`. 167 | 168 | ## 0.6.0.0 -- 2018-08-02 169 | 170 | - Fix the espace parsing of `{{` and `}}` as `{` and `}` 171 | 172 | ## 0.5.0.0 -- 2018-04-16 173 | 174 | - Support for negative zero 175 | - Support for 0 modifier 176 | - Exponential formatter now behaves as python 177 | - Support for alternate floatting point represenation 178 | - Lot of documentation 179 | - Test are auto verified with the python reference implementation 180 | 181 | ## 0.4.0.0 -- 2018-04-13 182 | 183 | - Support for grouping option 184 | - Support for inner allignment 185 | - Correct display of NaN and Infinity 186 | - Fix a few cosmetic with python implementation 187 | - Introduce `PyF.Formatters`, type safe generic number formatter solution 188 | - Remove dependency to `scientific` 189 | 190 | ## 0.3.0.0 -- 2018-04-01 191 | 192 | - Support for haskell subexpression 193 | 194 | ## 0.1.1.0 -- 2018-01-07 195 | 196 | - Add support for the `sign` field. 197 | 198 | ## 0.1.0.0 -- 2018-01-03 199 | 200 | - First version. Released on an unsuspecting world. 201 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Guillaume Bouchard 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Guillaume Bouchard nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /PyF.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 2.4 2 | name: PyF 3 | version: 0.11.4.0 4 | synopsis: 5 | Quasiquotations for a python like interpolated string formatter 6 | 7 | description: 8 | Quasiquotations for a python like interpolated string formatter. 9 | 10 | license: BSD-3-Clause 11 | license-file: LICENSE 12 | author: Guillaume Bouchard 13 | maintainer: guillaum.bouchard@gmail.com 14 | category: Text 15 | build-type: Simple 16 | extra-source-files: 17 | ChangeLog.md 18 | Readme.md 19 | test/golden/*.golden 20 | test/golden96/*.golden 21 | 22 | library 23 | exposed-modules: 24 | PyF 25 | PyF.Class 26 | PyF.Formatters 27 | PyF.Internal.Meta 28 | PyF.Internal.Parser 29 | PyF.Internal.ParserEx 30 | PyF.Internal.PythonSyntax 31 | PyF.Internal.QQ 32 | 33 | build-depends: 34 | , base >=4.12 && <4.22 35 | , bytestring >=0.10.8 && <0.13 36 | , ghc >=8.6.1 37 | , mtl >=2.2.2 && <2.4 38 | , parsec >=3.1.13 && <3.2 39 | , template-haskell >=2.14.0 && <2.24 40 | , text >=1.2.3 && <2.2 41 | , time >=1.8.0 && <1.15 42 | 43 | if impl(ghc <9.2.1) 44 | build-depends: ghc-boot >=8.6.1 && <9.7 45 | 46 | hs-source-dirs: src 47 | ghc-options: -Wall -Wunused-packages -Wincomplete-uni-patterns 48 | default-language: Haskell2010 49 | default-extensions: QuasiQuotes 50 | 51 | test-suite pyf-test 52 | type: exitcode-stdio-1.0 53 | hs-source-dirs: test 54 | main-is: Spec.hs 55 | other-modules: SpecCustomDelimiters 56 | build-depends: 57 | , base 58 | , bytestring 59 | , hspec 60 | , PyF 61 | , template-haskell 62 | , text 63 | , time 64 | 65 | ghc-options: 66 | -Wall -threaded -rtsopts -with-rtsopts=-N -Wunused-packages 67 | 68 | default-language: Haskell2010 69 | 70 | test-suite pyf-overloaded 71 | type: exitcode-stdio-1.0 72 | hs-source-dirs: test 73 | main-is: SpecOverloaded.hs 74 | build-depends: 75 | , base 76 | , bytestring 77 | , hspec 78 | , PyF 79 | , text 80 | 81 | ghc-options: 82 | -Wall -threaded -rtsopts -with-rtsopts=-N -Wunused-packages 83 | 84 | default-language: Haskell2010 85 | 86 | test-suite pyf-failure 87 | type: exitcode-stdio-1.0 88 | hs-source-dirs: test 89 | main-is: SpecFail.hs 90 | build-depends: 91 | , base 92 | , deepseq 93 | , filepath 94 | , hspec 95 | , HUnit 96 | , process 97 | , PyF 98 | , temporary 99 | , text 100 | 101 | ghc-options: 102 | -Wall -threaded -rtsopts -with-rtsopts=-N -Wunused-packages 103 | 104 | default-language: Haskell2010 105 | 106 | source-repository head 107 | type: git 108 | location: http://github.com/guibou/PyF 109 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # PyF 2 | 3 | *PyF* is a Haskell library for string interpolation and formatting. 4 | 5 | *PyF* exposes a quasiquoter `fmt` which introduces string interpolation and formatting with a mini language inspired by printf and Python. 6 | 7 | # Quick Start 8 | 9 | ```haskell 10 | >>> :set -XQuasiQuotes 11 | >>> import PyF 12 | 13 | >>> name = "Dave" 14 | >>> age = 54 15 | 16 | >>> [fmt|Person's name is {name}, age is {age}|] 17 | "Person's name is Dave, age is 54" 18 | ``` 19 | 20 | The formatting mini language can represent: 21 | 22 | - Numbers with different representations (fixed point, general representation, binary, hexadecimal, octal) 23 | - Padding, with the choice of padding char, alignment (left, right, around, between sign and number) 24 | - Sign handling, to display or not the `+` for positive number 25 | - Number grouping 26 | - Floating point representation 27 | - The interpolated value can be any Haskell expression. 28 | 29 | 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. 30 | 31 | 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. 32 | 33 | # More Examples 34 | 35 | ## Padding 36 | 37 | Left `<` / Right `>` / Around `^` padding: 38 | 39 | ```haskell 40 | >>> name = "Guillaume" 41 | >>> [fmt|{name:<11}|] 42 | "Guillaume " 43 | >>> [fmt|{name:>11}|] 44 | " Guillaume" 45 | >>> [fmt|{name:|^13}|] 46 | "||Guillaume||" 47 | ``` 48 | 49 | Padding inside `=` the sign 50 | 51 | ```haskell 52 | >>> [fmt|{-3:=6}|] 53 | "- 3" 54 | ``` 55 | 56 | ## Float rounding 57 | 58 | ```haskell 59 | >>> [fmt|{pi:.2}|] 60 | "3.14" 61 | ``` 62 | 63 | ## Binary / Octal / Hex representation (with or without prefix using `#`) 64 | 65 | ```haskell 66 | >>> v = 31 67 | >>> [fmt|Binary: {v:#b}|] 68 | "Binary: 0b11111" 69 | >>> [fmt|Octal: {v:#o}|] 70 | "Octal: 0o37" 71 | >>> [fmt|Octal (no prefix): {v:o}|] 72 | "Octal (no prefix): 37" 73 | >>> [fmt|Hexa (caps and prefix): {v:#X}|] 74 | "Hexa (caps and prefix): 0X1F" 75 | ``` 76 | 77 | ## Grouping 78 | 79 | Using `,` or `_`. 80 | 81 | ```haskell 82 | >>> [fmt|{10 ^ 9 - 1:,}|] 83 | "999,999,999" 84 | >>> [fmt|{2 ^ 32 -1:_b}|] 85 | "1111_1111_1111_1111_1111_1111_1111_1111" 86 | ``` 87 | 88 | ## Sign handling 89 | 90 | Using `+` to display the positive sign (if any) or ` ` to display a space instead: 91 | 92 | ```haskell 93 | >>> [fmt|{pi:+.3}|] 94 | "+3.142" 95 | >>> [fmt|{-pi:+.3} (Negative number)|] 96 | "-3.142 (Negative number)" 97 | >>> [fmt|{pi: .3}|] 98 | " 3.142" 99 | >>> [fmt|{-pi: .3} (Negative number)|] 100 | "-3.142 (Negative number)" 101 | ``` 102 | 103 | ## 0 104 | 105 | Preceding the width with a `0` enables sign-aware zero-padding, this is equivalent to inside `=` padding with a fill char of `0`. 106 | 107 | ```haskell 108 | >>> [fmt|{10:010}|] 109 | 0000000010 110 | >>> [fmt|{-10:010}|] 111 | -000000010 112 | ``` 113 | 114 | ## Sub-expressions 115 | 116 | First argument inside the curly braces can be a valid Haskell expression, for example: 117 | 118 | ```haskell 119 | >>> [fmt|2pi = {2* pi:.2}|] 120 | 2pi = 6.28 121 | >>> [fmt|tail "hello" = {tail "hello":->6}|] 122 | "tail \"hello\" = --ello" 123 | ``` 124 | 125 | However the expression must not contain `}` or `:` characters. 126 | 127 | ## Combined 128 | 129 | Most options can be combined. This generally leads to totally unreadable format string ;) 130 | 131 | ```haskell 132 | >>> [fmt|{pi:~>5.2}|] 133 | "~~3.14" 134 | ``` 135 | 136 | ## Multi-line strings 137 | 138 | You can ignore a line break with `\` if needed. For example: 139 | 140 | ```haskell 141 | [fmt|\ 142 | - a 143 | - b\ 144 | |] 145 | ``` 146 | 147 | Will returns `"- a\n- b"`. Note how the first and last line breaks are ignored. 148 | 149 | ## Arbitrary value for precision and padding 150 | 151 | The precision and padding width fields can be any Haskell expression (including variables) instead of a fixed number: 152 | 153 | ```haskell 154 | >>> [fmt|{pi:.{1+2}}|] 155 | 3.142 156 | ``` 157 | 158 | ```haskell 159 | >>> [fmt|{1986:^{2 * 10}d}|] 160 | " 1986 " 161 | ``` 162 | 163 | # Output type 164 | 165 | *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. 166 | 167 | ```haskell 168 | >>> [fmt|hello {pi.2}|] :: String 169 | "hello 3.14" 170 | ``` 171 | 172 | # Custom types 173 | 174 | PyF can format three categories of input types: 175 | 176 | - Floating. Using the `f`, `g`, `e`, ... type specifiers. Any type instance of `RealFloat` can be formated as such. 177 | - Integral. Using the `d`, `b`, `x`, `o`, ... type specifiers. Any type instance of `Integral` can be formated as such. 178 | - String. Using the `s` type specifier. Any type instance of `PyFToString` can be formated as such. 179 | 180 | See `PyF.Class` if you want to create new instances for the `PyFToString` class. 181 | 182 | 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. 183 | 184 | # Caveats 185 | 186 | ## Type inference 187 | 188 | Type inference with numeric literals can be unreliable if your variables are too polymorphic. A type annotation or the extension `ExtendedDefaultRules` will help. 189 | 190 | ```haskell 191 | >>> v = 10 :: Double 192 | >>> [fmt|A float: {v}|] 193 | A float: 10 194 | ``` 195 | 196 | ## Error reporting 197 | 198 | Template Haskell is generally known to give developers a lot of 199 | frustration when it comes to error messages, dumping an unreadable 200 | piece of generated code. 201 | 202 | However, in PyF, we took great care to provide clear error reporting, this means that: 203 | 204 | - Any parsing error on the mini language results in a clear indication of the error, for example: 205 | 206 | ```haskell 207 | foo = [fmt|{age:.3d}|] 208 | ``` 209 | 210 | ``` 211 | File.hs:77:19: error: 212 | | 213 | 1 | {age:.3d} 214 | | ^ 215 | Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field. 216 | ``` 217 | 218 | 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: 219 | 220 | ![Error reported in editor](error_example.png) 221 | 222 | - Error in variable name are also readable: 223 | 224 | ```haskell 225 | test/SpecUtils.hs:81:33: error: 226 | • Variable not found: chien 227 | • In the quasi-quotation: [fmt|A missing variable: {chien}|] 228 | | 229 | 81 | fiz = [fmt|A missing variable: {chien}|] 230 | | ^^^^^ 231 | ``` 232 | 233 | - However, if the interpolated name is not of a compatible type (or 234 | too polymorphic), you will get an awful error: 235 | 236 | ```haskell 237 | >>*> [fmt|{True:d}|] 238 | 239 | :80:10: error: 240 | • No instance for (Integral Bool) 241 | arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 242 | ... 243 | ``` 244 | 245 | - There is also one class of error related to alignement which can be triggered, when using alignement inside sign (i.e. `=`) with string: 246 | 247 | ```haskell 248 | *PyF PyF.Internal.QQ> [fmt|{"hello":=10}|] 249 | 250 | :89:10: error: 251 | • String Cannot be aligned with the inside `=` mode 252 | ... 253 | ``` 254 | 255 | - 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. 256 | 257 | ```haskell 258 | >>> [fmt|{3 + pi + "hello":10}|] 259 | 260 | :99:10: error: 261 | • No instance for (Floating [Char]) arising from a use of ‘pi’ 262 | ... 263 | ``` 264 | 265 | ## Custom Delimiters 266 | 267 | 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. 268 | 269 | For example, in `MyCustomDelimiter.hs`: 270 | 271 | ```haskell 272 | module MyCustomQQ where 273 | 274 | import Language.Haskell.TH.Quote 275 | 276 | import PyF 277 | 278 | myCustomFormatter :: QuasiQuoter 279 | myCustomFormatter = mkFormatter "fmtWithDelimiters" (fmtConfig { 280 | delimiters = ('@','!') 281 | }) 282 | ``` 283 | 284 | Later, in another module: 285 | 286 | ```haskell 287 | import MyCustomQQ 288 | 289 | -- ... 290 | 291 | [myCustomFormatter|pi = @pi:2.f!|] 292 | ``` 293 | 294 | Escaping still works by doubling the delimiters, `@@!!@@!!` will be formatted as `@!@!`. 295 | 296 | Have a look at `PyF.mkFormatter` for all the details about customization. 297 | 298 | ## Differences with the Python Syntax 299 | 300 | 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. 301 | 302 | ### Not supported 303 | 304 | - 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. 305 | - 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. 306 | - 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. 307 | - 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. 308 | 309 | ### Differences 310 | 311 | - 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. 312 | - Grouping options allows grouping with an `_` for floating point, Python only allows `,`. 313 | - Custom delimiters 314 | 315 | # Build / test 316 | 317 | Should work with `stack build; stack test`, and with `cabal` and (optionally) `nix`: 318 | 319 | ```shell 320 | nix-shell # Optional, if you use nix 321 | cabal build 322 | cabal test 323 | ``` 324 | 325 | There are a few available shells for you. 326 | 327 | - `nix-shell` is the default, current GHC version with language server available. 328 | - `nix-shell ./. -A pyf_xx.shell` is another GHC version (change `xx`) without language server. 329 | - `nix-shell ./. -A pyf_xx.shell_hls` is another GHC version (change `xx`) with language server. 330 | 331 | We also provide a few utility functions: 332 | 333 | - `nix-build ./ -A hlint` will check hlint. 334 | - `nix-shell ./ -A ormolu-fix` will format the codebase. 335 | 336 | # Library note 337 | 338 | `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: 339 | 340 | ```haskell 341 | >>> formatIntegral Binary Plus (Just (20, AlignInside, '~')) (Just (4, ',')) 255 342 | "+~~~~~~~~~~1111,1111" 343 | ``` 344 | 345 | # GHC compatibility 346 | 347 | This library is tested in CI with ghc 8.6 to 9.12. 348 | 349 | # Conclusion 350 | 351 | Don't hesitate to make any suggestion, I'll be more than happy to work on it. 352 | 353 | # Hacking 354 | 355 | Everything works with nix and flakes. But you can also try with manual cabal / stack if you wish. 356 | 357 | - `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: 358 | - `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`. 359 | 360 | Once in the shell, use `cabal build`, `cabal test`, `cabal repl`. 361 | 362 | There is a cachix available, used by CI, and already configured in flakes. You can manually run `cachix use guibou` if you want. 363 | 364 | You can locally build and test everything using: 365 | 366 | - `nix flake check` 367 | 368 | Don't hesitate to submit a PR not tested on all GHC versions. 369 | 370 | ## Formatting 371 | 372 | Please run: 373 | 374 | - `nix run fmt` 375 | 376 | Before submitting. 377 | 378 | ## Treesitter support 379 | 380 | Have a look in the [./tree-sitter-pyf/](./tree-sitter-pyf) directory for a 381 | parser of PyF which can be integrated in your treesitter compatible editor to 382 | get syntax highlighting for PyF. 383 | 384 | ![](./tree-sitter-pyf/nvim_ts_highlight.png) 385 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | 3 | main = defaultMain 4 | -------------------------------------------------------------------------------- /error_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guibou/PyF/cf03e5fa02d7c5211719661ac81eb9d8dd8af238/error_example.png -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1735863392, 24 | "narHash": "sha256-jdyYQ7GquKhseXmwG6lyx9ebCLo9lMX9iJ1htpbpOF8=", 25 | "owner": "nixos", 26 | "repo": "nixpkgs", 27 | "rev": "f371cdec06dbc42781824c5324657294e7ac581b", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "nixos", 32 | "ref": "haskell-updates", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "nixpkgs_2": { 38 | "locked": { 39 | "lastModified": 1735554305, 40 | "narHash": "sha256-zExSA1i/b+1NMRhGGLtNfFGXgLtgo+dcuzHzaWA6w3Q=", 41 | "owner": "nixos", 42 | "repo": "nixpkgs", 43 | "rev": "0e82ab234249d8eee3e8c91437802b32c74bb3fd", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "nixos", 48 | "ref": "nixpkgs-unstable", 49 | "repo": "nixpkgs", 50 | "type": "github" 51 | } 52 | }, 53 | "root": { 54 | "inputs": { 55 | "flake-utils": "flake-utils", 56 | "nixpkgs": "nixpkgs", 57 | "treefmt-nix": "treefmt-nix" 58 | } 59 | }, 60 | "systems": { 61 | "locked": { 62 | "lastModified": 1681028828, 63 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 64 | "owner": "nix-systems", 65 | "repo": "default", 66 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 67 | "type": "github" 68 | }, 69 | "original": { 70 | "owner": "nix-systems", 71 | "repo": "default", 72 | "type": "github" 73 | } 74 | }, 75 | "treefmt-nix": { 76 | "inputs": { 77 | "nixpkgs": "nixpkgs_2" 78 | }, 79 | "locked": { 80 | "lastModified": 1735905407, 81 | "narHash": "sha256-1hKMRIT+QZNWX46e4gIovoQ7H8QRb7803ZH4qSKI45o=", 82 | "owner": "numtide", 83 | "repo": "treefmt-nix", 84 | "rev": "29806abab803e498df96d82dd6f34b32eb8dd2c8", 85 | "type": "github" 86 | }, 87 | "original": { 88 | "owner": "numtide", 89 | "repo": "treefmt-nix", 90 | "type": "github" 91 | } 92 | } 93 | }, 94 | "root": "root", 95 | "version": 7 96 | } 97 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "PyF"; 3 | 4 | inputs.flake-utils.url = "github:numtide/flake-utils"; 5 | inputs.nixpkgs.url = "github:nixos/nixpkgs/haskell-updates"; 6 | inputs.treefmt-nix.url = "github:numtide/treefmt-nix"; 7 | 8 | nixConfig.extra-substituters = [ "https://guibou.cachix.org" ]; 9 | nixConfig.extra-trusted-public-keys = [ 10 | "guibou.cachix.org-1:GcGQvWEyTx8t0KfQac05E1mrlPNHqs5fGMExiN9/pbM=" 11 | ]; 12 | 13 | outputs = 14 | { 15 | self, 16 | nixpkgs, 17 | flake-utils, 18 | treefmt-nix, 19 | ... 20 | }: 21 | flake-utils.lib.eachDefaultSystem ( 22 | system: 23 | let 24 | pkgs = nixpkgs.legacyPackages.${system}; 25 | treefmtEval = treefmt-nix.lib.evalModule pkgs ./treefmt.nix; 26 | pyfBuilder = 27 | hPkgs: 28 | let 29 | shell = pkg.env.overrideAttrs (old: { 30 | nativeBuildInputs = old.nativeBuildInputs ++ (with pkgs; [ cabal-install ]); 31 | }); 32 | 33 | # Shell with haskell language server 34 | shell_hls = shell.overrideAttrs (old: { 35 | nativeBuildInputs = old.nativeBuildInputs ++ [ hPkgs.haskell-language-server ]; 36 | }); 37 | 38 | pkg = ((hPkgs.callCabal2nix "PyF" ./. { })).overrideAttrs (oldAttrs: { 39 | buildInputs = oldAttrs.buildInputs; 40 | passthru = oldAttrs.passthru // { 41 | inherit shell shell_hls; 42 | }; 43 | }); 44 | in 45 | # Add the GHC version in the package name 46 | pkg.overrideAttrs (old: { 47 | pname = "PyF-ghc${hPkgs.ghc.version}"; 48 | name = "PyF-ghc${hPkgs.ghc.version}-${old.version}"; 49 | }); 50 | 51 | in 52 | with pkgs; 53 | rec { 54 | checks = { 55 | inherit (packages) 56 | pyf_810 57 | pyf_90 58 | pyf_92 59 | pyf_94 60 | pyf_96 61 | pyf_98 62 | pyf_910 63 | pyf_912 64 | ; 65 | 66 | formatting = treefmtEval.config.build.check self; 67 | }; 68 | 69 | packages = { 70 | # GHC 8.6 is tested with stack, I'm stopping the testing with nix. 71 | # GHC 8.8 is not in nixpkgs anymore. 72 | 73 | pyf_810 = pyfBuilder haskell.packages.ghc810; 74 | pyf_90 = pyfBuilder haskell.packages.ghc90; 75 | pyf_92 = pyfBuilder haskell.packages.ghc92; 76 | pyf_94 = pyfBuilder haskell.packages.ghc94; 77 | pyf_96 = pyfBuilder haskell.packages.ghc96; 78 | pyf_98 = pkgs.haskell.lib.dontCheck (pyfBuilder haskell.packages.ghc98); 79 | 80 | pyf_910 = pyfBuilder haskell.packages.ghc910; 81 | pyf_912 = pyfBuilder haskell.packages.ghc912; 82 | 83 | default = pyfBuilder haskellPackages; 84 | }; 85 | 86 | formatter = treefmtEval.config.build.wrapper; 87 | 88 | devShells = (builtins.mapAttrs (name: value: value.shell) packages) // { 89 | treesitter = pkgs.mkShell { 90 | buildInputs = [ 91 | pkgs.tree-sitter 92 | pkgs.nodejs 93 | ]; 94 | }; 95 | default = packages.default.shell_hls; 96 | }; 97 | } 98 | ); 99 | } 100 | -------------------------------------------------------------------------------- /hie.yaml: -------------------------------------------------------------------------------- 1 | cradle: 2 | cabal: 3 | - path: "./src" 4 | component: "PyF:lib:PyF" 5 | - path: "./test" 6 | component: "PyF:test:pyf-test" 7 | - path: "./test/SpecFail.hs" 8 | component: "PyF:test:pyf-failure" 9 | -------------------------------------------------------------------------------- /nixpkgs.nix: -------------------------------------------------------------------------------- 1 | let 2 | # nixpkgs unstable 23-09-2021 3 | rev = "e0ce3c683ae6"; 4 | sha256 = "08ans3w6r4fbs1wx6lzlp4xwhy6p04x3spvvrjz5950w8mzxqm61"; 5 | in 6 | import (fetchTarball { 7 | inherit sha256; 8 | url = "https://github.com/nixos/nixpkgs/archive/${rev}.tar.gz"; 9 | }) 10 | -------------------------------------------------------------------------------- /src/PyF.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE BangPatterns #-} 2 | {-# LANGUAGE FlexibleInstances #-} 3 | {-# LANGUAGE TemplateHaskell #-} 4 | {-# LANGUAGE ViewPatterns #-} 5 | 6 | -- | A lot of quasiquoters to format and interpolate string expressions. 7 | module PyF 8 | ( fmt, 9 | fmtTrim, 10 | str, 11 | strTrim, 12 | raw, 13 | module PyF.Class, 14 | 15 | -- * Whitespace utilities 16 | trimIndent, 17 | 18 | -- * Configuration 19 | mkFormatter, 20 | defaultConfig, 21 | fmtConfig, 22 | strConfig, 23 | addTrim, 24 | addFormatting, 25 | ) 26 | where 27 | 28 | import Data.Char (isSpace) 29 | import Data.List (intercalate) 30 | import Language.Haskell.TH.Quote (QuasiQuoter (..)) 31 | import PyF.Class 32 | import PyF.Internal.QQ (Config (..), expQQ, toExp, wrapFromString) 33 | 34 | -- | Generic formatter, can format an expression to any @t@ as long as 35 | -- @t@ is an instance of 'IsString'. 36 | fmt :: QuasiQuoter 37 | fmt = mkFormatter "fmt" fmtConfig 38 | 39 | -- | Format with whitespace trimming. 40 | fmtTrim :: QuasiQuoter 41 | fmtTrim = let 42 | qq = mkFormatter "fmtTrim" fmtConfig 43 | in qq { quoteExp = \s -> quoteExp qq (trimIndent s) } 44 | 45 | -- | Multiline string, no interpolation. 46 | str :: QuasiQuoter 47 | str = mkFormatter "str" strConfig 48 | 49 | -- | Multiline string, no interpolation, but does indentation trimming. 50 | strTrim :: QuasiQuoter 51 | strTrim = let qq = mkFormatter "strTrim" strConfig 52 | in qq { quoteExp = \s -> quoteExp qq (trimIndent s) } 53 | 54 | -- | Raw string, neither interpolation nor escaping is performed. 55 | raw :: QuasiQuoter 56 | raw = expQQ "raw" (\s -> [|s|]) 57 | 58 | -- | Removes the trailing whitespace of a string. 59 | -- 60 | -- - First line is ignored if it only contains whitespaces 61 | -- - All other line common indentation is removed, ignoring lines with only whitespaces. 62 | -- 63 | -- >>> trimIndent "\n hello\n - a\n - b\n " 64 | -- "hello\n- a\n- b\n" 65 | -- 66 | -- See 'fmtTrim' for a quasiquoter with this behavior. 67 | trimIndent :: String -> String 68 | trimIndent s = 69 | case lines s of 70 | [] -> "" 71 | [_] -> s 72 | (firstLine : others) -> 73 | let -- Discard the first line if needed 74 | usedLines 75 | | all isSpace firstLine = others ++ trail 76 | | otherwise = firstLine : others ++ trail 77 | 78 | -- If the string ends with a newline, `lines` will discard it. We restore it. 79 | trail 80 | | last s == '\n' = [""] 81 | | otherwise = [] 82 | -- Find the minimum indent common to all lines 83 | biggestLines = map (length . takeWhile isSpace) (filter (not . all isSpace) usedLines) 84 | 85 | stripLen = case biggestLines of 86 | [] -> 0 87 | _ -> minimum biggestLines 88 | 89 | -- drop them 90 | trimmedLines = map (drop stripLen) usedLines 91 | in intercalate "\n" trimmedLines 92 | 93 | -- | This is an empty configuration. No formatting, no post processing 94 | defaultConfig :: Config 95 | defaultConfig = 96 | Config 97 | { delimiters = Nothing, 98 | postProcess = id 99 | } 100 | 101 | -- | Configuration for 'str'. It just wraps the multiline string with 'fromString'. 102 | strConfig :: Config 103 | strConfig = 104 | Config 105 | { delimiters = Nothing, 106 | postProcess = wrapFromString 107 | } 108 | 109 | -- | The config for 'fmt'. 110 | fmtConfig :: Config 111 | fmtConfig = addFormatting ('{', '}') strConfig 112 | 113 | -- | Add indentation trimming to a configuration. 114 | addTrim :: Config -> Config 115 | addTrim config = 116 | config 117 | { postProcess = \q -> postProcess config [|PyF.trimIndent $(q)|] 118 | } 119 | 120 | -- | Enable formatting. 121 | addFormatting :: (Char, Char) -> Config -> Config 122 | addFormatting delims c = c {delimiters = Just delims} 123 | 124 | -- | Build a formatter. See the 'Config' type for details, as well as 125 | -- 'fmtConfig' and 'strConfig' for examples. 126 | mkFormatter :: String -> Config -> QuasiQuoter 127 | mkFormatter name config = expQQ name (toExp config) 128 | -------------------------------------------------------------------------------- /src/PyF/Class.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE FlexibleInstances #-} 3 | {-# LANGUAGE MultiParamTypeClasses #-} 4 | {-# LANGUAGE ScopedTypeVariables #-} 5 | {-# LANGUAGE TypeApplications #-} 6 | {-# LANGUAGE TypeFamilies #-} 7 | {-# LANGUAGE UndecidableInstances #-} 8 | 9 | -- | You want to add formatting support for your custom type. This is the right module. 10 | -- 11 | -- In PyF, formatters are in three categories: 12 | -- 13 | -- - Integral numbers, which are numbers without fractional part 14 | -- - Fractional numbers, which are numbers with a fractional part 15 | -- - String, which represents text. 16 | -- 17 | -- The formatting can be either explicit or implicit. For example: 18 | -- 19 | -- >>> let x = 10 in [fmt|{x}|] 20 | -- 10 21 | -- 22 | -- Is an implicit formatting to number, but: 23 | -- 24 | -- >>> let x = 10 in [fmt|{x:d}|] 25 | -- 26 | -- Is an explicit formatting to Integral numbers, using @d@. 27 | -- 28 | -- Implicit formatting will only format to either Integral, Fractional or text, 29 | -- and this choice is done by the (open) type family `PyFCategory'. 30 | -- 31 | -- This modules also provides 3 type class for formatting. 32 | -- 33 | -- - 'PyfFormatFractional' and 'PyfFormatIntegral' are responsible for 34 | -- formatting integral and fractional numbers. Default instances are provided 35 | -- respectively for 'Real' and 'Integral'. 'PyFToString' is in charge of 36 | -- formatting text. 37 | module PyF.Class where 38 | 39 | import qualified Data.ByteString 40 | import qualified Data.ByteString.Lazy 41 | import Data.Char (ord) 42 | import Data.Int 43 | import qualified Data.Ratio 44 | import qualified Data.Text as SText 45 | import qualified Data.Text.Encoding as E 46 | import qualified Data.Text.Lazy as LText 47 | import qualified Data.Time 48 | import Data.Word 49 | import Numeric.Natural 50 | import PyF.Formatters 51 | 52 | -- * Default formatting classification 53 | 54 | -- | The three categories of formatting in 'PyF' 55 | data PyFCategory 56 | = -- | Format as an integral, no fractional part, precise value 57 | PyFIntegral 58 | | -- | Format as a fractional, approximate value with a fractional part 59 | PyFFractional 60 | | -- | Format as a string 61 | PyFString 62 | 63 | -- | Classify a type to a 'PyFCategory' 64 | -- This classification will be used to decide which formatting to 65 | -- use when no type specifier in provided. 66 | type family PyFClassify t :: PyFCategory 67 | 68 | type instance PyFClassify Integer = 'PyFIntegral 69 | 70 | type instance PyFClassify Int = 'PyFIntegral 71 | 72 | type instance PyFClassify Int8 = 'PyFIntegral 73 | 74 | type instance PyFClassify Int16 = 'PyFIntegral 75 | 76 | type instance PyFClassify Int32 = 'PyFIntegral 77 | 78 | type instance PyFClassify Int64 = 'PyFIntegral 79 | 80 | type instance PyFClassify Natural = 'PyFIntegral 81 | 82 | type instance PyFClassify Word = 'PyFIntegral 83 | 84 | type instance PyFClassify Word8 = 'PyFIntegral 85 | 86 | type instance PyFClassify Word16 = 'PyFIntegral 87 | 88 | type instance PyFClassify Word32 = 'PyFIntegral 89 | 90 | type instance PyFClassify Word64 = 'PyFIntegral 91 | 92 | -- Float numbers 93 | 94 | type instance PyFClassify Float = 'PyFFractional 95 | 96 | type instance PyFClassify Double = 'PyFFractional 97 | 98 | type instance PyFClassify Data.Time.DiffTime = 'PyFFractional 99 | 100 | type instance PyFClassify Data.Time.NominalDiffTime = 'PyFFractional 101 | 102 | type instance PyFClassify (Data.Ratio.Ratio i) = 'PyFFractional 103 | 104 | -- String 105 | 106 | type instance PyFClassify String = 'PyFString 107 | 108 | type instance PyFClassify LText.Text = 'PyFString 109 | 110 | type instance PyFClassify SText.Text = 'PyFString 111 | 112 | type instance PyFClassify Data.ByteString.ByteString = 'PyFString 113 | 114 | type instance PyFClassify Data.ByteString.Lazy.ByteString = 'PyFString 115 | 116 | type instance PyFClassify Char = 'PyFString 117 | 118 | -- * String formatting 119 | 120 | -- | Convert a type to string 121 | -- This is used for the string formatting. 122 | class PyFToString t where 123 | pyfToString :: t -> String 124 | 125 | instance PyFToString String where pyfToString = id 126 | 127 | instance PyFToString LText.Text where pyfToString = LText.unpack 128 | 129 | instance PyFToString SText.Text where pyfToString = SText.unpack 130 | 131 | instance PyFToString Data.ByteString.ByteString where pyfToString = SText.unpack . E.decodeUtf8 132 | 133 | instance PyFToString Data.ByteString.Lazy.ByteString where pyfToString = pyfToString . Data.ByteString.Lazy.toStrict 134 | 135 | instance PyFToString Char where pyfToString c = [c] 136 | 137 | -- | Default instance. Convert any type with a 'Show instance. 138 | instance {-# OVERLAPPABLE #-} (Show t) => PyFToString t where pyfToString = show 139 | 140 | -- * Real formatting (with optional fractional part) 141 | 142 | -- | Apply a fractional formatting to any type. 143 | -- 144 | -- A default instance for any 'Real' is provided which internally converts to 145 | -- 'Double', which may not be efficient or results in rounding errors. 146 | -- 147 | -- You can provide your own instance and internally use 'formatFractional' 148 | -- which does have the same signatures as 'pyfFormatFractional' but with a 149 | -- 'RealFrac' constraint. 150 | class PyfFormatFractional a where 151 | pyfFormatFractional :: 152 | (Integral paddingWidth, Integral precision) => 153 | Format t t' 'Fractional -> 154 | -- | Sign formatting 155 | SignMode -> 156 | -- | Padding 157 | Maybe (paddingWidth, AlignMode k, Char) -> 158 | -- | Grouping 159 | Maybe (Int, Char) -> 160 | -- | Precision 161 | Maybe precision -> 162 | a -> 163 | String 164 | 165 | -- | Default instance working for any 'Real'. Internally it converts the type to 'Double'. 166 | instance {-# OVERLAPPABLE #-} (Real t) => PyfFormatFractional t where 167 | pyfFormatFractional f s p g prec v = formatFractional f s p g prec (realToFrac @t @Double v) 168 | 169 | -- | This instance does not do any conversion. 170 | instance PyfFormatFractional Double where pyfFormatFractional = formatFractional 171 | 172 | -- | This instance does not do any conversion. 173 | instance PyfFormatFractional Float where pyfFormatFractional = formatFractional 174 | 175 | -- * Integral formatting 176 | 177 | -- | Apply an integral formatting to any type. 178 | -- 179 | -- A default instance for any 'Integral' is provided. 180 | -- 181 | -- You can provide your own instance and internally use 'formatIntegral' 182 | -- which does have the same signatures as 'pyfFormatIntegral' but with an 183 | -- 'Integral' constraint. 184 | class PyfFormatIntegral i where 185 | pyfFormatIntegral :: 186 | (Integral paddingWidth) => 187 | Format t t' 'Integral -> 188 | -- | Sign formatting 189 | SignMode -> 190 | -- | Padding 191 | Maybe (paddingWidth, AlignMode k, Char) -> 192 | -- | Grouping 193 | Maybe (Int, Char) -> 194 | i -> 195 | String 196 | 197 | -- | Default instance for any 'Integral'. 198 | instance {-# OVERLAPPABLE #-} (Integral t) => PyfFormatIntegral t where 199 | pyfFormatIntegral f s p g v = formatIntegral f s p g v 200 | 201 | -- | Returns the numerical value of a 'Char' 202 | -- >>> [fmt|{'a':d}|] 203 | -- 97 204 | instance PyfFormatIntegral Char where 205 | pyfFormatIntegral f s p g v = formatIntegral f s p g (ord v) 206 | -------------------------------------------------------------------------------- /src/PyF/Formatters.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE DataKinds #-} 2 | {-# LANGUAGE DeriveDataTypeable #-} 3 | {-# LANGUAGE DeriveLift #-} 4 | {-# LANGUAGE GADTs #-} 5 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 6 | {-# LANGUAGE KindSignatures #-} 7 | {-# LANGUAGE LambdaCase #-} 8 | {-# LANGUAGE OverloadedStrings #-} 9 | {-# LANGUAGE ScopedTypeVariables #-} 10 | {-# LANGUAGE StandaloneDeriving #-} 11 | {-# LANGUAGE TypeApplications #-} 12 | {-# LANGUAGE ViewPatterns #-} 13 | 14 | -- | 15 | -- 16 | -- Formatters for integral / fractional and strings. 17 | -- 18 | -- The following is supported: 19 | -- 20 | -- For all types: 21 | -- 22 | -- * Grouping of the integral part (i.e. adding a custom char to separate groups of digits) 23 | -- * Padding (left, right, around, and between the sign and the number) 24 | -- * Sign handling (i.e. display the positive sign or not) 25 | -- 26 | -- For floating: 27 | -- 28 | -- * Precision 29 | -- * Fixed / Exponential / Generic formatting 30 | -- 31 | -- For integrals: 32 | -- 33 | -- * Binary / Hexadecimal / Octal / Character representation 34 | module PyF.Formatters 35 | ( -- * Generic formatting function 36 | formatString, 37 | formatIntegral, 38 | formatFractional, 39 | 40 | -- * Formatter details 41 | AltStatus (..), 42 | UpperStatus (..), 43 | FormatType (..), 44 | Format (..), 45 | SignMode (..), 46 | AnyAlign (..), 47 | 48 | -- * Internal usage only 49 | AlignMode (..), 50 | getAlignForString, 51 | AlignForString (..), 52 | ) 53 | where 54 | 55 | import Data.Char (chr, toUpper) 56 | import Data.Data (Data) 57 | import Data.List (intercalate) 58 | import Language.Haskell.TH.Syntax 59 | import qualified Numeric 60 | 61 | -- ADT for API 62 | 63 | -- | Sign handling 64 | data SignMode 65 | = -- | Display '-' sign and '+' sign 66 | Plus 67 | | -- | Only display '-' sign 68 | Minus 69 | | -- | Display '-' sign and a space for positive numbers 70 | Space 71 | deriving (Show, Data) 72 | 73 | data AlignForString = AlignAll | AlignNumber 74 | deriving (Show) 75 | 76 | -- | Alignement 77 | data AlignMode (k :: AlignForString) where 78 | -- | Left padding 79 | AlignLeft :: AlignMode 'AlignAll 80 | -- | Right padding 81 | AlignRight :: AlignMode 'AlignAll 82 | -- | Padding will be added between the sign and the number 83 | AlignInside :: AlignMode 'AlignNumber 84 | -- | Padding will be added around the value 85 | AlignCenter :: AlignMode 'AlignAll 86 | 87 | deriving instance Show (AlignMode k) 88 | 89 | -- The generic version 90 | 91 | -- | Existential version of 'AlignMode' 92 | data AnyAlign where 93 | AnyAlign :: AlignMode (k :: AlignForString) -> AnyAlign 94 | 95 | deriving instance Show AnyAlign 96 | 97 | deriving instance Lift AnyAlign 98 | 99 | -- I hate how a must list all cases, any solution ? 100 | -- o = Just o does not work 101 | getAlignForString :: AlignMode k -> Maybe (AlignMode 'AlignAll) 102 | getAlignForString AlignInside = Nothing 103 | getAlignForString AlignRight = Just AlignRight 104 | getAlignForString AlignCenter = Just AlignCenter 105 | getAlignForString AlignLeft = Just AlignLeft 106 | 107 | -- | This formatter supports an alternate version. 108 | data AltStatus = CanAlt | NoAlt 109 | 110 | -- | This formatter support an uppercase version. 111 | data UpperStatus = CanUpper | NoUpper 112 | 113 | -- | This formatter formats an integral or a fractional. 114 | data FormatType = Fractional | Integral 115 | 116 | -- | All the Formatters. 117 | data Format (k :: AltStatus) (k' :: UpperStatus) (k'' :: FormatType) where 118 | -- Integrals 119 | Decimal :: Format 'NoAlt 'NoUpper 'Integral 120 | Character :: Format 'NoAlt 'NoUpper 'Integral 121 | Binary :: Format 'CanAlt 'NoUpper 'Integral 122 | Hexa :: Format 'CanAlt 'CanUpper 'Integral 123 | Octal :: Format 'CanAlt 'NoUpper 'Integral 124 | -- Fractionals 125 | Fixed :: Format 'CanAlt 'CanUpper 'Fractional 126 | Exponent :: Format 'CanAlt 'CanUpper 'Fractional 127 | Generic :: Format 'CanAlt 'CanUpper 'Fractional 128 | Percent :: Format 'CanAlt 'NoUpper 'Fractional 129 | -- Meta formats 130 | Alternate :: Format 'CanAlt u f -> Format 'NoAlt u f 131 | -- Upper should come AFTER Alt, so this disallow any future alt 132 | Upper :: Format alt 'CanUpper f -> Format 'NoAlt 'NoUpper f 133 | 134 | newtype ShowIntegral i = ShowIntegral i 135 | deriving (Real, Enum, Ord, Eq, Num, Integral) 136 | 137 | -- | Stupid instance in order to use 'Numeric.showIntAtBase' which needs a 138 | -- 'Show' constraint for error reporting when number are negative. 139 | -- However, in 'reprIntegral', there is no negative number, so the case is 140 | -- impossible, but it allows the removal of the 'Show' constraint. 141 | instance Show (ShowIntegral i) where 142 | show _ = error "show should not be called on ShowIntegral" 143 | 144 | -- Internal Integral 145 | -- Needed for debug in Numeric function, this is painful 146 | reprIntegral :: (Integral i) => Format t t' 'Integral -> i -> Repr 147 | reprIntegral fmt i = IntegralRepr sign $ format fmt 148 | where 149 | format :: Format t t' 'Integral -> String 150 | format = \case 151 | Decimal -> Numeric.showInt iAbs "" 152 | Octal -> Numeric.showOct (ShowIntegral iAbs) "" 153 | Binary -> Numeric.showIntAtBase 2 (\digit -> if digit == 0 then '0' else '1') (ShowIntegral iAbs) "" 154 | Hexa -> Numeric.showHex (ShowIntegral iAbs) "" 155 | Upper fmt' -> map toUpper $ format fmt' 156 | Character -> [chr (fromIntegral i)] 157 | Alternate fmt' -> format fmt' 158 | (sign, iAbs) = splitSign i 159 | 160 | prefixIntegral :: Format t t' 'Integral -> String 161 | prefixIntegral (Alternate Octal) = "0o" 162 | prefixIntegral (Alternate Binary) = "0b" 163 | prefixIntegral (Alternate Hexa) = "0x" 164 | prefixIntegral (Upper f) = toUpper <$> prefixIntegral f 165 | prefixIntegral _ = "" 166 | 167 | splitSign :: (Num b, Ord b) => b -> (Sign, b) 168 | splitSign v = (if v < 0 then Negative else Positive, abs v) 169 | 170 | -- Internal Fractional 171 | reprFractional :: (RealFloat f) => Format t t' 'Fractional -> Maybe Int -> f -> Repr 172 | reprFractional fmt precision f 173 | | isInfinite f = Infinite sign (upperIt "inf") 174 | | isNaN f = NaN (upperIt "nan") 175 | | isNegativeZero f = case reprFractional fmt precision (abs f) of 176 | FractionalRepr Positive aa bb cc -> FractionalRepr Negative aa bb cc 177 | other -> error $ "reprFractional (isNegativeZero f): The impossible happened : " ++ show other ++ ". Please open an issue at https://github.com/guibou/PyF/issues/" 178 | | otherwise = FractionalRepr sign decimalPart fractionalPart suffixPart 179 | where 180 | upperIt s = case fmt of 181 | Upper _ -> toUpper <$> s 182 | _ -> s 183 | (sign, iAbs) = splitSign f 184 | (decimalPart, fractionalPart, suffixPart) = format fmt 185 | format :: Format t t' 'Fractional -> (String, String, String) 186 | format = \case 187 | Fixed -> splitFractional (Numeric.showFFloatAlt precision iAbs "") 188 | Exponent -> overrideExponent precision $ splitFractionalExp (Numeric.showEFloat precision iAbs "") 189 | Generic -> splitFractionalExp (Numeric.showGFloatAlt precision iAbs "") 190 | Percent -> case splitFractional (Numeric.showFFloatAlt precision (iAbs * 100) "") of 191 | (a, b, "") -> (a, b, "%") 192 | other -> error $ "reprFractional (format): The impossible happened : " ++ show other ++ ". Please open an issue at https://github.com/guibou/PyF/issues/" 193 | Alternate fmt' -> format fmt' 194 | Upper fmt' -> 195 | let (a, b, c) = format fmt' 196 | in (a, b, map toUpper c) 197 | splitFractional :: String -> (String, String, String) 198 | splitFractional s = 199 | let (a, b) = break (== '.') s 200 | in (a, drop 1 b, "") 201 | 202 | overrideExponent :: Maybe Int -> (String, String, String) -> (String, String, String) 203 | overrideExponent (Just 0) (a, "0", c) = (a, "", c) 204 | overrideExponent _ o = o 205 | 206 | splitFractionalExp :: String -> (String, String, String) 207 | splitFractionalExp s = 208 | let (a, b') = break (\c -> c == '.' || c == 'e') s 209 | b = drop 1 b' 210 | (fpart, e) = case b' of 211 | 'e' : _ -> ("", b') 212 | _ -> break (== 'e') b 213 | in ( a, 214 | fpart, 215 | case e of 216 | 'e' : '-' : n -> "e-" ++ pad n 217 | 'e' : n -> "e+" ++ pad n 218 | leftover -> leftover 219 | ) 220 | where 221 | pad n@[_] = '0' : n 222 | pad n = n 223 | 224 | -- Cases Integral / Fractional 225 | 226 | group :: Repr -> Maybe (Int, Char) -> Repr 227 | group (IntegralRepr s str) (Just (size, c)) = IntegralRepr s (groupIntercalate c size str) 228 | group (FractionalRepr s a b d) (Just (size, c)) = FractionalRepr s (groupIntercalate c size a) b d 229 | group i _ = i 230 | 231 | padAndSign :: (Integral paddingWidth) => Format t t' t'' -> String -> SignMode -> Maybe (paddingWidth, AlignMode k, Char) -> Repr -> String 232 | padAndSign format prefix sign padding repr = leftAlignMode <> prefixStr <> middleAlignMode <> content <> rightAlignMode 233 | where 234 | (signStr, content) = case repr of 235 | IntegralRepr s str -> (formatSign s sign, str) 236 | FractionalRepr s a b c -> (formatSign s sign, joinPoint format a b <> c) 237 | Infinite s str -> (formatSign s sign, str) 238 | NaN str -> ("", str) 239 | prefixStr = signStr <> prefix 240 | len = length prefixStr + length content 241 | (leftAlignMode, rightAlignMode, middleAlignMode) = case padding of 242 | Nothing -> ("", "", "") 243 | Just (fromIntegral -> pad, padMode, padC) -> 244 | let padNeeded = max 0 (pad - len) 245 | in case padMode of 246 | AlignLeft -> ("", replicate padNeeded padC, "") 247 | AlignRight -> (replicate padNeeded padC, "", "") 248 | AlignCenter -> (replicate (padNeeded `div` 2) padC, replicate (padNeeded - padNeeded `div` 2) padC, "") 249 | AlignInside -> ("", "", replicate padNeeded padC) 250 | 251 | joinPoint :: Format t t' t'' -> String -> String -> String 252 | joinPoint (Upper f) a b = joinPoint f a b 253 | joinPoint (Alternate _) a b = a <> "." <> b 254 | joinPoint _ a "" = a 255 | joinPoint _ a b = a <> "." <> b 256 | 257 | -- Generic 258 | data Repr 259 | = IntegralRepr Sign String 260 | | FractionalRepr Sign String String String 261 | | Infinite Sign String 262 | | NaN String 263 | deriving (Show) 264 | 265 | data Sign = Negative | Positive 266 | deriving (Show) 267 | 268 | formatSign :: Sign -> SignMode -> String 269 | formatSign Positive Plus = "+" 270 | formatSign Positive Minus = "" 271 | formatSign Positive Space = " " 272 | formatSign Negative _ = "-" 273 | 274 | groupIntercalate :: Char -> Int -> String -> String 275 | groupIntercalate c i s = intercalate [c] (reverse (pack (reverse s))) 276 | where 277 | pack "" = [] 278 | pack l = reverse (take i l) : pack (drop i l) 279 | 280 | -- Final formatters 281 | 282 | -- | Format an integral number. 283 | formatIntegral :: 284 | (Integral paddingWidth) => 285 | (Integral i) => 286 | Format t t' 'Integral -> 287 | SignMode -> 288 | -- | Padding 289 | Maybe (paddingWidth, AlignMode k, Char) -> 290 | -- | Grouping 291 | Maybe (Int, Char) -> 292 | i -> 293 | String 294 | formatIntegral f sign padding grouping i = padAndSign f (prefixIntegral f) sign padding (group (reprIntegral f i) grouping) 295 | 296 | -- | Format a fractional number. 297 | formatFractional :: 298 | (RealFloat f, Integral paddingWidth, Integral precision) => 299 | Format t t' 'Fractional -> 300 | SignMode -> 301 | -- | Padding 302 | Maybe (paddingWidth, AlignMode k, Char) -> 303 | -- | Grouping 304 | Maybe (Int, Char) -> 305 | -- | Precision 306 | Maybe precision -> 307 | f -> 308 | String 309 | formatFractional f sign padding grouping precision i = padAndSign f "" sign padding (group (reprFractional f (fmap fromIntegral precision) i) grouping) 310 | 311 | -- | Format a string. 312 | formatString :: 313 | forall paddingWidth precision. 314 | (Integral paddingWidth, Integral precision) => 315 | -- | Padding 316 | Maybe (paddingWidth, AlignMode 'AlignAll, Char) -> 317 | -- | Precision (will truncate before padding) 318 | Maybe precision -> 319 | String -> 320 | String 321 | formatString Nothing Nothing s = s 322 | formatString Nothing (Just i) s = take (fromIntegral i) s 323 | formatString (Just (fromIntegral -> padSize, padMode, padC)) size s = padLeft <> str <> padRight 324 | where 325 | str = formatString @paddingWidth Nothing size s 326 | paddingLength = max 0 (padSize - length str) 327 | (padLeft, padRight) = case padMode of 328 | AlignLeft -> ("", replicate paddingLength padC) 329 | AlignRight -> (replicate paddingLength padC, "") 330 | AlignCenter -> (replicate (paddingLength `div` 2) padC, replicate (paddingLength - paddingLength `div` 2) padC) 331 | 332 | -- TODO 333 | {- 334 | the . 335 | -} 336 | 337 | deriving instance Lift (AlignMode k) 338 | 339 | deriving instance Lift SignMode 340 | 341 | deriving instance Lift (Format k k' k'') 342 | -------------------------------------------------------------------------------- /src/PyF/Internal/Meta.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE GADTs #-} 3 | {-# LANGUAGE NamedFieldPuns #-} 4 | {-# LANGUAGE TemplateHaskellQuotes #-} 5 | {-# LANGUAGE ViewPatterns #-} 6 | 7 | module PyF.Internal.Meta (toExp, baseDynFlags, toName) where 8 | 9 | #if MIN_VERSION_ghc(9,2,0) 10 | import GHC.Hs.Type (HsWildCardBndrs (..), HsType (..), HsSigType(HsSig), sig_body) 11 | #elif MIN_VERSION_ghc(9,0,0) 12 | import GHC.Hs.Type (HsWildCardBndrs (..), HsType (..), HsImplicitBndrs(HsIB), hsib_body) 13 | #elif MIN_VERSION_ghc(8,10,0) 14 | import GHC.Hs.Types (HsWildCardBndrs (..), HsType (..), HsImplicitBndrs (HsIB, hsib_body)) 15 | #else 16 | import HsTypes (HsWildCardBndrs (..), HsType (..), HsImplicitBndrs (HsIB), hsib_body) 17 | #endif 18 | 19 | #if MIN_VERSION_ghc(9,6,0) 20 | import Language.Haskell.Syntax.Basic (field_label) 21 | #endif 22 | 23 | #if MIN_VERSION_ghc(8,10,0) 24 | import GHC.Hs.Expr as Expr 25 | import GHC.Hs.Extension as Ext 26 | import GHC.Hs.Pat as Pat 27 | import GHC.Hs.Lit 28 | #else 29 | import HsExpr as Expr 30 | import HsExtension as Ext 31 | import HsPat as Pat 32 | import HsLit 33 | #endif 34 | 35 | import qualified Data.ByteString as B 36 | import qualified Language.Haskell.TH.Syntax as GhcTH 37 | import qualified Language.Haskell.TH.Syntax as TH 38 | 39 | #if MIN_VERSION_ghc(9,6,0) 40 | import PyF.Internal.ParserEx (fakeSettings) 41 | #else 42 | import PyF.Internal.ParserEx (fakeLlvmConfig, fakeSettings) 43 | #endif 44 | 45 | #if MIN_VERSION_ghc(9,6,0) 46 | import GHC.Types.SourceText (il_value, rationalFromFractionalLit,SourceText(..)) 47 | #endif 48 | 49 | #if MIN_VERSION_ghc(9,0,0) 50 | import GHC.Types.SrcLoc 51 | import GHC.Types.Name 52 | import GHC.Types.Name.Reader 53 | import GHC.Data.FastString 54 | #if MIN_VERSION_ghc(9,2,0) 55 | import GHC.Utils.Outputable (ppr) 56 | import GHC.Types.Basic (Boxity(..)) 57 | #if MIN_VERSION_ghc(9,6,0) 58 | import GHC.Types.SourceText (FractionalLit) 59 | #else 60 | import GHC.Types.SourceText (il_value, rationalFromFractionalLit, FractionalLit) 61 | #endif 62 | import GHC.Driver.Ppr (showSDoc) 63 | #else 64 | import GHC.Utils.Outputable (ppr, showSDoc) 65 | import GHC.Types.Basic (il_value, fl_value, Boxity(..)) 66 | #endif 67 | import GHC.Driver.Session (DynFlags, xopt_set, defaultDynFlags) 68 | import qualified GHC.Unit.Module as Module 69 | #else 70 | import SrcLoc 71 | import Name 72 | import RdrName 73 | import FastString 74 | import Outputable (ppr, showSDoc) 75 | import BasicTypes (il_value, fl_value, Boxity(..)) 76 | import DynFlags (DynFlags, xopt_set, defaultDynFlags) 77 | import qualified Module 78 | #endif 79 | 80 | import GHC.Stack 81 | 82 | #if MIN_VERSION_ghc(9,2,0) 83 | -- TODO: why this disapears in GHC >= 9.2? 84 | fl_value :: FractionalLit -> Rational 85 | fl_value = rationalFromFractionalLit 86 | #endif 87 | 88 | toLit :: HsLit GhcPs -> TH.Lit 89 | toLit (HsChar _ c) = TH.CharL c 90 | toLit (HsCharPrim _ c) = TH.CharPrimL c 91 | toLit (HsString _ s) = TH.StringL (unpackFS s) 92 | toLit (HsStringPrim _ s) = TH.StringPrimL (B.unpack s) 93 | toLit (HsInt _ i) = TH.IntegerL (il_value i) 94 | toLit (HsIntPrim _ i) = TH.IntPrimL i 95 | toLit (HsWordPrim _ i) = TH.WordPrimL i 96 | toLit (HsInt64Prim _ i) = TH.IntegerL i 97 | toLit (HsWord64Prim _ i) = TH.WordPrimL i 98 | toLit (HsInteger _ i _) = TH.IntegerL i 99 | toLit (HsRat _ f _) = TH.FloatPrimL (fl_value f) 100 | toLit (HsFloatPrim _ f) = TH.FloatPrimL (fl_value f) 101 | toLit (HsDoublePrim _ f) = TH.DoublePrimL (fl_value f) 102 | #if MIN_VERSION_ghc(9,7,0) 103 | toLit (HsInt8Prim _ i) = TH.IntPrimL i 104 | toLit (HsInt16Prim _ i) = TH.IntPrimL i 105 | toLit (HsInt32Prim _ i) = TH.IntPrimL i 106 | toLit (HsWord8Prim _ i) = TH.WordPrimL i 107 | toLit (HsWord16Prim _ i) = TH.WordPrimL i 108 | toLit (HsWord32Prim _ i) = TH.WordPrimL i 109 | #endif 110 | 111 | #if !MIN_VERSION_ghc(9,0,0) 112 | toLit (XLit _) = noTH "toLit" "XLit" 113 | #endif 114 | 115 | toLit' :: OverLitVal -> TH.Lit 116 | toLit' (HsIntegral i) = TH.IntegerL (il_value i) 117 | toLit' (HsFractional f) = TH.RationalL (fl_value f) 118 | toLit' (HsIsString _ fs) = TH.StringL (unpackFS fs) 119 | 120 | toType :: HsType GhcPs -> TH.Type 121 | toType (HsWildCardTy _) = TH.WildCardT 122 | toType (HsTyVar _ _ n) = 123 | let n' = unLoc n 124 | in if isRdrTyVar n' 125 | then TH.VarT (toName n') 126 | else TH.ConT (toName n') 127 | toType t = todo "toType" (showSDoc (baseDynFlags []) . ppr $ t) 128 | 129 | toName :: RdrName -> TH.Name 130 | toName n = case n of 131 | (Unqual o) -> TH.mkName (occNameString o) 132 | (Qual m o) -> TH.mkName (Module.moduleNameString m <> "." <> occNameString o) 133 | (Orig _m _o) -> error "PyFMeta: not supported toName (Orig _)" 134 | (Exact nm) -> case getOccString nm of 135 | "[]" -> '[] 136 | "()" -> '() 137 | _ -> error "toName: exact name encountered" 138 | 139 | toFieldExp :: a 140 | toFieldExp = undefined 141 | 142 | toPat :: DynFlags -> Pat.Pat GhcPs -> TH.Pat 143 | toPat _dynFlags (Pat.VarPat _ (unLoc -> name)) = TH.VarP (toName name) 144 | 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) 145 | 146 | {- ORMOLU_DISABLE -} 147 | toExp :: DynFlags -> Expr.HsExpr GhcPs -> TH.Exp 148 | toExp _ (Expr.HsVar _ n) = 149 | let n' = unLoc n 150 | in if isRdrDataCon n' 151 | then TH.ConE (toName n') 152 | else TH.VarE (toName n') 153 | #if MIN_VERSION_ghc(9,6,0) 154 | toExp _ (Expr.HsUnboundVar _ n) = TH.UnboundVarE (TH.mkName . occNameString . rdrNameOcc $ n) 155 | #elif MIN_VERSION_ghc(9,0,0) 156 | toExp _ (Expr.HsUnboundVar _ n) = TH.UnboundVarE (TH.mkName . occNameString $ n) 157 | #else 158 | toExp _ (Expr.HsUnboundVar _ n) = TH.UnboundVarE (TH.mkName . occNameString . Expr.unboundVarOcc $ n) 159 | #endif 160 | toExp _ Expr.HsIPVar {} = noTH "toExp" "HsIPVar" 161 | toExp _ (Expr.HsLit _ l) = TH.LitE (toLit l) 162 | toExp _ (Expr.HsOverLit _ OverLit {ol_val}) = TH.LitE (toLit' ol_val) 163 | toExp d (Expr.HsApp _ e1 e2) = TH.AppE (toExp d . unLoc $ e1) (toExp d . unLoc $ e2) 164 | #if MIN_VERSION_ghc(9,10,0) 165 | toExp d (Expr.HsAppType _ e HsWC{hswc_body}) = TH.AppTypeE (toExp d . unLoc $ e) (toType . unLoc $ hswc_body) 166 | toExp d (Expr.ExprWithTySig _ e HsWC{hswc_body=unLoc -> HsSig{sig_body}}) = TH.SigE (toExp d . unLoc $ e) (toType . unLoc $ sig_body) 167 | #elif MIN_VERSION_ghc(9,6,0) 168 | toExp d (Expr.HsAppType _ e _ HsWC{hswc_body}) = TH.AppTypeE (toExp d . unLoc $ e) (toType . unLoc $ hswc_body) 169 | toExp d (Expr.ExprWithTySig _ e HsWC{hswc_body=unLoc -> HsSig{sig_body}}) = TH.SigE (toExp d . unLoc $ e) (toType . unLoc $ sig_body) 170 | #elif MIN_VERSION_ghc(9,2,0) 171 | toExp d (Expr.HsAppType _ e HsWC {hswc_body}) = TH.AppTypeE (toExp d . unLoc $ e) (toType . unLoc $ hswc_body) 172 | toExp d (Expr.ExprWithTySig _ e HsWC{hswc_body=unLoc -> HsSig{sig_body}}) = TH.SigE (toExp d . unLoc $ e) (toType . unLoc $ sig_body) 173 | #elif MIN_VERSION_ghc(8,8,0) 174 | toExp d (Expr.HsAppType _ e HsWC {hswc_body}) = TH.AppTypeE (toExp d . unLoc $ e) (toType . unLoc $ hswc_body) 175 | toExp d (Expr.ExprWithTySig _ e HsWC{hswc_body=HsIB{hsib_body}}) = TH.SigE (toExp d . unLoc $ e) (toType . unLoc $ hsib_body) 176 | #else 177 | toExp d (Expr.HsAppType HsWC {hswc_body} e) = TH.AppTypeE (toExp d . unLoc $ e) (toType . unLoc $ hswc_body) 178 | toExp d (Expr.ExprWithTySig HsWC{hswc_body=HsIB{hsib_body}} e) = TH.SigE (toExp d . unLoc $ e) (toType . unLoc $ hsib_body) 179 | #endif 180 | toExp d (Expr.OpApp _ e1 o e2) = TH.UInfixE (toExp d . unLoc $ e1) (toExp d . unLoc $ o) (toExp d . unLoc $ e2) 181 | toExp d (Expr.NegApp _ e _) = TH.AppE (TH.VarE 'negate) (toExp d . unLoc $ e) 182 | -- NOTE: for lambda, there is only one match 183 | #if MIN_VERSION_ghc(9,12,0) 184 | 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) 185 | #elif MIN_VERSION_ghc(9,10,0) 186 | 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) 187 | #elif MIN_VERSION_ghc(9,6,0) 188 | 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) 189 | #else 190 | 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) 191 | #endif 192 | -- toExp (Expr.Let _ bs e) = TH.LetE (toDecs bs) (toExp e) 193 | -- toExp (Expr.If _ a b c) = TH.CondE (toExp a) (toExp b) (toExp c) 194 | -- toExp (Expr.MultiIf _ ifs) = TH.MultiIfE (map toGuard ifs) 195 | -- toExp (Expr.Case _ e alts) = TH.CaseE (toExp e) (map toMatch alts) 196 | -- toExp (Expr.Do _ ss) = TH.DoE (map toStmt ss) 197 | -- toExp e@Expr.MDo{} = noTH "toExp" e 198 | #if MIN_VERSION_ghc(9, 2, 0) 199 | toExp d (Expr.ExplicitTuple _ args boxity) = ctor tupArgs 200 | #else 201 | toExp d (Expr.ExplicitTuple _ (map unLoc -> args) boxity) = ctor tupArgs 202 | #endif 203 | where 204 | toTupArg (Expr.Present _ e) = Just $ unLoc e 205 | toTupArg (Expr.Missing _) = Nothing 206 | toTupArg _ = error "impossible case" 207 | 208 | ctor = case boxity of 209 | Boxed -> TH.TupE 210 | Unboxed -> TH.UnboxedTupE 211 | 212 | #if MIN_VERSION_ghc(8,10,0) 213 | tupArgs = fmap ((fmap (toExp d)) . toTupArg) args 214 | #else 215 | tupArgs = case traverse toTupArg args of 216 | Nothing -> error "Tuple section are not supported by template haskell < 8.10" 217 | Just args' -> fmap (toExp d) args' 218 | #endif 219 | 220 | {- ORMOLU_ENABLE -} 221 | 222 | -- toExp (Expr.List _ xs) = TH.ListE (fmap toExp xs) 223 | #if MIN_VERSION_ghc(9,10,0) 224 | toExp d (Expr.HsPar _ e) = TH.ParensE (toExp d . unLoc $ e) 225 | #elif MIN_VERSION_ghc(9,3,0) 226 | toExp d (Expr.HsPar _ _ e _) = TH.ParensE (toExp d . unLoc $ e) 227 | #else 228 | toExp d (Expr.HsPar _ e) = TH.ParensE (toExp d . unLoc $ e) 229 | #endif 230 | toExp d (Expr.SectionL _ (unLoc -> a) (unLoc -> b)) = TH.InfixE (Just . toExp d $ a) (toExp d b) Nothing 231 | toExp d (Expr.SectionR _ (unLoc -> a) (unLoc -> b)) = TH.InfixE Nothing (toExp d a) (Just . toExp d $ b) 232 | toExp _ (Expr.RecordCon _ name HsRecFields {rec_flds}) = 233 | TH.RecConE (toName . unLoc $ name) (fmap toFieldExp rec_flds) 234 | 235 | -- toExp (Expr.RecUpdate _ e xs) = TH.RecUpdE (toExp e) (fmap toFieldExp xs) 236 | -- toExp (Expr.ListComp _ e ss) = TH.CompE $ map convert ss ++ [TH.NoBindS (toExp e)] 237 | -- where 238 | -- convert (Expr.QualStmt _ st) = toStmt st 239 | -- convert s = noTH "toExp ListComp" s 240 | -- toExp (Expr.ExpTypeSig _ e t) = TH.SigE (toExp e) (toType t) 241 | #if MIN_VERSION_ghc(9, 2, 0) 242 | toExp d (Expr.ExplicitList _ (map unLoc -> args)) = TH.ListE (map (toExp d) args) 243 | #else 244 | toExp d (Expr.ExplicitList _ _ (map unLoc -> args)) = TH.ListE (map (toExp d) args) 245 | #endif 246 | toExp d (Expr.ArithSeq _ _ e) = TH.ArithSeqE $ case e of 247 | (From a) -> TH.FromR (toExp d $ unLoc a) 248 | (FromThen a b) -> TH.FromThenR (toExp d $ unLoc a) (toExp d $ unLoc b) 249 | (FromTo a b) -> TH.FromToR (toExp d $ unLoc a) (toExp d $ unLoc b) 250 | (FromThenTo a b c) -> TH.FromThenToR (toExp d $ unLoc a) (toExp d $ unLoc b) (toExp d $ unLoc c) 251 | #if MIN_VERSION_ghc(9,12,0) 252 | toExp _ (HsOverLabel _ fs) = TH.LabelE (unpackFS fs) 253 | #elif MIN_VERSION_ghc(9,7,0) 254 | toExp _ (HsOverLabel _ lbl _) = TH.LabelE (fromSourceText lbl) 255 | where 256 | fromSourceText :: SourceText -> String 257 | fromSourceText (SourceText s) = unpackFS s 258 | fromSourceText NoSourceText = "" 259 | #elif MIN_VERSION_ghc(9,6,0) 260 | toExp _ (HsOverLabel _ lbl _) = TH.LabelE (fromSourceText lbl) 261 | where 262 | fromSourceText :: SourceText -> String 263 | fromSourceText (SourceText s) = s 264 | fromSourceText NoSourceText = "" 265 | #elif MIN_VERSION_ghc(9, 2, 0) 266 | toExp _ (HsOverLabel _ lbl) = TH.LabelE (unpackFS lbl) 267 | #else 268 | -- It's not quite clear what to do in case when overloaded syntax is 269 | -- enabled thus match on Nothing 270 | toExp _ (HsOverLabel _ Nothing lbl) = TH.LabelE (unpackFS lbl) 271 | #endif 272 | #if MIN_VERSION_ghc(9,12,0) 273 | toExp dynFlags (HsGetField _ expr field) = TH.GetFieldE (toExp dynFlags (unLoc expr)) (unpackFS . field_label . unLoc . dfoLabel . unLoc $ field) 274 | toExp _ (HsProjection _ fields) = TH.ProjectionE (fmap (unpackFS . unLoc . fmap field_label . dfoLabel) fields) 275 | #elif MIN_VERSION_ghc(9,6,0) 276 | toExp dynFlags (HsGetField _ expr field) = TH.GetFieldE (toExp dynFlags (unLoc expr)) (unpackFS . field_label . unLoc . dfoLabel . unLoc $ field) 277 | toExp _ (HsProjection _ fields) = TH.ProjectionE (fmap (unpackFS . unLoc . fmap field_label . dfoLabel . unLoc) fields) 278 | #elif MIN_VERSION_ghc(9, 4, 0) 279 | toExp dynFlags (HsGetField _ expr field) = TH.GetFieldE (toExp dynFlags (unLoc expr)) (unpackFS . unLoc . dfoLabel . unLoc $ field) 280 | toExp _ (HsProjection _ fields) = TH.ProjectionE (fmap (unpackFS . unLoc . dfoLabel . unLoc) fields) 281 | #elif MIN_VERSION_ghc(9, 2, 0) 282 | toExp dynFlags (HsGetField _ expr field) = TH.GetFieldE (toExp dynFlags (unLoc expr)) (unpackFS . unLoc . hflLabel . unLoc $ field) 283 | toExp _ (HsProjection _ fields) = TH.ProjectionE (fmap (unpackFS . unLoc . hflLabel . unLoc) fields) 284 | #endif 285 | toExp dynFlags e = todo "toExp" (showSDoc dynFlags . ppr $ e) 286 | 287 | todo :: (HasCallStack, Show e) => String -> e -> a 288 | todo fun thing = error . concat $ [moduleName, ".", fun, ": not implemented: ", show thing, "Please open an issue at https://github.com/guibou/PyF/issues"] 289 | 290 | noTH :: (HasCallStack, Show e) => String -> e -> a 291 | noTH fun thing = error . concat $ [moduleName, ".", fun, ": no TemplateHaskell for: ", show thing, "Please open an issue at https://github.com/guibou/PyF/issues"] 292 | 293 | moduleName :: String 294 | moduleName = "PyF.Internal.Meta" 295 | 296 | baseDynFlags :: [GhcTH.Extension] -> DynFlags 297 | baseDynFlags exts = foldl xopt_set dynFlags enable 298 | where 299 | enable = GhcTH.TemplateHaskellQuotes : exts 300 | #if MIN_VERSION_ghc(9,6,0) 301 | dynFlags = defaultDynFlags fakeSettings 302 | #else 303 | dynFlags = defaultDynFlags fakeSettings fakeLlvmConfig 304 | #endif 305 | -------------------------------------------------------------------------------- /src/PyF/Internal/Parser.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE ScopedTypeVariables #-} 3 | {-# LANGUAGE TypeApplications #-} 4 | {-# LANGUAGE ViewPatterns #-} 5 | 6 | -- | This module is here to parse Haskell expression using the GHC Api 7 | module PyF.Internal.Parser (parseExpression) where 8 | 9 | #if MIN_VERSION_ghc(9,6,0) 10 | import GHC.Parser.Errors.Types (PsMessage) 11 | #endif 12 | 13 | #if MIN_VERSION_ghc(9,0,0) 14 | import GHC.Parser.Lexer (ParseResult (..), PState (..)) 15 | #elif MIN_VERSION_ghc(8,10,0) 16 | import Lexer (ParseResult (..), PState (..)) 17 | #else 18 | import Lexer (ParseResult (..)) 19 | #endif 20 | 21 | #if MIN_VERSION_ghc(9,3,0) 22 | import GHC.Types.Error 23 | import GHC.Utils.Outputable 24 | import GHC.Utils.Error 25 | #endif 26 | 27 | #if MIN_VERSION_ghc(9,4,0) 28 | #elif MIN_VERSION_ghc(9,2,0) 29 | import qualified GHC.Parser.Errors.Ppr as ParserErrorPpr 30 | #endif 31 | 32 | #if MIN_VERSION_ghc(9,0,0) 33 | import qualified GHC.Types.SrcLoc as SrcLoc 34 | #endif 35 | 36 | #if MIN_VERSION_ghc(9,0,0) 37 | import GHC.Driver.Session (DynFlags) 38 | import GHC.Types.SrcLoc 39 | #else 40 | import DynFlags (DynFlags) 41 | import SrcLoc 42 | #endif 43 | 44 | #if MIN_VERSION_ghc(8,10,0) 45 | import GHC.Hs.Expr as Expr 46 | import GHC.Hs.Extension as Ext 47 | #else 48 | import HsExpr as Expr 49 | import HsExtension as Ext 50 | import Outputable (showSDoc) 51 | #endif 52 | 53 | import qualified PyF.Internal.ParserEx as ParseExp 54 | 55 | parseExpression :: RealSrcLoc -> String -> DynFlags -> Either (Int, Int, String) (HsExpr GhcPs) 56 | parseExpression initLoc s dynFlags = 57 | case ParseExp.parseExpression initLoc s dynFlags of 58 | POk _ locatedExpr -> 59 | let expr = SrcLoc.unLoc locatedExpr 60 | in Right 61 | expr 62 | 63 | {- ORMOLU_DISABLE #-} 64 | #if MIN_VERSION_ghc(9,2,0) 65 | -- TODO messages? 66 | PFailed PState{loc=SrcLoc.psRealLoc -> srcLoc, errors=errorMessages} -> 67 | #elif MIN_VERSION_ghc(9,0,0) 68 | PFailed PState{loc=SrcLoc.psRealLoc -> srcLoc, messages=msgs} -> 69 | #elif MIN_VERSION_ghc(8,10,0) 70 | PFailed PState{loc=srcLoc, messages=msgs} -> 71 | #else 72 | PFailed _ (SrcLoc.srcSpanEnd -> SrcLoc.RealSrcLoc srcLoc) doc -> 73 | #endif 74 | 75 | #if MIN_VERSION_ghc(9,7,0) 76 | let 77 | err = renderWithContext defaultSDocContext 78 | $ vcat 79 | $ map formatBulleted 80 | $ map (\psMessage -> diagnosticMessage (defaultDiagnosticOpts @PsMessage) psMessage) 81 | $ map errMsgDiagnostic 82 | $ sortMsgBag Nothing 83 | $ getMessages $ errorMessages 84 | line' = SrcLoc.srcLocLine srcLoc 85 | col = SrcLoc.srcLocCol srcLoc 86 | in Left (line', col, err) 87 | #elif MIN_VERSION_ghc(9,6,0) 88 | let 89 | err = renderWithContext defaultSDocContext 90 | $ vcat 91 | $ map (formatBulleted defaultSDocContext) 92 | $ map (\psMessage -> diagnosticMessage (defaultDiagnosticOpts @PsMessage) psMessage) 93 | $ map errMsgDiagnostic 94 | $ sortMsgBag Nothing 95 | $ getMessages $ errorMessages 96 | line' = SrcLoc.srcLocLine srcLoc 97 | col = SrcLoc.srcLocCol srcLoc 98 | in Left (line', col, err) 99 | #elif MIN_VERSION_ghc(9,3,0) 100 | let 101 | err = renderWithContext defaultSDocContext 102 | $ vcat 103 | $ map (formatBulleted defaultSDocContext) 104 | $ map diagnosticMessage 105 | $ map errMsgDiagnostic 106 | $ sortMsgBag Nothing 107 | $ getMessages $ errorMessages 108 | line = SrcLoc.srcLocLine srcLoc 109 | col = SrcLoc.srcLocCol srcLoc 110 | in Left (line, col, err) 111 | #elif MIN_VERSION_ghc(9,2,0) 112 | let 113 | psErrToString e = show $ ParserErrorPpr.pprError e 114 | err = concatMap psErrToString errorMessages 115 | line = SrcLoc.srcLocLine srcLoc 116 | col = SrcLoc.srcLocCol srcLoc 117 | in Left (line, col, err) 118 | #elif MIN_VERSION_ghc(8,10,0) 119 | let -- TODO: do not ignore "warnMessages" 120 | -- I have no idea what they can be 121 | (_warnMessages, errorMessages) = msgs dynFlags 122 | err = concatMap show errorMessages 123 | line = SrcLoc.srcLocLine srcLoc 124 | col = SrcLoc.srcLocCol srcLoc 125 | in Left (line, col, err) 126 | #else 127 | let err = showSDoc dynFlags doc 128 | line = SrcLoc.srcLocLine srcLoc 129 | col = SrcLoc.srcLocCol srcLoc 130 | in Left (line, col, err) 131 | #endif 132 | 133 | #if MIN_VERSION_ghc(8,10,0) 134 | #elif MIN_VERSION_ghc(8,6,0) 135 | -- Only here to satisfy GHC checker which was not able to consider this as total with GHC <8.10 136 | PFailed _ _ _ -> error "The impossible happen: this case is not possible" 137 | #endif 138 | -------------------------------------------------------------------------------- /src/PyF/Internal/ParserEx.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE PatternSynonyms #-} 3 | {-# LANGUAGE TupleSections #-} 4 | {-# LANGUAGE ViewPatterns #-} 5 | {-# OPTIONS_GHC -Wno-missing-fields -Wno-name-shadowing -Wno-unused-imports #-} 6 | 7 | module PyF.Internal.ParserEx (fakeSettings, fakeLlvmConfig, parseExpression) where 8 | 9 | {- ORMOLU_DISABLE -} 10 | 11 | #if MIN_VERSION_ghc(9,6,0) 12 | import GHC.Settings.Config 13 | import GHC.CmmToLlvm.Config (LlvmConfig(..)) 14 | import GHC.Utils.Fingerprint 15 | import GHC.Platform 16 | import GHC.Settings 17 | #elif MIN_VERSION_ghc(9,0,0) 18 | import GHC.Settings.Config 19 | import GHC.Driver.Session 20 | import GHC.Utils.Fingerprint 21 | import GHC.Platform 22 | import GHC.Settings 23 | #elif MIN_VERSION_ghc(8, 10, 0) 24 | import Config 25 | import DynFlags 26 | import Fingerprint 27 | import GHC.Platform 28 | import ToolSettings 29 | #else 30 | import Config 31 | import DynFlags 32 | import Fingerprint 33 | import Platform 34 | #endif 35 | 36 | #if MIN_VERSION_ghc(8,10,0) 37 | import GHC.Hs 38 | #else 39 | import HsSyn 40 | #endif 41 | 42 | #if MIN_VERSION_ghc(9, 2, 0) 43 | import GHC.Driver.Config 44 | #endif 45 | 46 | #if MIN_VERSION_ghc(9,0,0) 47 | import GHC.Parser.PostProcess 48 | import GHC.Driver.Session 49 | import GHC.Data.StringBuffer 50 | import GHC.Parser.Lexer 51 | import qualified GHC.Parser.Lexer as Lexer 52 | import qualified GHC.Parser as Parser 53 | import GHC.Data.FastString 54 | import GHC.Types.SrcLoc 55 | import GHC.Driver.Backpack.Syntax 56 | import GHC.Unit.Info 57 | import GHC.Types.Name.Reader 58 | #else 59 | import StringBuffer 60 | import Lexer 61 | import qualified Parser 62 | import FastString 63 | import SrcLoc 64 | import RdrName 65 | #endif 66 | 67 | #if MIN_VERSION_ghc(9, 0, 0) 68 | #else 69 | import RdrHsSyn 70 | #endif 71 | 72 | import Data.Data hiding (Fixity) 73 | 74 | #if MIN_VERSION_ghc(9,0,0) 75 | import GHC.Hs 76 | 77 | #if MIN_VERSION_ghc(9, 2, 0) 78 | import GHC.Types.Fixity 79 | import GHC.Types.SourceText 80 | #else 81 | import GHC.Types.Basic 82 | #endif 83 | 84 | import GHC.Types.Name.Reader 85 | import GHC.Types.Name 86 | import GHC.Types.SrcLoc 87 | #elif MIN_VERSION_ghc(8, 10, 0) 88 | import BasicTypes 89 | import OccName 90 | #else 91 | import BasicTypes 92 | import OccName 93 | #endif 94 | 95 | #if MIN_VERSION_ghc(9,3,0) 96 | import GHC.Driver.Config.Parser (initParserOpts) 97 | #endif 98 | 99 | import Data.Maybe 100 | 101 | fakeSettings :: Settings 102 | fakeSettings = Settings 103 | #if MIN_VERSION_ghc(9, 2, 0) 104 | { sGhcNameVersion=ghcNameVersion 105 | , sFileSettings=fileSettings 106 | , sTargetPlatform=platform 107 | , sPlatformMisc=platformMisc 108 | , sToolSettings=toolSettings 109 | } 110 | #elif MIN_VERSION_ghc(8, 10, 0) 111 | { sGhcNameVersion=ghcNameVersion 112 | , sFileSettings=fileSettings 113 | , sTargetPlatform=platform 114 | , sPlatformMisc=platformMisc 115 | , sPlatformConstants=platformConstants 116 | , sToolSettings=toolSettings 117 | } 118 | #else 119 | { sTargetPlatform=platform 120 | , sPlatformConstants=platformConstants 121 | , sProjectVersion=cProjectVersion 122 | , sProgramName="ghc" 123 | , sOpt_P_fingerprint=fingerprint0 124 | } 125 | #endif 126 | where 127 | #if MIN_VERSION_ghc(8, 10, 0) 128 | toolSettings = ToolSettings { 129 | toolSettings_opt_P_fingerprint=fingerprint0 130 | } 131 | fileSettings = FileSettings {} 132 | platformMisc = PlatformMisc {} 133 | ghcNameVersion = 134 | GhcNameVersion{ghcNameVersion_programName="ghc" 135 | ,ghcNameVersion_projectVersion=cProjectVersion 136 | } 137 | #endif 138 | platform = 139 | Platform{ 140 | #if MIN_VERSION_ghc(9, 0, 0) 141 | -- It doesn't matter what values we write here as these fields are 142 | -- not referenced for our purposes. However the fields are strict 143 | -- so we must say something. 144 | platformByteOrder=LittleEndian 145 | , platformHasGnuNonexecStack=True 146 | , platformHasIdentDirective=False 147 | , platformHasSubsectionsViaSymbols=False 148 | , platformIsCrossCompiling=False 149 | , platformLeadingUnderscore=False 150 | , platformTablesNextToCode=False 151 | #if MIN_VERSION_ghc(9, 2, 0) 152 | , platform_constants=platformConstants 153 | #endif 154 | #if MIN_VERSION_ghc(9, 3, 0) 155 | , platformHasLibm=True 156 | #endif 157 | 158 | , 159 | #endif 160 | 161 | #if MIN_VERSION_ghc(9, 2, 0) 162 | platformWordSize=PW8 163 | , platformArchOS=ArchOS {archOS_arch=ArchUnknown, archOS_OS=OSUnknown} 164 | #elif MIN_VERSION_ghc(8, 10, 0) 165 | platformWordSize=PW8 166 | , platformMini=PlatformMini {platformMini_arch=ArchUnknown, platformMini_os=OSUnknown} 167 | #else 168 | platformWordSize=8 169 | , platformOS=OSUnknown 170 | #endif 171 | , platformUnregisterised=True 172 | } 173 | #if MIN_VERSION_ghc(9, 2, 0) 174 | platformConstants = Nothing 175 | #else 176 | platformConstants = 177 | PlatformConstants{pc_DYNAMIC_BY_DEFAULT=False,pc_WORD_SIZE=8} 178 | #endif 179 | 180 | #if MIN_VERSION_ghc(8, 10, 0) 181 | fakeLlvmConfig :: LlvmConfig 182 | fakeLlvmConfig = LlvmConfig [] [] 183 | #else 184 | fakeLlvmConfig :: (LlvmTargets, LlvmPasses) 185 | fakeLlvmConfig = ([], []) 186 | #endif 187 | 188 | -- From Language.Haskell.GhclibParserEx.GHC.Parser 189 | 190 | parse :: RealSrcLoc -> P a -> String -> DynFlags -> ParseResult a 191 | parse initLoc p str flags = 192 | Lexer.unP p parseState 193 | where 194 | buffer = stringToStringBuffer str 195 | parseState = 196 | #if MIN_VERSION_ghc(9, 2, 0) 197 | initParserState (initParserOpts flags) buffer initLoc 198 | #else 199 | mkPState flags buffer initLoc 200 | #endif 201 | 202 | #if MIN_VERSION_ghc(9, 2, 0) 203 | parseExpression :: RealSrcLoc -> String -> DynFlags -> ParseResult (LocatedA (HsExpr GhcPs)) 204 | parseExpression initLoc s flags = 205 | case parse initLoc Parser.parseExpression s flags of 206 | POk s e -> unP (runPV (unECP e)) s 207 | PFailed ps -> PFailed ps 208 | #elif MIN_VERSION_ghc(8, 10, 0) 209 | parseExpression :: RealSrcLoc -> String -> DynFlags -> ParseResult (Located (HsExpr GhcPs)) 210 | parseExpression initLoc s flags = 211 | case parse initLoc Parser.parseExpression s flags of 212 | POk s e -> unP (runECP_P e) s 213 | PFailed ps -> PFailed ps 214 | #else 215 | parseExpression :: RealSrcLoc -> String -> DynFlags -> ParseResult (LHsExpr GhcPs) 216 | parseExpression initLoc = parse initLoc Parser.parseExpression 217 | #endif 218 | -------------------------------------------------------------------------------- /src/PyF/Internal/PythonSyntax.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE DataKinds #-} 3 | {-# LANGUAGE DeriveDataTypeable #-} 4 | {-# LANGUAGE FlexibleContexts #-} 5 | {-# LANGUAGE GADTs #-} 6 | {-# LANGUAGE KindSignatures #-} 7 | {-# LANGUAGE OverloadedStrings #-} 8 | {-# LANGUAGE PatternSynonyms #-} 9 | 10 | -- | 11 | -- This module provides a parser for . 12 | module PyF.Internal.PythonSyntax 13 | ( parseGenericFormatString, 14 | Item (..), 15 | FormatMode (..), 16 | Padding (..), 17 | Precision (..), 18 | TypeFormat (..), 19 | AlternateForm (..), 20 | pattern DefaultFormatMode, 21 | Parser, 22 | ParsingContext (..), 23 | ExprOrValue (..), 24 | ) 25 | where 26 | 27 | import Control.Applicative (some) 28 | import Control.Monad (replicateM_, void) 29 | import Control.Monad.Reader (Reader, asks) 30 | import qualified Data.Char 31 | import Data.Data (Data) 32 | import Data.Maybe (fromMaybe) 33 | import GHC (GhcPs, HsExpr) 34 | import Language.Haskell.TH.LanguageExtensions (Extension (..)) 35 | import Language.Haskell.TH.Syntax (Exp) 36 | import PyF.Formatters 37 | import PyF.Internal.Meta 38 | import qualified PyF.Internal.Parser as ParseExp 39 | import Text.Parsec 40 | 41 | #if MIN_VERSION_ghc(9,7,0) 42 | 43 | #elif MIN_VERSION_ghc(9,6,0) 44 | -- For some reasons, theses function are not exported anymore by some others 45 | import Data.Functor (void) 46 | import Control.Monad (replicateM_) 47 | #endif 48 | 49 | #if MIN_VERSION_ghc(9,0,0) 50 | import GHC.Types.SrcLoc 51 | import GHC.Data.FastString 52 | #else 53 | import SrcLoc 54 | import FastString 55 | #endif 56 | 57 | type Parser t = ParsecT String () (Reader ParsingContext) t 58 | 59 | data ParsingContext = ParsingContext 60 | { delimiters :: Maybe (Char, Char), 61 | enabledExtensions :: [Extension] 62 | } 63 | deriving (Show) 64 | 65 | {- 66 | -- TODO: 67 | - Better parsing of integer 68 | - Recursive replacement field, so "{string:.{precision}} can be parsed 69 | - f_expression / conversion 70 | - Not (Yet) implemented: 71 | - types: n 72 | -} 73 | 74 | {- 75 | f_string ::= (literal_char | "{{" | "}}" | replacement_field)* 76 | replacement_field ::= "{" f_expression ["!" conversion] [":" format_spec] "}" 77 | f_expression ::= (conditional_expression | "*" or_expr) 78 | ("," conditional_expression | "," "*" or_expr)* [","] 79 | | yield_expression 80 | conversion ::= "s" | "r" | "a" 81 | format_spec ::= (literal_char | NULL | replacement_field)* 82 | literal_char ::= 83 | -} 84 | 85 | -- | A format string is composed of many chunks of raw string or replacement 86 | data Item 87 | = -- | A raw string 88 | Raw String 89 | | -- | A replacement string, composed of an arbitrary Haskell expression followed by an optional formatter 90 | Replacement (HsExpr GhcPs, Exp) (Maybe FormatMode) 91 | 92 | -- | 93 | -- Parse a string, returns a list of raw string or replacement fields 94 | -- 95 | -- >>> import Text.Megaparsec 96 | -- >>> parse parsePythonFormatString "" "hello {1+1:>10.2f}" 97 | -- Right [ 98 | -- Raw "hello ", 99 | -- Replacement "1+1" 100 | -- ( 101 | -- Just (FormatMode 102 | -- (Padding 10 (Just (Nothing,AnyAlign AlignRight))) 103 | -- (FixedF (Precision 2) NormalForm Minus) 104 | -- Nothing))] 105 | parseGenericFormatString :: Parser [Item] 106 | parseGenericFormatString = do 107 | delimitersM <- asks delimiters 108 | 109 | case delimitersM of 110 | Nothing -> many (rawString Nothing) 111 | Just _ -> many (rawString delimitersM <|> escapedParenthesis <|> replacementField) <* eof 112 | 113 | rawString :: Maybe (Char, Char) -> Parser Item 114 | rawString delimsM = do 115 | let delims = case delimsM of 116 | Nothing -> [] 117 | Just (openingChar, closingChar) -> [openingChar, closingChar] 118 | 119 | -- lookahead 120 | let p = some (noneOf delims) 121 | chars <- lookAhead p 122 | 123 | case escapeChars chars of 124 | Left remaining -> do 125 | -- Consume up to the error location 126 | void $ count (length chars - length remaining) anyChar 127 | fail "Lexical error in literal section" 128 | Right escaped -> do 129 | -- Consumne everything 130 | void p 131 | return (Raw escaped) 132 | 133 | escapedParenthesis :: Parser Item 134 | escapedParenthesis = do 135 | Just (openingChar, closingChar) <- asks delimiters 136 | Raw <$> (parseRaw openingChar <|> parseRaw closingChar) 137 | where 138 | parseRaw c = [c] <$ try (string (replicate 2 c)) 139 | 140 | -- | Replace escape chars with their value. Results in a Left with the 141 | -- remainder of the string on encountering a lexical error (such as a bad escape 142 | -- sequence). 143 | -- >>> escapeChars "hello \\n" 144 | -- Right "hello \n" 145 | -- >>> escapeChars "hello \\x" 146 | -- Left "\\x" 147 | escapeChars :: String -> Either String String 148 | escapeChars "" = Right "" 149 | escapeChars ('\\' : '\n' : xs) = escapeChars xs 150 | escapeChars ('\\' : '\\' : xs) = ('\\' :) <$> escapeChars xs 151 | escapeChars s = case Data.Char.readLitChar s of 152 | ((c, xs) : _) -> (c :) <$> escapeChars xs 153 | _ -> Left s 154 | 155 | -- | Parses the expression field (i.e. what's appear before the format field) 156 | parseExpressionString :: Parser String 157 | parseExpressionString = do 158 | Just (_charOpening, charClosing) <- asks delimiters 159 | -- Special case for "::", we want to parse it as part of an expression, 160 | -- unless it may be the end of the format field (':'), followed by a padding 161 | -- char (':') followed by a padding specifier. 162 | res <- some (try (string "::" <* notFollowedBy (oneOf "<>=^")) <|> (pure <$> noneOf (charClosing : ":" :: String))) 163 | pure $ concat res 164 | 165 | replacementField :: Parser Item 166 | replacementField = do 167 | exts <- asks enabledExtensions 168 | Just (charOpening, charClosing) <- asks delimiters 169 | _ <- char charOpening 170 | expr <- evalExpr exts (parseExpressionString "an haskell expression") 171 | fmt <- optionMaybe $ do 172 | _ <- char ':' 173 | formatSpec 174 | _ <- char charClosing 175 | pure (Replacement expr fmt) 176 | 177 | -- | Default formatting mode, no padding, default precision, no grouping, no sign handling 178 | pattern DefaultFormatMode :: FormatMode 179 | pattern DefaultFormatMode = FormatMode PaddingDefault (DefaultF PrecisionDefault Minus) Nothing 180 | 181 | -- | A Formatter, listing padding, format and and grouping char 182 | data FormatMode = FormatMode Padding TypeFormat (Maybe Char) 183 | 184 | -- | Padding, containing the padding width, the padding char and the alignement mode 185 | data Padding 186 | = PaddingDefault 187 | | Padding (ExprOrValue Int) (Maybe (Maybe Char, AnyAlign)) 188 | 189 | -- | Represents a value of type @t@ or an Haskell expression supposed to represents that value 190 | data ExprOrValue t 191 | = Value t 192 | | HaskellExpr (HsExpr GhcPs, Exp) 193 | deriving (Data) 194 | 195 | -- | Floating point precision 196 | data Precision 197 | = PrecisionDefault 198 | | Precision (ExprOrValue Int) 199 | deriving (Data) 200 | 201 | {- 202 | 203 | Python format mini language 204 | 205 | format_spec ::= [[fill]align][sign][#][0][width][grouping_option][.precision][type] 206 | fill ::= 207 | align ::= "<" | ">" | "=" | "^" 208 | sign ::= "+" | "-" | " " 209 | width ::= integer 210 | grouping_option ::= "_" | "," 211 | precision ::= integer 212 | type ::= "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%" 213 | -} 214 | 215 | data TypeFlag = Flagb | Flagc | Flagd | Flage | FlagE | Flagf | FlagF | Flagg | FlagG | Flagn | Flago | Flags | Flagx | FlagX | FlagPercent 216 | deriving (Show) 217 | 218 | -- | All formatting type 219 | data TypeFormat 220 | = -- | Default, depends on the infered type of the expression 221 | DefaultF Precision SignMode 222 | | -- | Binary, such as `0b0121` 223 | BinaryF AlternateForm SignMode 224 | | -- | Character, will convert an integer to its character representation 225 | CharacterF 226 | | -- | Decimal, base 10 integer formatting 227 | DecimalF SignMode 228 | | -- | Exponential notation for floatting points 229 | ExponentialF Precision AlternateForm SignMode 230 | | -- | Exponential notation with capitalised @e@ 231 | ExponentialCapsF Precision AlternateForm SignMode 232 | | -- | Fixed number of digits floating point 233 | FixedF Precision AlternateForm SignMode 234 | | -- | Capitalized version of the previous 235 | FixedCapsF Precision AlternateForm SignMode 236 | | -- | General formatting: `FixedF` or `ExponentialF` depending on the number magnitude 237 | GeneralF Precision AlternateForm SignMode 238 | | -- | Same as `GeneralF` but with upper case @E@ and infinite / NaN 239 | GeneralCapsF Precision AlternateForm SignMode 240 | | -- | Octal, such as 00245 241 | OctalF AlternateForm SignMode 242 | | -- | Simple string 243 | StringF Precision 244 | | -- | Hexadecimal, such as 0xaf3e 245 | HexF AlternateForm SignMode 246 | | -- | Hexadecimal with capitalized letters, such as 0XAF3E 247 | HexCapsF AlternateForm SignMode 248 | | -- | Percent representation 249 | PercentF Precision AlternateForm SignMode 250 | deriving (Data) 251 | 252 | -- | If the formatter use its alternate form 253 | data AlternateForm = AlternateForm | NormalForm 254 | deriving (Show, Data) 255 | 256 | evalExpr :: [Extension] -> Parser String -> Parser (HsExpr GhcPs, Exp) 257 | evalExpr exts exprParser = do 258 | exprPos <- getPosition 259 | -- Inject the correct source location in the GHC parser, so it already match 260 | -- the input source file. 261 | let initLoc = mkRealSrcLoc (mkFastString (sourceName exprPos)) (sourceLine exprPos) (sourceColumn exprPos) 262 | s <- lookAhead exprParser 263 | -- Setup the dyn flags using the provided list of extensions 264 | let dynFlags = baseDynFlags exts 265 | case ParseExp.parseExpression initLoc s dynFlags of 266 | Right expr -> do 267 | -- Consume the expression 268 | void exprParser 269 | pure (expr, toExp dynFlags expr) 270 | Left (lineError, colError, err) -> do 271 | -- In case of error, we just advance the parser to the error location. 272 | -- Note: we have to remove what was introduced in `initLoc` 273 | -- Skip lines 274 | replicateM_ (lineError - sourceLine exprPos) (manyTill anyChar newline) 275 | -- Skip columns 276 | -- This is a bit more counter intuitive. If we have skipped not lines, we 277 | -- must remove the introduced column offset, otherwise no. 278 | let columnSkip 279 | | lineError - sourceLine exprPos == 0 = colError - 1 - sourceColumn exprPos 280 | | otherwise = colError - 2 281 | void $ count columnSkip anyChar 282 | fail $ err <> " in haskell expression" 283 | 284 | overrideAlignmentIfZero :: Bool -> Maybe (Maybe Char, AnyAlign) -> Maybe (Maybe Char, AnyAlign) 285 | overrideAlignmentIfZero True Nothing = Just (Just '0', AnyAlign AlignInside) 286 | overrideAlignmentIfZero True (Just (Nothing, al)) = Just (Just '0', al) 287 | overrideAlignmentIfZero _ v = v 288 | 289 | formatSpec :: Parser FormatMode 290 | formatSpec = do 291 | al' <- optionMaybe alignment 292 | s <- optionMaybe sign 293 | alternateForm <- option NormalForm (AlternateForm <$ char '#') 294 | hasZero <- option False (True <$ char '0') 295 | let al = overrideAlignmentIfZero hasZero al' 296 | w <- optionMaybe parseWidth 297 | grouping <- optionMaybe groupingOption 298 | prec <- option PrecisionDefault parsePrecision 299 | 300 | t <- optionMaybe $ lookAhead type_ 301 | let padding = case w of 302 | Just p -> Padding p al 303 | Nothing -> PaddingDefault 304 | case t of 305 | Nothing -> pure (FormatMode padding (DefaultF prec (fromMaybe Minus s)) grouping) 306 | Just flag -> case evalFlag flag padding grouping prec alternateForm s of 307 | Right fmt -> do 308 | -- Consumne the parser 309 | void type_ 310 | pure (FormatMode padding fmt grouping) 311 | Left typeError -> 312 | fail typeError 313 | 314 | parseWidth :: Parser (ExprOrValue Int) 315 | parseWidth = do 316 | exts <- asks enabledExtensions 317 | Just (charOpening, charClosing) <- asks delimiters 318 | choice 319 | [ Value <$> width, 320 | char charOpening *> (HaskellExpr <$> evalExpr exts (someTill (satisfy (/= charClosing)) (char charClosing) "an haskell expression")) 321 | ] 322 | 323 | parsePrecision :: Parser Precision 324 | parsePrecision = do 325 | exts <- asks enabledExtensions 326 | Just (charOpening, charClosing) <- asks delimiters 327 | _ <- char '.' 328 | choice 329 | [ Precision . Value <$> precision, 330 | char charOpening *> (Precision . HaskellExpr <$> evalExpr exts (someTill (satisfy (/= charClosing)) (char charClosing) "an haskell expression")) 331 | ] 332 | 333 | -- | Similar to 'manyTill' but always parse one element. 334 | -- Be careful, @someTill p e@ may parse @e@ as first element if @e@ is a subset of @p@. 335 | someTill :: (Stream s m t) => ParsecT s u m a -> ParsecT s u m end -> ParsecT s u m [a] 336 | someTill p e = (:) <$> p <*> manyTill p e 337 | 338 | evalFlag :: TypeFlag -> Padding -> Maybe Char -> Precision -> AlternateForm -> Maybe SignMode -> Either String TypeFormat 339 | evalFlag Flagb _pad _grouping prec alt s = failIfPrec prec (BinaryF alt (defSign s)) 340 | evalFlag Flagc _pad _grouping prec alt s = failIfS s =<< failIfPrec prec =<< failIfAlt alt CharacterF 341 | evalFlag Flagd _pad _grouping prec alt s = failIfPrec prec =<< failIfAlt alt (DecimalF (defSign s)) 342 | evalFlag Flage _pad _grouping prec alt s = pure $ ExponentialF prec alt (defSign s) 343 | evalFlag FlagE _pad _grouping prec alt s = pure $ ExponentialCapsF prec alt (defSign s) 344 | evalFlag Flagf _pad _grouping prec alt s = pure $ FixedF prec alt (defSign s) 345 | evalFlag FlagF _pad _grouping prec alt s = pure $ FixedCapsF prec alt (defSign s) 346 | evalFlag Flagg _pad _grouping prec alt s = pure $ GeneralF prec alt (defSign s) 347 | evalFlag FlagG _pad _grouping prec alt s = pure $ GeneralCapsF prec alt (defSign s) 348 | evalFlag Flagn _pad _grouping _prec _alt _s = Left ("Type 'n' not handled (yet). " ++ errgGn) 349 | evalFlag Flago _pad _grouping prec alt s = failIfPrec prec $ OctalF alt (defSign s) 350 | evalFlag Flags pad grouping prec alt s = failIfGrouping grouping =<< failIfInsidePadding pad =<< failIfS s =<< failIfAlt alt (StringF prec) 351 | evalFlag Flagx _pad _grouping prec alt s = failIfPrec prec $ HexF alt (defSign s) 352 | evalFlag FlagX _pad _grouping prec alt s = failIfPrec prec $ HexCapsF alt (defSign s) 353 | evalFlag FlagPercent _pad _grouping prec alt s = pure $ PercentF prec alt (defSign s) 354 | 355 | defSign :: Maybe SignMode -> SignMode 356 | defSign Nothing = Minus 357 | defSign (Just s) = s 358 | 359 | failIfGrouping :: Maybe Char -> TypeFormat -> Either String TypeFormat 360 | failIfGrouping (Just _) _t = Left "String type is incompatible with grouping (_ or ,)." 361 | failIfGrouping Nothing t = Right t 362 | 363 | failIfInsidePadding :: Padding -> TypeFormat -> Either String TypeFormat 364 | failIfInsidePadding (Padding _ (Just (_, AnyAlign AlignInside))) _t = Left "String type is incompatible with inside padding (=)." 365 | failIfInsidePadding _ t = Right t 366 | 367 | errgGn :: String 368 | errgGn = "Use one of {'b', 'c', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 's', 'x', 'X', '%'}." 369 | 370 | failIfPrec :: Precision -> TypeFormat -> Either String TypeFormat 371 | failIfPrec PrecisionDefault i = Right i 372 | 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.") 373 | where 374 | showExpr = case e of 375 | Value v -> show v 376 | HaskellExpr (_, expr) -> show expr 377 | 378 | failIfAlt :: AlternateForm -> TypeFormat -> Either String TypeFormat 379 | failIfAlt NormalForm i = Right i 380 | failIfAlt _ _ = Left "Type incompatible with alternative form (#), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 'x', 'X', '%'} or remove the alternative field." 381 | 382 | failIfS :: Maybe SignMode -> TypeFormat -> Either String TypeFormat 383 | failIfS Nothing i = Right i 384 | failIfS (Just s) _ = Left ("Type incompatible with sign field (" ++ [toSignMode s] ++ "), use any of {'b', 'd', 'e', 'E', 'f', 'F', 'g', 'G', 'n', 'o', 'x', 'X', '%'} or remove the sign field.") 385 | 386 | toSignMode :: SignMode -> Char 387 | toSignMode Plus = '+' 388 | toSignMode Minus = '-' 389 | toSignMode Space = ' ' 390 | 391 | alignment :: Parser (Maybe Char, AnyAlign) 392 | alignment = 393 | choice 394 | [ try $ do 395 | c <- fill 396 | mode <- align 397 | pure (Just c, mode), 398 | do 399 | mode <- align 400 | pure (Nothing, mode) 401 | ] 402 | 403 | fill :: Parser Char 404 | fill = anyChar 405 | 406 | align :: Parser AnyAlign 407 | align = 408 | choice 409 | [ AnyAlign AlignLeft <$ char '<', 410 | AnyAlign AlignRight <$ char '>', 411 | AnyAlign AlignCenter <$ char '^', 412 | AnyAlign AlignInside <$ char '=' 413 | ] 414 | 415 | sign :: Parser SignMode 416 | sign = 417 | choice 418 | [ Plus <$ char '+', 419 | Minus <$ char '-', 420 | Space <$ char ' ' 421 | ] 422 | 423 | width :: Parser Int 424 | width = integer 425 | 426 | integer :: Parser Int 427 | integer = read <$> some (oneOf ['0' .. '9']) -- incomplete: see: https://docs.python.org/3/reference/lexical_analysis.html#grammar-token-integer 428 | 429 | groupingOption :: Parser Char 430 | groupingOption = oneOf ("_," :: String) 431 | 432 | precision :: Parser Int 433 | precision = integer 434 | 435 | type_ :: Parser TypeFlag 436 | type_ = 437 | choice 438 | [ Flagb <$ char 'b', 439 | Flagc <$ char 'c', 440 | Flagd <$ char 'd', 441 | Flage <$ char 'e', 442 | FlagE <$ char 'E', 443 | Flagf <$ char 'f', 444 | FlagF <$ char 'F', 445 | Flagg <$ char 'g', 446 | FlagG <$ char 'G', 447 | Flagn <$ char 'n', 448 | Flago <$ char 'o', 449 | Flags <$ char 's', 450 | Flagx <$ char 'x', 451 | FlagX <$ char 'X', 452 | FlagPercent <$ char '%' 453 | ] 454 | -------------------------------------------------------------------------------- /src/PyF/Internal/QQ.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE DataKinds #-} 3 | {-# LANGUAGE DisambiguateRecordFields #-} 4 | {-# LANGUAGE FlexibleContexts #-} 5 | {-# LANGUAGE FlexibleInstances #-} 6 | {-# LANGUAGE GADTs #-} 7 | {-# LANGUAGE MultiParamTypeClasses #-} 8 | {-# LANGUAGE NamedFieldPuns #-} 9 | {-# LANGUAGE OverloadedStrings #-} 10 | {-# LANGUAGE ScopedTypeVariables #-} 11 | {-# LANGUAGE TemplateHaskell #-} 12 | {-# LANGUAGE TupleSections #-} 13 | {-# LANGUAGE TypeApplications #-} 14 | {-# LANGUAGE TypeFamilies #-} 15 | {-# LANGUAGE UndecidableInstances #-} 16 | {-# LANGUAGE ViewPatterns #-} 17 | {-# OPTIONS_GHC -Wno-name-shadowing #-} 18 | 19 | -- | This module uses the python mini language detailed in 20 | -- 'PyF.Internal.PythonSyntax' to build an template haskell expression 21 | -- representing a formatted string. 22 | module PyF.Internal.QQ 23 | ( toExp, 24 | Config (..), 25 | wrapFromString, 26 | expQQ, 27 | ) 28 | where 29 | 30 | import Control.Monad.Reader 31 | import Data.Data (Data (gmapQ), Typeable, cast) 32 | import Data.Kind 33 | import Data.List (intercalate) 34 | import Data.Maybe (catMaybes, fromMaybe, isJust) 35 | import Data.Proxy 36 | import Data.String (fromString) 37 | 38 | #if MIN_VERSION_ghc(9,0,0) 39 | import GHC.Tc.Utils.Monad (addErrAt) 40 | import GHC.Tc.Types (TcM) 41 | import GHC.Types.Name (occNameString) 42 | #else 43 | import OccName 44 | import TcRnTypes (TcM) 45 | import TcRnMonad (addErrAt) 46 | #endif 47 | 48 | #if MIN_VERSION_ghc(9,6,0) 49 | #else 50 | import GHC (moduleNameString) 51 | #endif 52 | 53 | #if MIN_VERSION_ghc(9,3,0) 54 | import GHC.Tc.Errors.Types 55 | import GHC.Types.Error 56 | import GHC.Utils.Outputable (text) 57 | 58 | #if MIN_VERSION_ghc(9,6,0) 59 | #else 60 | import GHC.Driver.Errors.Types 61 | import GHC.Parser.Errors.Types 62 | #endif 63 | #endif 64 | 65 | #if MIN_VERSION_ghc(9,0,0) 66 | import GHC.Types.Name.Reader 67 | #else 68 | import RdrName 69 | #endif 70 | 71 | #if MIN_VERSION_ghc(8,10,0) 72 | import GHC.Hs.Expr as Expr 73 | import GHC.Hs.Extension as Ext 74 | import GHC.Hs.Pat as Pat 75 | #else 76 | import HsExpr as Expr 77 | import HsExtension as Ext 78 | import HsPat as Pat 79 | #endif 80 | 81 | #if MIN_VERSION_ghc(9,0,0) 82 | import GHC.Types.SrcLoc 83 | #else 84 | import SrcLoc 85 | #endif 86 | 87 | #if MIN_VERSION_ghc(9,2,0) 88 | import GHC.Hs 89 | #endif 90 | 91 | import GHC.TypeLits 92 | import Language.Haskell.TH hiding (Type) 93 | import Language.Haskell.TH.Quote 94 | import Language.Haskell.TH.Syntax (Q (Q)) 95 | import PyF.Class 96 | import PyF.Formatters (AnyAlign (..)) 97 | import qualified PyF.Formatters as Formatters 98 | import PyF.Internal.Meta (toName) 99 | import PyF.Internal.PythonSyntax 100 | import Text.Parsec 101 | import Text.Parsec.Error 102 | ( errorMessages, 103 | messageString, 104 | showErrorMessages, 105 | ) 106 | import Text.Parsec.Pos (initialPos) 107 | import Text.ParserCombinators.Parsec.Error (Message (..)) 108 | import Unsafe.Coerce (unsafeCoerce) 109 | 110 | -- | Configuration for the quasiquoter 111 | data Config = Config 112 | { -- | What are the delimiters for interpolation. 'Nothing' means no 113 | -- interpolation / formatting. 114 | delimiters :: Maybe (Char, Char), 115 | -- | Post processing. The input 'Exp' represents a 'String'. Common use 116 | -- case includes using 'wrapFromString' to add 'fromString' in the context 117 | -- of 'OverloadedStrings'. 118 | postProcess :: Q Exp -> Q Exp 119 | } 120 | 121 | -- | Build a quasiquoter for expression 122 | expQQ :: String -> (String -> Q Exp) -> QuasiQuoter 123 | expQQ fName qExp = 124 | QuasiQuoter 125 | { quoteExp = qExp, 126 | quotePat = err "pattern", 127 | quoteType = err "type", 128 | quoteDec = err "declaration" 129 | } 130 | where 131 | err :: String -> t 132 | err name = error (fName ++ ": This QuasiQuoter can not be used as a " ++ name ++ "!") 133 | 134 | -- | If 'OverloadedStrings' is enabled, from the input expression with 135 | -- 'fromString'. 136 | wrapFromString :: ExpQ -> Q Exp 137 | wrapFromString e = do 138 | exts <- extsEnabled 139 | if OverloadedStrings `elem` exts 140 | then [|fromString $(e)|] 141 | else e 142 | 143 | -- | Parse a string and return a formatter for it 144 | toExp :: Config -> String -> Q Exp 145 | toExp Config {delimiters = expressionDelimiters, postProcess} s = do 146 | loc <- location 147 | exts <- extsEnabled 148 | let context = ParsingContext expressionDelimiters exts 149 | 150 | -- Setup the parser so it matchs the real original position in the source 151 | -- code. 152 | let filename = loc_filename loc 153 | let initPos = setSourceColumn (setSourceLine (initialPos filename) (fst $ loc_start loc)) (snd $ loc_start loc) 154 | case runReader (runParserT (setPosition initPos >> parseGenericFormatString) () filename s) context of 155 | Left err -> do 156 | reportParserErrorAt err 157 | -- returns a dummy exp, so TH continues its life. This TH code won't be 158 | -- executed anyway, there is an error 159 | [|()|] 160 | Right items -> do 161 | checkResult <- checkVariables items 162 | case checkResult of 163 | Nothing -> postProcess (goFormat items) 164 | Just (srcSpan, msg) -> do 165 | reportErrorAt srcSpan msg 166 | [|()|] 167 | 168 | findFreeVariablesInFormatMode :: Maybe FormatMode -> [(SrcSpan, RdrName)] 169 | findFreeVariablesInFormatMode Nothing = [] 170 | findFreeVariablesInFormatMode (Just (FormatMode padding tf _)) = 171 | findFreeVariables tf <> case padding of 172 | PaddingDefault -> [] 173 | Padding eoi _ -> findFreeVariables eoi 174 | 175 | checkOneItem :: Item -> Q (Maybe (SrcSpan, String)) 176 | checkOneItem (Raw _) = pure Nothing 177 | checkOneItem (Replacement (hsExpr, _) formatMode) = do 178 | let allNames = findFreeVariables hsExpr <> findFreeVariablesInFormatMode formatMode 179 | res <- mapM doesExists allNames 180 | let resFinal = catMaybes res 181 | 182 | case resFinal of 183 | [] -> pure Nothing 184 | ((err, span) : _) -> pure $ Just (span, err) 185 | 186 | {- ORMOLU_DISABLE -} 187 | findFreeVariables :: Data a => a -> [(SrcSpan, RdrName)] 188 | findFreeVariables item = allNames 189 | where 190 | -- Find all free Variables in an HsExpr 191 | f :: forall a. (Data a, Typeable a) => a -> [Located RdrName] 192 | f e = case cast @_ @(HsExpr GhcPs) e of 193 | #if MIN_VERSION_ghc(9,2,0) 194 | Just (HsVar _ l@(L a _)) -> [L (locA a) (unLoc l)] 195 | #else 196 | Just (HsVar _ l) -> [l] 197 | #endif 198 | 199 | #if MIN_VERSION_ghc(9,12,0) 200 | Just (HsLam _ _ (MG _ (unLoc -> (map unLoc -> [Expr.Match _ _ (unLoc -> map unLoc -> ps) (GRHSs _ [unLoc -> GRHS _ _ (unLoc -> e)] _)])))) -> filter keepVar subVars 201 | where 202 | keepVar (L _ n) = n `notElem` subPats 203 | subVars = concat $ gmapQ f [e] 204 | subPats = concat $ gmapQ findPats ps 205 | #elif MIN_VERSION_ghc(9,10,0) 206 | Just (HsLam _ _ (MG _ (unLoc -> (map unLoc -> [Expr.Match _ _ (map unLoc -> ps) (GRHSs _ [unLoc -> GRHS _ _ (unLoc -> e)] _)])))) -> filter keepVar subVars 207 | where 208 | keepVar (L _ n) = n `notElem` subPats 209 | subVars = concat $ gmapQ f [e] 210 | subPats = concat $ gmapQ findPats ps 211 | #elif MIN_VERSION_ghc(9,6,0) 212 | Just (HsLam _ (MG _ (unLoc -> (map unLoc -> [Expr.Match _ _ (map unLoc -> ps) (GRHSs _ [unLoc -> GRHS _ _ (unLoc -> e)] _)])))) -> filter keepVar subVars 213 | where 214 | keepVar (L _ n) = n `notElem` subPats 215 | subVars = concat $ gmapQ f [e] 216 | subPats = concat $ gmapQ findPats ps 217 | #else 218 | Just (HsLam _ (MG _ (unLoc -> (map unLoc -> [Expr.Match _ _ (map unLoc -> ps) (GRHSs _ [unLoc -> GRHS _ _ (unLoc -> e)] _)])) _)) -> filter keepVar subVars 219 | where 220 | keepVar (L _ n) = n `notElem` subPats 221 | subVars = concat $ gmapQ f [e] 222 | subPats = concat $ gmapQ findPats ps 223 | #endif 224 | _ -> concat $ gmapQ f e 225 | 226 | -- Find all Variables bindings (i.e. patterns) in an HsExpr 227 | findPats :: forall a. (Data a, Typeable a) => a -> [RdrName] 228 | findPats p = case cast @_ @(Pat.Pat GhcPs) p of 229 | Just (VarPat _ (unLoc -> name)) -> [name] 230 | _ -> concat $ gmapQ findPats p 231 | -- Be careful, we wrap hsExpr in a list, so the toplevel hsExpr will be 232 | -- seen by gmapQ. Otherwise it will miss variables if they are the top 233 | -- level expression: gmapQ only checks sub constructors. 234 | allVars = concat $ gmapQ f [item] 235 | allNames = map (\(L l e) -> (l, e)) allVars 236 | {- ORMOLU_ENABLE -} 237 | 238 | lookupName :: RdrName -> Q Bool 239 | lookupName n = case n of 240 | (Unqual o) -> isJust <$> lookupValueName (occNameString o) 241 | (Qual m o) -> isJust <$> lookupValueName (moduleNameString m <> "." <> occNameString o) 242 | -- No idea how to lookup for theses names, so consider that they exists 243 | (Orig _m _o) -> pure True 244 | (Exact _) -> pure True 245 | 246 | doesExists :: (b, RdrName) -> Q (Maybe (String, b)) 247 | doesExists (loc, name) = do 248 | res <- lookupName name 249 | if res 250 | then pure Nothing 251 | else pure (Just ("Variable not in scope: " <> show (toName name), loc)) 252 | 253 | -- | Check that all variables used in 'Item' exists, otherwise, fail. 254 | checkVariables :: [Item] -> Q (Maybe (SrcSpan, String)) 255 | checkVariables [] = pure Nothing 256 | checkVariables (x : xs) = do 257 | r <- checkOneItem x 258 | case r of 259 | Nothing -> checkVariables xs 260 | Just err -> pure $ Just err 261 | 262 | -- Stolen from: https://www.tweag.io/blog/2021-01-07-haskell-dark-arts-part-i/ 263 | -- This allows to hack inside the the GHC api and use function not exported by template haskell. 264 | -- This may not be always safe, see https://github.com/guibou/PyF/issues/115, 265 | -- hence keep that for "failing path" (i.e. error reporting), but not on 266 | -- codepath which are executed otherwise. 267 | unsafeRunTcM :: TcM a -> Q a 268 | unsafeRunTcM m = Q (unsafeCoerce m) 269 | 270 | -- | This function is similar to TH reportError, however it also provide 271 | -- correct SrcSpan, so error are localised at the correct position in the TH 272 | -- splice instead of being at the beginning. 273 | reportErrorAt :: SrcSpan -> String -> Q () 274 | reportErrorAt loc msg = unsafeRunTcM $ addErrAt loc msg' 275 | where 276 | #if MIN_VERSION_ghc(9,7,0) 277 | msg' = TcRnUnknownMessage (UnknownDiagnostic (const NoDiagnosticOpts) (mkPlainError noHints (text msg))) 278 | #elif MIN_VERSION_ghc(9,6,0) 279 | msg' = TcRnUnknownMessage (UnknownDiagnostic $ mkPlainError noHints $ 280 | text msg) 281 | #elif MIN_VERSION_ghc(9,3,0) 282 | msg' = TcRnUnknownMessage (GhcPsMessage $ PsUnknownMessage $ mkPlainError noHints $ 283 | text msg) 284 | #else 285 | msg' = fromString msg 286 | #endif 287 | 288 | reportParserErrorAt :: ParseError -> Q () 289 | reportParserErrorAt err = reportErrorAt span msg 290 | where 291 | msg = intercalate "\n" $ formatErrorMessages err 292 | 293 | span :: SrcSpan 294 | span = mkSrcSpan loc loc' 295 | 296 | loc = srcLocFromParserError (errorPos err) 297 | loc' = srcLocFromParserError (incSourceColumn (errorPos err) 1) 298 | 299 | srcLocFromParserError :: SourcePos -> SrcLoc 300 | srcLocFromParserError sourceLoc = srcLoc 301 | where 302 | line = sourceLine sourceLoc 303 | column = sourceColumn sourceLoc 304 | name = sourceName sourceLoc 305 | 306 | srcLoc = mkSrcLoc (fromString name) line column 307 | 308 | formatErrorMessages :: ParseError -> [String] 309 | formatErrorMessages err 310 | -- If there is an explicit error message from parsec, use only that 311 | | not $ null messages = map messageString messages 312 | -- Otherwise, uses parsec formatting 313 | | otherwise = [showErrorMessages "or" "unknown parse error" "expecting" "unexpected" "end of input" (errorMessages err)] 314 | where 315 | (_sysUnExpect, msgs1) = span (SysUnExpect "" ==) (errorMessages err) 316 | (_unExpect, msgs2) = span (UnExpect "" ==) msgs1 317 | (_expect, messages) = span (Expect "" ==) msgs2 318 | 319 | {- 320 | Note: Empty String Lifting 321 | 322 | Empty string are lifted as [] instead of "", so I'm using LitE (String L) instead 323 | -} 324 | 325 | goFormat :: [Item] -> Q Exp 326 | -- We special case on empty list in order to generate an empty string 327 | goFormat [] = pure $ LitE (StringL "") -- see [Empty String Lifting] 328 | goFormat items = foldl1 sappendQ <$> mapM toFormat items 329 | 330 | -- | call `<>` between two 'Exp' 331 | sappendQ :: Exp -> Exp -> Exp 332 | sappendQ s0 s1 = InfixE (Just s0) (VarE '(<>)) (Just s1) 333 | 334 | -- Real formatting is here 335 | 336 | toFormat :: Item -> Q Exp 337 | toFormat (Raw x) = pure $ LitE (StringL x) -- see [Empty String Lifting] 338 | toFormat (Replacement (_, expr) y) = do 339 | formatExpr <- padAndFormat (fromMaybe DefaultFormatMode y) 340 | pure (formatExpr `AppE` expr) 341 | 342 | -- | Default precision for floating point 343 | defaultFloatPrecision :: Maybe Int 344 | defaultFloatPrecision = Just 6 345 | 346 | -- | Precision to maybe 347 | splicePrecision :: Maybe Int -> Precision -> Q Exp 348 | splicePrecision def PrecisionDefault = [|def :: Maybe Int|] 349 | splicePrecision _ (Precision p) = [|Just $(exprToInt p)|] 350 | 351 | toGrp :: Maybe Char -> Int -> Q Exp 352 | toGrp mb a = [|grp|] 353 | where 354 | grp = (a,) <$> mb 355 | 356 | withAlt :: AlternateForm -> Formatters.Format t t' t'' -> Q Exp 357 | withAlt NormalForm e = [|e|] 358 | withAlt AlternateForm e = [|Formatters.Alternate e|] 359 | 360 | padAndFormat :: FormatMode -> Q Exp 361 | padAndFormat (FormatMode padding tf grouping) = case tf of 362 | -- Integrals 363 | BinaryF alt s -> [|formatAnyIntegral $(withAlt alt Formatters.Binary) s $(newPaddingQ padding) $(toGrp grouping 4)|] 364 | CharacterF -> [|formatAnyIntegral Formatters.Character Formatters.Minus $(newPaddingQ padding) Nothing|] 365 | DecimalF s -> [|formatAnyIntegral Formatters.Decimal s $(newPaddingQ padding) $(toGrp grouping 3)|] 366 | HexF alt s -> [|formatAnyIntegral $(withAlt alt Formatters.Hexa) s $(newPaddingQ padding) $(toGrp grouping 4)|] 367 | OctalF alt s -> [|formatAnyIntegral $(withAlt alt Formatters.Octal) s $(newPaddingQ padding) $(toGrp grouping 4)|] 368 | HexCapsF alt s -> [|formatAnyIntegral (Formatters.Upper $(withAlt alt Formatters.Hexa)) s $(newPaddingQ padding) $(toGrp grouping 4)|] 369 | -- Floating 370 | ExponentialF prec alt s -> [|formatAnyFractional $(withAlt alt Formatters.Exponent) s $(newPaddingQ padding) $(toGrp grouping 3) $(splicePrecision defaultFloatPrecision prec)|] 371 | ExponentialCapsF prec alt s -> [|formatAnyFractional (Formatters.Upper $(withAlt alt Formatters.Exponent)) s $(newPaddingQ padding) $(toGrp grouping 3) $(splicePrecision defaultFloatPrecision prec)|] 372 | GeneralF prec alt s -> [|formatAnyFractional $(withAlt alt Formatters.Generic) s $(newPaddingQ padding) $(toGrp grouping 3) $(splicePrecision defaultFloatPrecision prec)|] 373 | GeneralCapsF prec alt s -> [|formatAnyFractional (Formatters.Upper $(withAlt alt Formatters.Generic)) s $(newPaddingQ padding) $(toGrp grouping 3) $(splicePrecision defaultFloatPrecision prec)|] 374 | FixedF prec alt s -> [|formatAnyFractional $(withAlt alt Formatters.Fixed) s $(newPaddingQ padding) $(toGrp grouping 3) $(splicePrecision defaultFloatPrecision prec)|] 375 | FixedCapsF prec alt s -> [|formatAnyFractional (Formatters.Upper $(withAlt alt Formatters.Fixed)) s $(newPaddingQ padding) $(toGrp grouping 3) $(splicePrecision defaultFloatPrecision prec)|] 376 | PercentF prec alt s -> [|formatAnyFractional $(withAlt alt Formatters.Percent) s $(newPaddingQ padding) $(toGrp grouping 3) $(splicePrecision defaultFloatPrecision prec)|] 377 | -- Default / String 378 | DefaultF prec s -> [|formatAny s $(paddingToPaddingK padding) $(toGrp grouping 3) $(splicePrecision Nothing prec)|] 379 | StringF prec -> [|Formatters.formatString (newPaddingKForString $(paddingToPaddingK padding)) $(splicePrecision Nothing prec) . pyfToString|] 380 | 381 | newPaddingQ :: Padding -> Q Exp 382 | newPaddingQ padding = case padding of 383 | PaddingDefault -> [|Nothing :: Maybe (Int, AnyAlign, Char)|] 384 | (Padding i al) -> case al of 385 | Nothing -> [|Just ($(exprToInt i), AnyAlign Formatters.AlignRight, ' ')|] -- Right align and space is default for any object, except string 386 | Just (Nothing, a) -> [|Just ($(exprToInt i), a, ' ')|] 387 | Just (Just c, a) -> [|Just ($(exprToInt i), a, c)|] 388 | 389 | exprToInt :: ExprOrValue Int -> Q Exp 390 | -- Note: this is a literal provided integral. We use explicit case to ::Int so it won't warn about defaulting 391 | exprToInt (Value i) = [|$(pure $ LitE (IntegerL (fromIntegral i))) :: Int|] 392 | exprToInt (HaskellExpr (_, e)) = [|$(pure e)|] 393 | 394 | data PaddingK k i where 395 | PaddingDefaultK :: PaddingK 'Formatters.AlignAll Int 396 | PaddingK :: i -> Maybe (Maybe Char, Formatters.AlignMode k) -> PaddingK k i 397 | 398 | paddingToPaddingK :: Padding -> Q Exp 399 | paddingToPaddingK p = case p of 400 | PaddingDefault -> [|PaddingDefaultK|] 401 | Padding i Nothing -> [|PaddingK ($(exprToInt i)) Nothing :: PaddingK 'Formatters.AlignAll Int|] 402 | Padding i (Just (c, AnyAlign a)) -> [|PaddingK $(exprToInt i) (Just (c, a))|] 403 | 404 | paddingKToPadding :: PaddingK k i -> Maybe (i, AnyAlign, Char) 405 | paddingKToPadding p = case p of 406 | PaddingDefaultK -> Nothing 407 | (PaddingK i al) -> case al of 408 | Nothing -> Just (i, AnyAlign Formatters.AlignRight, ' ') -- Right align and space is default for any object, except string 409 | Just (Nothing, a) -> Just (i, AnyAlign a, ' ') 410 | Just (Just c, a) -> Just (i, AnyAlign a, c) 411 | 412 | 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 413 | formatAnyIntegral f s Nothing grouping i = pyfFormatIntegral @i @paddingWidth f s Nothing grouping i 414 | formatAnyIntegral f s (Just (padSize, AnyAlign alignMode, c)) grouping i = pyfFormatIntegral f s (Just (padSize, alignMode, c)) grouping i 415 | 416 | 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 417 | formatAnyFractional f s Nothing grouping p i = pyfFormatFractional @i @paddingWidth @precision f s Nothing grouping p i 418 | formatAnyFractional f s (Just (padSize, AnyAlign alignMode, c)) grouping p i = pyfFormatFractional f s (Just (padSize, alignMode, c)) grouping p i 419 | 420 | class FormatAny i k where 421 | formatAny :: forall paddingWidth precision. (Integral paddingWidth, Integral precision) => Formatters.SignMode -> PaddingK k paddingWidth -> Maybe (Int, Char) -> Maybe precision -> i -> String 422 | 423 | instance (FormatAny2 (PyFClassify t) t k) => FormatAny t k where 424 | formatAny = formatAny2 (Proxy :: Proxy (PyFClassify t)) 425 | 426 | class FormatAny2 (c :: PyFCategory) (i :: Type) (k :: Formatters.AlignForString) where 427 | formatAny2 :: forall paddingWidth precision. (Integral paddingWidth, Integral precision) => Proxy c -> Formatters.SignMode -> PaddingK k paddingWidth -> Maybe (Int, Char) -> Maybe precision -> i -> String 428 | 429 | instance (Show t, Integral t) => FormatAny2 'PyFIntegral t k where 430 | formatAny2 _ s a p _precision = formatAnyIntegral Formatters.Decimal s (paddingKToPadding a) p 431 | 432 | instance (PyfFormatFractional t) => FormatAny2 'PyFFractional t k where 433 | formatAny2 _ s a = formatAnyFractional Formatters.Generic s (paddingKToPadding a) 434 | 435 | newPaddingKForString :: (Integral i) => PaddingK 'Formatters.AlignAll i -> Maybe (Int, Formatters.AlignMode 'Formatters.AlignAll, Char) 436 | newPaddingKForString padding = case padding of 437 | PaddingDefaultK -> Nothing 438 | PaddingK i Nothing -> Just (fromIntegral i, Formatters.AlignLeft, ' ') -- default align left and fill with space for string 439 | PaddingK i (Just (mc, a)) -> Just (fromIntegral i, a, fromMaybe ' ' mc) 440 | 441 | -- TODO: _s(ign) and _grouping should trigger errors 442 | instance (PyFToString t) => FormatAny2 'PyFString t 'Formatters.AlignAll where 443 | formatAny2 _ _s a _grouping precision t = Formatters.formatString (newPaddingKForString a) precision (pyfToString t) 444 | 445 | instance (TypeError ('Text "String type is incompatible with inside padding (=).")) => FormatAny2 'PyFString t 'Formatters.AlignNumber where 446 | formatAny2 = error "Unreachable" 447 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | resolver: lts-15.4 2 | packages: 3 | - . 4 | nix: 5 | # Not enabled by default. It will be on nixos, but not on CI 6 | enable: false 7 | packages: [python3] 8 | -------------------------------------------------------------------------------- /test/SpecCustomDelimiters.hs: -------------------------------------------------------------------------------- 1 | module SpecCustomDelimiters where 2 | 3 | import Language.Haskell.TH.Quote 4 | import PyF 5 | import PyF.Internal.QQ 6 | 7 | myCustomFormatter :: QuasiQuoter 8 | myCustomFormatter = 9 | mkFormatter 10 | "fmt" 11 | ( fmtConfig 12 | { delimiters = Just ('@', '!') 13 | } 14 | ) 15 | -------------------------------------------------------------------------------- /test/SpecFail.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE CPP #-} 2 | {-# LANGUAGE DataKinds #-} 3 | {-# LANGUAGE ExtendedDefaultRules #-} 4 | {-# LANGUAGE QuasiQuotes #-} 5 | {-# LANGUAGE ScopedTypeVariables #-} 6 | 7 | import Control.DeepSeq 8 | import Control.Exception 9 | import Data.Bits (Bits (..)) 10 | import Data.Char (ord) 11 | import qualified Data.Text as Text 12 | import PyF 13 | import System.Exit 14 | import System.FilePath 15 | import System.IO.Temp 16 | import System.Process (readProcessWithExitCode) 17 | import Test.HUnit.Lang 18 | import Test.Hspec 19 | 20 | -- * Check compilation with external GHC (this is usefull to test compilation failure) 21 | 22 | data CompilationStatus 23 | = -- | Fails during compilation (with error) 24 | CompileError String 25 | | RuntimeError String 26 | | Ok String 27 | deriving (Show, Eq) 28 | 29 | makeTemplate :: String -> String 30 | makeTemplate s = 31 | [fmt|{{-# LANGUAGE QuasiQuotes, ExtendedDefaultRules, TypeApplications #-}} 32 | import PyF 33 | truncate' = truncate @Float @Int 34 | hello = "hello" 35 | number = 3.14 :: Float 36 | main :: IO () 37 | main = putStrLn [fmt|{s}|] 38 | <> "|]\n" 39 | 40 | -- | Compile a formatting string 41 | -- 42 | -- >>> checkCompile fileContent 43 | -- CompileError "Bla bla bla, Floating cannot be formatted as hexa (`x`) 44 | checkCompile :: (HasCallStack) => String -> IO CompilationStatus 45 | checkCompile content = withSystemTempDirectory "PyF" $ \dirPath -> do 46 | let path = dirPath "PyFTest.hs" 47 | writeFile path content 48 | (ecode, _stdout, stderr) <- 49 | readProcessWithExitCode 50 | "ghc" 51 | [ path, 52 | -- Include all PyF files 53 | "-isrc", 54 | -- Disable the usage of the annoying .ghc environment file 55 | "-package-env", 56 | "-", 57 | -- Move the ".o" in the temporary dir 58 | "-odir", 59 | dirPath, 60 | "-hidir", 61 | dirPath, 62 | -- Tests use a filename in a temporary directory which may have a long filename which triggers 63 | -- line wrapping, reducing the reproducibility of error message 64 | -- By setting the column size to a high value, we ensure reproducible error messages 65 | "-dppr-cols=10000000000000", 66 | -- Clean package environment 67 | "-hide-all-packages", 68 | "-package base", 69 | "-package bytestring", 70 | "-package parsec", 71 | "-package text", 72 | "-package template-haskell", 73 | "-package ghc-boot", 74 | "-package mtl", 75 | "-package ghc", 76 | "-package time", 77 | "-package containers" 78 | ] 79 | "" 80 | case ecode of 81 | ExitFailure _ -> pure (CompileError (sanitize path stderr)) 82 | ExitSuccess -> do 83 | (ecode', stdout', stderr') <- readProcessWithExitCode (take (length path - 3) path) [] "" 84 | case ecode' of 85 | ExitFailure _ -> pure (RuntimeError stderr') 86 | ExitSuccess -> pure (Ok stdout') 87 | 88 | -- sanitize a compilation result by removing variables strings such as 89 | -- temporary files name 90 | sanitize :: FilePath -> String -> String 91 | sanitize path = 92 | Text.unpack 93 | -- Strip the filename 94 | . Text.replace (Text.pack path) (Text.pack "INITIALPATH") 95 | -- GHC 9.0 replaces [Char] by String everywhere 96 | . Text.replace (Text.pack "[Char]") (Text.pack "String") 97 | . Text.pack 98 | 99 | {- ORMOLU_DISABLE -} 100 | golden :: HasCallStack => String -> String -> IO () 101 | golden name output = do 102 | let 103 | #if __GLASGOW_HASKELL__ >= 906 104 | goldenFile = "test/golden96" (name <> ".golden") 105 | #else 106 | goldenFile = "test/golden" (name <> ".golden") 107 | #endif 108 | actualFile = "test/golden" (name <> ".actual") 109 | -- It can fail if the golden file does not exists 110 | goldenContentE :: Either SomeException String <- try $ readFile goldenFile 111 | let -- if no golden file, the golden file is the content 112 | goldenContent = case goldenContentE of 113 | Right e -> e 114 | Left _ -> output 115 | -- Flush lazy IO 116 | _ <- evaluate (force goldenContent) 117 | -- Apparently the whitespaces in GHC 9.10 changed 118 | -- By stripping, we just keep the test working as expected, but we are 119 | -- compatible with different version of GHC. 120 | -- 121 | -- TODO: use GHC API to build directly the example and gather errors in a 122 | -- more reproducible way. 123 | if Text.strip (Text.pack output) /= Text.strip (Text.pack goldenContent) 124 | then do 125 | writeFile actualFile output 126 | (_, diffOutput, _) <- readProcessWithExitCode "diff" ["-b", goldenFile, actualFile] "" 127 | putStrLn diffOutput 128 | -- Update golden file 129 | writeFile goldenFile (Text.unpack $ Text.strip (Text.pack output)) 130 | assertFailure diffOutput 131 | else writeFile goldenFile (Text.unpack $ Text.strip (Text.pack output)) 132 | {- ORMOLU_ENABLE -} 133 | 134 | failCompile :: (HasCallStack) => String -> Spec 135 | failCompile s = failCompileContent s s (makeTemplate s) 136 | 137 | failCompileContent :: (HasCallStack) => String -> String -> String -> Spec 138 | failCompileContent h caption fileContent = 139 | before (checkCompile fileContent) $ do 140 | let goldenName = concatMap cleanSpecialChars h 141 | -- Add an unique identifier, so golden files won't conflict on case 142 | -- insensitive systems 143 | -- See: bug #97. 144 | goldenPath = goldenName ++ "." ++ show (stableHash goldenName) 145 | it (show caption) $ \res -> case res of 146 | CompileError output -> golden goldenPath output 147 | _ -> assertFailure (show $ ".golden/" <> goldenPath <> "\n" <> show res) 148 | 149 | -- | A stable hash from string, based on 150 | -- https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function#FNV-1_hash 151 | stableHash :: String -> Word 152 | stableHash [] = 14695981039346656037 153 | stableHash (x : xs) = fromIntegral (ord x) * stableHash xs `xor` 1099511628211 154 | 155 | -- Remove chars which are not accepted in a path name 156 | -- The encoding is rather approximative, I'm trying to avoid too long names. 157 | cleanSpecialChars :: Char -> [Char] 158 | cleanSpecialChars '/' = "SL" 159 | cleanSpecialChars '\\' = "BS" 160 | cleanSpecialChars ':' = "CL" 161 | cleanSpecialChars '\n' = "NL" 162 | cleanSpecialChars e = pure e 163 | 164 | main :: IO () 165 | main = hspec $ parallel spec 166 | 167 | spec :: Spec 168 | spec = 169 | describe "error reporting" $ do 170 | describe "string" $ do 171 | describe "integral / fractional qualifiers" $ do 172 | failCompile "{hello:f}" 173 | failCompile "{hello:d}" 174 | failCompile "{hello:e}" 175 | failCompile "{hello:b}" 176 | failCompile "{hello:E}" 177 | failCompile "{hello:G}" 178 | failCompile "{hello:g}" 179 | failCompile "{hello:%}" 180 | failCompile "{hello:x}" 181 | failCompile "{hello:X}" 182 | failCompile "{hello:o}" 183 | describe "padding center" $ do 184 | failCompile "{hello:=100s}" 185 | failCompile "{hello:=100}" 186 | describe "grouping" $ do 187 | failCompile "{hello:_s}" 188 | failCompile "{hello:,s}" 189 | describe "sign" $ do 190 | failCompile "{hello:+s}" 191 | failCompile "{hello: s}" 192 | failCompile "{hello:-s}" 193 | describe "number with precision" $ do 194 | failCompile "{truncate number:.3d}" 195 | failCompile "{truncate number:.3o}" 196 | failCompile "{truncate number:.3b}" 197 | failCompile "{truncate number:.3x}" 198 | describe "floats" $ do 199 | failCompile "{number:o}" 200 | failCompile "{number:b}" 201 | failCompile "{number:x}" 202 | failCompile "{number:X}" 203 | failCompile "{number:d}" 204 | -- XXX: this are not failing for now, it should be fixed 205 | xdescribe "not specified" $ do 206 | failCompile "{truncate number:.3}" 207 | failCompile "{hello:#}" 208 | failCompile "{hello:+}" 209 | failCompile "{hello: }" 210 | failCompile "{hello:-}" 211 | failCompile "{hello:_}" 212 | failCompile "{hello:,}" 213 | describe "multiples lines" $ 214 | failCompile "hello\n\n\n{pi:l}" 215 | describe "on haskell expression parsing" $ do 216 | describe "single line" $ 217 | failCompile "{1 + - / lalalal}" 218 | describe "empty expression" $ 219 | failCompile "{}" 220 | describe "sub expression" $ do 221 | describe "simple failure" $ 222 | failCompile "{pi:.{/}}" 223 | describe "empty failure" $ 224 | failCompile "{pi:.{}}" 225 | describe "multiples lines" $ 226 | failCompile "hello\n {\nlet a = 5\n b = 10\nin 1 + - / lalalal}" 227 | describe "non-doubled delimiters" $ do 228 | failCompile "hello } world" 229 | failCompile "hello { world" 230 | describe "lexical errors" $ do 231 | describe "single line" $ 232 | failCompile "foo\\Pbar" 233 | describe "multiple line" $ 234 | failCompile "foo\nbli\\Pbar" 235 | describe "Wrong type" $ do 236 | failCompile "{True}" 237 | failCompile "{True:f}" 238 | failCompile "{True:d}" 239 | describe "Missing variables" $ do 240 | failCompile "Hello {name}" 241 | failCompile "Hello {length name}" 242 | failCompile "Hello {pi:.{precision}}" 243 | failCompile "Hello {pi:.{truncate number + precision}}" 244 | failCompile "Hello {pi:.{precision}}" 245 | failCompile "Hello {pi:{width}}" 246 | -------------------------------------------------------------------------------- /test/SpecOverloaded.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE QuasiQuotes #-} 3 | {-# LANGUAGE TemplateHaskell #-} 4 | 5 | import Data.ByteString 6 | import Data.Text 7 | import PyF 8 | import Test.Hspec 9 | 10 | main :: IO () 11 | main = hspec spec 12 | 13 | spec :: Spec 14 | spec = do 15 | let ten = 10 :: Int 16 | describe "Test formatting with different types" $ do 17 | it "String" $ 18 | [fmt|hello {ten:d}|] `shouldBe` ("hello 10" :: String) 19 | it "Text" $ 20 | [fmt|hello {ten:d}|] `shouldBe` ("hello 10" :: Text) 21 | it "ByteString" $ 22 | [fmt|hello {ten:d}|] `shouldBe` ("hello 10" :: ByteString) 23 | -------------------------------------------------------------------------------- /test/golden/Hello {length name}.16675806454852491955.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:36: error: 3 | • Variable not in scope: name 4 | • In the quasi-quotation: [fmt|Hello {length name}|] 5 | | 6 | 7 | main = putStrLn [fmt|Hello {length name}|] 7 | | ^^^^ 8 | -------------------------------------------------------------------------------- /test/golden/Hello {name}.16618004304593959603.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:29: error: 3 | • Variable not in scope: name 4 | • In the quasi-quotation: [fmt|Hello {name}|] 5 | | 6 | 7 | main = putStrLn [fmt|Hello {name}|] 7 | | ^^^^ 8 | -------------------------------------------------------------------------------- /test/golden/Hello {piCL.{precision}}.9628757040831863475.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:34: error: 3 | • Variable not in scope: precision 4 | • In the quasi-quotation: [fmt|Hello {pi:.{precision}}|] 5 | | 6 | 7 | main = putStrLn [fmt|Hello {pi:.{precision}}|] 7 | | ^^^^^^^^^ 8 | -------------------------------------------------------------------------------- /test/golden/Hello {piCL.{truncate number + precision}}.18094049741103110835.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:52: error: 3 | • Variable not in scope: precision 4 | • In the quasi-quotation: [fmt|Hello {pi:.{truncate number + precision}}|] 5 | | 6 | 7 | main = putStrLn [fmt|Hello {pi:.{truncate number + precision}}|] 7 | | ^^^^^^^^^ 8 | -------------------------------------------------------------------------------- /test/golden/Hello {piCL{width}}.15463239215289408179.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:33: error: 3 | • Variable not in scope: width 4 | • In the quasi-quotation: [fmt|Hello {pi:{width}}|] 5 | | 6 | 7 | main = putStrLn [fmt|Hello {pi:{width}}|] 7 | | ^^^^^ 8 | -------------------------------------------------------------------------------- /test/golden/fooBSPbar.17645057532673886893.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:25: error: 3 | • Lexical error in literal section 4 | • In the quasi-quotation: [fmt|foo\Pbar|] 5 | | 6 | 7 | main = putStrLn [fmt|foo\Pbar|] 7 | | ^ 8 | -------------------------------------------------------------------------------- /test/golden/fooNLbliBSPbar.16759496276764189145.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:8:4: error: 3 | • Lexical error in literal section 4 | • In the quasi-quotation: [fmt|foo 5 | bli\Pbar|] 6 | | 7 | 8 | bli\Pbar|] 8 | | ^ 9 | -------------------------------------------------------------------------------- /test/golden/hello { world.10778336899993839283.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:35: error: 3 | • 4 | unexpected end of input 5 | expecting "::", ":" or "}" 6 | • In the quasi-quotation: [fmt|hello { world|] 7 | | 8 | 7 | main = putStrLn [fmt|hello { world|] 9 | | ^ 10 | -------------------------------------------------------------------------------- /test/golden/hello } world.5295037443422799539.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:28: error: 3 | • 4 | unexpected '}' 5 | expecting "{{", "}}", "{" or end of input 6 | • In the quasi-quotation: [fmt|hello } world|] 7 | | 8 | 7 | main = putStrLn [fmt|hello } world|] 9 | | ^ 10 | -------------------------------------------------------------------------------- /test/golden/helloNL {NLlet a = 5NL b = 10NLin 1 + - SL lalalal}.3018767237106994099.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:11:10: error: 3 | • parse error on input `/' in haskell expression 4 | • In the quasi-quotation: [fmt|hello 5 | { 6 | let a = 5 7 | b = 10 8 | in 1 + - / lalalal}|] 9 | | 10 | 11 | in 1 + - / lalalal}|] 11 | | ^ 12 | -------------------------------------------------------------------------------- /test/golden/helloNLNLNL{piCLl}.13123157148160021427.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:10:6: error: 3 | • 4 | unexpected "}" 5 | expecting "<", ">", "^" or "=" 6 | • In the quasi-quotation: [fmt|hello 7 | 8 | 9 | {pi:l}|] 10 | | 11 | 10 | {pi:l}|] 12 | | ^ 13 | -------------------------------------------------------------------------------- /test/golden/{1 + - SL lalalal}.14923086665437293731.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:29: error: 3 | • parse error on input `/' in haskell expression 4 | • In the quasi-quotation: [fmt|{1 + - / lalalal}|] 5 | | 6 | 7 | main = putStrLn [fmt|{1 + - / lalalal}|] 7 | | ^ 8 | -------------------------------------------------------------------------------- /test/golden/{TrueCLd}.12627313193367841398.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Integral Bool) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 4 | • 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)’ 5 | In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) True) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{True:d}|] 9 | | ^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{TrueCLf}.18281408089045870326.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Real Bool) arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’ 4 | • 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)’ 5 | 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) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{True:f}|] 9 | | ^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{True}.16254223077612353942.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (PyF.Internal.QQ.FormatAny2 (PyFClassify Bool) Bool 'PyF.Formatters.AlignAll) arising from a use of ‘PyF.Internal.QQ.formatAny’ 4 | • In the first argument of ‘putStrLn’, namely ‘(((((PyF.Internal.QQ.formatAny PyF.Formatters.Minus) PyF.Internal.QQ.PaddingDefaultK) Nothing) (Nothing :: Maybe Int)) True)’ 5 | In the expression: putStrLn (((((PyF.Internal.QQ.formatAny PyF.Formatters.Minus) PyF.Internal.QQ.PaddingDefaultK) Nothing) (Nothing :: Maybe Int)) True) 6 | In an equation for ‘main’: main = putStrLn (((((PyF.Internal.QQ.formatAny PyF.Formatters.Minus) PyF.Internal.QQ.PaddingDefaultK) Nothing) (Nothing :: Maybe Int)) True) 7 | | 8 | 7 | main = putStrLn [fmt|{True}|] 9 | | ^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{helloCL s}.13047921915648718386.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:30: error: 3 | • 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. 4 | • In the quasi-quotation: [fmt|{hello: s}|] 5 | | 6 | 7 | main = putStrLn [fmt|{hello: s}|] 7 | | ^ 8 | -------------------------------------------------------------------------------- /test/golden/{helloCL%}.1257653362598537778.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Real String) arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’ 4 | • 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)’ 5 | 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) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{hello:%}|] 9 | | ^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{helloCL+s}.1657517030647448626.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:30: error: 3 | • 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. 4 | • In the quasi-quotation: [fmt|{hello:+s}|] 5 | | 6 | 7 | main = putStrLn [fmt|{hello:+s}|] 7 | | ^ 8 | -------------------------------------------------------------------------------- /test/golden/{helloCL,s}.14139635988852178482.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:30: error: 3 | • String type is incompatible with grouping (_ or ,). 4 | • In the quasi-quotation: [fmt|{hello:,s}|] 5 | | 6 | 7 | main = putStrLn [fmt|{hello:,s}|] 7 | | ^ 8 | -------------------------------------------------------------------------------- /test/golden/{helloCL-s}.12627805606214404146.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:30: error: 3 | • 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. 4 | • In the quasi-quotation: [fmt|{hello:-s}|] 5 | | 6 | 7 | main = putStrLn [fmt|{hello:-s}|] 7 | | ^ 8 | -------------------------------------------------------------------------------- /test/golden/{helloCL=100s}.14374776122070431282.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:33: error: 3 | • String type is incompatible with inside padding (=). 4 | • In the quasi-quotation: [fmt|{hello:=100s}|] 5 | | 6 | 7 | main = putStrLn [fmt|{hello:=100s}|] 7 | | ^ 8 | -------------------------------------------------------------------------------- /test/golden/{helloCL=100}.9444838110946424370.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • String type is incompatible with inside padding (=). 4 | • 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)’ 5 | 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) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{hello:=100}|] 9 | | ^^^^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{helloCLE}.15676531368138664498.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Real String) arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’ 4 | • 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)’ 5 | 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) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{hello:E}|] 9 | | ^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{helloCLG}.17442699390234010162.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Real String) arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’ 4 | • 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)’ 5 | 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) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{hello:G}|] 9 | | ^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{helloCLX}.8447528333473699378.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Integral String) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 4 | • 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)’ 5 | 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) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{hello:X}|] 9 | | ^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{helloCL_s}.1094067961907256370.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:30: error: 3 | • String type is incompatible with grouping (_ or ,). 4 | • In the quasi-quotation: [fmt|{hello:_s}|] 5 | | 6 | 7 | main = putStrLn [fmt|{hello:_s}|] 7 | | ^ 8 | -------------------------------------------------------------------------------- /test/golden/{helloCLb}.14869862508711808562.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Integral String) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 4 | • 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)’ 5 | In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{hello:b}|] 9 | | ^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{helloCLd}.1892681375540151858.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Integral String) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 4 | • 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)’ 5 | In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{hello:d}|] 9 | | ^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{helloCLe}.13933826712837941810.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Real String) arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’ 4 | • 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)’ 5 | 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) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{hello:e}|] 9 | | ^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{helloCLf}.14332487603622862386.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Real String) arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’ 4 | • 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)’ 5 | 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) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{hello:f}|] 9 | | ^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{helloCLg}.9607247906229690930.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Real String) arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’ 4 | • 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)’ 5 | 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) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{hello:g}|] 9 | | ^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{helloCLo}.9389880575827657266.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Integral String) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 4 | • 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)’ 5 | In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{hello:o}|] 9 | | ^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{helloCLx}.14710080644372944434.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Integral String) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 4 | • 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)’ 5 | In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) hello) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{hello:x}|] 9 | | ^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{numberCLX}.4609648040604121432.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Integral Float) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 4 | • 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)’ 5 | 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) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{number:X}|] 9 | | ^^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{numberCLb}.8801685868342243288.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Integral Float) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 4 | • 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)’ 5 | In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{number:b}|] 9 | | ^^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{numberCLd}.13336740346716692056.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Integral Float) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 4 | • 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)’ 5 | In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{number:d}|] 9 | | ^^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{numberCLo}.12467189151987896600.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Integral Float) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 4 | • 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)’ 5 | In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{number:o}|] 9 | | ^^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{numberCLx}.14457861675063419224.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:22: error: 3 | • No instance for (Integral Float) arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 4 | • 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)’ 5 | In the expression: putStrLn (((((PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa) PyF.Formatters.Minus) (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char))) Nothing) number) 6 | 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 | | 8 | 7 | main = putStrLn [fmt|{number:x}|] 9 | | ^^^^^^^^^^^^ 10 | -------------------------------------------------------------------------------- /test/golden/{piCL.{SL}}.6840925804160914882.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:28: error: 3 | • parse error on input `/' in haskell expression 4 | • In the quasi-quotation: [fmt|{pi:.{/}}|] 5 | | 6 | 7 | main = putStrLn [fmt|{pi:.{/}}|] 7 | | ^ 8 | -------------------------------------------------------------------------------- /test/golden/{piCL.{}}.9894464503607709506.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:28: error: 3 | • 4 | unexpected "}" 5 | expecting an haskell expression 6 | • In the quasi-quotation: [fmt|{pi:.{}}|] 7 | | 8 | 7 | main = putStrLn [fmt|{pi:.{}}|] 9 | | ^ 10 | -------------------------------------------------------------------------------- /test/golden/{truncate numberCL.3b}.11608798523190422838.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:41: error: 3 | • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field. 4 | • In the quasi-quotation: [fmt|{truncate number:.3b}|] 5 | | 6 | 7 | main = putStrLn [fmt|{truncate number:.3b}|] 7 | | ^ 8 | -------------------------------------------------------------------------------- /test/golden/{truncate numberCL.3d}.9142976352287206710.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:41: error: 3 | • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field. 4 | • In the quasi-quotation: [fmt|{truncate number:.3d}|] 5 | | 6 | 7 | main = putStrLn [fmt|{truncate number:.3d}|] 7 | | ^ 8 | -------------------------------------------------------------------------------- /test/golden/{truncate numberCL.3o}.1443712191031422262.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:41: error: 3 | • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field. 4 | • In the quasi-quotation: [fmt|{truncate number:.3o}|] 5 | | 6 | 7 | main = putStrLn [fmt|{truncate number:.3o}|] 7 | | ^ 8 | -------------------------------------------------------------------------------- /test/golden/{truncate numberCL.3x}.12613302271643495734.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:41: error: 3 | • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field. 4 | • In the quasi-quotation: [fmt|{truncate number:.3x}|] 5 | | 6 | 7 | main = putStrLn [fmt|{truncate number:.3x}|] 7 | | ^ 8 | -------------------------------------------------------------------------------- /test/golden/{}.14986928820806517861.golden: -------------------------------------------------------------------------------- 1 | 2 | INITIALPATH:7:23: error: 3 | • 4 | unexpected "}" 5 | expecting an haskell expression 6 | • In the quasi-quotation: [fmt|{}|] 7 | | 8 | 7 | main = putStrLn [fmt|{}|] 9 | | ^ 10 | -------------------------------------------------------------------------------- /test/golden96/Hello {length name}.16675806454852491955.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:36: error: 2 | • Variable not in scope: name 3 | • In the quasi-quotation: [fmt|Hello {length name}|] 4 | | 5 | 7 | main = putStrLn [fmt|Hello {length name}|] 6 | | ^^^^ -------------------------------------------------------------------------------- /test/golden96/Hello {name}.16618004304593959603.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:29: error: 2 | • Variable not in scope: name 3 | • In the quasi-quotation: [fmt|Hello {name}|] 4 | | 5 | 7 | main = putStrLn [fmt|Hello {name}|] 6 | | ^^^^ -------------------------------------------------------------------------------- /test/golden96/Hello {piCL.{precision}}.9628757040831863475.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:34: error: 2 | • Variable not in scope: precision 3 | • In the quasi-quotation: [fmt|Hello {pi:.{precision}}|] 4 | | 5 | 7 | main = putStrLn [fmt|Hello {pi:.{precision}}|] 6 | | ^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/Hello {piCL.{truncate number + precision}}.18094049741103110835.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:52: error: 2 | • Variable not in scope: precision 3 | • In the quasi-quotation: [fmt|Hello {pi:.{truncate number + precision}}|] 4 | | 5 | 7 | main = putStrLn [fmt|Hello {pi:.{truncate number + precision}}|] 6 | | ^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/Hello {piCL{width}}.15463239215289408179.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:33: error: 2 | • Variable not in scope: width 3 | • In the quasi-quotation: [fmt|Hello {pi:{width}}|] 4 | | 5 | 7 | main = putStrLn [fmt|Hello {pi:{width}}|] 6 | | ^^^^^ -------------------------------------------------------------------------------- /test/golden96/fooBSPbar.17645057532673886893.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:25: error: 2 | • Lexical error in literal section 3 | • In the quasi-quotation: [fmt|foo\Pbar|] 4 | | 5 | 7 | main = putStrLn [fmt|foo\Pbar|] 6 | | ^ -------------------------------------------------------------------------------- /test/golden96/fooNLbliBSPbar.16759496276764189145.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:8:4: error: 2 | • Lexical error in literal section 3 | • In the quasi-quotation: [fmt|foo 4 | bli\Pbar|] 5 | | 6 | 8 | bli\Pbar|] 7 | | ^ -------------------------------------------------------------------------------- /test/golden96/hello { world.10778336899993839283.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:35: error: 2 | • 3 | unexpected end of input 4 | expecting "::", ":" or "}" 5 | • In the quasi-quotation: [fmt|hello { world|] 6 | | 7 | 7 | main = putStrLn [fmt|hello { world|] 8 | | ^ -------------------------------------------------------------------------------- /test/golden96/hello } world.5295037443422799539.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:28: error: 2 | • 3 | unexpected '}' 4 | expecting "{{", "}}", "{" or end of input 5 | • In the quasi-quotation: [fmt|hello } world|] 6 | | 7 | 7 | main = putStrLn [fmt|hello } world|] 8 | | ^ -------------------------------------------------------------------------------- /test/golden96/helloNL {NLlet a = 5NL b = 10NLin 1 + - SL lalalal}.3018767237106994099.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:11:10: error: 2 | • parse error on input `/' in haskell expression 3 | • In the quasi-quotation: [fmt|hello 4 | { 5 | let a = 5 6 | b = 10 7 | in 1 + - / lalalal}|] 8 | | 9 | 11 | in 1 + - / lalalal}|] 10 | | ^ -------------------------------------------------------------------------------- /test/golden96/helloNLNLNL{piCLl}.13123157148160021427.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:10:6: error: 2 | • 3 | unexpected "}" 4 | expecting "<", ">", "^" or "=" 5 | • In the quasi-quotation: [fmt|hello 6 | 7 | 8 | {pi:l}|] 9 | | 10 | 10 | {pi:l}|] 11 | | ^ -------------------------------------------------------------------------------- /test/golden96/{1 + - SL lalalal}.14923086665437293731.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:29: error: 2 | • parse error on input `/' in haskell expression 3 | • In the quasi-quotation: [fmt|{1 + - / lalalal}|] 4 | | 5 | 7 | main = putStrLn [fmt|{1 + - / lalalal}|] 6 | | ^ -------------------------------------------------------------------------------- /test/golden96/{TrueCLd}.12627313193367841398.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Integral Bool’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 3 | • 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)’ 4 | In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing True) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{True:d}|] 8 | | ^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{TrueCLf}.18281408089045870326.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Real Bool’ arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’ 3 | • 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)’ 4 | 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) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{True:f}|] 8 | | ^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{True}.16254223077612353942.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘PyF.Internal.QQ.FormatAny2 (PyFClassify Bool) Bool PyF.Formatters.AlignAll’ arising from a use of ‘PyF.Internal.QQ.formatAny’ 3 | • In the first argument of ‘putStrLn’, namely ‘(PyF.Internal.QQ.formatAny PyF.Formatters.Minus PyF.Internal.QQ.PaddingDefaultK Nothing (Nothing :: Maybe Int) True)’ 4 | In the expression: putStrLn (PyF.Internal.QQ.formatAny PyF.Formatters.Minus PyF.Internal.QQ.PaddingDefaultK Nothing (Nothing :: Maybe Int) True) 5 | In an equation for ‘main’: main = putStrLn (PyF.Internal.QQ.formatAny PyF.Formatters.Minus PyF.Internal.QQ.PaddingDefaultK Nothing (Nothing :: Maybe Int) True) 6 | | 7 | 7 | main = putStrLn [fmt|{True}|] 8 | | ^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{helloCL s}.13047921915648718386.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:30: error: 2 | • 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. 3 | • In the quasi-quotation: [fmt|{hello: s}|] 4 | | 5 | 7 | main = putStrLn [fmt|{hello: s}|] 6 | | ^ -------------------------------------------------------------------------------- /test/golden96/{helloCL%}.1257653362598537778.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Real String’ arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’ 3 | • 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)’ 4 | 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) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{hello:%}|] 8 | | ^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{helloCL+s}.1657517030647448626.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:30: error: 2 | • 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. 3 | • In the quasi-quotation: [fmt|{hello:+s}|] 4 | | 5 | 7 | main = putStrLn [fmt|{hello:+s}|] 6 | | ^ -------------------------------------------------------------------------------- /test/golden96/{helloCL,s}.14139635988852178482.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:30: error: 2 | • String type is incompatible with grouping (_ or ,). 3 | • In the quasi-quotation: [fmt|{hello:,s}|] 4 | | 5 | 7 | main = putStrLn [fmt|{hello:,s}|] 6 | | ^ -------------------------------------------------------------------------------- /test/golden96/{helloCL-s}.12627805606214404146.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:30: error: 2 | • 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. 3 | • In the quasi-quotation: [fmt|{hello:-s}|] 4 | | 5 | 7 | main = putStrLn [fmt|{hello:-s}|] 6 | | ^ -------------------------------------------------------------------------------- /test/golden96/{helloCL=100s}.14374776122070431282.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:33: error: 2 | • String type is incompatible with inside padding (=). 3 | • In the quasi-quotation: [fmt|{hello:=100s}|] 4 | | 5 | 7 | main = putStrLn [fmt|{hello:=100s}|] 6 | | ^ -------------------------------------------------------------------------------- /test/golden96/{helloCL=100}.9444838110946424370.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-64725] 2 | • String type is incompatible with inside padding (=). 3 | • 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)’ 4 | 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) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{hello:=100}|] 8 | | ^^^^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{helloCLE}.15676531368138664498.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Real String’ arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’ 3 | • 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)’ 4 | 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) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{hello:E}|] 8 | | ^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{helloCLG}.17442699390234010162.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Real String’ arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’ 3 | • 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)’ 4 | 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) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{hello:G}|] 8 | | ^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{helloCLX}.8447528333473699378.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Integral String’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 3 | • 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)’ 4 | 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) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{hello:X}|] 8 | | ^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{helloCL_s}.1094067961907256370.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:30: error: 2 | • String type is incompatible with grouping (_ or ,). 3 | • In the quasi-quotation: [fmt|{hello:_s}|] 4 | | 5 | 7 | main = putStrLn [fmt|{hello:_s}|] 6 | | ^ -------------------------------------------------------------------------------- /test/golden96/{helloCLb}.14869862508711808562.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Integral String’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 3 | • 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)’ 4 | In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{hello:b}|] 8 | | ^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{helloCLd}.1892681375540151858.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Integral String’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 3 | • 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)’ 4 | In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{hello:d}|] 8 | | ^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{helloCLe}.13933826712837941810.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Real String’ arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’ 3 | • 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)’ 4 | 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) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{hello:e}|] 8 | | ^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{helloCLf}.14332487603622862386.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Real String’ arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’ 3 | • 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)’ 4 | 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) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{hello:f}|] 8 | | ^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{helloCLg}.9607247906229690930.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Real String’ arising from a use of ‘PyF.Internal.QQ.formatAnyFractional’ 3 | • 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)’ 4 | 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) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{hello:g}|] 8 | | ^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{helloCLo}.9389880575827657266.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Integral String’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 3 | • 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)’ 4 | In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{hello:o}|] 8 | | ^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{helloCLx}.14710080644372944434.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Integral String’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 3 | • 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)’ 4 | In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing hello) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{hello:x}|] 8 | | ^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{numberCLX}.4609648040604121432.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Integral Float’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 3 | • 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)’ 4 | 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) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{number:X}|] 8 | | ^^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{numberCLb}.8801685868342243288.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Integral Float’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 3 | • 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)’ 4 | In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Binary PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{number:b}|] 8 | | ^^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{numberCLd}.13336740346716692056.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Integral Float’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 3 | • 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)’ 4 | In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Decimal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{number:d}|] 8 | | ^^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{numberCLo}.12467189151987896600.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Integral Float’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 3 | • 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)’ 4 | In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Octal PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{number:o}|] 8 | | ^^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{numberCLx}.14457861675063419224.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:22: error: [GHC-39999] 2 | • No instance for ‘Integral Float’ arising from a use of ‘PyF.Internal.QQ.formatAnyIntegral’ 3 | • 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)’ 4 | In the expression: putStrLn (PyF.Internal.QQ.formatAnyIntegral PyF.Formatters.Hexa PyF.Formatters.Minus (Nothing :: Maybe (Int, PyF.Formatters.AnyAlign, Char)) Nothing number) 5 | 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) 6 | | 7 | 7 | main = putStrLn [fmt|{number:x}|] 8 | | ^^^^^^^^^^^^ -------------------------------------------------------------------------------- /test/golden96/{piCL.{SL}}.6840925804160914882.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:28: error: 2 | • parse error on input `/' in haskell expression 3 | • In the quasi-quotation: [fmt|{pi:.{/}}|] 4 | | 5 | 7 | main = putStrLn [fmt|{pi:.{/}}|] 6 | | ^ -------------------------------------------------------------------------------- /test/golden96/{piCL.{}}.9894464503607709506.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:28: error: 2 | • 3 | unexpected "}" 4 | expecting an haskell expression 5 | • In the quasi-quotation: [fmt|{pi:.{}}|] 6 | | 7 | 7 | main = putStrLn [fmt|{pi:.{}}|] 8 | | ^ -------------------------------------------------------------------------------- /test/golden96/{truncate numberCL.3b}.11608798523190422838.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:41: error: 2 | • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field. 3 | • In the quasi-quotation: [fmt|{truncate number:.3b}|] 4 | | 5 | 7 | main = putStrLn [fmt|{truncate number:.3b}|] 6 | | ^ -------------------------------------------------------------------------------- /test/golden96/{truncate numberCL.3d}.9142976352287206710.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:41: error: 2 | • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field. 3 | • In the quasi-quotation: [fmt|{truncate number:.3d}|] 4 | | 5 | 7 | main = putStrLn [fmt|{truncate number:.3d}|] 6 | | ^ -------------------------------------------------------------------------------- /test/golden96/{truncate numberCL.3o}.1443712191031422262.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:41: error: 2 | • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field. 3 | • In the quasi-quotation: [fmt|{truncate number:.3o}|] 4 | | 5 | 7 | main = putStrLn [fmt|{truncate number:.3o}|] 6 | | ^ -------------------------------------------------------------------------------- /test/golden96/{truncate numberCL.3x}.12613302271643495734.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:41: error: 2 | • Type incompatible with precision (.3), use any of {'e', 'E', 'f', 'F', 'g', 'G', 'n', 's', '%'} or remove the precision field. 3 | • In the quasi-quotation: [fmt|{truncate number:.3x}|] 4 | | 5 | 7 | main = putStrLn [fmt|{truncate number:.3x}|] 6 | | ^ -------------------------------------------------------------------------------- /test/golden96/{}.14986928820806517861.golden: -------------------------------------------------------------------------------- 1 | INITIALPATH:7:23: error: 2 | • 3 | unexpected "}" 4 | expecting an haskell expression 5 | • In the quasi-quotation: [fmt|{}|] 6 | | 7 | 7 | main = putStrLn [fmt|{}|] 8 | | ^ -------------------------------------------------------------------------------- /tree-sitter-pyf/.envrc: -------------------------------------------------------------------------------- 1 | use flake .#treesitter 2 | -------------------------------------------------------------------------------- /tree-sitter-pyf/Readme.md: -------------------------------------------------------------------------------- 1 | # Syntax highlighting with tree-sitter 2 | 3 | With this grammar for tree-sitter, you can get PyF syntax highlighting into your 4 | tree-sitter compatible editor, such as nvim or emacs. 5 | 6 | ![](nvim_ts_highlight.png) 7 | ## Installation 8 | 9 | ### Neovim 10 | 11 | You can use this documentation: [NVim documentation for adding parsers](https://github.com/nvim-treesitter/nvim-treesitter#adding-parsers). 12 | 13 | I've added the following in my nvim lua configuration file: 14 | 15 | ```lua 16 | -- Install my own parser 17 | local parser_config = require "nvim-treesitter.parsers".get_parser_configs() 18 | parser_config.pyf = { 19 | install_info = { 20 | url = "https://github.com/guibou/PyF", 21 | files = {"src/parser.c"}, 22 | branch = "main", 23 | location = "tree-sitter-pyf", 24 | generate_requires_npm = false, -- if stand-alone parser without npm dependencies 25 | }, 26 | } 27 | ``` 28 | 29 | Then do `:TSInstallFromGrammar pyf` and you are good to go. 30 | 31 | Then, install the content of [./vim-plugin](./vim-plugin) as a vim plugin. I'm using [vim-plug](https://github.com/junegunn/vim-plug) to do it: 32 | 33 | ```vim 34 | Plug 'guibou/PyF', { 'rtp': 'tree-sitter-pyf/vim-plugin' } 35 | ``` 36 | 37 | ### Other editors 38 | 39 | Ensure tree-sitter support, and then, the `highlight.scm` and `injections.scm` 40 | files are in the [./vim-plugin](./vim-plugin) directory. The parser is located 41 | here, in [./grammar.js](./grammar.js). Please open an issue if you know / want 42 | more informations for specific editors. 43 | 44 | ## Development 45 | 46 | ## Building the grammar 47 | 48 | Go into this directory, start `nix develop` (Or install `tree-sitter CLI`) and then: 49 | 50 | ```bash 51 | $ tree-sitter generate 52 | ``` 53 | 54 | You can also experiment with `tree-sitter parse example-file` 55 | 56 | ```sexp 57 | (source_file [0, 0] - [1, 0] 58 | (text [0, 0] - [0, 19]) 59 | (escape [0, 19] - [0, 21]) 60 | (text [0, 21] - [0, 27]) 61 | (interpolation [0, 27] - [0, 48] 62 | (interpolation_content [0, 28] - [0, 47])) 63 | (text [0, 48] - [0, 49]) 64 | (escape [0, 49] - [0, 51]) 65 | (text [0, 51] - [0, 56]) 66 | (interpolation [0, 56] - [0, 67] 67 | (interpolation_content [0, 57] - [0, 62]) 68 | (format_string [0, 62] - [0, 66] 69 | (format_spec [0, 62] - [0, 66] 70 | (precision_dot [0, 63] - [0, 64]) 71 | (precision [0, 64] - [0, 65]) 72 | (type [0, 65] - [0, 66])))) 73 | (text [0, 67] - [0, 108]) 74 | (interpolation [0, 108] - [0, 121] 75 | (interpolation_content [0, 109] - [0, 118]) 76 | (format_string [0, 118] - [0, 120] 77 | (format_spec [0, 118] - [0, 120] 78 | (type [0, 119] - [0, 120])))) 79 | (text [0, 121] - [1, 0])) 80 | ``` 81 | 82 | -------------------------------------------------------------------------------- /tree-sitter-pyf/example-file: -------------------------------------------------------------------------------- 1 | this is an example {{ with {interpolated_values} }} and {1 + 1:.3x} and also text and the final weird field {10 :: Int:d}. 2 | -------------------------------------------------------------------------------- /tree-sitter-pyf/grammar.js: -------------------------------------------------------------------------------- 1 | module.exports = grammar({ 2 | name: 'pyf', 3 | 4 | rules: { 5 | // TODO: add the actual grammar rules 6 | source_file: $ => repeat($._text_or_interpolation), 7 | 8 | _text_or_interpolation: $ => choice( 9 | $.escape, 10 | $.interpolation, 11 | $.text), 12 | interpolation: $ => seq( 13 | '{', 14 | $.interpolation_content, 15 | optional($.format_string), 16 | '}' 17 | ), 18 | interpolation_content: $ => repeat1(choice(/[^:]/, "::")), 19 | text: $ => /[^{}]+/, 20 | escape: $ => choice( 21 | "}}", 22 | "{{", 23 | ), 24 | // format_string: $ => /:[^}]+/, 25 | format_string: $ => $.format_spec, 26 | format_spec: $ => seq(":", 27 | optional(seq(optional($.fill), $.align)), 28 | optional($.sign), 29 | optional($.alternate), 30 | optional($.zero), 31 | optional($.width), 32 | optional($.grouping_option), 33 | optional(seq($.precision_dot, $.precision)), 34 | optional($.type), 35 | ), 36 | zero: $ => "0", 37 | alternate: $ => "#", 38 | fill: $ => /./, 39 | align: $ => choice("<", ">", "=", "^"), 40 | sign: $ => choice("+", "-", " "), 41 | width: $ => /[0-9]+/, 42 | grouping_option: $ => choice("_", ","), 43 | precision: $ => /[0-9]+/, 44 | precision_dot: $ => ".", 45 | type: $ => choice("b" , "c" , "d" , "e" , "E" , "f" , "F" , "g" , "G" , "n" , "o" , "s" , "x" , "X" , "%"), 46 | } 47 | }); 48 | -------------------------------------------------------------------------------- /tree-sitter-pyf/nvim_ts_highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guibou/PyF/cf03e5fa02d7c5211719661ac81eb9d8dd8af238/tree-sitter-pyf/nvim_ts_highlight.png -------------------------------------------------------------------------------- /tree-sitter-pyf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tree-sitter-pyf", 3 | "version": "1.0.0", 4 | "description": "Parser for PyF interpolated strings", 5 | "main": "bindings/node", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Guillaume Bouchard", 10 | "license": "ISC" 11 | } 12 | -------------------------------------------------------------------------------- /tree-sitter-pyf/tree-sitter.json: -------------------------------------------------------------------------------- 1 | { 2 | "grammars": [ 3 | { 4 | "name": "pyf", 5 | "camelcase": "Pyf", 6 | "scope": "source.pyf", 7 | "path": ".", 8 | "file-types": [ 9 | "pyf" 10 | ] 11 | } 12 | ], 13 | "metadata": { 14 | "version": "1.0.0", 15 | "license": "ISC", 16 | "description": "Parser for PyF interpolated strings", 17 | "authors": [ 18 | { 19 | "name": "Guillaume Bouchard" 20 | } 21 | ], 22 | "links": { 23 | "repository": "https://github.com/guibou/PyF/tree-sitter-pyf" 24 | } 25 | }, 26 | "bindings": { 27 | "c": true, 28 | "go": true, 29 | "node": true, 30 | "python": true, 31 | "rust": true, 32 | "swift": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tree-sitter-pyf/vim-plugin/after/queries/haskell/highlights.scm: -------------------------------------------------------------------------------- 1 | (quasiquote_body) @string 2 | -------------------------------------------------------------------------------- /tree-sitter-pyf/vim-plugin/after/queries/haskell/injections.scm: -------------------------------------------------------------------------------- 1 | ; extends 2 | (quasiquote 3 | (quoter) @_name 4 | (#eq? @_name "fmt") 5 | ((quasiquote_body) @injection.content) 6 | (#set! injection.language "pyf")) 7 | 8 | (quasiquote 9 | (quoter) @_name 10 | (#eq? @_name "fmtTrim") 11 | ((quasiquote_body) @injection.content) 12 | (#set! injection.language "pyf")) 13 | -------------------------------------------------------------------------------- /tree-sitter-pyf/vim-plugin/after/queries/pyf/highlights.scm: -------------------------------------------------------------------------------- 1 | (text) @string 2 | (escape) @string.escape 3 | (interpolation) @punctuation.special 4 | 5 | (type) @string.escape 6 | (width) @number 7 | (precision) @number 8 | -------------------------------------------------------------------------------- /tree-sitter-pyf/vim-plugin/after/queries/pyf/injections.scm: -------------------------------------------------------------------------------- 1 | ; extends 2 | (interpolation 3 | ((interpolation_content) @injection.content) 4 | (#set! injection.language "haskell")) 5 | 6 | -------------------------------------------------------------------------------- /treefmt.nix: -------------------------------------------------------------------------------- 1 | { ... }: 2 | { 3 | projectRootFile = "flake.nix"; 4 | 5 | programs.ormolu.enable = true; 6 | programs.nixfmt.enable = true; 7 | programs.yamlfmt.enable = true; 8 | programs.cabal-fmt.enable = true; 9 | programs.mdformat.enable = true; 10 | 11 | settings.excludes = [ 12 | "*.golden" 13 | "flake.lock" 14 | "*.png" 15 | ".gitignore" 16 | ".envrc" 17 | "LICENSE" 18 | "tree-sitter-pyf/*" 19 | ]; 20 | } 21 | --------------------------------------------------------------------------------