summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorisovector <>2019-05-06 18:21:00 (GMT)
committerhdiff <hdiff@hdiff.luite.com>2019-05-06 18:21:00 (GMT)
commit76a4b83979c0744337dc8999813552679cf09930 (patch)
treea9883cc87de93f5882829a405b90b36dc11555a8
version 0.1.0.0HEAD0.1.0.0master
-rw-r--r--ChangeLog.md0
-rw-r--r--LICENSE117
-rw-r--r--README.md71
-rw-r--r--Setup.hs2
-rw-r--r--interpolatedstring-qq2.cabal57
-rw-r--r--src/Text/InterpolatedString/QQ2.hs165
-rw-r--r--tests/Test.hs53
7 files changed, 465 insertions, 0 deletions
diff --git a/ChangeLog.md b/ChangeLog.md
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ChangeLog.md
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2c4afab
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,117 @@
+CC0 1.0 Universal
+
+Statement of Purpose
+
+The laws of most jurisdictions throughout the world automatically confer
+exclusive Copyright and Related Rights (defined below) upon the creator and
+subsequent owner(s) (each and all, an "owner") of an original work of
+authorship and/or a database (each, a "Work").
+
+Certain owners wish to permanently relinquish those rights to a Work for the
+purpose of contributing to a commons of creative, cultural and scientific
+works ("Commons") that the public can reliably and without fear of later
+claims of infringement build upon, modify, incorporate in other works, reuse
+and redistribute as freely as possible in any form whatsoever and for any
+purposes, including without limitation commercial purposes. These owners may
+contribute to the Commons to promote the ideal of a free culture and the
+further production of creative, cultural and scientific works, or to gain
+reputation or greater distribution for their Work in part through the use and
+efforts of others.
+
+For these and/or other purposes and motivations, and without any expectation
+of additional consideration or compensation, the person associating CC0 with a
+Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
+and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
+and publicly distribute the Work under its terms, with knowledge of his or her
+Copyright and Related Rights in the Work and the meaning and intended legal
+effect of CC0 on those rights.
+
+1. Copyright and Related Rights. A Work made available under CC0 may be
+protected by copyright and related or neighboring rights ("Copyright and
+Related Rights"). Copyright and Related Rights include, but are not limited
+to, the following:
+
+ i. the right to reproduce, adapt, distribute, perform, display, communicate,
+ and translate a Work;
+
+ ii. moral rights retained by the original author(s) and/or performer(s);
+
+ iii. publicity and privacy rights pertaining to a person's image or likeness
+ depicted in a Work;
+
+ iv. rights protecting against unfair competition in regards to a Work,
+ subject to the limitations in paragraph 4(a), below;
+
+ v. rights protecting the extraction, dissemination, use and reuse of data in
+ a Work;
+
+ vi. database rights (such as those arising under Directive 96/9/EC of the
+ European Parliament and of the Council of 11 March 1996 on the legal
+ protection of databases, and under any national implementation thereof,
+ including any amended or successor version of such directive); and
+
+ vii. other similar, equivalent or corresponding rights throughout the world
+ based on applicable law or treaty, and any national implementations thereof.
+
+2. Waiver. To the greatest extent permitted by, but not in contravention of,
+applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
+unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
+and Related Rights and associated claims and causes of action, whether now
+known or unknown (including existing as well as future claims and causes of
+action), in the Work (i) in all territories worldwide, (ii) for the maximum
+duration provided by applicable law or treaty (including future time
+extensions), (iii) in any current or future medium and for any number of
+copies, and (iv) for any purpose whatsoever, including without limitation
+commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
+the Waiver for the benefit of each member of the public at large and to the
+detriment of Affirmer's heirs and successors, fully intending that such Waiver
+shall not be subject to revocation, rescission, cancellation, termination, or
+any other legal or equitable action to disrupt the quiet enjoyment of the Work
+by the public as contemplated by Affirmer's express Statement of Purpose.
+
+3. Public License Fallback. Should any part of the Waiver for any reason be
+judged legally invalid or ineffective under applicable law, then the Waiver
+shall be preserved to the maximum extent permitted taking into account
+Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
+is so judged Affirmer hereby grants to each affected person a royalty-free,
+non transferable, non sublicensable, non exclusive, irrevocable and
+unconditional license to exercise Affirmer's Copyright and Related Rights in
+the Work (i) in all territories worldwide, (ii) for the maximum duration
+provided by applicable law or treaty (including future time extensions), (iii)
+in any current or future medium and for any number of copies, and (iv) for any
+purpose whatsoever, including without limitation commercial, advertising or
+promotional purposes (the "License"). The License shall be deemed effective as
+of the date CC0 was applied by Affirmer to the Work. Should any part of the
+License for any reason be judged legally invalid or ineffective under
+applicable law, such partial invalidity or ineffectiveness shall not
+invalidate the remainder of the License, and in such case Affirmer hereby
+affirms that he or she will not (i) exercise any of his or her remaining
+Copyright and Related Rights in the Work or (ii) assert any associated claims
+and causes of action with respect to the Work, in either case contrary to
+Affirmer's express Statement of Purpose.
+
+4. Limitations and Disclaimers.
+
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
+ surrendered, licensed or otherwise affected by this document.
+
+ b. Affirmer offers the Work as-is and makes no representations or warranties
+ of any kind concerning the Work, express, implied, statutory or otherwise,
+ including without limitation warranties of title, merchantability, fitness
+ for a particular purpose, non infringement, or the absence of latent or
+ other defects, accuracy, or the present or absence of errors, whether or not
+ discoverable, all to the greatest extent permissible under applicable law.
+
+ c. Affirmer disclaims responsibility for clearing rights of other persons
+ that may apply to the Work or any use thereof, including without limitation
+ any person's Copyright and Related Rights in the Work. Further, Affirmer
+ disclaims responsibility for obtaining any necessary consents, permissions
+ or other rights required for any use of the Work.
+
+ d. Affirmer understands and acknowledges that Creative Commons is not a
+ party to this document and has no duty or obligation with respect to this
+ CC0 or use of the Work.
+
+For more information, please see
+<http://creativecommons.org/publicdomain/zero/1.0/>
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d1c2c6a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,71 @@
+# Text.InterpolatedString.QQ2
+
+[![Build Status](https://api.travis-ci.org/isovector/interpolatedstring-qq2.svg?branch=master)](https://travis-ci.org/isovector/interpolatedstring-qq2)
+[![Hackage](https://img.shields.io/hackage/v/interpolatedstring-qq2.svg?logo=haskell)](https://hackage.haskell.org/package/interpolatedstring-qq2)
+
+QuasiQuoter for QQ2-style multi-line interpolated strings with "q", "qq" and
+"qc" support.
+
+## Description
+
+QuasiQuoter for interpolated strings using Perl 6 syntax.
+
+The q form does one thing and does it well: It contains a multi-line string with
+no interpolation at all:
+
+```haskell
+{-# LANGUAGE QuasiQuotes, ExtendedDefaultRules #-}
+import Text.InterpolatedString.QQ2 (q)
+foo :: String -- Text, ByteString etc also works
+foo = [q|
+
+Well here is a
+ multi-line string!
+
+|]
+```
+
+Any instance of the IsString class is permitted.
+
+The qc form interpolates curly braces: expressions inside #{} will be
+directly interpolated if it's a Char, String, Text or ByteString, or
+it will have show called if it is not.
+
+Escaping of '{' is done with backslash.
+
+For interpolating numeric expressions without an explicit type signature,
+use the ExtendedDefaultRules lanuage pragma, as shown below:
+
+```haskell
+{-# LANGUAGE QuasiQuotes, ExtendedDefaultRules #-}
+import Text.InterpolatedString.QQ2 (qc)
+bar :: String
+bar = [qc| Well #{"hello" ++ " there"} #{6 * 7} |]
+```
+
+bar will have the value " Well hello there 42 ".
+
+If you want control over how show works on your types, define a custom
+ShowQ instance:
+
+For example, this instance allows you to display interpolated lists of strings as
+a sequence of words, removing those pesky brackets, quotes, and escape sequences.
+
+```haskell
+{-# LANGUAGE FlexibleInstances #-}
+import Text.InterpolatedString.QQ2 (qc, ShowQ(..))
+instance ShowQ [String] where
+ showQ = unwords
+```
+
+qc permits output to any types with both IsString and Monoid
+instances.
+
+```haskell
+{-# LANGUAGE QuasiQuotes, OverloadedStrings #-}
+import Text.InterpolatedString.QQ2 (qc)
+import Data.Text (Text)
+import Data.ByteString.Char8 (ByteString)
+qux :: ByteString
+qux = [qc| This will convert #{"Text" :: Text} to #{"ByteString" :: ByteString} |]
+```
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
index 0000000..9a994af
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/interpolatedstring-qq2.cabal b/interpolatedstring-qq2.cabal
new file mode 100644
index 0000000..2d843b5
--- /dev/null
+++ b/interpolatedstring-qq2.cabal
@@ -0,0 +1,57 @@
+cabal-version: 1.12
+name: interpolatedstring-qq2
+version: 0.1.0.0
+license: BSD3
+license-file: LICENSE
+copyright: 2019 Sandy Maguire
+maintainer: sandy@sandymaguire.me
+author: Sandy Maguire
+homepage: https://github.com/isovector/interpolatedstring-qq2#readme
+bug-reports: https://github.com/isovector/interpolatedstring-qq2/issues
+synopsis: QuasiQuoter for multi-line interpolated strings
+description:
+ Please see the README on GitHub at <https://github.com/isovector/interpolatedstring-qq2#readme>
+category: Data
+build-type: Simple
+extra-source-files:
+ README.md
+ ChangeLog.md
+
+source-repository head
+ type: git
+ location: https://github.com/isovector/interpolatedstring-qq2
+
+library
+ exposed-modules:
+ Text.InterpolatedString.QQ2
+ hs-source-dirs: src
+ other-modules:
+ Paths_interpolatedstring_qq2
+ default-language: Haskell2010
+ default-extensions: TemplateHaskell TypeSynonymInstances
+ FlexibleInstances UndecidableInstances
+ build-depends:
+ base >=4.7 && <5,
+ bytestring >=0.10.8.2 && <0.11,
+ haskell-src-meta >=0.8.0.3 && <0.9,
+ template-haskell >=2.14.0.0 && <2.15,
+ text >=1.2.3.1 && <1.3
+
+test-suite test
+ type: exitcode-stdio-1.0
+ main-is: Test.hs
+ hs-source-dirs: tests
+ other-modules:
+ Paths_interpolatedstring_qq2
+ default-language: Haskell2010
+ default-extensions: TemplateHaskell TypeSynonymInstances
+ FlexibleInstances UndecidableInstances
+ ghc-options: -threaded -rtsopts -with-rtsopts=-N
+ build-depends:
+ HUnit >=1.6.0.0 && <1.7,
+ base >=4.7 && <5,
+ bytestring >=0.10.8.2 && <0.11,
+ haskell-src-meta >=0.8.0.3 && <0.9,
+ interpolatedstring-qq2 -any,
+ template-haskell >=2.14.0.0 && <2.15,
+ text >=1.2.3.1 && <1.3
diff --git a/src/Text/InterpolatedString/QQ2.hs b/src/Text/InterpolatedString/QQ2.hs
new file mode 100644
index 0000000..b7906cd
--- /dev/null
+++ b/src/Text/InterpolatedString/QQ2.hs
@@ -0,0 +1,165 @@
+{-# LANGUAGE TemplateHaskell, TypeSynonymInstances, FlexibleInstances,
+ UndecidableInstances, MultiParamTypeClasses #-}
+
+-- | QuasiQuoter for interpolated strings using Perl 6 syntax.
+--
+-- The 'q' form does one thing and does it well: It contains a multi-line string with
+-- no interpolation at all:
+--
+-- @
+-- {-\# LANGUAGE QuasiQuotes, ExtendedDefaultRules #-}
+-- import Text.InterpolatedString.QQ2 (q)
+-- foo :: String -- 'Text', 'ByteString' etc also works
+-- foo = [q|
+--
+-- Well here is a
+-- multi-line string!
+--
+-- |]
+-- @
+--
+-- Any instance of the 'IsString' class is permitted.
+--
+-- The 'qc' form interpolates curly braces: expressions inside #{} will be
+-- directly interpolated if it's a 'Char', 'String', 'Text' or 'ByteString', or
+-- it will have 'show' called if it is not.
+--
+-- Escaping of '{' is done with backslash.
+--
+-- For interpolating numeric expressions without an explicit type signature,
+-- use the ExtendedDefaultRules lanuage pragma, as shown below:
+--
+-- @
+-- {-\# LANGUAGE QuasiQuotes, ExtendedDefaultRules #-}
+-- import Text.InterpolatedString.QQ2 (qc)
+-- bar :: String
+-- bar = [qc| Well #{\"hello\" ++ \" there\"} #{6 * 7} |]
+-- @
+--
+-- bar will have the value \" Well hello there 42 \".
+--
+-- If you want control over how 'show' works on your types, define a custom
+-- 'ShowQ' instance:
+--
+-- For example, this instance allows you to display interpolated lists of strings as
+-- a sequence of words, removing those pesky brackets, quotes, and escape sequences.
+--
+-- @
+-- {-\# LANGUAGE FlexibleInstances #-}
+-- import Text.InterpolatedString.QQ2 (qc, ShowQ(..))
+-- instance ShowQ [String] where
+-- showQ = unwords
+-- @
+--
+-- 'qc' permits output to any types with both 'IsString' and 'Monoid'
+-- instances.
+--
+-- @
+-- {-# LANGUAGE QuasiQuotes, OverloadedStrings #-}
+-- import Text.InterpolatedString.QQ2 (qc)
+-- import Data.Text (Text)
+-- import Data.ByteString.Char8 (ByteString)
+-- qux :: ByteString
+-- qux = [qc| This will convert #{\"Text\" :: Text} to #{\"ByteString\" :: ByteString} |]
+-- @
+
+module Text.InterpolatedString.QQ2 (qc, q, ShowQ(..)) where
+
+import Data.ByteString.Char8 as Strict (ByteString, unpack)
+import Data.ByteString.Lazy.Char8 as Lazy (ByteString, unpack)
+import Data.Monoid (Monoid(..))
+import Data.Text as T (Text, unpack)
+import Data.Text.Lazy as LazyT(Text, unpack)
+import GHC.Exts (IsString(..))
+import Language.Haskell.Meta.Parse
+import qualified Language.Haskell.TH as TH
+import Language.Haskell.TH.Quote
+
+-- |A class for types that use special interpolation rules.
+-- Instances of 'ShowQ' that are also instances of 'IsString' should obey the
+-- following law:
+--
+-- @
+-- fromString (showQ s) == s
+-- @
+--
+-- because this library relies on this fact to optimize
+-- away needless string conversions.
+class ShowQ a where
+ showQ :: a -> String
+
+instance ShowQ Char where
+ showQ = (:[])
+
+instance ShowQ String where
+ showQ = id
+
+instance ShowQ Strict.ByteString where
+ showQ = Strict.unpack
+
+instance ShowQ Lazy.ByteString where
+ showQ = Lazy.unpack
+
+instance ShowQ T.Text where
+ showQ = T.unpack
+
+instance ShowQ LazyT.Text where
+ showQ = LazyT.unpack
+
+instance {-# OVERLAPPABLE #-} Show a => ShowQ a where
+ showQ = show
+
+-- todo: this should really be rewritten into RULES pragmas, but so far
+-- I can't convince GHC to let the rules fire.
+class QQ a string where
+ toQQ :: a -> string
+
+instance {-# INCOHERENT #-} IsString s => QQ s s where
+ toQQ = id
+
+instance {-# INCOHERENT #-} (ShowQ a, IsString s) => QQ a s where
+ toQQ = fromString . showQ
+
+data StringPart = Literal String | AntiQuote String deriving Show
+
+unQC :: [Char] -> [Char] -> [StringPart]
+unQC a [] = [Literal (reverse a)]
+unQC a ('\\':x:xs) = unQC (x:a) xs
+unQC a ('\\':[]) = unQC ('\\':a) []
+unQC a ('}':xs) = AntiQuote (reverse a) : parseQC [] xs
+unQC a (x:xs) = unQC (x:a) xs
+
+parseQC :: [Char] -> [Char] -> [StringPart]
+parseQC a [] = [Literal (reverse a)]
+parseQC a ('\\':'\\':xs) = parseQC ('\\':a) xs
+parseQC a ('\\':'#':xs) = parseQC ('#':a) xs
+parseQC a ('\\':[]) = parseQC ('\\':a) []
+parseQC a ('#':'{':xs) = Literal (reverse a) : unQC [] xs
+parseQC a (x:xs) = parseQC (x:a) xs
+
+makeExpr :: [StringPart] -> TH.Q TH.Exp
+makeExpr [] = [| mempty |]
+makeExpr ((Literal a):xs) = TH.appE [| mappend (fromString a) |]
+ $ makeExpr xs
+makeExpr ((AntiQuote a):xs) = TH.appE [| mappend (toQQ $(reify a)) |]
+ $ makeExpr xs
+
+reify :: String -> TH.Q TH.Exp
+reify s =
+ case parseExp s of
+ Left s' -> TH.reportError s' >> [| mempty |]
+ Right e -> return e
+
+-- | QuasiQuoter for interpolating '{expr}' into a string literal. The pattern portion is undefined.
+qc :: QuasiQuoter
+qc = QuasiQuoter (makeExpr . parseQC [] . filter (/= '\r'))
+ (error "Cannot use qc as a pattern")
+ (error "Cannot use qc as a type")
+ (error "Cannot use qc as a dec")
+
+-- | QuasiQuoter for a non-interpolating string literal. The pattern portion is undefined.
+q :: QuasiQuoter
+q = QuasiQuoter ((\a -> [|fromString a|]) . filter (/= '\r'))
+ (error "Cannot use q as a pattern")
+ (error "Cannot use q as a type")
+ (error "Cannot use q as a dec")
diff --git a/tests/Test.hs b/tests/Test.hs
new file mode 100644
index 0000000..c0d6932
--- /dev/null
+++ b/tests/Test.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE QuasiQuotes, ExtendedDefaultRules, OverloadedStrings, IncoherentInstances #-}
+
+module Main where
+
+import Data.ByteString.Char8 as BS(ByteString, pack)
+import Data.Text as T(Text, pack)
+import GHC.Exts(fromString)
+import Test.HUnit
+import Text.InterpolatedString.QQ2
+
+data Foo = Foo Int String deriving Show
+
+t1 :: String
+t1 = "字元"
+
+testEmpty = assertBool "" ([qc||] == "")
+testCharLiteral = assertBool "" ([qc|#{1+2}|] == "3")
+testString = assertBool "" ([qc|a string #{t1} is here|] == "a string 字元 is here")
+testEscape = assertBool "" ([qc|\#{ }|] == "#{ }")
+testComplex = assertBool "" ([qc|
+ \ok
+#{Foo 4 "Great!" : [Foo 3 "Scott!"]}
+ then
+|] == ("\n" ++
+ " \\ok\n" ++
+ "[Foo 4 \"Great!\",Foo 3 \"Scott!\"]\n" ++
+ " then\n"))
+testConvert = assertBool ""
+ (([qc|#{fromString "a"::Text} #{fromString "b"::ByteString}|] :: String)
+ == "a b")
+
+tests = TestList
+ [ TestLabel "Empty String" $ TestCase testEmpty
+ , TestLabel "Character Literal" $ TestCase testCharLiteral
+ , TestLabel "String Variable" $ TestCase testString
+ , TestLabel "Escape Sequences" $ TestCase testEscape
+ , TestLabel "Complex Expression" $ TestCase testComplex
+ , TestLabel "String Conversion" $ TestCase testConvert
+ , TestLabel "ByteString Test" $ TestCase testByteString
+ , TestLabel "Text Test" $ TestCase testText
+ ]
+
+main = runTestTT tests
+
+
+-- the primary purpose of these tests is to ensure that
+-- the Text and ByteString rewrite rules are firing, to avoid
+-- needlessly converting string types
+testByteString = assertBool "" $ [qc|#{"a" :: ByteString} #{"b" :: ByteString}|]
+ == BS.pack ("a b")
+testText = assertBool "" $ [qc|#{"a" :: Text} #{"b" :: Text}|]
+ == T.pack ("a b")
+