marvin-interpolate 0.3.0 → 0.4.0
raw patch · 4 files changed
+77/−78 lines, 4 filesdep ~marvin-interpolatePVP ok
version bump matches the API change (PVP)
Dependency ranges changed: marvin-interpolate
API changes (from Hackage documentation)
+ Marvin.Interpolate: parser :: ParseM Parsed
Files
- README.md +9/−14
- marvin-interpolate.cabal +2/−2
- src/Marvin/Interpolate.hs +43/−27
- test/Spec.hs +23/−35
README.md view
@@ -15,7 +15,7 @@ import Marvin.Interpolate -myStr = [iq|some string %{show $ map succ [1,2,3]} and data |]+myStr = [iq|some string #{show $ map succ [1,2,3]} and data |] -- "some string [2,3,4] and data" ``` @@ -26,11 +26,11 @@ import Marvin.Interpolate -myStr = $(is "some string %{show $ map succ [1,2,3]} and data")+myStr = $(is "some string #{show $ map succ [1,2,3]} and data") -- "some string [2,3,4] and data" ``` -It basically transforms the interpolated string `[iq|interpolated string|]`, or in splices `$(is "interpolated string")` into a concatenation of all string bits and the expressions in `%{}`.+It basically transforms the interpolated string `[iq|interpolated string|]`, or in splices `$(is "interpolated string")` into a concatenation of all string bits and the expressions in `#{}`. Therefore it is not limited to `String` alone, rather it produces a literal at compile time, which can either be interpreted as `String` or, using the [`OverloadedStrings`](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#overloaded-string-literals) extension, as `Text` or `ByteString` or any other string type. `i` (for *interpolate quoter*) and `is` (for *interpolate splice*) is the basic interpolator, which inserts the expressions verbatim. Hence when using `iq` or `is` all expressions must return the desired string type.@@ -53,28 +53,23 @@ Interpolation uses the quasi quoter sytax, which starts with `[interpolator_name|` and ends with `|]` or splice syntax `$(interpolator "interpolated string")`. Anything in between is interpreted by the library. -The format string in between uses the syntax `%{expression}`.+The format string in between uses the syntax `#{expression}`. Any valid Haskell expression can be used inside the braces. And all names which are in scope can be used, like so. ```haskell-let x = 5 in [iS|x equals %{x}|] -- > "x equals 5"+let x = 5 in [iS|x equals #{x}|] -- > "x equals 5" ``` -There are four escape sequences to allow literal `%{` and `|]`+There are four escape sequences to allow literal `#{` and `|]` | Input | Output | |-------|--------|-| `~]` | `]` |-| `~%` | `%` |-| `~}` | `}` | -| `~~` | `~` |+| `#]` | `]` |+| `##` | `#` | -As a result the sequence `~%{` will show up as a literal `%{` in the output and `|~]` results in a literal `|]`.-Note that these are simple substitutions. -In general the characters themselves, if not escaped, will not throw errors, aka `~` will be `~` again in the output.-Escaping is only necessary in cases where these cahracters would have a special meaning otherwise.+As a result the sequence `##{` will show up as a literal `#{` in the output and `|#]` results in a literal `|]`. ## Differences to/Advantages over other libraries
marvin-interpolate.cabal view
@@ -1,5 +1,5 @@ name: marvin-interpolate-version: 0.3.0+version: 0.4.0 cabal-version: >=1.10 build-type: Simple license: BSD3@@ -43,7 +43,7 @@ main-is: Spec.hs build-depends: base >=4.9.0.0 && <4.10,- marvin-interpolate >=0.3.0 && <0.4,+ marvin-interpolate >=0.4.0 && <0.5, hspec >=2.2.4 && <2.3, text >=1.2.2.1 && <1.3 default-language: Haskell2010
src/Marvin/Interpolate.hs view
@@ -15,6 +15,7 @@ , iq -- * Internals/extension points , interpolateInto+ , parser ) where @@ -36,41 +37,56 @@ escapeChar :: Char escapeChar = '~' +type ParseM = Parsec String Int -parser :: Parsec String () Parsed++parser :: ParseM Parsed parser = manyTill (parseInterpolation <|> parseString) eof -parseString :: Parsec String () (Either String String)-parseString = Right <$> parseTillEscape "%{" True+parseString :: ParseM (Either String String)+parseString = do+ chunk <- many $ noneOf ['#']+ fmap (Right . (chunk ++)) $ (eof >> return "") <|> (lookAhead (try (char '#' >> anyChar)) >>= endOrEscape) <|> fmap return anyChar+ where+ endOrEscape :: Char -> ParseM String+ endOrEscape '{' = return ""+ endOrEscape '#' = count 2 anyChar >> return "#"+ endOrEscape ']' = count 2 anyChar >> return "]"+ endOrEscape _ = fail "" -parseInterpolation :: Parsec String () (Either String String)-parseInterpolation = Left <$> between (try $ string "%{") (char '}') (parseTillEscape "}" False) -parseTillEscape :: String -> Bool -> Parsec String () String-parseTillEscape endSeq@(endChar:_) allowEOF = do- chunk <- many $ noneOf [escapeChar, endChar]- rest <- eofEND- <|> (char escapeChar >> parseEscaped)- <|> (lookAhead (try $ string endSeq) >> return "")- <|> (return <$> char endChar)- return $ chunk <> rest+parseInterpolation :: ParseM (Either String String)+parseInterpolation = (try $ string "#{") >> (Left <$> parseExpr) where- eofEND- | allowEOF = eof >> return "" -- <|> (try (char escapeChar >> eof) >> char escapeChar >> return [escapeChar])- | otherwise = fail "EOF not allowed in interpolation"+ parseExpr = do+ chunk <- many $ noneOf ['}', '"', '\'', '{']+ fmap (chunk ++) $ (eof >> error "eof in interpolation") <|> (anyChar >>= continue) - parseEscaped = (eof >> return [escapeChar]) <|> do- next <- anyChar- let escaped- | next == escapeChar = [escapeChar]- | next == '%' = "%"- | next == ']' = "]"- | next == '}' = "}"- | otherwise = escapeChar : [next]- rest <- parseTillEscape endSeq allowEOF- return $ escaped <> rest+ continue :: Char -> ParseM String+ continue '{' = modifyState succ >> fmap ('{':) parseExpr+ continue '}' = do+ s <- getState+ if s == 0+ then return ""+ else ('}':) <$> (modifyState succ >> parseExpr)+ continue '\"' = ('"':) <$> parseStr+ continue '\'' = do+ char '\''+ inner <- ((:) <$> char '\\' <*> fmap return anyChar) <|> fmap return anyChar+ char '\''+ return $ '\'':inner ++ "'" + parseStr = do+ chunk <- many $ noneOf ['"', '\\']+ fmap (chunk ++) $ (eof >>= fail "eof in string literal") + <|> (anyChar >>= continueStr)+ where + continueStr '"' = ('"':) <$> parseExpr+ continueStr '\\' = do+ escaped <- anyChar+ (\a -> '\\':escaped:a) <$> parseStr + evalExprs :: Parsed -> [Either Exp String] evalExprs l = evalState (mapM stitch l) decls where@@ -103,7 +119,7 @@ foldl f (LitE (StringL "")) interleaved where- parsed = either (error . show) id $ parse parser "inline" str+ parsed = either (error . show) id $ runParser parser 0 "inline" str interleaved = evalExprs parsed f expr bit = AppE (VarE 'mappend) expr `AppE` bitExpr
test/Spec.hs view
@@ -28,26 +28,14 @@ main :: IO () main = hspec $ do describe "parsing to itself" $ do- it "%" $- [iq|%|] `shouldBe` "%"- it "%anything" $- [iq|%anything|] `shouldBe` "%anything"- it "~" $- [iq|~|] `shouldBe` "~"- it "~anything" $- $(is "~anything") `shouldBe` "~anything"+ it "#" $+ [iq|#|] `shouldBe` "#"+ it "#anything" $+ [iq|#anything|] `shouldBe` "#anything" - describe "parsing escape sequences" $ do- it "parses ~% as %" $- [iq|~%|] `shouldBe` "%"- it "parses ~] as ]" $- [iq|~]|] `shouldBe` "]"- it "parses ~%{} as %{}" $- [iq|~%{}|] `shouldBe` "%{}"- it "parses |~] as |]" $- [iq||~]|] `shouldBe` "|]"- it "parses ~~ as ~" $- [iq|~~|] `shouldBe` "~"+ describe "parsing escape sequences" $+ it "parses ## as #" $+ [iq|##|] `shouldBe` "#" describe "interpolation substitution" $ do it "leaves an empty string" $@@ -56,32 +44,32 @@ [iq|this is not changed|] `shouldBe` "this is not changed" it "interpolates just an external variable" $ let y = "str" in- [iq|%{y}|] `shouldBe` y+ [iq|#{y}|] `shouldBe` y it "interpolates an external variable" $- let y = "str2" in [iq| hello you %{y} end|] `shouldBe` " hello you " ++ y ++ " end"+ let y = "str2" in [iq| hello you #{y} end|] `shouldBe` " hello you " ++ y ++ " end" it "interpolates the variable x (used to be special)" $- let x = "str" in [iq| hello x: %{x}|] `shouldBe` " hello x: " ++ x+ let x = "str" in [iq| hello x: #{x}|] `shouldBe` " hello x: " ++ x it "interpolates infix with $" $- [iq|str %{show $ 4 + (5 :: Int)} str|] `shouldBe` "str 9 str"+ [iq|str #{show $ 4 + (5 :: Int)} str|] `shouldBe` "str 9 str" it "interpolates multiple bindings" $ let x = "multiple" y = "can" z = "local scope"- in [iq|We %{y} interpolate %{x} bindings from %{z}|]+ in [iq|We #{y} interpolate #{x} bindings from #{z}|] `shouldBe` "We can interpolate multiple bindings from local scope" it "interpolates complex expressions" $ let x = ["haskell", "expression"] y = " can be"- in [iq|Any %{intercalate " " x ++ y} interpolated|]+ in [iq|Any #{intercalate " " x ++ y} interpolated|] `shouldBe` "Any haskell expression can be interpolated" describe "splice interpolation" $ it "interpolates a splice" $- let x = 5 :: Int in $(isS "%{x}") `shouldBe` "5"+ let x = 5 :: Int in $(isS "#{x}") `shouldBe` "5" describe "'is' generic interpolation" $ do it "to string" $@@ -95,24 +83,24 @@ describe "'isS' interpolation to String" $ do it "calls show on Int" $- $(isS "%{x}") `shouldBe` "5"+ $(isS "#{x}") `shouldBe` "5" it "calls showStr if available" $- $(isS "%{G}") `shouldBe` "showStr"+ $(isS "#{G}") `shouldBe` "showStr" it "does not change Text" $- $(isS "%{\"str\" :: T.Text}") `shouldBe` "str"+ $(isS "#{\"str\" :: T.Text}") `shouldBe` "str" describe "'isT' interpolation to Text" $ do it "calls show on Int" $- $(isT "%{x}") `shouldBe` "5"+ $(isT "#{x}") `shouldBe` "5" it "calls showT if available" $- $(isT "%{G}") `shouldBe` "showT"+ $(isT "#{G}") `shouldBe` "showT" it "does not change Text" $- $(isT "%{\"str\" :: T.Text}") `shouldBe` "str"+ $(isT "#{\"str\" :: T.Text}") `shouldBe` "str" describe "'isL' interpolation to lazy Text" $ do it "calls show on Int" $- $(isL "%{x}") `shouldBe` "5"+ $(isL "#{x}") `shouldBe` "5" it "calls showStr if available" $- $(isL "%{G}") `shouldBe` "showL"+ $(isL "#{G}") `shouldBe` "showL" it "does not change Text" $- $(isL "%{\"str\" :: T.Text}") `shouldBe` "str"+ $(isL "#{\"str\" :: T.Text}") `shouldBe` "str"