-
Notifications
You must be signed in to change notification settings - Fork 2
Add a cmd quasiquoter #75
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
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 : streamly@composewell.com | ||
| -- 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
Member
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
Member
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
Member
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 | ||
|
Member
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
Member
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: | ||
|
Member
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.
Member
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.