Skip to content

Commit 556f6b3

Browse files
committed
fix: make atto parser faster than the scala parser combinator one
1 parent b46e338 commit 556f6b3

File tree

1 file changed

+32
-13
lines changed

1 file changed

+32
-13
lines changed

bench/src/main/scala/cron4s/atto/package.scala

+32-13
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,33 @@ package cron4s
1919
import _root_.atto._
2020
import Atto._
2121
import cats.implicits._
22-
2322
import cron4s.expr._
2423

2524
package object atto {
2625
import CronField._
2726
import CronUnit._
2827

29-
private val sexagesimal: Parser[Int] = int.filter(x => x >= 0 && x < 60)
30-
private val decimal: Parser[Int] = int.filter(x => x >= 0)
31-
private val literal: Parser[String] = takeWhile1(_ >= ' ')
28+
private def oneOrTwoDigitsPositiveInt: Parser[Int] = {
29+
30+
val getDigits = for {
31+
d1 <- digit
32+
d2 <- opt(digit)
33+
} yield d2.fold(s"$d1")(x => s"$d1$x")
34+
35+
getDigits.flatMap(s =>
36+
try {
37+
ok(s.toInt)
38+
} catch {
39+
// scala-js can't parse non-alpha digits so we just fail in that case.
40+
case _: java.lang.NumberFormatException =>
41+
err[Int]("https://github.com/scala-js/scala-js/issues/2935")
42+
}
43+
)
44+
} namedOpaque "oneOrTwoDigitsPositiveInt"
45+
46+
private val sexagesimal: Parser[Int] = oneOrTwoDigitsPositiveInt.filter(x => x >= 0 && x < 60)
47+
48+
private val literal: Parser[String] = takeWhile1(x => x != ' ' && x != '-')
3249

3350
private val hyphen: Parser[Char] = elem(_ == '-', "hyphen")
3451
private val comma: Parser[Char] = elem(_ == ',', "comma")
@@ -54,17 +71,17 @@ package object atto {
5471
// Hours
5572

5673
val hours: Parser[ConstNode[Hour]] =
57-
decimal.filter(x => (x >= 0) && (x < 24)).map(ConstNode[Hour](_))
74+
oneOrTwoDigitsPositiveInt.filter(x => (x >= 0) && (x < 24)).map(ConstNode[Hour](_))
5875

5976
// Days Of Month
6077

6178
val daysOfMonth: Parser[ConstNode[DayOfMonth]] =
62-
decimal.filter(x => (x >= 1) && (x <= 31)).map(ConstNode[DayOfMonth](_))
79+
oneOrTwoDigitsPositiveInt.filter(x => (x >= 1) && (x <= 31)).map(ConstNode[DayOfMonth](_))
6380

6481
// Months
6582

6683
private[this] val numericMonths =
67-
decimal.filter(_ <= 12).map(ConstNode[Month](_))
84+
oneOrTwoDigitsPositiveInt.filter(x => (x >= 0) && (x <= 12)).map(ConstNode[Month](_))
6885

6986
private[this] val textualMonths =
7087
literal.filter(Months.textValues.contains).map { value =>
@@ -78,7 +95,7 @@ package object atto {
7895
// Days Of Week
7996

8097
private[this] val numericDaysOfWeek =
81-
decimal.filter(_ < 7).map(ConstNode[DayOfWeek](_))
98+
oneOrTwoDigitsPositiveInt.filter(x => (x >= 0) && (x <= 6)).map(ConstNode[DayOfWeek](_))
8299

83100
private[this] val textualDaysOfWeek =
84101
literal.filter(DaysOfWeek.textValues.contains).map { value =>
@@ -111,9 +128,10 @@ package object atto {
111128
unit: CronUnit[F]
112129
): Parser[SeveralNode[F]] = {
113130
def compose(b: => Parser[EnumerableNode[F]]) =
114-
sepBy1(b, comma)
115-
.filter(_.size > 1)
116-
.map(values => SeveralNode.fromSeq[F](values.toList).get)
131+
sepBy(b, comma)
132+
.collect {
133+
case first :: second :: tail => SeveralNode(first, second, tail:_*)
134+
}
117135

118136
compose(between(base).map(between2Enumerable) | base.map(const2Enumerable))
119137
}
@@ -122,7 +140,7 @@ package object atto {
122140
unit: CronUnit[F]
123141
): Parser[EveryNode[F]] = {
124142
def compose(b: => Parser[DivisibleNode[F]]) =
125-
((b <~ slash) ~ decimal.filter(_ > 0)).map {
143+
((b <~ slash) ~ oneOrTwoDigitsPositiveInt.filter(_ > 0)).map {
126144
case (exp, freq) => EveryNode[F](exp, freq)
127145
}
128146

@@ -166,8 +184,9 @@ package object atto {
166184
} yield CronExpr(sec, min, hour, day, month, weekDay)
167185

168186
def parse(e: String): Either[Error, CronExpr] =
169-
(cron.parseOnly(e): @unchecked) match {
187+
(phrase(cron).parseOnly(e): @unchecked) match {
170188
case ParseResult.Done(_, result) => Right(result)
189+
case ParseResult.Fail("", _, _) => Left(ExprTooShort)
171190
case ParseResult.Fail(rest, _, msg) =>
172191
val position = e.length() - rest.length() + 1
173192
Left(ParseFailed(msg, position, Some(rest)))

0 commit comments

Comments
 (0)