diff --git a/CHANGELOG.md b/CHANGELOG.md index ba221d51..e610530f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,34 @@ # Changelog for `distributors` +## 0.5.0.0 - 2026-04-16 + +### Changes + +- `MonadTry` now implies `BackusNaurForm` (so `rule` tracing/failure semantics are available) + and `Filtrator` (via `MonadPlus`, with `filtrate = mfiltrate`). +- Simplified the default implementation of `terminal`. +- Added `applicativeG` and `monadG` generators via `Joker` orphan and non-orphan instances. +- Made nomenclature consistent with use of "fail" and "failure", not "error". + +### Internal + +- Moved orphan instances and Template Haskell internals to `Control.Lens.Grammar.Internal`. + +### Documentation + +- Expanded `BackusNaurForm` documentation with separate motivation from: + category-theoretic structure and failure-tracing semantics (both called “trace” + in different senses, and combined by BNF-style rules). +- Added a `monadG` Megaparsec example. +- Fixed typo in the `makeNestedPrisms` example. + ## 0.4.0.0 - 2026-04-10 ### New Modules - `Control.Monad.Fail.Try` - `MonadTry` class with `try` & `fail` for backtracking parsers -- `Data.Profunctor.Grammar.Parsector` - Invertible LL(1) parser with Parsec-style error reporting: - `ParsecState`, `ParsecError`, `parsecP`, `unparsecP`; implements hints, LL(1) commitment +- `Data.Profunctor.Grammar.Parsector` - Invertible LL(1) parser with Parsec-style failure reporting: + `ParsecState`, `ParsecFailure`, `parsecP`, `unparsecP`; implements hints, LL(1) commitment via `parsecLooked`, and `try` for explicit backtracking - `Data.Profunctor.Separator` - Separator/delimiter combinators: `sepWith`, `noSep`, `beginWith`, `endWith`, `several`, `several1`, `intercalateP`, `chain`, `chain1` diff --git a/distributors.cabal b/distributors.cabal index 7350ef47..546551fa 100644 --- a/distributors.cabal +++ b/distributors.cabal @@ -5,7 +5,7 @@ cabal-version: 2.2 -- see: https://github.com/sol/hpack name: distributors -version: 0.4.0.0 +version: 0.5.0.0 synopsis: Unifying Parsers, Printers & Grammars description: Distributors provides mathematically inspired abstractions for coders to write parsers that can also be inverted to printers. category: Profunctors, Optics, Parsing @@ -33,11 +33,12 @@ library Control.Lens.Grammar Control.Lens.Grammar.BackusNaur Control.Lens.Grammar.Boole + Control.Lens.Grammar.Internal.NestedPrismTH + Control.Lens.Grammar.Internal.Orphanage Control.Lens.Grammar.Kleene Control.Lens.Grammar.Symbol Control.Lens.Grammar.Token Control.Lens.Grate - Control.Lens.Internal.NestedPrismTH Control.Lens.Monocle Control.Lens.PartialIso Control.Lens.Wither @@ -186,6 +187,7 @@ test-suite test , doctest >=0.18 && <1 , hspec >=2.7 && <3 , lens >=5.0 && <6 + , megaparsec >=9.0 && <10 , mtl >=2.2 && <3 , profunctors >=5.6 && <6 , tagged >=0.8 && <1 diff --git a/package.yaml b/package.yaml index a4bcd1c3..9f1c9a07 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: distributors -version: 0.4.0.0 +version: 0.5.0.0 github: "morphismtech/distributors" license: BSD-3-Clause author: "Eitan Chatav" @@ -97,4 +97,5 @@ tests: - distributors - doctest >= 0.18 && < 1 - hspec >= 2.7 && < 3 + - megaparsec >= 9.0 && < 10 - QuickCheck >= 2.14 && < 3 diff --git a/src/Control/Lens/Bifocal.hs b/src/Control/Lens/Bifocal.hs index b8e014cc..439a07ff 100644 --- a/src/Control/Lens/Bifocal.hs +++ b/src/Control/Lens/Bifocal.hs @@ -59,8 +59,7 @@ type Bifocal s t a b = forall p f. (Alternator p, Filtrator p, Alternative f, Filterable f) => p a (f b) -> p s (f t) -{- | If you see `ABifocal` in a signature for a function, -the function is expecting a `Bifocal`. -} +{- | `ABifocal` is monomorphically a `Bifocal`. -} type ABifocal s t a b = Binocular a b a (Maybe b) -> Binocular a b s (Maybe t) diff --git a/src/Control/Lens/Grammar.hs b/src/Control/Lens/Grammar.hs index 6612325c..0cf344f7 100644 --- a/src/Control/Lens/Grammar.hs +++ b/src/Control/Lens/Grammar.hs @@ -23,6 +23,7 @@ module Control.Lens.Grammar , RegBnf (..) , regbnfG , regbnfGrammar + , applicativeG -- * Context-sensitive grammar , CtxGrammar , printG @@ -30,6 +31,8 @@ module Control.Lens.Grammar , unparseG , parsecG , unparsecG + , readG + , monadG -- * Utility , putStringLn -- * Re-exports @@ -44,6 +47,7 @@ import Control.Lens.Grammar.Boole import Control.Lens.Grammar.Kleene import Control.Lens.Grammar.Token import Control.Lens.Grammar.Symbol +import Data.Bifunctor.Joker import Data.Maybe hiding (mapMaybe) import Data.Monoid import Data.Profunctor.Distributor @@ -56,6 +60,7 @@ import Data.Profunctor.Separator import Data.String import GHC.Exts import Prelude hiding (filter) +import Text.ParserCombinators.ReadP (ReadP, readP_to_S) import Witherable -- Re-exports @@ -94,7 +99,7 @@ data SemVer = SemVer -- e.g., 2.1.5-rc.1+build.123 We'd like to define an optic @_SemVer@, corresponding to the constructor pattern @SemVer@. -You _could_ generate it with the TemplateHaskell combinator, +You could generate it with the TemplateHaskell combinator, `makeNestedPrisms`. @makeNestedPrisms ''SemVer@ @@ -275,8 +280,8 @@ and generator support for `ruleRec`. -} type Grammar token a = forall p. ( Lexical token p - , forall x. BackusNaurForm (p x x) , Alternator p + , forall x. BackusNaurForm (p x x) ) => p a a {- | For context-sensitivity, @@ -358,7 +363,7 @@ the context-sensitivity of `CtxGrammar` implies unrestricted filtration of grammars by computable predicates, which can recognize the larger class of recursively enumerable languages. -Finally, `CtxGrammar`s support error reporting and backtracking. +Finally, `CtxGrammar`s support failure reporting and backtracking. This has no effect on `printG`, `parseG` or `unparseG`; but it effects `parsecG` and `unparsecG`. For context, an @LL@ grammar can be (un)parsed by an @LL@ parser. @@ -373,18 +378,17 @@ Since both `Parsor` & `Parsector` are @LL@ parsers they diverge if the `CtxGrammar` they're run on is left-recursive. >>> parsecG (rule "foo" (fail "bar") <|> fail "baz") "abc" -ParsecState {parsecLooked = False, parsecOffset = 0, parsecStream = "abc", parsecError = ParsecError {parsecExpect = TokenClass (OneOf (fromList "")), parsecLabels = [Node {rootLabel = "foo", subForest = [Node {rootLabel = "bar", subForest = []}]},Node {rootLabel = "baz", subForest = []}]}, parsecResult = Nothing} +ParsecState {parsecLooked = False, parsecOffset = 0, parsecStream = "abc", parsecFailure = ParsecFailure {parsecExpect = TokenClass (OneOf (fromList "")), parsecLabels = [Node {rootLabel = "foo", subForest = [Node {rootLabel = "bar", subForest = []}]},Node {rootLabel = "baz", subForest = []}]}, parsecResult = Nothing} >>> parsecG (manyP (token 'a') >*< asIn @Char DecimalNumber) "aaab" -ParsecState {parsecLooked = True, parsecOffset = 3, parsecStream = "b", parsecError = ParsecError {parsecExpect = TokenClass (Alternate (TokenClass (OneOf (fromList "a"))) (TokenClass (NotOneOf (fromList "") (AndAsIn DecimalNumber)))), parsecLabels = []}, parsecResult = Nothing} +ParsecState {parsecLooked = True, parsecOffset = 3, parsecStream = "b", parsecFailure = ParsecFailure {parsecExpect = TokenClass (Alternate (TokenClass (OneOf (fromList "a"))) (TokenClass (NotOneOf (fromList "") (AndAsIn DecimalNumber)))), parsecLabels = []}, parsecResult = Nothing} >>> unparsecG (tokens "abc") "abx" "" -ParsecState {parsecLooked = True, parsecOffset = 2, parsecStream = "ab", parsecError = ParsecError {parsecExpect = TokenClass (OneOf (fromList "c")), parsecLabels = []}, parsecResult = Nothing} +ParsecState {parsecLooked = True, parsecOffset = 2, parsecStream = "ab", parsecFailure = ParsecFailure {parsecExpect = TokenClass (OneOf (fromList "c")), parsecLabels = []}, parsecResult = Nothing} -} type CtxGrammar token a = forall p. ( Lexical token p - , forall x. BackusNaurForm (p x x) , Alternator p , Filtrator p , MonadicTry p @@ -768,7 +772,7 @@ regbnfGrammar :: Grammar Char RegBnf regbnfGrammar = rule "regbnf" $ _RegBnf . _Bnf >~ terminal "{start} = " >* regexGrammar >*< several noSep (terminal "\n" >* nonterminalG *< terminal " = " >*< regexGrammar) - + {- | `regstringG` generates a `RegString` from a regular grammar. Since context-free `Grammar`s and `CtxGrammar`s aren't necessarily regular, @@ -841,7 +845,7 @@ the type system will allow `parsecG` to be applied to them. Running the parser on an input string value `uncons`es tokens from the beginning of an input string from left to right, returning `parsecResult` as `Nothing` on failure or `Just` -an output syntax value, with parse failure stored in `parsecError`, +an output syntax value, with parse failure stored in `parsecFailure`, and a remaining output `parsecStream`. -} parsecG @@ -870,6 +874,77 @@ unparsecG -> ParsecState string a unparsecG parsector = unparsecP parsector +{- | Generate any `Applicative` parser backend +from a `Grammar` with `applicativeG`. +It works the same way as `monadG`, +for parsers without `Monad` instances. +That permits backends to use algorithms +that can only parse context-free `Grammar`s. +-} +applicativeG + :: ( Alternative f + , TokenAlgebra token (f token) + , TerminalSymbol token (f ()) + , forall x. BackusNaurForm (f x) + ) + => Grammar token a -- ^ context-free grammar + -> f a +applicativeG joker = runJoker joker + +{- | Generate a `ReadP` backend from a `CtxGrammar` `Char`. -} +readG :: CtxGrammar Char a -> ReadP a +readG joker = monadG joker + +{- | Generate any parser `Monad` backend +from a `CtxGrammar` with `monadG`. +Let's see how to do this without orphan instances, +using the Megaparsec library. + +@ +import qualified Text.Megaparsec as M +import qualified Text.Megaparsec.Char as M +import Control.Lens.Grammar + +newtype WrapMega a = WrapMega {unwrapMega :: M.Parsec String String a} + deriving newtype + ( Functor, Applicative, Alternative + , Monad, MonadPlus, MonadFail + ) +instance TerminalSymbol Char (WrapMega ()) where + terminal str = WrapMega (M.chunk str *> pure ()) +instance TokenAlgebra Char (WrapMega Char) where + tokenClass exam = WrapMega $ M.label (show exam) (M.satisfy (tokenClass exam)) +instance Tokenized Char (WrapMega Char) where + anyToken = WrapMega M.anySingle + token = WrapMega . M.single + oneOf = WrapMega . M.oneOf + notOneOf = WrapMega . M.noneOf + asIn cat = WrapMega $ M.label ("in category " ++ show cat) (M.satisfy (asIn cat)) + notAsIn cat = WrapMega $ M.label ("not in category " ++ show cat) (M.satisfy (notAsIn cat)) +instance BackusNaurForm (WrapMega a) where + rule lbl (WrapMega p) = WrapMega (M.label lbl p) + ruleRec lbl = rule lbl . fix +instance Filterable WrapMega where + catMaybes m = m >>= maybe (fail "unrestricted filtration") pure +instance MonadTry WrapMega where + try (WrapMega p) = WrapMega (M.try p) + +megaparsecG + :: CtxGrammar Char a + -> M.Parsec String String a +megaparsecG gram = unwrapMega (monadG gram) +@ + +-} +monadG + :: ( MonadTry m + , TokenAlgebra token (m token) + , TerminalSymbol token (m ()) + ) + => CtxGrammar token a -- ^ context-sensitive grammar + -> m a +monadG joker = runJoker joker + {- | `putStringLn` is a utility that generalizes `putStrLn` to string-like interfaces such as `RegString` and `RegBnf`. -} @@ -882,7 +957,7 @@ instance IsList RegString where = fromMaybe zeroK . listToMaybe . mapMaybe prsF - . parseP regexGrammar + . readP_to_S (readG regexGrammar) where prsF (rex,"") = Just rex prsF _ = Nothing @@ -901,7 +976,7 @@ instance IsList RegBnf where = fromMaybe zeroK . listToMaybe . mapMaybe prsF - . parseP regbnfGrammar + . readP_to_S (readG regbnfGrammar) where prsF (regbnf,"") = Just regbnf prsF _ = Nothing diff --git a/src/Control/Lens/Grammar/BackusNaur.hs b/src/Control/Lens/Grammar/BackusNaur.hs index c37b8636..bd47607e 100644 --- a/src/Control/Lens/Grammar/BackusNaur.hs +++ b/src/Control/Lens/Grammar/BackusNaur.hs @@ -29,35 +29,64 @@ import Control.Lens.Extras import Control.Lens.Grammar.Kleene import Control.Lens.Grammar.Token import Control.Lens.Grammar.Symbol +import Data.Bifunctor.Joker import Data.Coerce import Data.Foldable import Data.Function import Data.MemoTrie import qualified Data.Set as Set import Data.Set (Set) +import Text.ParserCombinators.ReadP (ReadP) -{- | `BackusNaurForm` grammar combinators formalize -`rule` abstraction and general recursion. Both context-free -`Control.Lens.Grammar.Grammar`s & `Control.Lens.Grammar.CtxGrammar`s -support the `BackusNaurForm` interface. +{- | `BackusNaurForm` grammar combinators formalize traced +`rule` abstraction and general recursion with `ruleRec`, +related by this invariant. + +prop> rule label bnf = ruleRec label (\_ -> bnf) + +The `BackusNaurForm` interface is reminiscent of +two distinct notions of "trace". +First as a [traced Cartesian monoidal category] +(https://ncatlab.org/nlab/show/traced+monoidal+category#in_cartesian_monoidal_categories) +which models general recursion abstractly, +and second as a `Debug.Trace.trace`-like label for `rule` abstraction. +The category @(->)@ already has a traced @(,)@-monoidal structure +in the form of `Data.Profunctor.unfirst` @=@ `Control.Arrow.loop` +or equivalently the fixpoint function `fix`, +determining default methods for a `BackusNaurForm`. -prop> rule name bnf = ruleRec name (\_ -> bnf) +prop> rule _ = id +prop> ruleRec _ = fix +The `BackusNaurForm` interface permits overloading these methods, +and tracing them with a label. + +Both context-free `Control.Lens.Grammar.Grammar`s +& `Control.Lens.Grammar.CtxGrammar`s +support the `BackusNaurForm` interface. See Breitner, [Showcasing Applicative] -(https://www.joachim-breitner.de/blog/710-Showcasing_Applicative). +(https://www.joachim-breitner.de/blog/710-Showcasing_Applicative), +for the original interface. + -} class BackusNaurForm bnf where - {- | Rule abstraction, `rule` can be used to detail parse errors. -} + {- | Rule abstraction. -} rule :: String -> bnf -> bnf rule _ = id - {- | General recursion, using `ruleRec`, rules can refer to themselves. -} + {- | General recursion. -} ruleRec :: String -> (bnf -> bnf) -> bnf ruleRec _ = fix {- | A `Bnf` consists of a distinguished starting rule -and a set of named rules, supporting the `BackusNaurForm` interface. -} +and a set of named rules. When a `Bnf` supports `NonTerminalSymbol`s, +then it supports the `BackusNaurForm` interface +by replacing recursive calls with `nonTerminal`s. + +prop> ruleRec label f = rule label (f (nonTerminal label)) + +-} data Bnf rule = Bnf { startBnf :: rule , rulesBnf :: Set (String, rule) @@ -145,14 +174,14 @@ rulesNamed nameX = foldl' (flip inserter) Set.empty where -- instances instance (Ord rule, NonTerminalSymbol rule) => BackusNaurForm (Bnf rule) where - rule name = ruleRec name . const - ruleRec name f = - let - newStart = nonTerminal name - Bnf newRule oldRules = f (Bnf newStart mempty) - newRules = Set.insert (name, newRule) oldRules - in - Bnf newStart newRules + rule label (Bnf newRule oldRules) = (nonTerminal label) + {rulesBnf = Set.insert (label, newRule) oldRules} + ruleRec label f = rule label (f (nonTerminal label)) +instance (forall x. BackusNaurForm (f x)) + => BackusNaurForm (Joker f a b) where + rule name = Joker . rule name . runJoker + ruleRec name = Joker . ruleRec name . dimap Joker runJoker +instance BackusNaurForm (ReadP a) instance (Ord rule, TerminalSymbol token rule) => TerminalSymbol token (Bnf rule) where terminal = liftBnf0 . terminal diff --git a/src/Control/Lens/Grammar/Boole.hs b/src/Control/Lens/Grammar/Boole.hs index 5b6409d8..e1e6da3f 100644 --- a/src/Control/Lens/Grammar/Boole.hs +++ b/src/Control/Lens/Grammar/Boole.hs @@ -1,6 +1,6 @@ {- | Module : Control.Lens.Grammar.Boole -Description : Boolean algebras & token classes +Description : Boolean algebras Copyright : (C) 2026 - Eitan Chatav License : BSD-style (see the file LICENSE) Maintainer : Eitan Chatav @@ -9,7 +9,6 @@ Portability : non-portable See Boole, [The Mathematical Analysis of Logic] (https://www.gutenberg.org/files/36884/36884-pdf.pdf). -Categorized token classes form a Boolean algebra. -} module Control.Lens.Grammar.Boole diff --git a/src/Control/Lens/Internal/NestedPrismTH.hs b/src/Control/Lens/Grammar/Internal/NestedPrismTH.hs similarity index 97% rename from src/Control/Lens/Internal/NestedPrismTH.hs rename to src/Control/Lens/Grammar/Internal/NestedPrismTH.hs index 76c7e08c..773da1b8 100644 --- a/src/Control/Lens/Internal/NestedPrismTH.hs +++ b/src/Control/Lens/Grammar/Internal/NestedPrismTH.hs @@ -1,5 +1,5 @@ {- | -Module : Control.Lens.Internal.NestedPrismTH +Module : Control.Lens.Grammar.Internal.NestedPrismTH Description : nested pair prisms Copyright : (C) 2026 - Eitan Chatav License : BSD-style (see the file LICENSE) @@ -11,7 +11,7 @@ Code is duplicated from `Control.Lens.Internal.PrismTH`, with small tweaks to support nested pairs. -} -module Control.Lens.Internal.NestedPrismTH +module Control.Lens.Grammar.Internal.NestedPrismTH ( -- * Nested prisms makeNestedPrisms ) where @@ -63,11 +63,11 @@ import Prelude -- will create -- -- @ --- _Foo :: Prism (FooBarBaz a) (FooBarBaz b) a b --- _Bar :: Prism' (FooBarBaz a) Int --- _Baz :: Prism' (FooBarBaz a) (Int, Char) --- _Buzz :: Prism' (FooBarBaz a) (Double, (String, Bool)) --- _Boop :: Prism' (FooBarBaz a) () +-- _Foo :: Prism (FooBar a) (FooBar b) a b +-- _Bar :: Prism' (FooBar a) Int +-- _Baz :: Prism' (FooBar a) (Int, Char) +-- _Buzz :: Prism' (FooBar a) (Double, (String, Bool)) +-- _Boop :: Prism' (FooBar a) () -- @ makeNestedPrisms :: Name -> DecsQ makeNestedPrisms typeName = diff --git a/src/Control/Lens/Grammar/Internal/Orphanage.hs b/src/Control/Lens/Grammar/Internal/Orphanage.hs new file mode 100644 index 00000000..bccd3001 --- /dev/null +++ b/src/Control/Lens/Grammar/Internal/Orphanage.hs @@ -0,0 +1,140 @@ +{-# OPTIONS_GHC -Wno-orphans #-} + +{- | +Module : Control.Lens.Grammar.Internal.Orphanage +Description : orphanage +Copyright : (C) 2026 - Eitan Chatav +License : BSD-style (see the file LICENSE) +Maintainer : Eitan Chatav +Stability : provisional +Portability : non-portable + +An orphanage for instances without a home. +-} + +module Control.Lens.Grammar.Internal.Orphanage () where + +import Control.Applicative hiding (WrappedArrow) +import Control.Applicative qualified as Ap (WrappedArrow) +import Control.Arrow +import Control.Lens +import Control.Lens.Internal.Prism +import Control.Lens.Internal.Profunctor +import Control.Monad +import Data.Bifunctor.Clown +import Data.Bifunctor.Joker +import Data.Bifunctor.Product +import Data.Distributive +import Data.Functor.Compose +import Data.Functor.Contravariant.Divisible +import Data.Profunctor hiding (WrappedArrow) +import Data.Profunctor qualified as Pro (WrappedArrow) +import Data.Profunctor.Cayley +import Data.Profunctor.Composition +import Data.Profunctor.Monad +import Data.Profunctor.Yoneda +import Text.ParserCombinators.ReadP (ReadP) +import Witherable + +-- Orphanage -- +instance (Profunctor p, Functor f) + => Functor (WrappedPafb f p a) where fmap = rmap +deriving via Compose (p a) f instance + (Profunctor p, Functor (p a), Filterable f) + => Filterable (WrappedPafb f p a) +instance (Profunctor p, Filterable f) + => Cochoice (WrappedPafb f p) where + unleft (WrapPafb p) = WrapPafb $ + dimap Left (mapMaybe (either Just (const Nothing))) p + unright (WrapPafb p) = WrapPafb $ + dimap Right (mapMaybe (either (const Nothing) Just)) p +instance (Profunctor p, Filterable (p a)) + => Filterable (Yoneda p a) where + catMaybes = proreturn . catMaybes . proextract +instance (Profunctor p, Filterable (p a)) + => Filterable (Coyoneda p a) where + catMaybes = proreturn . catMaybes . proextract +instance Filterable f => Filterable (Star f a) where + catMaybes (Star f) = Star (catMaybes . f) +instance Monoid r => Applicative (Forget r a) where + pure _ = Forget mempty + Forget f <*> Forget g = Forget (f <> g) +instance Filterable (Forget r a) where + catMaybes (Forget f) = Forget f +instance Decidable f => Applicative (Clown f a) where + pure _ = Clown conquer + Clown x <*> Clown y = Clown (divide (id &&& id) x y) +deriving newtype instance Applicative f => Applicative (Joker f a) +deriving newtype instance Alternative f => Alternative (Joker f a) +deriving newtype instance Filterable f => Filterable (Joker f a) +deriving newtype instance Monad m => Monad (Joker m a) +deriving newtype instance MonadFail m => MonadFail (Joker m a) +deriving newtype instance MonadPlus m => MonadPlus (Joker m a) +instance Filterable f => Cochoice (Joker f) where + unleft (Joker x) = Joker + (mapMaybe (either Just (const Nothing)) x) + unright (Joker x) = Joker + (mapMaybe (either (const Nothing) Just) x) +instance Filterable ReadP where + catMaybes m = m >>= maybe empty pure +deriving via Compose (p a) f instance + (Profunctor p, Applicative (p a), Applicative f) + => Applicative (WrappedPafb f p a) +deriving via Compose (p a) f instance + (Profunctor p, Alternative (p a), Applicative f) + => Alternative (WrappedPafb f p a) +instance (Closed p, Distributive f) + => Closed (WrappedPafb f p) where + closed (WrapPafb p) = WrapPafb (rmap distribute (closed p)) +deriving via (Ap.WrappedArrow p a) instance Arrow p + => Functor (Pro.WrappedArrow p a) +deriving via (Ap.WrappedArrow p a) instance Arrow p + => Applicative (Pro.WrappedArrow p a) +deriving via (Pro.WrappedArrow p) instance Arrow p + => Profunctor (Ap.WrappedArrow p) +instance + ( forall x. Applicative (p x), Profunctor p + , Applicative (q a), Profunctor q + ) => Applicative (Procompose p q a) where + pure b = Procompose (pure b) (pure b) + Procompose wb aw <*> Procompose vb av = Procompose + (liftA2 ($) (lmap fst wb) (lmap snd vb)) + (liftA2 (,) aw av) +instance (forall x. Applicative (p x), forall x. Applicative (q x)) + => Applicative (Product p q a) where + pure b = Pair (pure b) (pure b) + Pair x0 y0 <*> Pair x1 y1 = Pair (x0 <*> x1) (y0 <*> y1) +instance (Functor f, Functor (p a)) => Functor (Cayley f p a) where + fmap f (Cayley x) = Cayley (fmap (fmap f) x) +instance (Applicative f, Applicative (p a)) => Applicative (Cayley f p a) where + pure b = Cayley (pure (pure b)) + Cayley x <*> Cayley y = Cayley ((<*>) <$> x <*> y) +instance (Profunctor p, Applicative (p a)) + => Applicative (Yoneda p a) where + pure = proreturn . pure + ab <*> cd = proreturn (proextract ab <*> proextract cd) +instance (Profunctor p, Applicative (p a)) + => Applicative (Coyoneda p a) where + pure = proreturn . pure + ab <*> cd = proreturn (proextract ab <*> proextract cd) +instance (Profunctor p, Alternative (p a)) + => Alternative (Yoneda p a) where + empty = proreturn empty + ab <|> cd = proreturn (proextract ab <|> proextract cd) + many = proreturn . many . proextract +instance (Profunctor p, Alternative (p a)) + => Alternative (Coyoneda p a) where + empty = proreturn empty + ab <|> cd = proreturn (proextract ab <|> proextract cd) + many = proreturn . many . proextract +instance Applicative (Market a b s) where + pure t = Market (pure t) (pure (Left t)) + Market f0 g0 <*> Market f1 g1 = Market + (\b -> f0 b (f1 b)) + (\s -> + case g0 s of + Left bt -> case g1 s of + Left b -> Left (bt b) + Right a -> Right a + Right a -> Right a + ) diff --git a/src/Control/Lens/Grammar/Kleene.hs b/src/Control/Lens/Grammar/Kleene.hs index cf65d386..52d74473 100644 --- a/src/Control/Lens/Grammar/Kleene.hs +++ b/src/Control/Lens/Grammar/Kleene.hs @@ -1,6 +1,6 @@ {- | Module : Control.Lens.Grammar.Kleene -Description : Kleene star algebras & regular expressions +Description : Kleene star algebras, regular expressions & token classes Copyright : (C) 2026 - Eitan Chatav License : BSD-style (see the file LICENSE) Maintainer : Eitan Chatav @@ -29,6 +29,7 @@ import Control.Applicative import Control.Lens.Grammar.Boole import Control.Lens.Grammar.Symbol import Control.Lens.Grammar.Token +import Data.Bifunctor.Joker import Data.Foldable import Data.MemoTrie import Data.Monoid @@ -37,6 +38,8 @@ import Data.Profunctor.Distributor import Data.Set (Set) import qualified Data.Set as Set import GHC.Generics +import Text.ParserCombinators.ReadP (ReadP) +import qualified Text.ParserCombinators.ReadP as ReadP {- | A `KleeneStarAlgebra` is a ring with a generally non-commutative multiplication, @@ -217,6 +220,11 @@ instance Categorized token => TokenAlgebra token (RegEx token) where NotOneOf as catTest -> RegExam (NotOneOf as catTest) Alternate exam1 exam2 -> RegExam (Alternate (tokenClass exam1) (tokenClass exam2)) +instance TokenAlgebra token (f token) + => TokenAlgebra token (Joker f token token) where + tokenClass = Joker . tokenClass +instance TokenAlgebra Char (ReadP Char) where + tokenClass = ReadP.satisfy . tokenClass instance Categorized token => Monoid (RegEx token) where mempty = SeqEmpty instance Categorized token => Semigroup (RegEx token) where diff --git a/src/Control/Lens/Grammar/Symbol.hs b/src/Control/Lens/Grammar/Symbol.hs index d5ab6b3e..a36ec396 100644 --- a/src/Control/Lens/Grammar/Symbol.hs +++ b/src/Control/Lens/Grammar/Symbol.hs @@ -17,17 +17,26 @@ module Control.Lens.Grammar.Symbol import Control.Lens import Control.Lens.PartialIso import Control.Lens.Grammar.Token +import Data.Bifunctor.Joker import Data.Profunctor import Data.Profunctor.Monoidal +import Text.ParserCombinators.ReadP (ReadP, string) -- | A `terminal` symbol in a grammar. class TerminalSymbol token s | s -> token where terminal :: [token] -> s default terminal - :: (p () () ~ s, Tokenized token (p token token), Monoidal p, Cochoice p) + :: (p () () ~ s, Tokenized token (p token token), Monoidal p, Choice p, Cochoice p) => [token] -> s - terminal = foldr (\a p -> only a ?< token a *> p) oneP + terminal str = only str ?< tokens str -- | A `nonTerminal` symbol in a grammar. class NonTerminalSymbol s where nonTerminal :: String -> s + +-- instances +instance TerminalSymbol token (f ()) + => TerminalSymbol token (Joker f () ()) where + terminal = Joker . terminal @token +instance TerminalSymbol Char (ReadP ()) where + terminal str = string str *> pure () diff --git a/src/Control/Lens/Grammar/Token.hs b/src/Control/Lens/Grammar/Token.hs index 7aaf2bc6..884d5963 100644 --- a/src/Control/Lens/Grammar/Token.hs +++ b/src/Control/Lens/Grammar/Token.hs @@ -20,10 +20,13 @@ module Control.Lens.Grammar.Token import Control.Lens import Control.Lens.PartialIso +import Data.Bifunctor.Joker import Data.Char import Data.Profunctor import Data.Profunctor.Monoidal import Data.Word +import Text.ParserCombinators.ReadP (ReadP) +import qualified Text.ParserCombinators.ReadP as ReadP {- | `Categorized` provides a type family `Categorize` and a function to `categorize` tokens into disjoint categories. @@ -96,14 +99,6 @@ class Categorized token => Tokenized token p | p -> token where => Categorize token -> p notAsIn = satisfy . notAsIn -instance Categorized token => Tokenized token (token -> Bool) where - anyToken _ = True - token = (==) - oneOf = flip elem - notOneOf = flip notElem - asIn = lmap categorize . (==) - notAsIn = lmap categorize . (/=) - {- | A single token that satisfies a predicate. -} satisfy :: (Tokenized a (p a a), Choice p, Cochoice p) @@ -118,3 +113,27 @@ tokens ) => f a -> p s s tokens = foldr ((>:<) . token) asEmpty + +-- instances +instance Categorized token => Tokenized token (token -> Bool) where + anyToken _ = True + token = (==) + oneOf = flip elem + notOneOf = flip notElem + asIn = lmap categorize . (==) + notAsIn = lmap categorize . (/=) +instance Tokenized token (f token) + => Tokenized token (Joker f token token) where + anyToken = Joker (anyToken @token) + token = Joker . token @token + oneOf = Joker . oneOf @token + notOneOf = Joker . notOneOf @token + asIn = Joker . asIn @token + notAsIn = Joker . notAsIn @token +instance Tokenized Char (ReadP Char) where + anyToken = ReadP.get + token = ReadP.char + oneOf = ReadP.satisfy . oneOf + notOneOf = ReadP.satisfy . notOneOf + asIn = ReadP.satisfy . asIn + notAsIn = ReadP.satisfy . notAsIn diff --git a/src/Control/Lens/PartialIso.hs b/src/Control/Lens/PartialIso.hs index 95ab57a9..fb105e75 100644 --- a/src/Control/Lens/PartialIso.hs +++ b/src/Control/Lens/PartialIso.hs @@ -11,8 +11,6 @@ See Rendel & Ostermann, [Invertible syntax descriptions](https://www.informatik.uni-marburg.de/~rendel/unparse/) -} -{-# OPTIONS_GHC -Wno-orphans #-} - module Control.Lens.PartialIso ( -- * PartialIso dimapMaybe @@ -55,15 +53,13 @@ module Control.Lens.PartialIso ) where import Control.Lens -import Control.Lens.Internal.NestedPrismTH +import Control.Lens.Grammar.Internal.Orphanage () +import Control.Lens.Grammar.Internal.NestedPrismTH import Control.Lens.Internal.Profunctor import Control.Lens.Iso import Control.Lens.Prism import Control.Monad -import Data.Functor.Compose import Data.Profunctor -import Data.Profunctor.Monad -import Data.Profunctor.Yoneda import Witherable {- | The `dimapMaybe` function endows @@ -333,27 +329,3 @@ difoldr difoldr pattern = dimap (Empty,) (fmap snd) . difoldr1 pattern - --- Orphanage -- - -instance (Profunctor p, Functor f) - => Functor (WrappedPafb f p a) where fmap = rmap -deriving via Compose (p a) f instance - (Profunctor p, Functor (p a), Filterable f) - => Filterable (WrappedPafb f p a) -instance (Profunctor p, Filterable f) - => Cochoice (WrappedPafb f p) where - unleft (WrapPafb p) = WrapPafb $ - dimap Left (mapMaybe (either Just (const Nothing))) p - unright (WrapPafb p) = WrapPafb $ - dimap Right (mapMaybe (either (const Nothing) Just)) p -instance (Profunctor p, Filterable (p a)) - => Filterable (Yoneda p a) where - catMaybes = proreturn . catMaybes . proextract -instance (Profunctor p, Filterable (p a)) - => Filterable (Coyoneda p a) where - catMaybes = proreturn . catMaybes . proextract -instance Filterable (Forget r a) where - catMaybes (Forget f) = Forget f -instance Filterable f => Filterable (Star f a) where - catMaybes (Star f) = Star (catMaybes . f) diff --git a/src/Control/Monad/Fail/Try.hs b/src/Control/Monad/Fail/Try.hs index 7420bf43..48fa94a3 100644 --- a/src/Control/Monad/Fail/Try.hs +++ b/src/Control/Monad/Fail/Try.hs @@ -17,25 +17,35 @@ module Control.Monad.Fail.Try , MonadPlus (..) -- * Alternative , Alternative (..) + -- * Filterable + , Filterable (..) ) where import Control.Applicative +import Control.Lens.Grammar.BackusNaur +import Control.Lens.PartialIso () import Control.Monad +import Data.Bifunctor.Joker +import Text.ParserCombinators.ReadP (ReadP) +import Witherable -{- | `MonadTry` is a failure handling interface, -with `fail` & `try` and redundant alternation operators. +{- | `MonadTry` is a failure handling interface, with `fail` & `try` +and redundant alternation & filtration operators. prop> empty = mzero prop> (<|>) = mplus +prop> filter = mfilter -When a `MonadTry` is also a -`Control.Lens.Grammar.BackusNaur.BackusNaurForm`, -then the following invariant should hold. +`MonadTry` also supports the `BackusNaurForm` interface +for tracing failures and the following invariant should hold. prop> fail label = rule label empty -} -class (MonadFail m, MonadPlus m) => MonadTry m where +class + ( MonadFail m, MonadPlus m, Filterable m + , forall x. BackusNaurForm (m x) + ) => MonadTry m where {- | A handler for failures. Used for backtracking state on failure in @@ -44,3 +54,7 @@ class (MonadFail m, MonadPlus m) => MonadTry m where try :: m a -> m a default try :: m a -> m a try = id + +instance MonadTry m => MonadTry (Joker m a) where + try = Joker . try . runJoker +instance MonadTry ReadP diff --git a/src/Data/Profunctor/Distributor.hs b/src/Data/Profunctor/Distributor.hs index c2df0391..b7633bf4 100644 --- a/src/Data/Profunctor/Distributor.hs +++ b/src/Data/Profunctor/Distributor.hs @@ -276,3 +276,9 @@ instance Alternator p => Alternator (Yoneda p) where alternate (Right p) = proreturn (alternate (Right (proextract p))) someP = proreturn . someP . proextract optionP def = proreturn . optionP def . proextract +instance Alternative f => Alternator (Joker f) where + alternate (Left (Joker x)) = Joker (Left <$> x) + alternate (Right (Joker y)) = Joker (Right <$> y) + someP (Joker x) = Joker (some x) + optionP def (Joker x) = + Joker (x <|> withPrism def (\f _ -> pure (f ()))) diff --git a/src/Data/Profunctor/Filtrator.hs b/src/Data/Profunctor/Filtrator.hs index 5ea8ef7b..4adc28df 100644 --- a/src/Data/Profunctor/Filtrator.hs +++ b/src/Data/Profunctor/Filtrator.hs @@ -19,6 +19,7 @@ import Control.Arrow import Control.Lens.PartialIso import Control.Lens.Internal.Profunctor import Control.Monad +import Data.Bifunctor.Joker import Data.Profunctor import Data.Profunctor.Distributor import Data.Profunctor.Monad @@ -38,8 +39,8 @@ class (Cochoice p, forall x. Filterable (p x)) => Filtrator p where {- | - prop> unleft = fst . filtrate - prop> unright = snd . filtrate + prop> unleft = fst . filtrate = lmap Left . mapMaybe (either Just (const Nothing)) + prop> unright = snd . filtrate = lmap Right . mapMaybe (either (const Nothing) Just) `filtrate` is a distant relative to `Data.Either.partitionEithers`. `filtrate` can be given a default value for `Monadic` @@ -100,3 +101,8 @@ instance Filtrator (PartialExchange a b) where ( PartialExchange (f . Left) (either Just (pure Nothing) <=< g) , PartialExchange (f . Right) (either (pure Nothing) Just <=< g) ) +instance Filterable f => Filtrator (Joker f) where + filtrate (Joker x) = + ( Joker (mapMaybe (either Just (const Nothing)) x) + , Joker (mapMaybe (either (const Nothing) Just) x) + ) diff --git a/src/Data/Profunctor/Grammar.hs b/src/Data/Profunctor/Grammar.hs index 30279a2d..b721355d 100644 --- a/src/Data/Profunctor/Grammar.hs +++ b/src/Data/Profunctor/Grammar.hs @@ -40,7 +40,6 @@ import Data.Profunctor.Monoidal import Data.Void import Prelude hiding (id, (.)) import GHC.Exts -import Witherable -- | `Printor` is a simple printer `Profunctor`. newtype Printor s f a b = Printor {runPrintor :: a -> f (b, s -> s)} @@ -180,7 +179,8 @@ instance instance BackusNaurForm (Parsor s m a b) instance (Alternative m, Monad m) => MonadFail (Parsor s m a) where fail _ = empty -instance (Alternative m, Monad m) => MonadTry (Parsor s m a) +instance (Alternative m, Monad m, Filterable m) + => MonadTry (Parsor s m a) instance AsEmpty s => Matching s (Parsor s [] a b) where word =~ p = case [ () | (_, remaining) <- runParsor p Nothing word @@ -289,7 +289,8 @@ instance instance BackusNaurForm (Printor s m a b) instance (Alternative m, Monad m) => MonadFail (Printor s m a) where fail _ = empty -instance (Alternative m, Monad m) => MonadTry (Printor s m a) +instance (Alternative m, Monad m, Filterable m) + => MonadTry (Printor s m a) -- Grammor instances instance Functor (Grammor k a) where fmap _ = coerce diff --git a/src/Data/Profunctor/Grammar/Parsector.hs b/src/Data/Profunctor/Grammar/Parsector.hs index e6c438a3..bf00e228 100644 --- a/src/Data/Profunctor/Grammar/Parsector.hs +++ b/src/Data/Profunctor/Grammar/Parsector.hs @@ -1,6 +1,6 @@ {-| Module : Data.Profunctor.Grammar.Parsector -Description : grammar distributor with errors +Description : grammar distributor with failures Copyright : (C) 2026 - Eitan Chatav License : BSD-style (see the file LICENSE) Maintainer : Eitan Chatav @@ -18,7 +18,7 @@ module Data.Profunctor.Grammar.Parsector , parsecP , unparsecP , ParsecState (..) - , ParsecError (..) + , ParsecFailure (..) ) where import Control.Applicative @@ -41,10 +41,9 @@ import Data.Profunctor.Monoidal import Data.Tree import GHC.Exts import Prelude hiding (id, (.)) -import Witherable {- | `Parsector` is an invertible @LL(1)@ parser which is intended -to provide detailed error information, based on [Parsec] +to provide detailed failure information, based on [Parsec] (https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/parsec-paper-letter.pdf). -} newtype Parsector s a b = Parsector @@ -91,11 +90,11 @@ data ParsecState s a = ParsecState , parsecOffset :: !Word -- ^ Number of tokens consumed from the start of the stream. , parsecStream :: s -- ^ stream - , parsecError :: ParsecError s - {- ^ `ParsecError` channel. + , parsecFailure :: ParsecFailure s + {- ^ `ParsecFailure` channel. * If `parsecResult` is `Nothing`, this is the hard failure. - * If `parsecResult` is `Just`, this is deferred error/hint info + * If `parsecResult` is `Just`, this is deferred failure/hint info from empty-failing alternatives at the current position. `<|>` and `>>=` propagate and merge this field to preserve @@ -106,22 +105,22 @@ data ParsecState s a = ParsecState As input, `Nothing` means parse mode and `Just` means print mode with an input syntax value. - As output `Nothing` means failure (inspect `parsecError`) and + As output `Nothing` means failure (inspect `parsecFailure`) and `Just` means success with an output syntax value. -} } -{- | `ParsecError` is the error payload produced by `Parsector`, -stored in `parsecError`. -`ParsecError` is a `Monoid` and `Parsector` merges errors/hints +{- | `ParsecFailure` is the failure payload produced by `Parsector`, +stored in `parsecFailure`. +`ParsecFailure` is a `Monoid` and `Parsector` merges failures/hints when control flow reaches the same offset without commitment. -} -data ParsecError s = ParsecError +data ParsecFailure s = ParsecFailure { parsecExpect :: TokenClass (Item s) {- ^ Class of expected token `Item`s at the `parsecOffset`. `tokenClass`es and `Tokenized` combinators specify expectations. Under `<>`, expectations are combined with disjunction `>||<`. - In case of a parse error, contrast with the actual `parsecStream`, + In case of a parse failure, contrast with the actual `parsecStream`, which is either unexpectedly empty or begins with an unexpected token. -} , parsecLabels :: [Tree String] @@ -133,21 +132,21 @@ data ParsecError s = ParsecError -} } --- ParsecError instances +-- ParsecFailure instances deriving stock instance ( Categorized (Item s) , Show (Item s), Show (Categorize (Item s)) - ) => Show (ParsecError s) + ) => Show (ParsecFailure s) deriving stock instance ( Categorized (Item s) , Read (Item s), Read (Categorize (Item s)) - ) => Read (ParsecError s) -deriving stock instance Categorized (Item s) => Eq (ParsecError s) -deriving stock instance Categorized (Item s) => Ord (ParsecError s) -instance Categorized (Item s) => Semigroup (ParsecError s) where - ParsecError e1 l1 <> ParsecError e2 l2 = ParsecError (e1 >||< e2) (l1 ++ l2) -instance Categorized (Item s) => Monoid (ParsecError s) where - mempty = ParsecError falseB [] + ) => Read (ParsecFailure s) +deriving stock instance Categorized (Item s) => Eq (ParsecFailure s) +deriving stock instance Categorized (Item s) => Ord (ParsecFailure s) +instance Categorized (Item s) => Semigroup (ParsecFailure s) where + ParsecFailure e1 l1 <> ParsecFailure e2 l2 = ParsecFailure (e1 >||< e2) (l1 ++ l2) +instance Categorized (Item s) => Monoid (ParsecFailure s) where + mempty = ParsecFailure falseB [] -- ParsecState instances deriving stock instance Functor (ParsecState s) @@ -194,13 +193,13 @@ instance offset = parsecOffset query replyOk tok str = query { parsecLooked = True - , parsecError = mempty + , parsecFailure = mempty , parsecStream = str , parsecOffset = offset + 1 , parsecResult = Just tok } replyErr = query - { parsecError = ParsecError test [] + { parsecFailure = ParsecFailure test [] , parsecResult = Nothing } in callback $ case mode of @@ -221,9 +220,9 @@ instance BackusNaurForm (Parsector s a b) where flip (runParsector p) query $ \reply -> callback $ case parsecResult reply of Nothing -> reply - { parsecError = - let ParsecError expect labels = parsecError reply - in ParsecError expect [Node name labels] + { parsecFailure = + let ParsecFailure expect labels = parsecFailure reply + in ParsecFailure expect [Node name labels] } Just _ -> reply ruleRec name = rule name . fix @@ -246,10 +245,10 @@ instance Categorized (Item s) => Monad (Parsector s a) where Nothing -> callback reply { parsecResult = Nothing } Just b -> let - hintP = parsecError reply + hintP = parsecFailure reply fQuery = reply { parsecLooked = False - , parsecError = mempty + , parsecFailure = mempty , parsecResult = parsecResult query } in @@ -258,12 +257,12 @@ instance Categorized (Item s) => Monad (Parsector s a) where then fReply else fReply { parsecLooked = parsecLooked reply - , parsecError = hintP <> parsecError fReply + , parsecFailure = hintP <> parsecFailure fReply } instance Categorized (Item s) => Alternative (Parsector s a) where -- | Always fails without consuming input; expects nothing. empty = Parsector $ \callback query -> - callback query { parsecError = mempty, parsecResult = Nothing } + callback query { parsecFailure = mempty, parsecResult = Nothing } p <|> q = Parsector $ \callback query -> flip (runParsector p) query $ \replyP -> callback $ case parsecResult replyP of @@ -273,15 +272,15 @@ instance Categorized (Item s) => Alternative (Parsector s a) where Nothing | parsecLooked replyP -> replyP -- if p failed without consuming, try q Nothing -> - let errP = parsecError replyP + let errP = parsecFailure replyP in flip (runParsector q) query $ \replyQ -> case (parsecLooked replyQ, parsecResult replyQ) of -- q consumed (ok or err): propagate as-is, drop errP (True, _) -> replyQ -- q empty ok: carry errP forward as hint for downstream - (False, Just _) -> replyQ { parsecError = errP <> parsecError replyQ } - -- both empty fail: merge errors - (False, Nothing) -> replyP { parsecError = errP <> parsecError replyQ } + (False, Just _) -> replyQ { parsecFailure = errP <> parsecFailure replyQ } + -- both empty fail: merge failures + (False, Nothing) -> replyP { parsecFailure = errP <> parsecFailure replyQ } instance Categorized (Item s) => MonadPlus (Parsector s a) instance Categorized (Item s) => MonadFail (Parsector s a) where fail msg = rule msg empty @@ -295,7 +294,7 @@ instance Categorized (Item s) => MonadTry (Parsector s a) where case parsecResult reply of Nothing -> query { parsecLooked = False - , parsecError = parsecError reply + , parsecFailure = parsecFailure reply , parsecResult = Nothing } Just _ -> reply @@ -348,7 +347,7 @@ instance Categorized (Item s) => Alternator (Parsector s) where Just (Left a) -> Just a Just (Right _) -> Nothing } - replyErr = query { parsecError = mempty, parsecResult = Nothing } + replyErr = query { parsecFailure = mempty, parsecResult = Nothing } in case (parsecResult query, parsecResult replyOk) of (Just _, Nothing) -> replyErr @@ -363,7 +362,7 @@ instance Categorized (Item s) => Alternator (Parsector s) where Just (Left _) -> Nothing Just (Right b) -> Just b } - replyErr = query { parsecError = mempty, parsecResult = Nothing } + replyErr = query { parsecFailure = mempty, parsecResult = Nothing } in case (parsecResult query, parsecResult replyOk) of (Just _, Nothing) -> replyErr @@ -382,18 +381,18 @@ instance Categorized (Item s) => Filtrator (Parsector s) where ( Parsector $ \callback query -> flip (runParsector p) (Left <$> query) $ \reply -> callback reply - { parsecError = case parsecResult reply of + { parsecFailure = case parsecResult reply of Just (Right _) -> mempty - _ -> parsecError reply + _ -> parsecFailure reply , parsecResult = parsecResult reply >>= either Just (const Nothing) } , Parsector $ \callback query -> flip (runParsector p) (Right <$> query) $ \reply -> callback reply - { parsecError = case parsecResult reply of + { parsecFailure = case parsecResult reply of Just (Left _) -> mempty - _ -> parsecError reply + _ -> parsecFailure reply , parsecResult = parsecResult reply >>= either (const Nothing) Just } diff --git a/src/Data/Profunctor/Monoidal.hs b/src/Data/Profunctor/Monoidal.hs index d19368f8..5b5c7d1b 100644 --- a/src/Data/Profunctor/Monoidal.hs +++ b/src/Data/Profunctor/Monoidal.hs @@ -20,26 +20,10 @@ module Data.Profunctor.Monoidal , meander, eotFunList ) where -import Control.Applicative hiding (WrappedArrow) -import Control.Applicative qualified as Ap (WrappedArrow) -import Control.Arrow -import Control.Lens hiding (chosen) +import Control.Lens import Control.Lens.Internal.Context -import Control.Lens.Internal.Prism -import Control.Lens.Internal.Profunctor import Control.Lens.PartialIso -import Data.Bifunctor.Clown -import Data.Bifunctor.Joker -import Data.Bifunctor.Product import Data.Distributive -import Data.Functor.Compose -import Data.Functor.Contravariant.Divisible -import Data.Profunctor hiding (WrappedArrow) -import Data.Profunctor qualified as Pro (WrappedArrow) -import Data.Profunctor.Cayley -import Data.Profunctor.Composition -import Data.Profunctor.Monad -import Data.Profunctor.Yoneda import GHC.IsList -- Monoidal -- @@ -161,11 +145,11 @@ replicateP replicateP n _ | n <= 0 = asEmpty replicateP n a = a >:< replicateP (n-1) a -{- | For any `Monoidal`, `Choice` & `Strong` `Profunctor`, +{- | For any `Monoidal`, `Choice` & `Data.Profunctor.Strong` `Profunctor`, `meander` is invertible and gives a default implementation for the `Data.Profunctor.Traversing.wander` method of `Data.Profunctor.Traversing.Traversing`, -though `Strong` is not needed for its definition. +though `Data.Profunctor.Strong` is not needed for its definition. See Pickering, Gibbons & Wu, [Profunctor Optics - Modular Data Accessors](https://arxiv.org/abs/1703.10857) @@ -217,72 +201,3 @@ instance Applicative (FunList a b) where MoreFun a h -> \l -> MoreFun a (flip <$> h <*> fromFun l) instance Sellable (->) FunList where sell b = MoreFun b (pure id) - --- Orphanage -- - -instance Monoid r => Applicative (Forget r a) where - pure _ = Forget mempty - Forget f <*> Forget g = Forget (f <> g) -instance Decidable f => Applicative (Clown f a) where - pure _ = Clown conquer - Clown x <*> Clown y = Clown (divide (id &&& id) x y) -deriving newtype instance Applicative f => Applicative (Joker f a) -deriving via Compose (p a) f instance - (Profunctor p, Applicative (p a), Applicative f) - => Applicative (WrappedPafb f p a) -deriving via Compose (p a) f instance - (Profunctor p, Alternative (p a), Applicative f) - => Alternative (WrappedPafb f p a) -instance (Closed p, Distributive f) - => Closed (WrappedPafb f p) where - closed (WrapPafb p) = WrapPafb (rmap distribute (closed p)) -deriving via (Ap.WrappedArrow p a) instance Arrow p - => Functor (Pro.WrappedArrow p a) -deriving via (Ap.WrappedArrow p a) instance Arrow p - => Applicative (Pro.WrappedArrow p a) -deriving via (Pro.WrappedArrow p) instance Arrow p - => Profunctor (Ap.WrappedArrow p) -instance (Monoidal p, Applicative (q a)) - => Applicative (Procompose p q a) where - pure b = Procompose (pure b) (pure b) - Procompose wb aw <*> Procompose vb av = Procompose - (dimap2 fst snd ($) wb vb) - (liftA2 (,) aw av) -instance (Monoidal p, Monoidal q) - => Applicative (Product p q a) where - pure b = Pair (pure b) (pure b) - Pair x0 y0 <*> Pair x1 y1 = Pair (x0 <*> x1) (y0 <*> y1) -instance (Functor f, Functor (p a)) => Functor (Cayley f p a) where - fmap f (Cayley x) = Cayley (fmap (fmap f) x) -instance (Applicative f, Applicative (p a)) => Applicative (Cayley f p a) where - pure b = Cayley (pure (pure b)) - Cayley x <*> Cayley y = Cayley ((<*>) <$> x <*> y) -instance (Profunctor p, Applicative (p a)) - => Applicative (Yoneda p a) where - pure = proreturn . pure - ab <*> cd = proreturn (proextract ab <*> proextract cd) -instance (Profunctor p, Applicative (p a)) - => Applicative (Coyoneda p a) where - pure = proreturn . pure - ab <*> cd = proreturn (proextract ab <*> proextract cd) -instance (Profunctor p, Alternative (p a)) - => Alternative (Yoneda p a) where - empty = proreturn empty - ab <|> cd = proreturn (proextract ab <|> proextract cd) - many = proreturn . many . proextract -instance (Profunctor p, Alternative (p a)) - => Alternative (Coyoneda p a) where - empty = proreturn empty - ab <|> cd = proreturn (proextract ab <|> proextract cd) - many = proreturn . many . proextract -instance Applicative (Market a b s) where - pure t = Market (pure t) (pure (Left t)) - Market f0 g0 <*> Market f1 g1 = Market - (\b -> f0 b (f1 b)) - (\s -> - case g0 s of - Left bt -> case g1 s of - Left b -> Left (bt b) - Right a -> Right a - Right a -> Right a - ) diff --git a/test/Main.hs b/test/Main.hs index 0fbb159b..9398364f 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -4,12 +4,14 @@ import Data.Foldable hiding (toList) import Control.Lens.Grammar import Control.Monad (when) import Data.IORef +import Data.Function (fix) import Data.List (genericLength) import Data.Maybe (isJust) import Data.Profunctor.Types (Star (..)) import System.Environment (lookupEnv) import Test.DocTest import Test.Hspec +import qualified Text.Megaparsec as M import Examples.Arithmetic import Examples.Chain @@ -138,13 +140,43 @@ testCtxGrammar isLL1 grammar (expectedSyntax, expectedString) = do let actualSyntax = parsecG grammar expectedString let expectedLength = genericLength expectedString let actualLooked = parsecLooked actualSyntax - let actualError = parsecError actualSyntax + let actualFailure = parsecFailure actualSyntax actualSyntax `shouldBe` - (ParsecState actualLooked expectedLength "" actualError (Just expectedSyntax)) + (ParsecState actualLooked expectedLength "" actualFailure (Just expectedSyntax)) it ("should unparsecG to " <> expectedString <> " correctly") $ do let actualString = unparsecG grammar expectedSyntax "" let expectedLength = genericLength expectedString let actualLooked = parsecLooked actualString - let actualError = parsecError actualString + let actualFailure = parsecFailure actualString actualString `shouldBe` - (ParsecState actualLooked expectedLength expectedString actualError (Just expectedSyntax)) + (ParsecState actualLooked expectedLength expectedString actualFailure (Just expectedSyntax)) + it ("should parse with megaparsec to " <> expectedString <> " correctly") $ do + let megaparsec = unwrapMega (monadG grammar) + let actualSyntax = M.parse megaparsec "" expectedString + actualSyntax `shouldBe` Right expectedSyntax + +newtype WrapMega a = WrapMega {unwrapMega :: M.Parsec String String a} + deriving newtype + ( Functor, Applicative, Alternative + , Monad, MonadPlus, MonadFail + ) +instance TerminalSymbol Char (WrapMega ()) where + terminal str = WrapMega (M.chunk str *> pure ()) +instance TokenAlgebra Char (WrapMega Char) where + tokenClass exam = WrapMega $ M.label (show exam) (M.satisfy (tokenClass exam)) +instance Tokenized Char (WrapMega Char) where + anyToken = WrapMega M.anySingle + token = WrapMega . M.single + oneOf = WrapMega . M.oneOf + notOneOf = WrapMega . M.noneOf + asIn cat = WrapMega $ M.label ("in category " ++ show cat) + (M.satisfy (tokenClass (asIn cat))) + notAsIn cat = WrapMega $ M.label ("not in category " ++ show cat) + (M.satisfy (tokenClass (notAsIn cat))) +instance BackusNaurForm (WrapMega a) where + rule lbl (WrapMega p) = WrapMega (M.label lbl p) + ruleRec lbl = rule lbl . fix +instance Filterable WrapMega where + catMaybes m = m >>= maybe (fail "unrestricted filtration") pure +instance MonadTry WrapMega where + try (WrapMega p) = WrapMega (M.try p)