Skip to content

Commit 6f29a76

Browse files
committed
Test cases for generic parser combinators
Some test cases that demonstrate that Scala's type system is currently not precise enough when it comes to dependent classes.
1 parent a6f8167 commit 6f29a76

File tree

3 files changed

+168
-0
lines changed

3 files changed

+168
-0
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import collection.mutable
2+
3+
/// A parser combinator.
4+
trait Combinator[T]:
5+
6+
/// The context from which elements are being parsed, typically a stream of tokens.
7+
type Context
8+
/// The element being parsed.
9+
type Element
10+
11+
extension (self: T)
12+
/// Parses and returns an element from `context`.
13+
def parse(context: Context): Option[Element]
14+
end Combinator
15+
16+
final case class Apply[C, E](action: C => Option[E])
17+
final case class Combine[A, B](first: A, second: B)
18+
19+
object test:
20+
21+
class apply[C, E] extends Combinator[Apply[C, E]]:
22+
type Context = C
23+
type Element = E
24+
extension(self: Apply[C, E])
25+
def parse(context: C): Option[E] = self.action(context)
26+
27+
def apply[C, E]: apply[C, E] = new apply[C, E]
28+
29+
class combine[A, B](
30+
val f: Combinator[A],
31+
val s: Combinator[B] { type Context = f.Context}
32+
) extends Combinator[Combine[A, B]]:
33+
type Context = f.Context
34+
type Element = (f.Element, s.Element)
35+
extension(self: Combine[A, B])
36+
def parse(context: Context): Option[Element] = ???
37+
38+
def combine[A, B](
39+
_f: Combinator[A],
40+
_s: Combinator[B] { type Context = _f.Context}
41+
): combine[A, B] {
42+
type Context = _f.Context
43+
type Element = (_f.Element, _s.Element)
44+
} = new combine[A, B](_f, _s).asInstanceOf
45+
// cast is needed since the type of new combine[A, B](_f, _s)
46+
// drops the required refinement.
47+
48+
extension [A] (buf: mutable.ListBuffer[A]) def popFirst() =
49+
if buf.isEmpty then None
50+
else try Some(buf.head) finally buf.remove(0)
51+
52+
@main def hello: Unit = {
53+
val source = (0 to 10).toList
54+
val stream = source.to(mutable.ListBuffer)
55+
56+
val n = Apply[mutable.ListBuffer[Int], Int](s => s.popFirst())
57+
val m = Combine(n, n)
58+
59+
val c = combine[
60+
Apply[mutable.ListBuffer[Int], Int],
61+
Apply[mutable.ListBuffer[Int], Int]
62+
](
63+
apply[mutable.ListBuffer[Int], Int],
64+
apply[mutable.ListBuffer[Int], Int]
65+
)
66+
val r = c.parse(m)(stream) // type mismatch, found `mutable.ListBuffer[Int]`, required `?1.Context`
67+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import collection.mutable
2+
3+
/// A parser combinator.
4+
trait Combinator[T]:
5+
6+
/// The context from which elements are being parsed, typically a stream of tokens.
7+
type Context
8+
/// The element being parsed.
9+
type Element
10+
11+
extension (self: T)
12+
/// Parses and returns an element from `context`.
13+
def parse(context: Context): Option[Element]
14+
end Combinator
15+
16+
final case class Apply[C, E](action: C => Option[E])
17+
final case class Combine[A, B](first: A, second: B)
18+
19+
given apply[C, E]: Combinator[Apply[C, E]] with {
20+
type Context = C
21+
type Element = E
22+
extension(self: Apply[C, E]) {
23+
def parse(context: C): Option[E] = self.action(context)
24+
}
25+
}
26+
27+
given combine[A, B, C](using
28+
f: Combinator[A] { type Context = C },
29+
s: Combinator[B] { type Context = C }
30+
): Combinator[Combine[A, B]] with {
31+
type Context = f.Context
32+
type Element = (f.Element, s.Element)
33+
extension(self: Combine[A, B]) {
34+
def parse(context: Context): Option[Element] = ???
35+
}
36+
}
37+
38+
extension [A] (buf: mutable.ListBuffer[A]) def popFirst() =
39+
if buf.isEmpty then None
40+
else try Some(buf.head) finally buf.remove(0)
41+
42+
@main def hello: Unit = {
43+
val source = (0 to 10).toList
44+
val stream = source.to(mutable.ListBuffer)
45+
46+
val n = Apply[mutable.ListBuffer[Int], Int](s => s.popFirst())
47+
val m = Combine(n, n)
48+
49+
val r = m.parse(stream) // works, but Element type is not resolved correctly
50+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import collection.mutable
2+
3+
/// A parser combinator.
4+
trait Combinator[T]:
5+
6+
/// The context from which elements are being parsed, typically a stream of tokens.
7+
type Context
8+
/// The element being parsed.
9+
type Element
10+
11+
extension (self: T)
12+
/// Parses and returns an element from `context`.
13+
def parse(context: Context): Option[Element]
14+
end Combinator
15+
16+
final case class Apply[C, E](action: C => Option[E])
17+
final case class Combine[A, B](first: A, second: B)
18+
19+
given apply[C, E]: Combinator[Apply[C, E]] with {
20+
type Context = C
21+
type Element = E
22+
extension(self: Apply[C, E]) {
23+
def parse(context: C): Option[E] = self.action(context)
24+
}
25+
}
26+
27+
given combine[A, B, C](using
28+
f: Combinator[A] { type Context = C },
29+
s: Combinator[B] { type Context = C }
30+
): Combinator[Combine[A, B]] with {
31+
type Context = f.Context
32+
type Element = (f.Element, s.Element)
33+
extension(self: Combine[A, B]) {
34+
def parse(context: Context): Option[Element] = ???
35+
}
36+
}
37+
38+
extension [A] (buf: mutable.ListBuffer[A]) def popFirst() =
39+
if buf.isEmpty then None
40+
else try Some(buf.head) finally buf.remove(0)
41+
42+
@main def hello: Unit = {
43+
val source = (0 to 10).toList
44+
val stream = source.to(mutable.ListBuffer)
45+
46+
val n = Apply[mutable.ListBuffer[Int], Int](s => s.popFirst())
47+
val m = Combine(n, n)
48+
49+
// val r = m.parse(stream) // error: type mismatch, found `mutable.ListBuffer[Int]`, required `?1.Context`
50+
// it would be great if this worked
51+
}

0 commit comments

Comments
 (0)