Skip to content

Commit 6627dfc

Browse files
committed
make flatMap consume whitespace, introduce flatMapX that doesn't
1 parent 19e0fd0 commit 6627dfc

File tree

7 files changed

+50
-14
lines changed

7 files changed

+50
-14
lines changed

fastparse/src/fastparse/internal/MacroImpls.scala

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ object MacroImpls {
163163
}
164164

165165

166-
def flatMapMacro[T: c.WeakTypeTag, V: c.WeakTypeTag]
166+
def flatMapXMacro[T: c.WeakTypeTag, V: c.WeakTypeTag]
167167
(c: Context)
168168
(f: c.Expr[T => ParsingRun[V]]): c.Expr[ParsingRun[V]] = {
169169
import c.universe._
@@ -176,6 +176,28 @@ object MacroImpls {
176176
}
177177
}
178178

179+
def flatMapMacro[T: c.WeakTypeTag, V: c.WeakTypeTag]
180+
(c: Context)
181+
(f: c.Expr[T => ParsingRun[V]])
182+
(whitespace: c.Expr[ParsingRun[Any] => ParsingRun[Unit]]): c.Expr[ParsingRun[V]] = {
183+
import c.universe._
184+
185+
val lhs0 = c.prefix.asInstanceOf[c.Expr[EagerOps[T]]]
186+
reify {
187+
val lhs = lhs0.splice.parse0
188+
whitespace.splice match{ case ws =>
189+
if (!lhs.isSuccess) lhs.asInstanceOf[ParsingRun[V]]
190+
else {
191+
val oldCapturing = lhs.noDropBuffer
192+
lhs.noDropBuffer = true
193+
ws(lhs)
194+
lhs.noDropBuffer = oldCapturing
195+
f.splice(lhs.successValue.asInstanceOf[T])
196+
}
197+
}
198+
}
199+
}
200+
179201
def eitherMacro[T: c.WeakTypeTag, V >: T: c.WeakTypeTag]
180202
(c: Context)
181203
(other: c.Expr[ParsingRun[V]])

