diff --git a/CHANGELOG.md b/CHANGELOG.md index 9256025..16bdef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index af21bac..f06ca3c 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 diff --git a/test/Main.purs b/test/Main.purs index 62ad146..b7b4b28 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -8,7 +8,7 @@ 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 @@ -16,6 +16,7 @@ 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) @@ -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) + }