qm-interpolated-string
Implementation of interpolated multiline strings
https://github.com/unclechu/haskell-qm-interpolated-string
LTS Haskell 22.43: | 0.3.1.0 |
Stackage Nightly 2023-12-26: | 0.3.1.0 |
Latest on Hackage: | 0.3.1.0 |
qm-interpolated-string-0.3.1.0@sha256:2713530102b0060a6f9f29d0d0ce3322df495fc30912a4239b88a2651254f757,2466
Module documentation for 0.3.1.0
- Text
- Text.InterpolatedString
- Text.InterpolatedString.QM
- Text.InterpolatedString.QM.ShowQ
- Text.InterpolatedString.QM
- Text.InterpolatedString
[qm|interpolated-string|]
Implementation of interpolated multiline string QuasiQuoter that ignores indentation and trailing whitespaces.
Actually it’s modification of interpolatedstring-perl6 package. I’ve forked it to implement my own strings I really like.
This implementation based on qc
from interpolatedstring-perl6 package
but ignores any indentation, line breaks
(except explicitly written using \n
char)
and trailing whitespaces.
- ‘m’ in
qm
means ‘Multiline’. - ‘n’ in
qn
means ‘No interpolation’. - ‘b’ in
qmb
/qnb
means ‘line Breaks’. - ‘s’ in
qms
/qns
means ‘Spaces’.
Write a decoratively formatted string and your decorative indentation and line breaks wont go to result string, but when you really need it, you could just escape it using backslash.
Usage example
{-# LANGUAGE QuasiQuotes #-}
import Text.InterpolatedString.QM
main :: IO ()
main = do
-- Hello, world! Pi is 3.14…
putStrLn [qms| Hello,
world!
Pi is {floor pi}.{floor $ (pi - 3) * 100}… |]
-- Some examples with HTML below to demonstrate the difference
-- between all of the quasi-quoters.
let title = "Testing"
text = "Some testing text"
-- <article><h1>Testing</h1><p>Some testing text</p></article>
putStrLn [qm|
<article>
<h1>{title}</h1>
<p>{text}</p>
</article>
|]
-- <article><h1>{title}</h1><p>{text}</p></article>
putStrLn [qn|
<article>
<h1>{title}</h1>
<p>{text}</p>
</article>
|]
-- <article> <h1>Testing</h1> <p>Some testing text</p> </article>
putStrLn [qms|
<article>
<h1>{title}</h1>
<p>{text}</p>
</article>
|]
-- <article> <h1>{title}</h1> <p>{text}</p> </article>
putStrLn [qns|
<article>
<h1>{title}</h1>
<p>{text}</p>
</article>
|]
-- <article>
-- <h1>Testing</h1>
-- <p>Some testing text</p>
-- </article>
putStrLn [qmb|
<article>
<h1>{title}</h1>
<p>{text}</p>
</article>
|]
-- <article>
-- <h1>{title}</h1>
-- <p>{text}</p>
-- </article>
putStrLn [qnb|
<article>
<h1>{title}</h1>
<p>{text}</p>
</article>
|]
Tables
All QuasiQuoters
| QuasiQuoter | Interpolation | Indentation | Line breaks | Trailing whitespaces |
|-------------|---------------|-------------|----------------------|----------------------|
| qm | ✓ | Removed | Removed | Removed |
| qn | ✗ | Removed | Removed | Removed |
| qmb | ✓ | Removed | Kept | Removed |
| qnb | ✗ | Removed | Kept | Removed |
| qms | ✓ | Removed | Replaced with spaces | Removed |
| qns | ✗ | Removed | Replaced with spaces | Removed |
About naming logic
| Contains in its name | What means | QuasiQuoters |
|----------------------|----------------------------------|--------------|
| m | Resolves interpolation blocks | qm, qmb, qms |
| n | Without interpolation | qn, qnb, qns |
| b | Keeps line breaks | qmb, qnb |
| s | Replaces line breaks with spaces | qms, qns |
About interpolation blocks
Along with all specifics of any of the quoters (which supports interpolation
blocks, which has m
in their names) interpolation blocks work different. When
curly bracket ({
) opens everything inside until it closes (by }
) is parsed
as bare as possible to be given to
haskell-src-meta
without any modifications, to be parsed as bare Haskell code.
But you might need use curly brackets inside an interpolation block. I don’t
think it would be a good idea, because complicated logic there may cause code
readability issues, but if you’re sure you need it then you get it. You just
need to escape closing bracket to prevent interpolation block from closing, like
this: \}
. I know it could parsed and opening curly brackets inside could be
used to prevent closing by next }
symbol, but I chose do it this way to
prevent any unobvious tricky behavior (e.g. consider }
appear inside a string,
[qm|foo {'x':'}':"y"} bar|]
, how that should be handled?). So I’ve decided to
not make parser to be very smart, just to follow simple logic. You just need to
explicitly escape every }
symbol inside that isn’t closer of an interpolation
block (you could find an example below).
About escaping
Symbols that can be escaped
Backslash is used for escaping these:
\n
- line break\
- space (space is supposed to be escaped when you’re going to keep some part of indentation)\↵
- backslash just before end of line cuts off line break (makes sense forqmb
,qnb
,qms
andqns
)\{
- opening bracket of interpolation block (only forqm
,qmb
andqms
, to prevent interpolation and interpret this block as plain text)\t
or\‣
(where‣
is real tab symbol) - tab (escaping it to keep some part of indentation, or if you need tab symbol for some reason, escaping real tabs makes sense only for keeping some part of indentation)\\
- backslash itself (for situations when you don’t want to escape other symbols but just want backslash symbol,\\t
,\\n
,\\↵
,\\{
, etc., if backslash doesn’t come with any of symbols from this list it is interpreted just as backslash symbol, keep in mind that\\\
(without any of symbols from this list after) and\\\\
are producing same result -\\
)\}
- closing bracket inside an interpolation block (it works only inside opened interpolation block) to prevent interpolation block from closing (useful to escape records modification)
Escaping examples
[qm| foo\nbar |] -- "foo\nbar"
[qm| foo\\nbar |] -- "foo\\nbar"
[qm| foo\tbar |] -- "foo\tbar"
[qm| foo\\tbar |] -- "foo\\tbar"
[qm| foo\‣bar |] -- "foo\tbar" (`‣` is real tab symbol)
[qm| foo\\‣bar |] -- "foo\\\tbar" (`‣` is real tab symbol)
[qm| foo\ bar |] -- "foo bar"
[qm| foo\\ bar |] -- "foo\\ bar"
[qm| foo\
bar |] -- "foobar"
[qm| foo\\
bar |] -- "foo\\bar"
[qmb| foo\
bar |] -- "foobar"
[qmb| foo\\
bar |] -- "foo\\\nbar"
[qm| foo\bar |] -- "foo\\bar"
[qm| foo\\bar |] -- "foo\\bar"
[qm| foo\\\bar |] -- "foo\\\\bar"
[qm| foo\\\\bar |] -- "foo\\\\bar"
More examples
[qm| you can escape spaces
\ when you need them |]
-- Result: "you can escape spaces when you need them"
[qm|
indentation and li
ne bre
aks are i
gno
red
|]
-- Result: "indentation and line breaks are ignored"
[qm| \ You can escape indentation or\n
line breaks when you need them! \ |]
-- Result: " You can escape indentation or\nline breaks when you need them! "
[qm| Interpolation blocks can be escaped too: {1+2} \{3+4} |]
-- Result: "Interpolation blocks can be escaped too: 3 {3+4}"
If you don’t need interpolation - just replace m
to n
in quasi-quoter name:
[qm| foo {1+2} |] -- Result: "foo 3"
[qn| foo {1+2} |] -- Result: "foo {1+2}"
[qms| foo {1+2} |] -- Result: "foo 3"
[qns| foo {1+2} |] -- Result: "foo {1+2}"
[qmb| foo {1+2} |] -- Result: "foo 3"
[qnb| foo {1+2} |] -- Result: "foo {1+2}"
That’s how you update some record inside interpolation block (you need to escape closing bracket):
{-# LANGUAGE QuasiQuotes #-}
import Text.InterpolatedString.QM (qm)
data Foo = Foo {bar :: Int, baz :: Int} deriving Show
main = let foo = Foo 10 20 in putStrLn [qm| Foo is: {foo {baz = 30\}} |]
-- Foo is: Foo {bar = 10, baz = 30}
Syntax highlighting
Vim or Neovim
“haskell-vim” plugin
If you use haskell-vim plugin (I personally use
my own fork which supports UnicodeSyntax
) just add to
your .vimrc
or init.vim
this:
fu! s:highlight_haskell_qm_interpolation_blocks()
sy match haskellQMStr "." containedin=haskellQM contained
sy region haskellQMBlock matchgroup=haskellDelimiter
\ start="\(^\|\(^\|[^\\]\)\(\\\\\)*\)\@<={" end="}"
\ contains=TOP,@Spell containedin=haskellQM contained
sy region haskellQM matchgroup=haskellTH
\ start="\[qm\(b\|s\)\?|" end="|\]"
hi def link haskellQMStr String
endf
au FileType haskell cal s:highlight_haskell_qm_interpolation_blocks()
Wanna make a contribution or maintain your own fork?
See CONTRIBUTING.md.
Author
Viacheslav Lotsmanov
This library is a heavily rewritten and extended fork of interpolatedstring-perl6 which was originally created by Audrey Tang.
License
Changes
0.3.1.0
-
Support GHC 9.2.3
-
Drop Travis CI (no longer available for free for open source projects)
-
Add GitHub Actions CI configuration
- Nix-based
- Plain Cabal & Stack setups with multiple GHC versions
-
Add Nix configuration (only affects development)
-
Add Stack support
-
Drop GHC 7.x support because I have no options on CI to test it. It doesn’t mean it will no longer work with 7.x for sure but it’s not tested anymore.
-
Increase minimal Cabal version requirement from
1.8
to1.10
because Hackage requires at least1.10
now. Also adddefault-language
to every component because Hackage reports this error:Packages using ‘cabal-version: >= 1.10’ and before ‘cabal-version: 3.4’ must specify the ‘default-language’ field for each component (e.g. Haskell98 or Haskell2010).
0.3.0.0
- Testing on GHC 8.2.2
- More tests to cover more usage scenarios
- Ability to use
}
inside an interpolation block by escaping it
WARNING! Breaking changes
-
\r
characters are no longer pre-removed.Up to v0.2.1.0 all
\r
characters were pre-removed. When you compile your code with GHC you can use either LF or CRLF for line-breaks but not CR alone. When I changed handling of interpolation blocks (see below) I needed to get contents of interpolations blocks without any modifications, so I replaced pre-removing all CRs with explicit handling of CRLF in patterns. If your code ever was depending on\r
symbols appearing alone inside quoters (that I can’t even imagine) it could break your code. But it will probably never happen, I’m just noticing it here. -
Fix for interpolation blocks parsing.
Once I noticed that
[qm|{"\n"}|]
compiles to"n"
, I considered this as a bug, I also realized that interpolation blocks aren’t interpreted as a bare Haskell code as I was expecting. My bad, I’ve missed that, haven’t written enough tests to cover such scenarios, it migrated from original interpolatedstring-perl6 package. So I had to fix this mistake, notwithstanding it can break your code when you update the library. Now everything inside interpolation blocks is taken as bare haskell code as possible.
0.2.1.0
- Support GHC 7.4.1
- Internal modules moved to
other-modules
section
0.2.0.0
- Added tab (
\t
) symbol escaping (breaks backward compatibility with v0.1.1.0) - Support new LTS Haskell 9.0 (ghc-8.0.2) (updated upper version of haskell-src-meta from 0.7.x to 0.8.x)
- Added
qmb
QuasiQuoter, it’sqm
+b
(line-Breaks), it works just asqm
but keeps line breaks (still ignores indendation) - Added
qnb
QuasiQuoter (qmb
without interpolation), it’sqn
+b
(line-Breaks), it works just asqn
but keeps line breaks (still ignores indendation) - Added
qms
QuasiQuoter, it’sqm
+s
(Spaces), it works just asqmb
but kept line breaks replaced with spaces - Added
qns
QuasiQuoter (qms
without interpolation), it’sqn
+s
(Spaces), it works just asqnb
but kept line breaks replaced with spaces - More docs and tests
0.1.1.0
- Added
qn
QuasiQuoter as alternative toqm
but without interpolation - Some code refactoring
- More docs and tests