fastparse/src/fastparse/package.scala

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -177,12 +177,21 @@ package object fastparse {
177177
(implicit ctx: P[Any]): P[T] = macro MacroImpls.filterMacro[T]
178178
/**
179179
* Transforms the result of this parser using the given function into a
180-
* new parser which is applied. Useful for doing dependent parsing, e.g.
181-
* when parsing JSON you may first parse a character to see if it's a `[`,
182-
* `{`, or `"`, and then deciding whether you next want to parse an array,
183-
* dictionary or string.
180+
* new parser which is applied (after whitespace). Useful for doing
181+
* dependent parsing, e.g. when parsing JSON you may first parse a
182+
* character to see if it's a `[`, `{`, or `"`, and then deciding whether
183+
* you next want to parse an array, dictionary or string.
184184
*/
185-
def flatMap[V](f: T => P[V]): P[V] = macro MacroImpls.flatMapMacro[T, V]
185+
def flatMap[V](f: T => P[V])
186+
(implicit whitespace: P[Any] => P[Unit]): P[V] = macro MacroImpls.flatMapMacro[T, V]
187+
/**
188+
* Transforms the result of this parser using the given function into a
189+
* new parser which is applied (without consuming whitespace). Useful for
190+
* doing dependent parsing, e.g. when parsing JSON you may first parse a
191+
* character to see if it's a `[`, `{`, or `"`, and then deciding whether
192+
* you next want to parse an array, dictionary or string.
193+
*/
194+
def flatMapX[V](f: T => P[V]): P[V] = macro MacroImpls.flatMapXMacro[T, V]
186195

187196
/**
188197
* Either-or operator: tries to parse the left-hand-side, and if that
@@ -195,7 +204,7 @@ package object fastparse {
195204
/**
196205
* Capture operator; makes the parser return the span of input it parsed
197206
* as a [[String]], which can then be processed further using [[~]],
198-
* [[map]] or [[flatMap]]
207+
* [[map]] or [[flatMapX]]
199208
*/
200209
def !(implicit ctx: P[Any]): P[String] = macro MacroImpls.captureMacro
201210

@@ -590,9 +599,9 @@ package object fastparse {
590599

591600
/**
592601
* Like [[AnyChar]], but returns the single character it parses. Useful
593-
* together with [[EagerOps.flatMap]] to provide one-character-lookahead
602+
* together with [[EagerOps.flatMapX]] to provide one-character-lookahead
594603
* style parsing: [[SingleChar]] consumes the single character, and then
595-
* [[EagerOps.flatMap]] can `match` on that single character and decide
604+
* [[EagerOps.flatMapX]] can `match` on that single character and decide
596605
* which downstream parser you wish to invoke
597606
*/
598607
def SingleChar(implicit ctx: P[_]): P[Char] = {

fastparse/test/src/fastparse/ExampleTests.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ object ExampleTests extends TestSuite{
192192
'flatMap{
193193
def leftTag[_: P] = P( "<" ~ (!">" ~ AnyChar).rep(1).! ~ ">")
194194
def rightTag[_: P](s: String) = P( "</" ~ s.! ~ ">" )
195-
def xml[_: P] = P( leftTag.flatMap(rightTag) )
195+
def xml[_: P] = P( leftTag.flatMapX(rightTag) )
196196

197197
val Parsed.Success("a", _) = parse("<a></a>", xml(_))
198198
val Parsed.Success("abcde", _) = parse("<abcde></abcde>", xml(_))

fastparse/test/src/fastparse/IndentationTests.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ object IndentationTests extends TestSuite{
2323
def number[_: P]: P[Int] = P( CharIn("0-9").rep(1).!.map(_.toInt) )
2424

2525
def deeper[_: P]: P[Int] = P( " ".rep(indent + 1).!.map(_.length) )
26-
def blockBody[_: P]: P[Seq[Int]] = "\n" ~ deeper.flatMap(i =>
26+
def blockBody[_: P]: P[Seq[Int]] = "\n" ~ deeper.flatMapX(i =>
2727
new Parser(indent = i).factor.rep(1, sep = ("\n" + " " * i)./)
2828
)
2929
def block[_: P]: P[Int] = P( CharIn("+\\-*/").! ~/ blockBody).map(eval)

fastparse/test/src/fastparse/ParsingTests.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ object ParsingTests extends TestSuite{
216216
// Broken out of the TestSuite block to avoid problems in our 2.10.x
217217
// build due to https://issues.scala-lang.org/browse/SI-7987
218218
def checkFlatmap() = {
219-
checkFail(implicit c => ("Hello" ~/ "Boo").flatMap(_ => Fail).?, ("HelloBoo", 0), 8)
220-
checkFail(implicit c => (("Hello" ~/ "Boo").flatMap(_ => Pass) ~ Fail).?, ("HelloBoo", 0), 8)
219+
checkFail(implicit c => ("Hello" ~/ "Boo").flatMapX(_ => Fail).?, ("HelloBoo", 0), 8)
220+
checkFail(implicit c => (("Hello" ~/ "Boo").flatMapX(_ => Pass) ~ Fail).?, ("HelloBoo", 0), 8)
221221
}
222222
}

pythonparse/src/pythonparse/Statements.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ class Statements(indent: Int){
189189
_.collectFirst{ case (s, None) => s}
190190
}.filter(_.isDefined).map(_.get)
191191
}
192-
def indented = P( deeper.flatMap{ nextIndent =>
192+
def indented = P( deeper.flatMapX{ nextIndent =>
193193
new Statements(nextIndent).stmt.repX(1, spaces.repX(1) ~~ (" " * nextIndent | "\t" * nextIndent)).map(_.flatten)
194194
} )
195195
P( indented | " ".rep ~ simple_stmt )

readme/WritingParsers.scalatex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,11 @@
196196
@p
197197
Which is equivalent and behaves exactly the same.
198198

199+
@p
200+
Note that @code{.flatMap} consumes whitespace between the first
201+
and second parsers; in cases where you do not want to do this,
202+
use @code{.flatMapX}
203+
199204
@sect{Filter}
200205
@hl.ref(tests/"ExampleTests.scala", Seq("'filter", ""))
201206

0 commit comments

Comments
 (0)