Skip to content

Start Pasers Combinators lesson #44

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 17 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ Icon
.AppleDB
.AppleDesktop
Network\ Trash\ Folder
Temporary Items
Temporary\ Items
.apdisk

# intelliJ idea And other editors
Expand Down
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ GEM
execjs (>= 0.3.0, < 3)

PLATFORMS
ruby
x86_64-linux

DEPENDENCIES
Expand Down
8 changes: 8 additions & 0 deletions _data/contents.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,11 @@ basics:
- control-structures
- pattern-matching
- containers
parsers:
- introduction
- readp
- parsec
- megaparsec
- attoparsec
- appar
- parsley
1 change: 1 addition & 0 deletions _data/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ sections:
blog: Blog
contributors: Contributors
translation_report: Translation Report
parsers: Parsers combinators

version_messages:
outdated: "Some contents of this translation may be outdated."
Expand Down
18 changes: 18 additions & 0 deletions en/lessons/parsers/Introduction.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module Introduction where

import Text.ParserCombinators.ReadP

tuples :: ReadP [(String, String)]
tuples = many tuple

tuple :: ReadP (String, String)
tuple =
between (char '(') (char ')') $ do
left <- munch1 (/= ',')
char ','
skipSpaces
right <- munch1 (/= ')')
return (left, right)

example :: [([(String, String)], String)]
example = readP_to_S tuples "(abc,def)(gh, ij)(k,l)"
26 changes: 26 additions & 0 deletions en/lessons/parsers/Parsec.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Parsec where

import Control.Monad
import Data.Functor.Identity
import Text.Parsec
import Text.Parsec.Char
import Text.Parsec.Language
import Text.Parsec.Token

tokenParser :: GenTokenParser String u Identity
tokenParser = makeTokenParser emptyDef

numParser :: Parsec String Integer Integer
numParser = do
n <- integer tokenParser
previous <- getState
guard $ n > previous
putState n
return n

increasingNumbersParser :: Parsec String Integer [Integer]
increasingNumbersParser =
numParser `sepBy` spaces

increasingNumbers :: String -> Either ParseError [Integer]
increasingNumbers = runParser increasingNumbersParser (-1) ""
112 changes: 112 additions & 0 deletions en/lessons/parsers/appar.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
version: 1.0.0
title: Appar
---

{% include toc.html %}

[Appar](https://hackage.haskell.org/package/appar) is different from the other
libraries such that, they are monadic, while `appar` is applicative.
See [this](https://stackoverflow.com/a/7863380/1599054) to have a good explanation
between the two.

# Definition

Let's start with the definition:

```haskell
data MkParser inp a = P {
runParser :: inp -> (Maybe a, inp)
}
```

It really looks like `attoparsec`'s `ZeptoT`, except that the `Monad` is inside
the result.

Let's see the instances:

```haskell
instance Functor (MkParser inp) where
f `fmap` p = return f <*> p

instance Applicative (MkParser inp) where
pure a = P $ \bs -> (Just a, bs)
(<*>) = ap

instance Alternative (MkParser inp) where
empty = mzero
(<|>) = mplus

instance Monad (MkParser inp) where
return = pure
p >>= f = P $ \bs -> case runParser p bs of
(Nothing, bs') -> (Nothing, bs')
(Just a, bs') -> runParser (f a) bs'

instance MonadPlus (MkParser inp) where
mzero = P $ \bs -> (Nothing, bs)
p `mplus` q = P $ \bs -> case runParser p bs of
(Nothing, bs') -> runParser q bs'
(Just a, bs') -> (Just a, bs')
```

One thing surprising is the `Monad` instance for an applicative parser, we can
conclude that the '`Applicative`ness' of the library cames from the lack of
shortcut, more than the implemented solution.

# Construction

How are parsers built:

```haskell
class Eq inp => Input inp where
-- | The head function for input
car :: inp -> Char
-- | The tail function for input
cdr :: inp -> inp
-- | The end of input
nil :: inp
-- | The function to check the end of input
isNil :: inp -> Bool

satisfy :: Input inp => (Char -> Bool) -> MkParser inp Char
satisfy predicate = P sat
where
sat bs
| isNil bs = (Nothing, nil)
| predicate b = (Just b, bs')
| otherwise = (Nothing, bs)
where
b = car bs
bs' = cdr bs
```

A bit abstract (and a bit lisp-like), but without surprises.

Anothor interesting one:

```haskell
try :: MkParser inp a -> MkParser inp a
try p = P $ \bs -> case runParser p bs of
(Nothing, _ ) -> (Nothing, bs)
(Just a, bs') -> (Just a, bs')
```

As expected: the parser is ran, if it succeeds, the input is consumer, or the
original input is returned.

# Running the parser

As we can expect, running the pparser will only consist in getting the computed
result:

```haskell
parse :: Input inp => MkParser inp a -> inp -> Maybe a
parse p bs = fst (runParser p bs)
```

# Conclusion

`appar` is really simple, but it is unique in the sense that it prevent
context-dependant parsing.

Loading