Skip to content

Commit f38c9ee

Browse files
committed
feat: prove lawful instances of Functor, Applicative and Alternative for Parser
1 parent 6e0d7d7 commit f38c9ee

File tree

2 files changed

+29
-26
lines changed

2 files changed

+29
-26
lines changed

parser-combinators.cabal

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ cabal-version: 1.12
44
--
55
-- see: https://github.com/sol/hpack
66
--
7-
-- hash: 431ab4fb1b73226f52fcb1b718332fbbedb7a2139aaf244e7c3dbeb952b40bc8
7+
-- hash: 71f3ca8abf5da7454836ff89392289ab895d955597e20575edd7257de0129f98
88

99
name: parser-combinators
1010
version: 0.1.0.0
@@ -29,6 +29,7 @@ library
2929
exposed-modules:
3030
Lib
3131
Parser
32+
Primitives
3233
other-modules:
3334
Paths_parser_combinators
3435
hs-source-dirs:

src/Parser.hs

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,43 @@
11
{-# LANGUAGE OverloadedStrings #-}
22

33
module Parser
4-
(
4+
( runParser,
5+
Parser (..),
56
)
67
where
78

8-
import qualified Data.Map as M
9+
import Control.Applicative
910
import qualified Data.Text as T
10-
import Text.Read (readMaybe)
11-
12-
data JsonVal
13-
= -- Maybe indicates the fractional part of the number. This will obviously be `Nothing` if it's
14-
-- just a plain integer.
15-
JsonNumber Integer (Maybe Integer)
16-
| JsonBool Bool
17-
| JsonNull
18-
| JsonString T.Text
19-
| JsonArray [JsonVal]
20-
| JsonObj (M.Map T.Text JsonVal)
21-
deriving (Eq, Show)
2211

12+
-- | Wraps a `parse` function into a newtype called `Parser` parameterized by some type `a`.
2313
newtype Parser a = Parser {parse :: T.Text -> Maybe (a, T.Text)}
2414

15+
-- Lawful instance of Functor implemented for Parser
16+
instance Functor Parser where
17+
-- Applies a function to the parsed stream yielding another Parser with the transformed stream
18+
fmap f p = Parser $ \s -> do
19+
(x, rest) <- parse p s
20+
Just (f x, rest)
21+
22+
instance Applicative Parser where
23+
pure x = Parser $ \s -> Just (x, s)
24+
25+
-- This is how we'll essentially end up chaining parsers together
26+
(<*>) (Parser p1) (Parser p2) = Parser $ \s -> do
27+
(f, s') <- p1 s
28+
(x, s'') <- p2 s'
29+
Just (f x, s'')
30+
31+
instance Alternative Parser where
32+
-- Picks the first non-empty `Maybe`
33+
(<|>) (Parser p1) (Parser p2) = Parser $ \s -> p1 s <|> p2 s
34+
35+
-- The "empty" value for Parser will be a parser that parses to `Nothing` which indicates a failure
36+
empty = Parser $ \_ -> Nothing
37+
2538
runParser :: Parser a -> T.Text -> a
2639
runParser m s =
2740
case parse m s of
2841
Just (res, "") -> res
2942
Just (_, rest) -> error "Parser did not consume the entire stream"
3043
Nothing -> error "Parser did not manage to parse anything"
31-
32-
-- A test parser
33-
digit :: Parser Int
34-
digit = Parser $ \s ->
35-
case s of
36-
"" -> Nothing
37-
t ->
38-
let ch = T.head t; cs = T.tail t
39-
in case readMaybe [ch] of
40-
Just x -> Just (x, cs)
41-
Nothing -> Nothing

0 commit comments

Comments
 (0)