Skip to content

README Recursion #214

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

Merged
merged 1 commit into from
Nov 10, 2022
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Other improvements:

- Better error messages for `manyIndex` (#211 by @jamesdbrock)
- Docs for `region` (#213 by @jamesdbrock)
- README Recursion (#214 by @jamesdbrock)

## [v10.0.0](https://github.com/purescript-contrib/purescript-parsing/releases/tag/v9.1.0) - 2022-07-18

Expand Down
44 changes: 42 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,48 @@ will return `Right [true, false, true]`.
Starting with v9.0.0, all parsers and combinators in this package are always
stack-safe.

## Recursion

For the most part, we can just write recursive parsers (parsers defined in
terms of themselves) and they will work as we expect.

In some cases like this:

```purescript
aye :: Parser String Char
aye = do
char 'a'
aye
```

we might get a compile-time *CycleInDeclaration* error which looks like this:

```
The value of aye is undefined here, so this reference is not allowed.


See https://github.com/purescript/documentation/blob/master/errors/CycleInDeclaration.md for more information,
or to contribute content related to this error.
```

This is happening because we tried to call `aye` recursively __“at a point
where such a reference would be unavailable because of *strict evaluation*.”__

The
[best way to solve](https://discourse.purescript.org/t/parsing-recursively-with-purescript-parsing/3184/2)
this is to stick a
[`Data.Lazy.defer`](https://pursuit.purescript.org/packages/purescript-lazy/docs/Data.Lazy#v:defer)
in front of the parser to break the cycle.

```purescript
aye :: Parser String Char
aye = defer \_ -> do
char 'a'
aye
```



## Resources

- [*Monadic Parsers at the Input Boundary* (YouTube)](https://www.youtube.com/watch?v=LLkbzt4ms6M) by James Brock is an introductory tutorial to monadic parser combinators with this package.
Expand All @@ -124,8 +166,6 @@ from a failed alternative.

- [*Parser Combinators in Haskell*](https://serokell.io/blog/parser-combinators-in-haskell) by Heitor Toledo Lassarote de Paula.

- [*Parsing recursively*](https://github.com/Thimoteus/SandScript/wiki/2.-Parsing-recursively) “Because PureScript is not evaluated lazily like Haskell is, this will bite us if we naïvely try to port recursive Haskell parsing to PureScript.”

There are lots of other great monadic parsing tutorials on the internet.

## Related Packages
Expand Down
25 changes: 24 additions & 1 deletion test/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ module Test.Main where
import Prelude hiding (between, when)

import Control.Alt ((<|>))
import Control.Lazy (fix)
import Control.Lazy (fix, defer)
import Control.Monad.State (State, lift, modify, runState)
import Data.Array (some, toUnfoldable)
import Data.Array as Array
import Data.Bifunctor (lmap, rmap)
import Data.Either (Either(..), either, fromLeft, hush)
import Data.Foldable (oneOf)
import Data.List (List(..), fromFoldable, (:))
import Data.List as List
import Data.List.NonEmpty (NonEmptyList(..), catMaybes, cons, cons')
import Data.List.NonEmpty as NE
import Data.Maybe (Maybe(..), fromJust, maybe)
Expand Down Expand Up @@ -1128,3 +1129,25 @@ main = do
, expected: [ "Expected letter at position index:6 (line:2, column:1)", "▼", "🍷bbbb" ]
}

log "\nTESTS recursion"

do
let
aye :: Parser String Char
aye = defer \_ -> char 'a' *> (aye <|> pure 'e')
assertEqual' "recusion aye"
{ actual: runParser "aaa" aye
, expected: Right 'e'
}

do
let
aye :: Parser String (List Char)
aye = defer \_ -> List.Cons <$> char 'a' <*> (aye <|> bee <|> pure List.Nil)

bee :: Parser String (List Char)
bee = defer \_ -> List.Cons <$> char 'b' <*> (aye <|> bee <|> pure List.Nil)
assertEqual' "mutual recusion aye bee"
{ actual: runParser "aabbaa" aye
, expected: Right ('a' : 'a' : 'b' : 'b' : 'a' : 'a' : List.Nil)
}