1{-# LANGUAGE TemplateHaskell #-}
3-- Module : Streamly.Internal.Unicode.String
4-- Copyright : (c) 2022 Composewell Technologies
5-- License : BSD-3-Clause
6-- Maintainer : streamly@composewell.com
7-- Stability : experimental
10-- Convenient template Haskell quasiquoters to format strings.
14-- Essential requirements are:
16-- Haskell expression expansion
17-- Newline treatment (continue without introducing a newline)
18-- Indentation treatment
20-- We choose #{expr} for patching a Haskell expression's value in a string. "$"
21-- instead of "#" was another option (like in neat-interpolation package) but
22-- we did not use that to avoid conflict with strings that are used as shell
23-- commands. Another option was to use just "{}" (like in PyF package) but we
24-- did not use that to avoid conflict with "${}" used in shell.
26-- We use a "#" at the end of line to continue the line. We could use a "\"
27-- as well but that may interfere with CPP.
29-- Stripping is not part of the quasiquoter as it can be done by a Haskell
30-- function. Other type of formatting on the Haskell expression can be done
31-- using Haskell functions.
33module Streamly.Internal.Unicode.String
38import Control.Applicative (Alternative(..))
39import Control.Exception (displayException)
40import Data.Functor.Identity (runIdentity)
41import Streamly.Internal.Data.Parser (Parser)
43import Language.Haskell.TH
44import Language.Haskell.TH.Quote
46import qualified Streamly.Data.Fold as Fold
47import qualified Streamly.Internal.Data.Parser as Parser
48 (some, many, takeWhile1)
49import qualified Streamly.Data.Stream as Stream (fromList, parse)
50import qualified Streamly.Internal.Unicode.Parser as Parser
54-- >>> :set -XQuasiQuotes
55-- >>> import Streamly.Internal.Unicode.String
57--------------------------------------------------------------------------------
59--------------------------------------------------------------------------------
66haskellIdentifier :: Monad m => Parser Char m String
68 let p = Parser.alphaNum <|> Parser.char '\'' <|> Parser.char '_'
69 in Parser.some p Fold.toList
71strParser :: Monad m => Parser Char m [StrSegment]
72strParser = Parser.many content Fold.toList
76 plainText = StrText <$> Parser.takeWhile1 (/= '#') Fold.toList
77 escHash = StrText . (: []) <$> (Parser.char '#' *> Parser.char '#')
78 lineCont = StrText [] <$ (Parser.char '#' *> Parser.char '\n')
85 plainHash = StrText . (: []) <$> Parser.char '#'
88 content = plainText <|> escHash <|> lineCont <|> var <|> plainHash
90strSegmentExp :: StrSegment -> Q Exp
91strSegmentExp (StrText text) = stringE text
92strSegmentExp (StrVar name) = do
93 valueName <- lookupValueName name
98 $ "str quote: Haskell symbol `" ++ name
99 ++ "` is not in scope"
101strExp :: [StrSegment] -> Q Exp
102strExp xs = appE [| concat |] $ listE $ map strSegmentExp xs
104expandVars :: String -> Q Exp
106 case runIdentity $ Stream.parse strParser (Stream.fromList ln) of
108 fail $ "str QuasiQuoter parse error: " ++ displayException e
112-- | A QuasiQuoter that treats the input as a string literal:
117-- Any @#{symbol}@ is replaced by the value of the Haskell symbol @symbol@
121-- >>> [str|#{x} world!|]
124-- @##@ means a literal @#@ without the special meaning for referencing
127-- >>> [str|##{x} world!|]
130-- A @#@ at the end of line means the line continues to the next line without
131-- introducing a newline character:
139-- Bugs: because of a bug in parsers, a lone # at the end of input gets
145 { quoteExp = expandVars
146 , quotePat = notSupported
147 , quoteType = notSupported
148 , quoteDec = notSupported
153 notSupported = error "str: Not supported."
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list