Skip to content

Commit 8e177f2

Browse files
authored
Merge pull request #194 from purescript-contrib/replace2
replaceT instead of streamEditT
2 parents e94bf23 + 490cfa8 commit 8e177f2

File tree

3 files changed

+75
-56
lines changed

3 files changed

+75
-56
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ New features:
4848
- Add the `anyTill` primitive `String` combinator. (#186 by @jamesdbrock)
4949
- Add the `Parsing.String.Replace` module, copied from
5050
https://github.com/jamesdbrock/purescript-parsing-replace (#188 by @jamesdbrock)
51+
`streamEditT` can be written in terms of `replaceT`.
52+
53+
`streamEditT input sep editor = replaceT input (sep >>= editor >>> lift)`
54+
55+
(#188 by @jamesdbrock, #194 by @jamesdbrock)
5156
- Add the `advance` and `manyIndex` combinators. (#193 by @jamesdbrock)
5257

5358
Bugfixes:

src/Parsing/String/Replace.purs

+46-39
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ module Parsing.String.Replace
6363
, breakCapT
6464
, splitCap
6565
, splitCapT
66-
, streamEdit
67-
, streamEditT
66+
, replace
67+
, replaceT
6868
) where
6969

7070
import Prelude
@@ -81,7 +81,7 @@ import Data.List (List(..), foldl, uncons, (:))
8181
import Data.List as List
8282
import Data.List.NonEmpty (NonEmptyList, fromList, singleton)
8383
import Data.Maybe (Maybe(..), fromJust, isJust, maybe)
84-
import Data.Newtype (unwrap, wrap)
84+
import Data.Newtype (unwrap)
8585
import Data.Nullable (Nullable, notNull, null)
8686
import Data.String (CodePoint, joinWith)
8787
import Data.String as CodePoint
@@ -410,20 +410,20 @@ splitCap
410410
-> NonEmptyList (Either String a)
411411
splitCap input sep = unwrap $ splitCapT input sep
412412

413-
-- | Monad transformer version of `streamEdit`. The `sep` parser and the
414-
-- | `editor` function will both run in the monad context.
413+
-- | Monad transformer version of `replace`.
415414
-- |
416415
-- | #### Example
417416
-- |
418417
-- | Find an environment variable in curly braces and replace it with its value
419418
-- | from the environment.
420-
-- | We can read from the environment because `streamEditT` is running the
421-
-- | `editor` function in `Effect`.
419+
-- | We can read from the environment with `lookupEnv` because `replaceT` is
420+
-- | running the `sep` parser in `Effect`.
422421
-- |
423422
-- | ```purescript
424-
-- | pattern = string "{" *> anyTill (string "}")
425-
-- | editor = fst >>> lookupEnv >=> fromMaybe "" >>> pure
426-
-- | streamEditT "◀ {HOME} ▶" pattern editor
423+
-- | replaceT "◀ {HOME} ▶" do
424+
-- | _ <- string "{"
425+
-- | Tuple home _ <- anyTill (string "}")
426+
-- | lift (lookupEnv home) >>= maybe empty pure
427427
-- | ```
428428
-- |
429429
-- | Result:
@@ -433,15 +433,14 @@ splitCap input sep = unwrap $ splitCapT input sep
433433
-- | ```
434434
-- |
435435
-- | [![Perl Problems](https://imgs.xkcd.com/comics/perl_problems.png)](https://xkcd.com/1171/)
436-
streamEditT
437-
:: forall m a
436+
replaceT
437+
:: forall m
438438
. (Monad m)
439439
=> (MonadRec m)
440440
=> String
441-
-> ParserT String m a
442-
-> (a -> m String)
441+
-> ParserT String m String
443442
-> m String
444-
streamEditT input sep editor = do
443+
replaceT input sep = do
445444
runParserT input (Tuple <$> (splitCapCombinator sep) <*> rest) >>= case _ of
446445
Left _ -> pure input
447446
Right (Tuple { carry, rlist, arraySize } remain) -> do
@@ -459,13 +458,12 @@ streamEditT input sep editor = do
459458

460459
let
461460
accum
462-
:: { index :: Int, rlist' :: List (Tuple String a) }
463-
-> m (Step { index :: Int, rlist' :: List (Tuple String a) } Unit)
461+
:: { index :: Int, rlist' :: List (Tuple String String) }
462+
-> m (Step { index :: Int, rlist' :: List (Tuple String String) } Unit)
464463
accum { index, rlist' } = case uncons rlist' of
465464
Nothing -> pure $ Done unit
466465
Just { head: Tuple unmatched matched, tail } -> do
467-
edited <- editor matched
468-
doST $ unsafePartial $ Array.ST.Partial.poke index (notNull edited) arr
466+
doST $ unsafePartial $ Array.ST.Partial.poke index (notNull matched) arr
469467
if String.null unmatched then
470468
do
471469
pure $ Loop { index: index - 1, rlist': tail }
@@ -487,12 +485,13 @@ streamEditT input sep editor = do
487485
doST = pure <<< unsafePerformEffect <<< toEffect
488486

489487
-- |
490-
-- | #### Stream editor
488+
-- | #### Find-and-replace
491489
-- |
492-
-- | Also known as “find-and-replace”, or “match-and-substitute”. Find all
490+
-- | Also called “match-and-substitute”. Find all
493491
-- | of the leftmost non-overlapping sections of the input string which match
494492
-- | the pattern parser `sep`, and
495-
-- | replace them with the result of the `editor` function.
493+
-- | replace them with the result of the parser.
494+
-- | The `sep` parser must return a result of type `String`.
496495
-- |
497496
-- | This function can be used instead of
498497
-- | [Data.String.replaceAll](https://pursuit.purescript.org/packages/purescript-strings/docs/Data.String#v:replaceAll)
@@ -501,39 +500,47 @@ streamEditT input sep editor = do
501500
-- |
502501
-- | #### Access the matched section of text in the `editor`
503502
-- |
504-
-- | If you want access to the matched string in the `editor` function,
505-
-- | then combine the pattern parser `sep`
506-
-- | with `match`. This will effectively change
507-
-- | the type of the `editor` function to `(String /\ a) -> String`.
508-
-- |
509-
-- | This allows us to write an `editor` function which can choose to not
510-
-- | edit the match and just leave it as it is. If the `editor` function
511-
-- | returns the first item in the tuple, then `streamEdit` will not change
512-
-- | the matched string.
503+
-- | To get access to the matched string for the replacement
504+
-- | combine the pattern parser `sep`
505+
-- | with `match`.
506+
-- | This allows us to write a `sep` parser which can choose to not
507+
-- | edit the match and just leave it as it is.
513508
-- |
514509
-- | So, for all `sep`:
515510
-- |
516511
-- | ```purescript
517-
-- | streamEdit input (match sep) fst == input
512+
-- | replace input (fst <$> match sep) == input
518513
-- | ```
519514
-- |
520515
-- | #### Example
521516
-- |
522517
-- | Find and uppercase the `"needle"` pattern.
523518
-- |
524519
-- | ```purescript
525-
-- | streamEdit "hay needle hay" (string "needle") toUpper
520+
-- | replace "hay needle hay" (toUpper <$> string "needle")
526521
-- | ```
527522
-- |
528523
-- | Result:
529524
-- |
530525
-- | ```purescript
531526
-- | "hay NEEDLE hay"
532527
-- | ```
533-
streamEdit
534-
:: forall a
535-
. String
536-
-> Parser String a
537-
-> (a -> String)
528+
-- |
529+
-- | #### Example
530+
-- |
531+
-- | Find integers and double them.
532+
-- |
533+
-- | ```purescript
534+
-- | replace "1 6 21 107" (show <$> (_*2) <$> intDecimal)
535+
-- | ```
536+
-- |
537+
-- | Result:
538+
-- |
539+
-- | ```purescript
540+
-- | "2 12 42 214"
541+
-- | ```
542+
replace
543+
:: String
544+
-> Parser String String
538545
-> String
539-
streamEdit input sep editor = unwrap $ streamEditT input sep (wrap <<< editor)
546+
replace input sep = unwrap $ replaceT input sep

test/Main.purs

+24-17
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Prelude hiding (between, when)
99

1010
import Control.Alt ((<|>))
1111
import Control.Lazy (fix)
12-
import Control.Monad.State (State, modify, runState)
12+
import Control.Monad.State (State, lift, modify, runState)
1313
import Data.Array (some, toUnfoldable)
1414
import Data.Array as Array
1515
import Data.Bifunctor (lmap, rmap)
@@ -18,7 +18,7 @@ import Data.Foldable (oneOf)
1818
import Data.List (List(..), fromFoldable, (:))
1919
import Data.List.NonEmpty (NonEmptyList(..), catMaybes, cons, cons')
2020
import Data.List.NonEmpty as NE
21-
import Data.Maybe (Maybe(..), fromJust)
21+
import Data.Maybe (Maybe(..), fromJust, maybe)
2222
import Data.NonEmpty ((:|))
2323
import Data.Number (infinity, nan)
2424
import Data.Number as Data.Number
@@ -34,12 +34,12 @@ import Effect.Console (log, logShow)
3434
import Effect.Unsafe (unsafePerformEffect)
3535
import Node.Process (lookupEnv)
3636
import Parsing (ParseError(..), Parser, ParserT, Position(..), consume, fail, initialPos, parseErrorMessage, parseErrorPosition, position, region, runParser)
37-
import Parsing.Combinators (advance, between, chainl, chainl1, chainr, chainr1, choice, endBy, endBy1, lookAhead, many, many1, many1Till, many1Till_, manyIndex, manyTill, manyTill_, notFollowedBy, optionMaybe, sepBy, sepBy1, sepEndBy, sepEndBy1, skipMany, skipMany1, try, (<?>), (<??>), (<~?>))
37+
import Parsing.Combinators (advance, between, chainl, chainl1, chainr, chainr1, choice, empty, endBy, endBy1, lookAhead, many, many1, many1Till, many1Till_, manyIndex, manyTill, manyTill_, notFollowedBy, optionMaybe, sepBy, sepBy1, sepEndBy, sepEndBy1, skipMany, skipMany1, try, (<?>), (<??>), (<~?>))
3838
import Parsing.Expr (Assoc(..), Operator(..), buildExprParser)
3939
import Parsing.Language (haskellDef, haskellStyle, javaStyle)
4040
import Parsing.String (anyChar, anyCodePoint, anyTill, char, eof, match, regex, rest, satisfy, string, takeN)
41-
import Parsing.String.Basic (intDecimal, number, letter, noneOfCodePoints, oneOfCodePoints, whiteSpace)
42-
import Parsing.String.Replace (breakCap, splitCap, splitCapT, streamEdit, streamEditT)
41+
import Parsing.String.Basic (intDecimal, letter, noneOfCodePoints, number, oneOfCodePoints, whiteSpace)
42+
import Parsing.String.Replace (breakCap, replace, replaceT, splitCap, splitCapT)
4343
import Parsing.Token (TokenParser, makeTokenParser, token, when)
4444
import Parsing.Token as Token
4545
import Partial.Unsafe (unsafePartial)
@@ -951,20 +951,16 @@ main = do
951951
{ actual: splitCap "abc" consume
952952
, expected: NonEmptyList $ Right unit :| Left "a" : Right unit : Left "b" : Right unit : Left "c" : Right unit : Nil
953953
}
954-
assertEqual' "streamEdit1"
955-
{ actual: streamEdit "aBc" (match $ string "B") fst
956-
, expected: "aBc"
957-
}
958-
assertEqual' "streamEdit2"
959-
{ actual: streamEdit "aBd" (string "B") (const "C")
954+
assertEqual' "replace2"
955+
{ actual: replace "aBd" (string "B" *> pure "C")
960956
, expected: "aCd"
961957
}
962-
assertEqual' "streamEdit3"
963-
{ actual: streamEdit "abcd" (takeN 1) toUpper
958+
assertEqual' "replace3"
959+
{ actual: replace "abcd" (toUpper <$> takeN 1)
964960
, expected: "ABCD"
965961
}
966-
assertEqual' "streamEdit4"
967-
{ actual: streamEdit "abc" (pure unit) (\_ -> "X")
962+
assertEqual' "replace4"
963+
{ actual: replace "abc" (pure "X")
968964
, expected: "XaXbXcX"
969965
}
970966
assertEqual' "String.Replace example0"
@@ -979,10 +975,17 @@ main = do
979975
{ actual: catMaybes $ hush <$> splitCap ".A...\n...A." (position <* string "A")
980976
, expected: (Position { index: 1, line: 1, column: 2 }) : (Position { index: 9, line: 2, column: 4 } : Nil)
981977
}
982-
assertEqual' "String.Replace example3"
983-
{ actual: unsafePerformEffect $ streamEditT "◀ {HOME} ▶" (string "{" *> anyTill (string "}")) (fst >>> lookupEnv >=> unsafePartial fromJust >>> pure)
978+
assertEqual' "String.Replace example3'"
979+
{ actual: unsafePerformEffect $ replaceT "◀ {HOME} ▶" do
980+
_ <- string "{"
981+
Tuple home _ <- anyTill (string "}")
982+
lift (lookupEnv home) >>= maybe empty pure
984983
, expected: "" <> unsafePartial (fromJust (unsafePerformEffect (lookupEnv "HOME"))) <> ""
985984
}
985+
assertEqual' "String.Replace example4'"
986+
{ actual: replace "1 6 21 107" (show <$> (_ * 2) <$> intDecimal)
987+
, expected: "2 12 42 214"
988+
}
986989
assertEqual' "String.Replace example4"
987990
{ actual:
988991
let
@@ -1006,6 +1009,10 @@ main = do
10061009
rmap fst <$> splitCap "((🌼)) (()())" (match balancedParens)
10071010
, expected: NonEmptyList $ Right "((🌼))" :| Left " " : Right "(()())" : Nil
10081011
}
1012+
assertEqual' "String.Replace example6"
1013+
{ actual: replace "hay needle hay" (toUpper <$> string "needle")
1014+
, expected: "hay NEEDLE hay"
1015+
}
10091016

10101017
log "\nTESTS manyIndex\n"
10111018

0 commit comments

Comments
 (0)