-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a cmd quasiquoter #75
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
-- | | ||
-- Module : Streamly.Internal.System | ||
-- Copyright : (c) 2022 Composewell Technologies | ||
-- License : Apache-2.0 | ||
-- Maintainer : [email protected] | ||
-- Stability : experimental | ||
-- Portability : GHC | ||
-- | ||
{-# LANGUAGE TemplateHaskell #-} | ||
|
||
module Streamly.Internal.System | ||
( cmd | ||
, trim | ||
) | ||
where | ||
|
||
import Control.Applicative (Alternative(..)) | ||
import Control.Exception (displayException) | ||
import Data.Functor.Identity (runIdentity) | ||
import Streamly.Internal.Data.Parser (Parser) | ||
|
||
import Language.Haskell.TH | ||
import Language.Haskell.TH.Quote | ||
|
||
import qualified Streamly.Data.Fold as Fold | ||
import qualified Streamly.Internal.Data.Parser as Parser | ||
(some, many, takeWhile1) | ||
import qualified Streamly.Data.Stream as Stream (fromList, parse) | ||
import qualified Streamly.Internal.Unicode.Parser as Parser | ||
|
||
data StrSegment | ||
= StrText String | ||
| StrVar String | ||
deriving (Show, Eq) | ||
|
||
formatSpace :: String -> String | ||
formatSpace = foldr go "" | ||
where | ||
go x acc = x:if x == ' ' then dropWhile (' ' ==) acc else acc | ||
|
||
-- | Replace a newline by a space and convert multiple spaces to single space | ||
-- | ||
-- >>> :set -XQuasiQuotes | ||
-- >>> import Streamly.Internal.System | ||
-- >>> trim " abc \n bbb \n ccc " | ||
-- " abc bbb ccc " | ||
-- | ||
trim :: String -> String | ||
trim = formatSpace <$> (unwords . fmap formatSpace . lines) | ||
Comment on lines
+36
to
+49
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You only need There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We already have idiomatic code to do this:
|
||
|
||
haskellIdentifier :: Monad m => Parser Char m String | ||
haskellIdentifier = | ||
let p = Parser.alphaNum <|> Parser.char '\'' <|> Parser.char '_' | ||
in Parser.some p Fold.toList | ||
|
||
strParser :: Monad m => Parser Char m [StrSegment] | ||
strParser = Parser.many content Fold.toList | ||
|
||
where | ||
|
||
plainText = StrText . trim <$> Parser.takeWhile1 (/= '#') Fold.toList | ||
escHash = StrText . (: []) <$> (Parser.char '#' *> Parser.char '#') | ||
lineCont = StrText [] <$ (Parser.char '#' *> Parser.char '\n') | ||
var = StrVar <$> | ||
( Parser.char '#' | ||
*> Parser.char '{' | ||
*> haskellIdentifier | ||
<* Parser.char '}' | ||
) | ||
plainHash = StrText . (: []) <$> Parser.char '#' | ||
|
||
-- order is important | ||
content = plainText <|> escHash <|> lineCont <|> var <|> plainHash | ||
|
||
strSegmentExp :: StrSegment -> Q Exp | ||
strSegmentExp (StrText text) = stringE text | ||
strSegmentExp (StrVar name) = do | ||
valueName <- lookupValueName name | ||
case valueName of | ||
Just vn -> varE vn | ||
Nothing -> | ||
fail | ||
$ "cmd quote: Haskell symbol `" ++ name | ||
++ "` is not in scope" | ||
|
||
strExp :: [StrSegment] -> Q Exp | ||
strExp xs = appE [| concat |] $ listE $ map strSegmentExp xs | ||
Comment on lines
+31
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't need all this. This is replicated. |
||
|
||
expandVars :: String -> Q Exp | ||
expandVars ln = | ||
case runIdentity $ Stream.parse strParser (Stream.fromList ln) of | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can import There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's import as much as we can from streamly-core instead of writing it again. |
||
Left e -> | ||
fail $ "cmd QuasiQuoter parse error: " ++ displayException e | ||
Right x -> | ||
strExp x | ||
|
||
-- | A QuasiQuoter that treats the input as a string literal: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You'll need to change the documentation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can point it to the |
||
-- | ||
-- >>> [cmd|x|] | ||
-- "x" | ||
-- | ||
-- Any @#{symbol}@ is replaced by the value of the Haskell symbol @symbol@ | ||
-- which is in scope: | ||
-- | ||
-- >>> x = "hello" | ||
-- >>> [cmd|#{x} world!|] | ||
-- "hello world!" | ||
-- | ||
-- @##@ means a literal @#@ without the special meaning for referencing | ||
-- haskell symbols: | ||
-- | ||
-- >>> [cmd|##{x} world!|] | ||
-- "#{x} world!" | ||
-- | ||
-- A @#@ at the end of line means the line continues to the next line without | ||
-- introducing a newline character: | ||
-- | ||
-- >>> :{ | ||
-- [cmd|hello# | ||
-- world!|] | ||
-- :} | ||
-- "hello world!" | ||
-- | ||
-- Bugs: because of a bug in parsers, a lone # at the end of input gets | ||
-- removed. | ||
-- | ||
cmd :: QuasiQuoter | ||
cmd = | ||
QuasiQuoter | ||
{ quoteExp = expandVars | ||
, quotePat = notSupported | ||
, quoteType = notSupported | ||
, quoteDec = notSupported | ||
} | ||
|
||
where | ||
|
||
notSupported = error "cmd: Not supported." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put this in the command module rather than creating a new module.