1 -- The default type signature of type class methods are changed
2 -- to introduce a Liftable constraint and the same type class but on the 'Output' repr,
3 -- this setup avoids to define the method with boilerplate code when its default
4 -- definition with lift* and 'trans' does what is expected by an instance
5 -- of the type class. This is almost as explained in:
6 -- https://ro-che.info/articles/2016-02-03-finally-tagless-boilerplate
7 {-# LANGUAGE DefaultSignatures #-}
8 {-# LANGUAGE DeriveLift #-} -- For TH.Lift (ErrorItem tok)
9 {-# LANGUAGE StandaloneDeriving #-} -- For Show (ErrorItem (InputToken inp))
10 {-# LANGUAGE TemplateHaskell #-}
11 -- | Semantic of the grammar combinators used to express parsers,
12 -- in the convenient tagless-final encoding.
13 module Symantic.Parser.Grammar.Combinators where
15 import Data.Bool (Bool(..), not, (||))
16 import Data.Char (Char)
17 import Data.Either (Either(..))
18 import Data.Eq (Eq(..))
19 import Data.Function ((.), flip, const)
21 import Data.Maybe (Maybe(..))
23 import Data.Proxy (Proxy(..))
24 import Data.String (String)
25 import GHC.TypeLits (KnownSymbol)
26 import Text.Show (Show(..))
27 import qualified Data.Functor as Functor
28 import qualified Data.List as List
29 import qualified Language.Haskell.TH as TH
30 import qualified Language.Haskell.TH.Syntax as TH
32 import qualified Symantic.Univariant.Trans as Sym
33 import qualified Symantic.Parser.Haskell as H
35 -- * Type 'TermGrammar'
36 type TermGrammar = H.Term H.ValueCode
38 -- * Class 'CombAlternable'
39 class CombAlternable repr where
40 -- | @(rl '<|>' rr)@ parses @(rl)@ and return its return value or,
41 -- if it fails, parses @(rr)@ from where @(rl)@ has left the input stream,
42 -- and returns its return value.
43 (<|>) :: repr a -> repr a -> repr a
44 -- | @(empty)@ parses nothing, always failing to return a value.
46 -- | @('try' ra)@ records the input stream position,
47 -- then parses like @(ra)@ and either returns its value it it succeeds or fails
48 -- if it fails but with a reset of the input stream to the recorded position.
49 -- Generally used on the first alternative: @('try' rl '<|>' rr)@.
50 try :: repr a -> repr a
52 Sym.Liftable2 repr => CombAlternable (Sym.Output repr) =>
53 repr a -> repr a -> repr a
55 Sym.Liftable repr => CombAlternable (Sym.Output repr) =>
58 Sym.Liftable1 repr => CombAlternable (Sym.Output repr) =>
60 (<|>) = Sym.lift2 (<|>)
61 empty = Sym.lift empty
63 -- | Like @('<|>')@ but with different returning types for the alternatives,
64 -- and a return value wrapped in an 'Either' accordingly.
65 (<+>) :: CombApplicable repr => CombAlternable repr => repr a -> repr b -> repr (Either a b)
66 p <+> q = H.left <$> p <|> H.right <$> q
69 optionally :: CombApplicable repr => CombAlternable repr => repr a -> TermGrammar b -> repr b
70 optionally p x = p $> x <|> pure x
72 optional :: CombApplicable repr => CombAlternable repr => repr a -> repr ()
73 optional = flip optionally H.unit
75 option :: CombApplicable repr => CombAlternable repr => TermGrammar a -> repr a -> repr a
76 option x p = p <|> pure x
78 choice :: CombAlternable repr => [repr a] -> repr a
79 choice = List.foldr (<|>) empty
80 -- FIXME: Here hlint suggests to use Data.Foldable.asum,
81 -- but at this point there is no asum for our own (<|>)
83 maybeP :: CombApplicable repr => CombAlternable repr => repr a -> repr (Maybe a)
84 maybeP p = option H.nothing (H.just <$> p)
86 manyTill :: CombApplicable repr => CombAlternable repr => repr a -> repr b -> repr [a]
87 manyTill p end = let go = end $> H.nil <|> p <:> go in go
90 -- * Class 'CombApplicable'
91 -- | This is like the usual 'Functor' and 'Applicative' type classes
92 -- from the @base@ package, but using @('TermGrammar' a)@ instead of just @(a)@
93 -- to be able to use and pattern match on some usual terms of type @(a)@ (like 'H.id')
94 -- and thus apply some optimizations.
95 -- @(repr)@, for "representation", is the usual tagless-final abstraction
96 -- over the many semantics that this syntax (formed by the methods
97 -- of type class like this one) will be interpreted.
98 class CombApplicable repr where
99 -- | @(a2b '<$>' ra)@ parses like @(ra)@ but maps its returned value with @(a2b)@.
100 (<$>) :: TermGrammar (a -> b) -> repr a -> repr b
101 (<$>) f = (pure f <*>)
103 -- | Like '<$>' but with its arguments 'flip'-ped.
104 (<&>) :: repr a -> TermGrammar (a -> b) -> repr b
107 -- | @(a '<$' rb)@ parses like @(rb)@ but discards its returned value by replacing it with @(a)@.
108 (<$) :: TermGrammar a -> repr b -> repr a
111 -- | @(ra '$>' b)@ parses like @(ra)@ but discards its returned value by replacing it with @(b)@.
112 ($>) :: repr a -> TermGrammar b -> repr b
115 -- | @('pure' a)@ parses the empty string, always succeeding in returning @(a)@.
116 pure :: TermGrammar a -> repr a
118 Sym.Liftable repr => CombApplicable (Sym.Output repr) =>
119 TermGrammar a -> repr a
120 pure = Sym.lift . pure
122 -- | @(ra2b '<*>' ra)@ parses sequentially @(ra2b)@ and then @(ra)@,
123 -- and returns the application of the function returned by @(ra2b)@
124 -- to the value returned by @(ra)@.
125 (<*>) :: repr (a -> b) -> repr a -> repr b
127 Sym.Liftable2 repr => CombApplicable (Sym.Output repr) =>
128 repr (a -> b) -> repr a -> repr b
129 (<*>) = Sym.lift2 (<*>)
131 -- | @('liftA2' a2b2c ra rb)@ parses sequentially @(ra)@ and then @(rb)@,
132 -- and returns the application of @(a2b2c)@ to the values returned by those parsers.
133 liftA2 :: TermGrammar (a -> b -> c) -> repr a -> repr b -> repr c
134 liftA2 f x = (<*>) (f <$> x)
136 -- | @(ra '<*' rb)@ parses sequentially @(ra)@ and then @(rb)@,
137 -- and returns like @(ra)@, discarding the return value of @(rb)@.
138 (<*) :: repr a -> repr b -> repr a
139 (<*) = liftA2 H.const
141 -- | @(ra '*>' rb)@ parses sequentially @(ra)@ and then @(rb)@,
142 -- and returns like @(rb)@, discarding the return value of @(ra)@.
143 (*>) :: repr a -> repr b -> repr b
144 x *> y = (H.id <$ x) <*> y
146 -- | Like '<*>' but with its arguments 'flip'-ped.
147 (<**>) :: repr a -> repr (a -> b) -> repr b
148 (<**>) = liftA2 (H.flip H..@ (H.$))
150 (<**>) :: repr a -> repr (a -> b) -> repr b
151 (<**>) = liftA2 (\a f -> f a)
153 infixl 4 <$>, <&>, <$, $>, <*>, <*, *>, <**>
157 (<:>) :: CombApplicable repr => repr a -> repr [a] -> repr [a]
158 (<:>) = liftA2 H.cons
160 sequence :: CombApplicable repr => [repr a] -> repr [a]
161 sequence = List.foldr (<:>) (pure H.nil)
163 traverse :: CombApplicable repr => (a -> repr b) -> [a] -> repr [b]
164 traverse f = sequence . List.map f
165 -- FIXME: Here hlint suggests to use Control.Monad.mapM,
166 -- but at this point there is no mapM for our own sequence
168 repeat :: CombApplicable repr => Int -> repr a -> repr [a]
169 repeat n p = traverse (const p) [1..n]
171 between :: CombApplicable repr => repr o -> repr c -> repr a -> repr a
172 between open close p = open *> p <* close
174 void :: CombApplicable repr => repr a -> repr ()
177 unit :: CombApplicable repr => repr ()
180 -- * Class 'CombFoldable'
181 class CombFoldable repr where
182 chainPre :: repr (a -> a) -> repr a -> repr a
183 chainPost :: repr a -> repr (a -> a) -> repr a
186 Sym.Liftable2 repr => CombFoldable (Sym.Output repr) =>
187 repr (a -> a) -> repr a -> repr a
189 Sym.Liftable2 repr => CombFoldable (Sym.Output repr) =>
190 repr a -> repr (a -> a) -> repr a
191 chainPre = Sym.lift2 chainPre
192 chainPost = Sym.lift2 chainPost
195 CombApplicable repr =>
196 CombAlternable repr =>
197 repr (a -> a) -> repr a -> repr a
199 CombApplicable repr =>
200 CombAlternable repr =>
201 repr a -> repr (a -> a) -> repr a
202 chainPre op p = go <*> p where go = (H..) <$> op <*> go <|> pure H.id
203 chainPost p op = p <**> go where go = (H..) <$> op <*> go <|> pure H.id
205 chainPre op p = flip (foldr ($)) <$> many op <*> p
206 chainPost p op = foldl' (flip ($)) <$> p <*> many op
210 conditional :: CombSelectable repr => [(TermGrammar (a -> Bool), repr b)] -> repr a -> repr b -> repr b
211 conditional cs p def = match p fs qs def
212 where (fs, qs) = List.unzip cs
217 CombApplicable repr => CombFoldable repr =>
218 TermGrammar (a -> b -> b) -> TermGrammar b -> repr a -> repr b
219 pfoldr f k p = chainPre (f <$> p) (pure k)
222 CombApplicable repr => CombFoldable repr =>
223 TermGrammar (a -> b -> b) -> TermGrammar b -> repr a -> repr b
224 pfoldr1 f k p = f <$> p <*> pfoldr f k p
227 CombApplicable repr => CombFoldable repr =>
228 TermGrammar (b -> a -> b) -> TermGrammar b -> repr a -> repr b
229 pfoldl f k p = chainPost (pure k) ((H.flip <$> pure f) <*> p)
232 CombApplicable repr => CombFoldable repr =>
233 TermGrammar (b -> a -> b) -> TermGrammar b -> repr a -> repr b
234 pfoldl1 f k p = chainPost (f <$> pure k <*> p) ((H.flip <$> pure f) <*> p)
238 CombApplicable repr => CombFoldable repr =>
239 TermGrammar (a -> b) -> repr a -> repr (b -> a -> b) -> repr b
240 chainl1' f p op = chainPost (f <$> p) (H.flip <$> op <*> p)
243 CombApplicable repr => CombFoldable repr =>
244 repr a -> repr (a -> a -> a) -> repr a
245 chainl1 = chainl1' H.id
248 chainr1' :: ParserOps rep => rep (a -> b) -> repr a -> repr (a -> b -> b) -> repr b
249 chainr1' f p op = newRegister_ H.id $ \acc ->
250 let go = bind p $ \x ->
251 modify acc (H.flip (H..@) <$> (op <*> x)) *> go
255 chainr1 :: repr a -> repr (a -> a -> a) -> repr a
256 chainr1 = chainr1' H.id
258 chainr :: repr a -> repr (a -> a -> a) -> TermGrammar a -> repr a
259 chainr p op x = option x (chainr1 p op)
263 CombApplicable repr => CombAlternable repr => CombFoldable repr =>
264 repr a -> repr (a -> a -> a) -> TermGrammar a -> repr a
265 chainl p op x = option x (chainl1 p op)
267 -- Derived Combinators
269 CombApplicable repr => CombFoldable repr =>
271 many = pfoldr H.cons H.nil
274 CombApplicable repr => CombFoldable repr =>
275 Int -> repr a -> repr [a]
276 manyN n p = List.foldr (const (p <:>)) (many p) [1..n]
279 CombApplicable repr => CombFoldable repr =>
284 CombApplicable repr => CombFoldable repr =>
286 --skipMany p = let skipManyp = p *> skipManyp <|> unit in skipManyp
287 skipMany = void . pfoldl H.const H.unit -- the void here will encourage the optimiser to recognise that the register is unused
290 CombApplicable repr => CombFoldable repr =>
291 Int -> repr a -> repr ()
292 skipManyN n p = List.foldr (const (p *>)) (skipMany p) [1..n]
295 CombApplicable repr => CombFoldable repr =>
297 skipSome = skipManyN 1
300 CombApplicable repr => CombAlternable repr => CombFoldable repr =>
301 repr a -> repr b -> repr [a]
302 sepBy p sep = option H.nil (sepBy1 p sep)
305 CombApplicable repr => CombAlternable repr => CombFoldable repr =>
306 repr a -> repr b -> repr [a]
307 sepBy1 p sep = p <:> many (sep *> p)
310 CombApplicable repr => CombAlternable repr => CombFoldable repr =>
311 repr a -> repr b -> repr [a]
312 endBy p sep = many (p <* sep)
315 CombApplicable repr => CombAlternable repr => CombFoldable repr =>
316 repr a -> repr b -> repr [a]
317 endBy1 p sep = some (p <* sep)
320 CombApplicable repr => CombAlternable repr => CombFoldable repr =>
321 repr a -> repr b -> repr [a]
322 sepEndBy p sep = option H.nil (sepEndBy1 p sep)
325 CombApplicable repr => CombAlternable repr => CombFoldable repr =>
326 repr a -> repr b -> repr [a]
328 let seb1 = p <**> (sep *> (H.flip H..@ H.cons <$> option H.nil seb1)
329 <|> pure (H.flip H..@ H.cons H..@ H.nil))
333 sepEndBy1 :: repr a -> repr b -> repr [a]
334 sepEndBy1 p sep = newRegister_ H.id $ \acc ->
335 let go = modify acc ((H.flip (H..)) H..@ H.cons <$> p)
336 *> (sep *> (go <|> get acc) <|> get acc)
340 -- * Class 'CombMatchable'
341 class CombMatchable repr where
343 Eq a => repr a -> [TermGrammar (a -> Bool)] -> [repr b] -> repr b -> repr b
344 default conditional ::
345 Sym.Unliftable repr => Sym.Liftable1 repr => CombMatchable (Sym.Output repr) =>
346 Eq a => repr a -> [TermGrammar (a -> Bool)] -> [repr b] -> repr b -> repr b
347 conditional a ps bs = Sym.lift1 (conditional (Sym.trans a) ps (Sym.trans Functor.<$> bs))
349 match :: Eq a => repr a -> [TermGrammar a] -> (TermGrammar a -> repr b) -> repr b -> repr b
350 match a as a2b = conditional a ((H.eq H..@) Functor.<$> as) (a2b Functor.<$> as)
351 -- match a as a2b = conditional a (((H.eq H..@ H.qual) H..@) Functor.<$> as) (a2b Functor.<$> as)
353 -- * Class 'CombSatisfiable'
354 class CombSatisfiable tok repr where
355 satisfy :: [ErrorItem tok] -> TermGrammar (tok -> Bool) -> repr tok
357 Sym.Liftable repr => CombSatisfiable tok (Sym.Output repr) =>
359 TermGrammar (tok -> Bool) -> repr tok
360 satisfy es = Sym.lift . satisfy es
363 item = satisfy [] (H.const H..@ H.bool True)
366 CombApplicable repr => CombAlternable repr =>
367 CombSatisfiable Char repr =>
368 [Char] -> repr [Char]
369 string = try . traverse char
372 TH.Lift tok => Eq tok =>
373 CombSatisfiable tok repr =>
375 oneOf ts = satisfy [ErrorItemLabel "oneOf"]
376 (Sym.trans H.ValueCode
377 { value = (`List.elem` ts)
378 , code = [||\t -> $$(ofChars ts [||t||])||] })
381 TH.Lift tok => Eq tok =>
382 CombSatisfiable tok repr =>
384 noneOf cs = satisfy (ErrorItemToken Functor.<$> cs) (Sym.trans H.ValueCode
385 { value = not . (`List.elem` cs)
386 , code = [||\c -> not $$(ofChars cs [||c||])||]
390 TH.Lift tok => Eq tok =>
391 {-alternatives-}[tok] ->
392 {-input-}TH.CodeQ tok ->
394 ofChars = List.foldr (\alt acc ->
395 \inp -> [|| alt == $$inp || $$(acc inp) ||])
398 more :: CombApplicable repr => CombSatisfiable Char repr => CombLookable repr => repr ()
399 more = look (void (item @Char))
402 CombApplicable repr => CombSatisfiable Char repr =>
404 char c = satisfy [ErrorItemToken c] (H.eq H..@ H.char c) $> H.char c
405 -- char c = satisfy [ErrorItemToken c] (H.eq H..@ H.qual H..@ H.char c) $> H.char c
407 anyChar :: CombSatisfiable Char repr => repr Char
408 anyChar = satisfy [] (H.const H..@ H.bool True)
411 TH.Lift tok => Show tok => Eq tok =>
412 CombApplicable repr => CombSatisfiable tok repr =>
414 token tok = satisfy [ErrorItemToken tok] (H.eq H..@ H.char tok) $> H.char tok
415 -- token tok = satisfy [ErrorItemToken tok] (H.eq H..@ H.qual H..@ H.char tok) $> H.char tok
418 TH.Lift tok => Eq tok => Show tok =>
419 CombApplicable repr => CombAlternable repr =>
420 CombSatisfiable tok repr => [tok] -> repr [tok]
421 tokens = try . traverse token
423 -- * Class 'CombSelectable'
424 class CombSelectable repr where
425 branch :: repr (Either a b) -> repr (a -> c) -> repr (b -> c) -> repr c
427 Sym.Liftable3 repr => CombSelectable (Sym.Output repr) =>
428 repr (Either a b) -> repr (a -> c) -> repr (b -> c) -> repr c
429 branch = Sym.lift3 branch
431 -- * Class 'CombThrowable'
432 class CombThrowable repr where
433 throw :: KnownSymbol lbl => Proxy lbl -> repr a
436 Sym.Liftable repr => CombThrowable (Sym.Output repr) =>
437 KnownSymbol lbl => Proxy lbl -> repr a
438 throw lbl = Sym.lift (throw lbl)
440 -- ** Type 'ErrorItem'
443 | ErrorItemLabel String
444 | ErrorItemHorizon Int
446 deriving instance Eq tok => Eq (ErrorItem tok)
447 deriving instance Ord tok => Ord (ErrorItem tok)
448 deriving instance Show tok => Show (ErrorItem tok)
449 deriving instance TH.Lift tok => TH.Lift (ErrorItem tok)
451 -- * Class 'CombLookable'
452 class CombLookable repr where
453 look :: repr a -> repr a
454 negLook :: repr a -> repr ()
455 default look :: Sym.Liftable1 repr => CombLookable (Sym.Output repr) => repr a -> repr a
456 default negLook :: Sym.Liftable1 repr => CombLookable (Sym.Output repr) => repr a -> repr ()
457 look = Sym.lift1 look
458 negLook = Sym.lift1 negLook
462 default eof :: Sym.Liftable repr => CombLookable (Sym.Output repr) => repr ()
463 -- eof = negLook (satisfy @Char [ErrorItemAny] (H.const H..@ H.bool True))
466 -- Composite Combinators
467 -- someTill :: repr a -> repr b -> repr [a]
468 -- someTill p end = negLook end *> (p <:> manyTill p end)
471 constp :: CombApplicable repr => repr a -> repr (b -> a)
472 constp = (H.const <$>)
477 (>>) :: CombApplicable repr => repr a -> repr b -> repr b
480 -- Monoidal Operations
483 (<~>) :: CombApplicable repr => repr a -> repr b -> repr (a, b)
484 (<~>) = liftA2 (H.runtime (,))
487 (<~) :: CombApplicable repr => repr a -> repr b -> repr a
491 (~>) :: CombApplicable repr => repr a -> repr b -> repr b
496 CombApplicable repr =>
497 TermGrammar (a -> b -> c) -> repr a -> repr b -> repr c
498 liftA2 f x = (<*>) (fmap f x)
501 CombApplicable repr =>
502 TermGrammar (a -> b -> c -> d) -> repr a -> repr b -> repr c -> repr d
503 liftA3 f a b c = liftA2 f a b <*> c
508 -- Combinators interpreters for 'Sym.Any'.
509 instance CombApplicable repr => CombApplicable (Sym.Any repr)
510 instance CombSatisfiable repr => CombSatisfiable (Sym.Any repr)
511 instance CombAlternable repr => CombAlternable (Sym.Any repr)
512 instance CombSelectable repr => CombSelectable (Sym.Any repr)
513 instance CombMatchable repr => CombMatchable (Sym.Any repr)
514 instance CombLookable repr => CombLookable (Sym.Any repr)
515 instance CombFoldable repr => CombFoldable (Sym.Any repr)