packages feed

parsley-garnish (empty) → 0.1.0.0

raw patch · 7 files changed

+352/−0 lines, 7 filesdep +basedep +ghcdep +ghc-tcplugins-extra

Dependencies added: base, ghc, ghc-tcplugins-extra, parsley, syb, template-haskell

Files

+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for `parsley-garnish`++## 0.1.0.0 -- 2021-06-10++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2021, Jamie Willis, Matthew Pickering++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Jamie Willis, Matthew Pickering, nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,51 @@+# Parsley Garnish+This repo houses the `parsley`-specific GHC plugins to help make writing +parsers with `parsley` take less boilerplate.++Each plugin here will in some way interact to produce values of either +type `Parsley.WQ` or `Parsley.Defunctionalized.Defunc`. These datatypes +are how `parsley`'s API interacts with user-land functions.++By default, the user can produce values of these types by using the +`makeQ` function:++```hs+makeQ :: Quapplicative q => a -> Code a -> q a+```++Where both `WQ` and `Defunc` are instances of `Quapplicative`. However, +this can be tedious to do by hand.++## `OverloadedQuotes`+The first plugin found in the garnish hijacks the regular Haskell syntax +for _Untyped_ Template Haskell (UTH). Since `parsley` uses _Typed_ +Template Haskell (TTH), it is unlikely that a user of the library will +need to be using UTH in the same file (with the possible exception of +top-level splices, or quotes other than the basic `[|x|]`). This plugin +will transform every UTH quote in a file so that it represents a value of +`Quapplicative q => q a`. This transformation is as follows:++```hs+qsucc :: Quapplicative q => q Int -> q Int+qsucc qx = [|$(qx) + 1|]+-- goes to:+qsucc qx = makeQ (_val qx + 1) [||$$(_code qx) + 1||]+```++Values of `Defunc` can also be spliced in directly:++```hs+diffcons :: Defunc a -> Defunc ([a] -> [a]) -> Defunc ([a] -> [a])+diffcons qx qdxs = [| $(COMPOSE) ($(CONS) $(qx)) $(qdxs) |]+```++And lambda abstraction works too (along with any other syntax):++```hs+diffcons' :: Defunc (a -> ([a] -> [a]) -> [a] -> [a])+diffcons' = [|\x dxs -> $(diffcons [|x|] [|dxs|])|]+```++The disadvantage to this plugin _currently_ is that it does not make any +attempt to  leverage the specialised parts of `Defunc` to improve the code +generation and inspectibility. The user would be left to use this manually.
+ parsley-garnish.cabal view
@@ -0,0 +1,51 @@+name:               parsley-garnish+version:            0.1.0.0+synopsis:           A collection of GHC plugins to work with parsley+description:        This package contains a collection (for now one) to help+                    remove boilerplate when writing parsers using @parsley@.+homepage:           https://github.com/j-mie6/parsley-garnish+bug-reports:        https://github.com/j-mie6/parsley-garnish/issues             +license:            BSD3+license-file:       LICENSE+author:             Jamie Willis, Garnish Contributors+-- When the lift-plugin + overloaded-syntax is in this can change+--, Matthew Pickering+maintainer:         Jamie Willis <j.willis19@imperial.ac.uk>+category:           Plugin+build-type:         Simple+extra-doc-files:    ChangeLog.md+                    README.md+cabal-version:      1.18++library+  exposed-modules:  +--                    Parsley.Garnish,+--                    Parsley.LiftPlugin+--                    Parsley.OverloadedSyntaxPlugin+                    Parsley.OverloadedQuotesPlugin+  other-modules:    Parsley.PluginUtils+--                    -- Lift Plugin+--                    Parsley.LiftPlugin.Plugin+--                    Parsley.LiftPlugin.Error+--                    Parsley.LiftPlugin.Fake+--                    Parsley.LiftPlugin.LiftReplace+--                    Parsley.LiftPlugin.LiftFind+                    -- Overloaded Syntax Plugin+--                    Parsley.OverloadedSyntaxPlugin.Plugin+                    -- Overloaded Quotes Plugin+                    Parsley.OverloadedQuotesPlugin.Plugin++  build-depends:    base                >= 4.10    && < 5,+                    parsley             >= 0.1     && < 0.2,+                    template-haskell    >= 2.14    && < 3,+                    ghc-tcplugins-extra >= 0.3     && < 0.5,+                    ghc                 >= 8.6     && < 9,+                    syb                 >= 0.7     && < 0.8++  hs-source-dirs:   src+  ghc-options:      -Wall+  default-language: Haskell2010++source-repository head+  type:                git+  location:            https://github.com/j-mie6/parsley-garnish
+ src/Parsley/OverloadedQuotesPlugin.hs view
@@ -0,0 +1,38 @@+{-|+Module      : Parsley.OverloadedQuotesPlugin+Description : Plugin for replacing Untyped Template Haskell quotes with+              @parsley@'s @Quapplicative@ values.+License     : BSD-3-CLAUSE+Maintainer  : Jamie Willis+Stability   : stable++This plugin hijacks the regular Haskell syntax +for /Untyped/ Template Haskell (UTH). Since @parsley@ uses /Typed/ +Template Haskell (TTH), it is unlikely that a user of the library will +need to be using UTH in the same file (with the possible exception of +top-level splices, or quotes other than the basic @[|x|]@). This plugin +will transform every UTH quote in a file so that it represents a value of +@Quapplicative q => q a@. This transformation is as follows:++> qsucc :: Quapplicative q => q Int -> q Int+> qsucc qx = [|$(qx) + 1|]+> -- goes to:+> qsucc qx = makeQ (_val qx + 1) [||$$(_code qx) + 1||]++Values of @Parsley.Defunctionalized.Defunc@ can also be spliced in directly:++> diffcons :: Defunc a -> Defunc ([a] -> [a]) -> Defunc ([a] -> [a])+> diffcons qx qdxs = [| $(COMPOSE) ($(CONS) $(qx)) $(qdxs) |]++And lambda abstraction works too (along with any other syntax):++> diffcons' :: Defunc (a -> ([a] -> [a]) -> [a] -> [a])+> diffcons' = [|\x dxs -> $(diffcons [|x|] [|dxs|])|]++The disadvantage to this plugin /currently/ is that it does not make any +attempt to  leverage the specialised parts of @Defunc@ to improve the code +generation and inspectibility. The user would be left to use this manually.+-}+module Parsley.OverloadedQuotesPlugin (module Plugin) where++import Parsley.OverloadedQuotesPlugin.Plugin as Plugin
+ src/Parsley/OverloadedQuotesPlugin/Plugin.hs view
@@ -0,0 +1,123 @@+{-# OPTIONS_GHC -Wno-missing-pattern-synonym-signatures #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DeriveFoldable #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE RankNTypes #-}+module Parsley.OverloadedQuotesPlugin.Plugin (plugin) where++import Plugins       (Plugin (..), defaultPlugin, purePlugin)+import TcRnTypes     (TcGblEnv, TcM)+import Data.Generics (GenericT, GenericQ, mkT, mkQ, everywhere, gmapT)+import GHC.Generics  (Generic)++import qualified GHC               (HsGroup, GhcRn, Name, GenLocated(L), SrcSpan)+import qualified GhcPlugins as GHC (CommandLineOption)+import qualified TcRnMonad as GHC  (getTopEnv)++#if __GLASGOW_HASKELL__ < 810+import qualified HsExpr as Expr+#else+import qualified GHC.Hs.Expr as Expr+#endif++import Parsley.PluginUtils (lookupModuleInPackage, lookupName, lookupNames)++#if __GLASGOW_HASKELL__ >= 810+import GHC.Hs.Extension+noExt :: NoExtField+noExt = noExtField+#else+import GHC (noExt)+#endif++type Expr = Expr.LHsExpr GHC.GhcRn++{-|+This plugin repurposes /Untyped/ Template Haskell quotes (and splices within them)+to be @Parsley.Quapplicative@ values.+-}+plugin :: Plugin+plugin = defaultPlugin { renamedResultAction = overloadedQuotes, pluginRecompile = purePlugin }++data QOps a = QOps {+    _code :: a,+    _val :: a,+    makeQ :: a+  } deriving (Functor, Foldable, Traversable, Generic)++quapplicativeStrings :: QOps String+quapplicativeStrings = QOps {+    _code = "_code",+    _val  = "_val",+    makeQ = "makeQ"+  }++overloadedQuotes :: [GHC.CommandLineOption] -> TcGblEnv -> GHC.HsGroup GHC.GhcRn -> TcM (TcGblEnv, GHC.HsGroup GHC.GhcRn)+overloadedQuotes _ gEnv rn = do+  hscEnv <- GHC.getTopEnv+  parsley <- lookupModuleInPackage hscEnv "parsley" "Parsley.Internal.Common.Utils"+  qops <- lookupNames parsley quapplicativeStrings+  prelude <- lookupModuleInPackage hscEnv "base" "GHC.Err"+  undef <- lookupName prelude "undefined"+  -- This is a little inefficient, since the top-down transformation means no quotes can be+  -- found under a top-level one: we use a top-down version of everywhereBut to stop traversal+  -- of this whenever it fires+  return (gEnv, onlyTopmost (mkQ False isQuote) (mkT (transformUTHQuote qops undef)) rn)++mkApp :: GHC.SrcSpan -> Expr -> Expr -> Expr+mkApp s f = GHC.L s . Expr.HsApp noExt f . mkPar s++mkVar :: GHC.SrcSpan -> GHC.Name -> Expr+mkVar s = GHC.L s . Expr.HsVar noExt . GHC.L s++mkPar :: GHC.SrcSpan -> Expr -> Expr+mkPar s = GHC.L s . Expr.HsPar noExt++pattern LUTHQuote s ex1 ex2 x <- GHC.L s (Expr.HsRnBracketOut ex1 (Expr.ExpBr ex2 x) _)+pattern LUTHSplice s ex1 ex2 dec name x = GHC.L s (Expr.HsSpliceE ex1 (Expr.HsUntypedSplice ex2 dec name x))++isQuote :: Expr -> Bool+isQuote (LUTHQuote _ _ _ _) = True+isQuote _                   = False++-- The goal here is to find [|e|] and turn it into makeQ e [||e||]+-- The catch is that for any $qe in the quote, it must be hoisted out, let bound and then re-incorporated+-- such that `[|x .. $qe .. y|]` ~> `let qe' = qe in makeQ (x .. _val qe' .. y) [||x .. $$(_code qe') .. y||]`+-- This means that the content of the splice _cannot_ use any of the free-variables defined within+-- the original quote. Perhaps in that case we could just inline the definition into both holes...+-- As `transform` works bottom up, we can always assume nested quotes are already handled: this might+-- get tricky, however.+transformUTHQuote :: QOps GHC.Name -> GHC.Name -> Expr -> Expr+transformUTHQuote ops undef (LUTHQuote s ex ex' x) = --pprTouch "new quote" $+  mkPar s (makeQS `mkAppS` everywhere (mkT (transformUTHQuoteVar (_val ops) makeVal)) x+                  `mkAppS` mkQuote (everywhere (mkT (transformUTHQuoteCode (_code ops) makeCode)) x))+  where+    mkQuote y = GHC.L s (Expr.HsBracket ex (Expr.TExpBr ex' y))+    mkAppS = mkApp s+    makeQS = mkVar s (makeQ ops)+    makeVal y = mkPar s (makeQS `mkAppS` y `mkAppS` mkVar s undef)+    makeCode y = mkPar s (makeQS `mkAppS` mkVar s undef `mkAppS` y)+transformUTHQuote _ _ x = x++transformUTHQuoteVar :: GHC.Name -> (Expr -> Expr) -> Expr -> Expr+transformUTHQuoteVar _    makeVal (LUTHQuote _ _ _ x)      = makeVal x+transformUTHQuoteVar _val _       (LUTHSplice s _ _ _ _ x) = mkApp s (mkVar s _val) x+transformUTHQuoteVar _    _       x                        = x++transformUTHQuoteCode :: GHC.Name -> (Expr -> Expr) -> Expr -> Expr+transformUTHQuoteCode _     makeCode (LUTHQuote s ex ex' x)         = makeCode (GHC.L s (Expr.HsBracket ex (Expr.TExpBr ex' x)))+transformUTHQuoteCode _code _        (LUTHSplice s ex ex' d name x) = GHC.L s . Expr.HsSpliceE ex . Expr.HsTypedSplice ex' d name $+  mkApp s (mkVar s _code) x+transformUTHQuoteCode _     _        x                              = x++onlyTopmost :: GenericQ Bool -> GenericT -> GenericT+onlyTopmost q f = go+  where+    go :: GenericT+    go x+      | q x = f x+      | otherwise = gmapT go x
+ src/Parsley/PluginUtils.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}+module Parsley.PluginUtils where++import qualified GHC.TcPluginM.Extra as TCPluginExtra (lookupName)++-- GHC API+import TcRnTypes (TcM, TcPluginM)+import Outputable++-- ghc+import qualified GhcPlugins as GHC+import qualified IfaceEnv as GHC (lookupOrig)+import Finder (findImportedModule, FindResult(Found))+import FastString (mkFastString)+import Module (Module, mkModuleName)+import Name (Name)+import Control.Monad.IO.Class ( liftIO )++class Monad m => Lookup m where+  lookupOrig :: Module -> GHC.OccName -> m Name++instance Lookup TcM where+  lookupOrig = GHC.lookupOrig++instance Lookup TcPluginM where+  lookupOrig = TCPluginExtra.lookupName++pprTouch :: Outputable a => String -> a -> a+pprTouch name x = pprTrace name (ppr x) x++lookupNames :: (Lookup m, Traversable t) => Module -> t String -> m (t Name)+lookupNames = traverse . lookupName++lookupName :: Lookup m => Module -> String -> m Name+lookupName pm = lookupOrig pm . GHC.mkVarOcc++lookupClass :: Lookup m => Module -> String -> m Name+lookupClass pm = lookupOrig pm . GHC.mkTcOcc++lookupIds :: Traversable t => Module -> t String -> TcM (t GHC.Id)+lookupIds = traverse . lookupId++lookupId :: Module -> String -> TcM GHC.Id+lookupId pm name = lookupName pm name >>= GHC.lookupId++lookupModule :: GHC.HscEnv -> String -> TcM Module+lookupModule hscEnv modName = do+  Found _ md <- liftIO (findImportedModule hscEnv (mkModuleName modName) Nothing)+  return md++lookupModuleInPackage :: GHC.HscEnv -> String -> String -> TcM Module+lookupModuleInPackage hscEnv package modName = do+  Found _ md <- liftIO (findImportedModule hscEnv (mkModuleName modName) (Just (mkFastString package)))+  return md