Skip to content

Commit ee5b1fe

Browse files
authored
Factory Iteration (#258)
This PR, which closes #141, introduces `Factory`-forms for all iterative combinators. These allow them to return any arbitrary type `C` (not limited to sequences, `String` is possible, as are `Set`s and `Map`s). This is done by including a `Factory[A, C]` into these combinators. Such factories can be obtained by implicit conversion from `IterableFactory` etc, which are the base types for the companion objects of various collections. For instance `many(p)` is equivalent to `many(p, List)`. In Scala 2, the inference for these can be a bit shaky, and full type ascription may be required (unfortunately). In Scala 3 this is not an issue. To improve the situation on Scala 2, additional overloads could be added to take explicitly `IterableFactory` (this is done for some combinators anyway, as they would otherwise always need the ascription even in Scala 3); however, this would increase API noise and doesn't account for other special cases like `EvidenceIterableFactory`, which allows for the construction of `Array` and `SortedSet`s, say. One fix to this problem would be to bite the bullet and move the combinators to suffix-form: ```scala p.many, p.many(List) p.sepBy(sep), p.sepBy(sep, List) ... ``` The question would be where to draw the line. This would have the advantage of improving the autocompletion of combinators!
2 parents 5ed54f2 + 3981352 commit ee5b1fe

File tree

12 files changed

+699
-140
lines changed

12 files changed

+699
-140
lines changed

parsley/shared/src/main/scala/parsley/Parsley.scala

+67-13
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ final class Parsley[+A] private [parsley] (private [parsley] val internal: front
396396
*/
397397
def <**>[B](pf: =>Parsley[A => B]): Parsley[B] = lift.lift2[A, A=>B, B]((x, f) => f(x), this, pf).uo("<**>")
398398

399-
// FIXME: move into nodes
399+
// FIXME: names move into nodes
400400
@inline private def lseq[B](q: =>Parsley[B], name: String): Parsley[A] = new Parsley(new frontend.<*(this.internal, q.internal)).uo(name)
401401
@inline private def rseq[B](q: =>Parsley[B], name: String): Parsley[B] = new Parsley(new frontend.*>(this.internal, q.internal)).uo(name)
402402

@@ -1020,7 +1020,7 @@ object Parsley extends ParsleyImpl with PlatformSpecific {
10201020
* problem: a recursion point inside one of these strict fields
10211021
* will cause an infinite loop at runtime! This can be fixed by
10221022
* ensuring that this becomes part of a lazy argument. This is
1023-
* a solution described by the [[combinator$.sequence `sequence`]]
1023+
* a solution described by the [[combinator$.sequence[A]* `sequence`]]
10241024
* combinator, for instance: `p <::> sequence(q, .., r)` will ensure
10251025
* that the `sequence` is in a lazy position in `<::>` meaning that
10261026
* even if any of `q` to `r` must be lazy, they can go in the strict
@@ -1097,6 +1097,7 @@ private [parsley] abstract class ParsleyImpl {
10971097
* @group basic
10981098
*/
10991099
final def fresh[A](x: =>A): Parsley[A] = new Parsley(new singletons.Fresh(x))
1100+
@inline private [parsley] final def transFresh[A](x: =>A) = fresh(x).ut() // FIXME: switch round?
11001101

11011102
/** This combinator parses its first argument `either`, and then parses either `left` or `right` depending on its result.
11021103
*
@@ -1306,12 +1307,40 @@ private [parsley] abstract class ParsleyImpl {
13061307
* @group iter
13071308
*/
13081309
final def many[A](p: Parsley[A]): Parsley[List[A]] = many(p, List)
1310+
/** This combinator repeatedly parses a given parser '''zero''' or more times, collecting the results into a type `C` via a `Factory[A, C]`.
1311+
*
1312+
* Parses a given parser, `p`, repeatedly until it fails. If `p` failed having consumed input,
1313+
* this combinator fails. Otherwise when `p` fails '''without consuming input''', this combinator
1314+
* will return all of the results, `x,,1,,` through `x,,n,,` (with `n >= 0`), in a structure: `C(x,,1,,, .., x,,n,,)`.
1315+
* If `p` was never successful, an empty `C` is returned.
1316+
*
1317+
* @example {{{
1318+
* scala> import parsley.character.string
1319+
* scala> import parsley.Parsley.many
1320+
* scala> val p = many(string("ab"), Vector)
1321+
* scala> p.parse("")
1322+
* val res0 = Success(Vector.empty)
1323+
* scala> p.parse("ab")
1324+
* val res1 = Success(Vector("ab"))
1325+
* scala> p.parse("abababab")
1326+
* val res2 = Success(Vector("ab", "ab", "ab", "ab"))
1327+
* scala> p.parse("aba")
1328+
* val res3 = Failure(..)
1329+
* }}}
1330+
*
1331+
* @param p the parser to execute multiple times.
1332+
* @param factory a way to construct the result type `C`
1333+
* @tparam C a structure that can store `A`s inside it.
1334+
* @return a parser that parses `p` until it fails, returning all of all the successful results.
1335+
* @since 5.0.0
1336+
* @group iter
1337+
*/
1338+
final def many[A, C](p: Parsley[A], factory: Factory[A, C]): Parsley[C] = secretSome(fresh(factory.newBuilder), p, "many")
13091339
// this is needed for Scala 2 to avoid manual ascription (and ascribes cleaner), but isn't really needed for Scala 3 and increases doc/API footprint
1310-
// we can add them later if we really wanted to
1340+
// we can add them later if we really wanted to. Alternatively, p.many(factory) would remove this problem entirely and provide better intellisense.
13111341
//final private [parsley] def many[A, CC[_]](p: Parsley[A], factory: IterableFactory[CC]): Parsley[CC[A]] = many[A, CC[A]](p, factory)
13121342
// this is needed for Scala 2 (or manual ascription on A+C) for ArraySeq, but isn't really needed for Scala 3, and saves 2.12 work
13131343
//final def many[Ev[_], A: Ev, CC[_]](p: Parsley[A], factory: EvidenceIterableFactory[CC, Ev]): Parsley[CC[A]] = many[A, CC[A]](p, factory)
1314-
final private [parsley] def many[A, C](p: Parsley[A], factory: Factory[A, C]): Parsley[C] = new Parsley(new frontend.Many(p.internal, factory))
13151344

13161345
/** This combinator repeatedly parses a given parser '''one''' or more times, collecting the results into a list.
13171346
*
@@ -1339,15 +1368,40 @@ private [parsley] abstract class ParsleyImpl {
13391368
* @since 4.5.0
13401369
* @group iter
13411370
*/
1342-
final def some[A](p: Parsley[A]): Parsley[List[A]] = (p <::> many(p).ut()).uo("some")
1343-
private [parsley] final def some[A, C](p: Parsley[A], factory: Factory[A, C]): Parsley[C] = secretSome(p, p, factory).uo("some")
1344-
// This could be generalised to be the new many, where many(p, factory) = secretSome(fresh(factory.newBuilder), p, factory)
1345-
private [parsley] final def secretSome[A, C](init: Parsley[A], p: Parsley[A], factory: Factory[A, C]): Parsley[C] = {
1346-
secretSome(init.map(factory.newBuilder += _).ut(), p)
1371+
final def some[A](p: Parsley[A]): Parsley[List[A]] = some(p, List)
1372+
/** This combinator repeatedly parses a given parser '''one''' or more times, collecting the results into a type `C` via a `Factory[A, C]`.
1373+
*
1374+
* Parses a given parser, `p`, repeatedly until it fails. If `p` failed having consumed input,
1375+
* this combinator fails. Otherwise when `p` fails '''without consuming input''', this combinator
1376+
* will return all of the results, `x,,1,,` through `x,,n,,` (with `n >= 1`), in a structure: `C(x,,1,,, .., x,,n,,)`.
1377+
* If `p` was not successful at least one time, this combinator fails.
1378+
*
1379+
* @example {{{
1380+
* scala> import parsley.character.string
1381+
* scala> import parsley.Parsley.some
1382+
* scala> val p = some(string("ab"), Vector)
1383+
* scala> p.parse("")
1384+
* val res0 = Failure(..)
1385+
* scala> p.parse("ab")
1386+
* val res1 = Success(Vector("ab"))
1387+
* scala> p.parse("abababab")
1388+
* val res2 = Success(Vector("ab", "ab", "ab", "ab"))
1389+
* scala> p.parse("aba")
1390+
* val res3 = Failure(..)
1391+
* }}}
1392+
*
1393+
* @param p the parser to execute multiple times.
1394+
* @param factory a way to construct the result type `C`.
1395+
* @tparam C a structure that can store `A`s inside it.
1396+
* @return a parser that parses `p` until it fails, returning all of all the successful results.
1397+
* @since 5.0.0
1398+
* @group iter
1399+
*/
1400+
final def some[A, C](p: Parsley[A], factory: Factory[A, C]): Parsley[C] = secretSome(p, p, factory, "some")
1401+
private [parsley] final def secretSome[A, C](init: Parsley[A], p: Parsley[A], factory: Factory[A, C], debugName: String): Parsley[C] = {
1402+
secretSome(init.map(factory.newBuilder += _).ut(), p, debugName)
13471403
}
1348-
private [parsley] final def secretSome[A, C](init: Parsley[mutable.Builder[A, C]], p: Parsley[A]): Parsley[C] = {
1349-
val pf = transPure[(mutable.Builder[A, C], A) => mutable.Builder[A, C]](_ += _)
1350-
// Can't use the regular foldLeft1 here, because we need a fresh Builder each time.
1351-
expr.infix.secretLeft1(init, p, pf, null).map(_.result())
1404+
private [parsley] final def secretSome[A, C](init: Parsley[mutable.Builder[A, C]], p: Parsley[A], debugName: String): Parsley[C] = {
1405+
new Parsley(new frontend.Many(init.internal, p.internal, debugName))
13521406
}
13531407
}

parsley/shared/src/main/scala/parsley/character.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ private [parsley] trait character {
507507
* @since 4.0.0
508508
* @group string
509509
*/
510-
final def strings(str0: String, strs: String*): Parsley[String] = strings(str0 -> pure(str0), strs.map(s => s -> pure(s)): _*)
510+
final def strings(str0: String, strs: String*): Parsley[String] = strings(str0 -> pure(str0), strs.map(s => s -> pure(s))*)
511511

512512
/** This combinator tries to parse each of the key-value pairs `kvs` (and `kv0`), until one of them succeeds.
513513
*
@@ -552,7 +552,7 @@ private [parsley] trait character {
552552
choice(ss.groupBy(_._1.head).toList.sortBy(_._1).view.map(_._2).flatMap { s =>
553553
val (sLast, pLast) :: rest = s.toList.sortBy(_._1.length): @unchecked
554554
((string(sLast).ut() ~> pLast.ut()).ut() :: rest.map { case (s, p) => (atomic(string(s).ut()).ut() ~> p).ut() }).reverse
555-
}.toSeq: _*).uo((kv0._1 +: kvs.map(_._1)).mkString("strings(", ", ", ")"))
555+
}.toSeq*).uo((kv0._1 +: kvs.map(_._1)).mkString("strings(", ", ", ")"))
556556
}
557557

558558
/** This parser will parse '''any''' single character from the input, failing if there is no input remaining.

0 commit comments

Comments
 (0)