Skip to content

Commit 697d9cb

Browse files
committed
Merge branch 'delayed-description'
2 parents abab2a0 + ca9ea36 commit 697d9cb

12 files changed

+151
-82
lines changed

Base/src/main/scala-2/Expecting.scala

Lines changed: 0 additions & 14 deletions
This file was deleted.

Base/src/main/scala-2/package.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,8 @@ package object stringContextParserCombinator {
7272
case ExpectingSet.Empty() => {
7373
c.abort(c.enclosingPosition, "Parsing failed")
7474
}
75-
case ExpectingSet.NonEmpty(position, descriptions) => {
76-
// `sorted` to make result deterministic
77-
val descriptions2 = descriptions.toList.sortBy(_.toString).mkString("Expected ", " or ", "")
78-
c.abort(position, descriptions2)
75+
case set @ ExpectingSet.NonEmpty(position, _) => {
76+
c.abort(position, set.renderDescriptions)
7977
}
8078
}
8179
}

Base/src/main/scala-3/Expecting.scala

Lines changed: 0 additions & 17 deletions
This file was deleted.

Base/src/main/scala-3/package.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@ package object stringContextParserCombinator {
1717
case ExpectingSet.Empty() => {
1818
scala.quoted.quotes.reflect.report.errorAndAbort("Parsing failed")
1919
}
20-
case ExpectingSet.NonEmpty(position, descriptions) => {
21-
// `sorted` to make result deterministic
22-
val descriptions2 = descriptions.toList.sortBy(_.toString).mkString("Expected ", " or ", "")
23-
q.reflect.report.errorAndAbort(descriptions2, position + 1)
20+
case set @ ExpectingSet.NonEmpty(position, _) => {
21+
q.reflect.report.errorAndAbort(set.renderDescriptions, position + 1)
2422
}
2523
}
2624
}

