Skip to content

Commit 3bfc9d4

Browse files
committed
Fix consumed semantics
This effectively treats the consumed flag as a monoid appended on each sequential action (ie, Writer). This means it can always be a local decision so low-level combinators don't need to consult the previous state. Fixes purescript-contrib#235
1 parent 145c543 commit 3bfc9d4

File tree

3 files changed

+72
-18
lines changed

3 files changed

+72
-18
lines changed

src/Parsing.purs

+22-13
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ data ParseState s = ParseState s Position Boolean
9393
--
9494
-- http://blog.ezyang.com/2014/05/parsec-try-a-or-b-considered-harmful/
9595

96+
appendConsumed :: forall a. ParseState a -> ParseState a -> ParseState a
97+
appendConsumed (ParseState _ _ consumed1) state@(ParseState a b consumed2) =
98+
case consumed1, consumed2 of
99+
true, false -> ParseState a b true
100+
_, _ -> state
101+
96102
-- | The `Parser s` monad with a monad transformer parameter `m`.
97103
newtype ParserT s m a = ParserT
98104
-- The parser is implemented using continuation-passing-style with uncurried
@@ -231,11 +237,12 @@ instance Apply (ParserT s m) where
231237
more \_ ->
232238
runFn5 k1 state1 more lift throw
233239
( mkFn2 \state2 f ->
234-
more \_ ->
235-
runFn5 k2 state2 more lift throw
240+
more \_ -> do
241+
let state2' = state1 `appendConsumed` state2
242+
runFn5 k2 state2' more lift throw
236243
( mkFn2 \state3 a ->
237244
more \_ ->
238-
runFn2 done state3 (f a)
245+
runFn2 done (state2' `appendConsumed` state3) (f a)
239246
)
240247
)
241248
)
@@ -254,7 +261,7 @@ instance Bind (ParserT s m) where
254261
( mkFn2 \state2 a ->
255262
more \_ -> do
256263
let (ParserT k2) = next a
257-
runFn5 k2 state2 more lift throw done
264+
runFn5 k2 (state1 `appendConsumed` state2) more lift throw done
258265
)
259266
)
260267

@@ -271,15 +278,17 @@ instance MonadRec (ParserT s m) where
271278
loop = mkFn3 \state2 arg gas -> do
272279
let (ParserT k1) = next arg
273280
runFn5 k1 state2 more lift throw
274-
( mkFn2 \state3 step -> case step of
275-
Loop nextArg ->
276-
if gas == 0 then
277-
more \_ ->
278-
runFn3 loop state3 nextArg 30
279-
else
280-
runFn3 loop state3 nextArg (gas - 1)
281-
Done res ->
282-
runFn2 done state3 res
281+
( mkFn2 \state3 step -> do
282+
let state3' = state2 `appendConsumed` state3
283+
case step of
284+
Loop nextArg ->
285+
if gas == 0 then
286+
more \_ ->
287+
runFn3 loop state3' nextArg 30
288+
else
289+
runFn3 loop state3' nextArg (gas - 1)
290+
Done res ->
291+
runFn2 done state3' res
283292
)
284293
runFn3 loop state1 initArg 30
285294
)

src/Parsing/String.purs

+2-2
Original file line numberDiff line numberDiff line change
@@ -282,12 +282,12 @@ consumeWith
282282
. (String -> Either String { value :: a, consumed :: String, remainder :: String })
283283
-> ParserT String m a
284284
consumeWith f = ParserT
285-
( mkFn5 \state1@(ParseState input pos oldConsumed) _ _ throw done ->
285+
( mkFn5 \state1@(ParseState input pos _) _ _ throw done ->
286286
case f input of
287287
Left err ->
288288
runFn2 throw state1 (ParseError err pos)
289289
Right { value, consumed, remainder } ->
290-
runFn2 done (ParseState remainder (updatePosString pos consumed remainder) (oldConsumed || not (String.null consumed))) value
290+
runFn2 done (ParseState remainder (updatePosString pos consumed remainder) (not (String.null consumed))) value
291291
)
292292

293293
-- | Combinator which finds the first position in the input `String` where the

test/Test/Main.purs

+48-3
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,19 @@
55

66
module Test.Main where
77

8-
import Prelude (class Eq, class Show, Unit, append, bind, const, discard, div, flip, identity, map, negate, pure, show, unit, void, ($), ($>), (*), (*>), (+), (-), (/), (/=), (<$), (<$>), (<*), (<*>), (<>), (==), (>>=))
8+
import Prelude hiding (between, when)
99

1010
import Control.Alt ((<|>))
1111
import Control.Lazy (fix, defer)
12+
import Control.Monad.Rec.Class (Step(..), tailRecM)
1213
import Control.Monad.State (State, lift, modify, runState)
1314
import Data.Array (some, toUnfoldable)
1415
import Data.Array as Array
1516
import Data.Bifunctor (lmap, rmap)
1617
import Data.CodePoint.Unicode as CodePoint.Unicode
1718
import Data.Either (Either(..), either, fromLeft, hush)
1819
import Data.Foldable (oneOf)
20+
import Data.Function.Uncurried (mkFn5, runFn2)
1921
import Data.List (List(..), fromFoldable, (:))
2022
import Data.List as List
2123
import Data.List.NonEmpty (NonEmptyList(..), catMaybes, cons, cons')
@@ -35,7 +37,7 @@ import Effect (Effect)
3537
import Effect.Console (log, logShow)
3638
import Effect.Unsafe (unsafePerformEffect)
3739
import Node.Process (lookupEnv)
38-
import Parsing (ParseError(..), ParseState(..), Parser, ParserT, Position(..), consume, fail, getParserT, initialPos, parseErrorPosition, position, region, runParser)
40+
import Parsing (ParseError(..), ParseState(..), Parser, ParserT(..), Position(..), consume, fail, getParserT, initialPos, parseErrorPosition, position, region, runParser)
3941
import Parsing.Combinators (advance, between, chainl, chainl1, chainr, chainr1, choice, empty, endBy, endBy1, lookAhead, many, many1, many1Till, many1Till_, manyIndex, manyTill, manyTill_, notFollowedBy, optional, optionMaybe, replicateA, sepBy, sepBy1, sepEndBy, sepEndBy1, skipMany, skipMany1, try, tryRethrow, (<?>), (<??>), (<~?>))
4042
import Parsing.Combinators.Array as Combinators.Array
4143
import Parsing.Expr (Assoc(..), Operator(..), buildExprParser)
@@ -49,7 +51,7 @@ import Parsing.Token as Token
4951
import Partial.Unsafe (unsafePartial)
5052
import Test.Assert (assert', assertEqual')
5153
import Test.IndentationTests as IndentationTests
52-
import Test.Lib
54+
import Test.Lib (class ParseErrorHuman__OnlyString, TestM, mkParseErrorTestMessage, mkParseErrorTestPosition, mkParseTest)
5355

5456
parseTest :: forall s a. Show a => Eq a => ParseErrorHuman__OnlyString s => s -> a -> Parser s a -> Effect Unit
5557
parseTest = mkParseTest runParser
@@ -60,6 +62,13 @@ parseErrorTestPosition = mkParseErrorTestPosition runParser
6062
parseErrorTestMessage :: forall s a. Show a => Parser s a -> s -> String -> Effect Unit
6163
parseErrorTestMessage = mkParseErrorTestMessage runParser
6264

65+
parseState :: forall m s a. (ParseState s -> Tuple (ParseState s) a) -> ParserT s m a
66+
parseState k = ParserT
67+
( mkFn5 \state1 _ _ _ done -> do
68+
let Tuple state2 res = k state1
69+
runFn2 done state2 res
70+
)
71+
6372
parens :: forall m a. ParserT String m a -> ParserT String m a
6473
parens = between (string "(") (string ")")
6574

@@ -581,8 +590,44 @@ takeWhilePropagateFail = do
581590
"f"
582591
(Position { index: 1, line: 1, column: 2 })
583592

593+
applicativeSemantics :: Parser String String
594+
applicativeSemantics =
595+
( string "foo"
596+
<* parseState (\(ParseState a b _) -> Tuple (ParseState a b false) unit)
597+
<* fail "fail"
598+
)
599+
<|> pure ""
600+
601+
bindSemantics :: Parser String String
602+
bindSemantics =
603+
( do
604+
_ <- string "foo"
605+
parseState (\(ParseState a b _) -> Tuple (ParseState a b false) unit)
606+
fail "fail"
607+
)
608+
<|> pure ""
609+
610+
monadRecSemantics :: Parser String String
611+
monadRecSemantics = loop <|> pure ""
612+
where
613+
loop = tailRecM
614+
( case _ of
615+
1 -> do
616+
_ <- string "foo"
617+
pure (Loop 2)
618+
2 ->
619+
parseState (\(ParseState a b _) -> Tuple (ParseState a b false) (Loop 3))
620+
_ ->
621+
fail "fail"
622+
)
623+
1
624+
584625
main :: Effect Unit
585626
main = do
627+
log "\nTESTS Semantics\n"
628+
parseErrorTestMessage applicativeSemantics "foo" "fail"
629+
parseErrorTestMessage bindSemantics "foo" "fail"
630+
parseErrorTestMessage monadRecSemantics "foo" "fail"
586631

587632
log "\nTESTS Indentation\n"
588633
IndentationTests.testIndentationParser

0 commit comments

Comments
 (0)