Base/src/main/scala/Expecting.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package name.rayrobdod.stringContextParserCombinator
2+
3+
private[stringContextParserCombinator]
4+
final case class Expecting[Pos](
5+
val description:ExpectingDescription,
6+
val position:Pos,
7+
) {
8+
def where(condition:ExpectingDescription) =
9+
new Expecting(description.where(condition), position)
10+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package name.rayrobdod.stringContextParserCombinator
2+
3+
/** Represents a textual description of under what conditions a parser would return success */
4+
private[stringContextParserCombinator]
5+
sealed abstract class ExpectingDescription {
6+
private[stringContextParserCombinator]
7+
def where(condition:ExpectingDescription):ExpectingDescription
8+
private[stringContextParserCombinator]
9+
def render:String
10+
}
11+
12+
private[stringContextParserCombinator]
13+
object ExpectingDescription {
14+
def apply(backing: String): ExpectingDescription = {
15+
new ExpectingDescription {
16+
override def where(condition:ExpectingDescription):ExpectingDescription = {
17+
ExpectingDescription(s"${this.render} where ${condition.render}")
18+
}
19+
override def render: String = {
20+
backing
21+
}
22+
override def toString: String = {
23+
s"ExpectingDescription.eager($backing)"
24+
}
25+
}
26+
}
27+
28+
def delayed(backingFn: => String): ExpectingDescription = {
29+
new ExpectingDescription {
30+
private lazy val backingValue = backingFn
31+
override def where(condition:ExpectingDescription):ExpectingDescription = {
32+
ExpectingDescription.delayed(s"${this.render} where ${condition.render}")
33+
}
34+
override def render: String = {
35+
backingValue
36+
}
37+
override def toString: String = {
38+
s"ExpectingDescription.delayed(<fn>)"
39+
}
40+
}
41+
}
42+
}

Base/src/main/scala/ExpectingSet.scala

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ sealed trait ExpectingSet[Pos] {
88

99
private[stringContextParserCombinator]
1010
object ExpectingSet {
11-
final case class NonEmpty[Pos : Ordering](val maxPosition:Pos, val descriptionsAtMaxPosition:Set[ExpectingDescription]) extends ExpectingSet[Pos] {
11+
final case class NonEmpty[Pos : Ordering](
12+
val maxPosition:Pos,
13+
val descriptionsAtMaxPosition:Seq[ExpectingDescription]
14+
) extends ExpectingSet[Pos] {
1215
override def ++(other: ExpectingSet[Pos]): ExpectingSet[Pos] = {
1316
import Ordering.Implicits.infixOrderingOps
1417
other match {
@@ -22,19 +25,29 @@ object ExpectingSet {
2225
override def mapDescriptions(fn: ExpectingDescription => ExpectingDescription):ExpectingSet[Pos] = {
2326
new NonEmpty[Pos](maxPosition, descriptionsAtMaxPosition.map(fn))
2427
}
28+
29+
def renderDescriptions: String =
30+
// `sorted` and `distinct` to make result deterministic
31+
descriptionsAtMaxPosition
32+
.view
33+
.map(_.render)
34+
.distinct
35+
.toList
36+
.sorted
37+
.mkString("Expected ", " or ", "")
2538
}
2639

2740
final case class Empty[Pos]() extends ExpectingSet[Pos] {
2841
override def ++(other: ExpectingSet[Pos]):ExpectingSet[Pos] = other
2942
override def mapDescriptions(fn: ExpectingDescription => ExpectingDescription):ExpectingSet[Pos] = this
3043
}
3144

32-
def apply[Pos : Ordering](a:Expecting[Pos]):ExpectingSet[Pos] = new NonEmpty(a.position, Set(a.description))
45+
def apply[Pos : Ordering](a:Expecting[Pos]):ExpectingSet[Pos] = new NonEmpty(a.position, Seq(a.description))
3346

3447
def fromSpecific[Pos : Ordering](as:Iterable[Expecting[Pos]]):ExpectingSet[Pos] = {
3548
if (as.nonEmpty) {
3649
val maxPosition = as.map(_.position).reduce({(a, b) => if (Ordering[Pos].gt(a, b)) a else b})
37-
new NonEmpty(maxPosition, as.collect({case ex if ex.position == maxPosition => ex.description}).toSet)
50+
new NonEmpty(maxPosition, as.collect({case ex if ex.position == maxPosition => ex.description}).toSeq)
3851
} else {
3952
new Empty
4053
}

Base/src/main/scala/Extractor.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,8 @@ final class Extractor[Expr[_], Type[_], -A] private[stringContextParserCombinato
6969
case f:Failure[Int] => {
7070
val msg = f.expecting match {
7171
case ExpectingSet.Empty() => "Parsing Failed"
72-
case ExpectingSet.NonEmpty(position, descriptions) => {
73-
// `sorted` to make result deterministic
74-
val exp = descriptions.toList.sortBy(_.toString).mkString("Expected ", " or ", "")
72+
case set @ ExpectingSet.NonEmpty(position, _) => {
73+
val exp = set.renderDescriptions
7574
val instr = sc.parts.mkString(argString)
7675
val pointer = (" " * position) + "^"
7776

Base/src/main/scala/Interpolator.scala

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,8 @@ final class Interpolator[-Expr, +A] private[stringContextParserCombinator] (
6565
case f:Failure[Int] => {
6666
val msg = f.expecting match {
6767
case ExpectingSet.Empty() => "Parsing Failed"
68-
case ExpectingSet.NonEmpty(position, descriptions) => {
69-
// `sorted` to make result deterministic
70-
val exp = descriptions.toList.sortBy(_.toString).mkString("Expected ", " or ", "")
68+
case set @ ExpectingSet.NonEmpty(position, _) => {
69+
val exp = set.renderDescriptions
7170
val instr = sc.parts.mkString(argString)
7271
val pointer = (" " * position) + "^"
7372

Base/src/main/scala/internal/package.scala

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -51,47 +51,48 @@ package object internal {
5151
* Returns a string that describes which codepoints match the predicate
5252
*/
5353
private def describeCodepointPredicate(predicate:Int => Boolean, domainMax: Int):ExpectingDescription = {
54-
val builder = new StringBuilder()
55-
var inMatchingBlock:Boolean = false
56-
var firstCharOfBlock:Int = 0
57-
58-
(0 to domainMax).foreach({c =>
59-
if (predicate(c)) {
60-
if (inMatchingBlock) {
61-
// continue
62-
} else {
63-
inMatchingBlock = true
64-
firstCharOfBlock = c
65-
}
66-
} else {
67-
if (inMatchingBlock) {
68-
builder.++=("'")
69-
builder.++=(escape(firstCharOfBlock))
70-
if (firstCharOfBlock != c - 1) {
71-
builder.++=("'<=c<='")
72-
builder.++=(escape((c - 1)))
54+
ExpectingDescription.delayed({
55+
val builder = new StringBuilder()
56+
var inMatchingBlock:Boolean = false
57+
var firstCharOfBlock:Int = 0
58+
59+
(0 to domainMax).foreach({c =>
60+
if (predicate(c)) {
61+
if (inMatchingBlock) {
62+
// continue
63+
} else {
64+
inMatchingBlock = true
65+
firstCharOfBlock = c
7366
}
74-
builder.++=("' or ")
75-
inMatchingBlock = false
7667
} else {
77-
// continue
68+
if (inMatchingBlock) {
69+
builder.++=("'")
70+
builder.++=(escape(firstCharOfBlock))
71+
if (firstCharOfBlock != c - 1) {
72+
builder.++=("'<=c<='")
73+
builder.++=(escape((c - 1)))
74+
}
75+
builder.++=("' or ")
76+
inMatchingBlock = false
77+
} else {
78+
// continue
79+
}
7880
}
81+
})
82+
if (inMatchingBlock) {
83+
builder.++=("'")
84+
builder.++=(escape(firstCharOfBlock))
85+
builder.++=("'<=c<='")
86+
builder.++=(escape(domainMax))
87+
builder.++=("' or ")
7988
}
80-
})
81-
if (inMatchingBlock) {
82-
builder.++=("'")
83-
builder.++=(escape(firstCharOfBlock))
84-
builder.++=("'<=c<='")
85-
builder.++=(escape(domainMax))
86-
builder.++=("' or ")
87-
}
88-
ExpectingDescription(
89+
8990
if (builder.length > 4) {
9091
builder.substring(0, builder.length - 4)
9192
} else {
9293
"nothing"
9394
}
94-
)
95+
})
9596
}
9697

9798
/* * * Leaf parsers * * */

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## [Unreleased](https://github.com/rayrobdod/string-context-parser-combinator/compare/0.1.1...HEAD)
4+
* Improve performance of `codePointWhere` and `charWhere` if default error message is not needed
5+
36
## [0.1.1](https://github.com/rayrobdod/string-context-parser-combinator/compare/0.1.0...0.1.1) – 2025-02-04
47
* Add symbolic operators to Parser, Extractor and Interpolator
58
* `<~>` and `<|>` as aliases for `andThen` and `orElse`, respectively

build.sbt

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,50 @@ lazy val base = (projectMatrix in file("Base"))
115115
)
116116
case _ => Seq()
117117
}),
118-
mimaPreviousArtifacts := Set(organization.value %% name.value % "0.1.0"),
119-
tastyMiMaPreviousArtifacts := mimaPreviousArtifacts.value,
120118
console / initialCommands := """
121119
import scala.quoted.{Expr, Quotes}
122120
import name.rayrobdod.stringContextParserCombinator.Interpolator.idInterpolators._
123121
import name.rayrobdod.stringContextParserCombinator.typeclass._
124122
""",
123+
mimaPreviousArtifacts := Set(
124+
organization.value %% name.value % "0.1.0",
125+
organization.value %% name.value % "0.1.1",
126+
),
127+
tastyMiMaPreviousArtifacts := mimaPreviousArtifacts.value,
128+
mimaBinaryIssueFilters ++= {
129+
import com.typesafe.tools.mima.core._
130+
Seq(
131+
ProblemFilters.exclude[IncompatibleMethTypeProblem]("name.rayrobdod.stringContextParserCombinator.ExpectingSet#NonEmpty.*"),
132+
ProblemFilters.exclude[IncompatibleMethTypeProblem]("name.rayrobdod.stringContextParserCombinator.internal.*"),
133+
ProblemFilters.exclude[IncompatibleResultTypeProblem]("name.rayrobdod.stringContextParserCombinator.ExpectingSet#NonEmpty.*"),
134+
ProblemFilters.exclude[IncompatibleResultTypeProblem]("name.rayrobdod.stringContextParserCombinator.internal.package.describeCodepointPredicate"),
135+
ProblemFilters.exclude[MissingClassProblem]("name.rayrobdod.stringContextParserCombinator.Expecting$package"),
136+
ProblemFilters.exclude[MissingClassProblem]("name.rayrobdod.stringContextParserCombinator.Expecting$package$"),
137+
)
138+
},
139+
tastyMiMaConfig ~= { prevConfig =>
140+
import scala.collection.JavaConverters._
141+
import tastymima.intf._
142+
prevConfig
143+
.withMoreArtifactPrivatePackages(Seq(
144+
"name.rayrobdod.stringContextParserCombinator.internal",
145+
).asJava)
146+
.withMoreProblemFilters(Seq(
147+
ProblemMatcher.make(ProblemKind.AbstractClass, "name.rayrobdod.stringContextParserCombinator.ExpectingDescription"),
148+
ProblemMatcher.make(ProblemKind.IncompatibleTypeChange, "name.rayrobdod.stringContextParserCombinator.ExpectingSet.*"),
149+
ProblemMatcher.make(ProblemKind.InternalError, "name.rayrobdod.stringContextParserCombinator.Expecting.*"),
150+
ProblemMatcher.make(ProblemKind.InternalError, "name.rayrobdod.stringContextParserCombinator.ExpectingSet.*"),
151+
ProblemMatcher.make(ProblemKind.MissingClass, "name.rayrobdod.stringContextParserCombinator.Expecting$package$"),
152+
ProblemMatcher.make(ProblemKind.MissingParent, "name.rayrobdod.stringContextParserCombinator.ExpectingDescription"),
153+
ProblemMatcher.make(ProblemKind.MissingParent, "name.rayrobdod.stringContextParserCombinator.ExpectingDescription$"),
154+
ProblemMatcher.make(ProblemKind.MissingTermMember, "name.rayrobdod.stringContextParserCombinator.Expecting.*"),
155+
ProblemMatcher.make(ProblemKind.MissingTermMember, "name.rayrobdod.stringContextParserCombinator.ExpectingDescription.*"),
156+
ProblemMatcher.make(ProblemKind.MissingTermMember, "name.rayrobdod.stringContextParserCombinator.ExpectingSet.*"),
157+
ProblemMatcher.make(ProblemKind.MissingTermMember, "name.rayrobdod.stringContextParserCombinator.Input.consume"),
158+
ProblemMatcher.make(ProblemKind.MissingTermMember, "name.rayrobdod.stringContextParserCombinator.internal.*"),
159+
ProblemMatcher.make(ProblemKind.RestrictedVisibilityChange, "name.rayrobdod.stringContextParserCombinator.ExpectingDescription.*"),
160+
).asJava)
161+
},
125162
)
126163
.jvmPlatform(scalaVersions = Seq(
127164
scala212Ver,

0 commit comments

Comments
 (0)