-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathParserCombinator.sc
149 lines (113 loc) · 3.94 KB
/
ParserCombinator.sc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import Zipped.*
import Trampoline.*
trait Parser[+A] { self =>
import Parser.*
def eval(input: String): Eval[Either[String, (A, String)]]
def parse(input: String): Either[String, (A, String)] =
eval(input).value
def map[B](f: A => B): Parser[B] = { input =>
self.eval(input).map(_.map { case (a, left) => f(a) -> left })
}
def flatMap[B](f: A => Parser[B]): Parser[B] = { input =>
Eval.defer {
self.eval(input).flatMap {
case Left(msg) => Eval.now(Left(msg))
case Right((a, left)) => f(a).eval(left)
}
}
}
def as[B](b: => B): Parser[B] =
map(_ => b)
def zipWith[B, C](that: => Parser[B])(f: (A, B) => C): Parser[C] =
this.flatMap(l => that.map(r => f(l, r)))
def zip[B, A1 >: A, B1 >: B](that: => Parser[B]): Parser[Zipped.Type[A1, B1]] =
this.zipWith(that)(Zipped.apply)
def or[A1 >: A](that: => Parser[A1]): Parser[A1] = Parser.eval { input =>
parse(input) match {
case Left(_) => that.eval(input)
case r => Eval.now(r)
}
}
def opt: Parser[Option[A]] = Parser { input =>
parse(input) match {
case Left(_) => Right(None -> input)
case Right((a, left)) => Right(Some(a) -> left)
}
}
def many: Parser[List[A]] = Parser { input =>
@scala.annotation.tailrec
def loop(in: String, acc: List[A]): (List[A], String) = this.parse(in) match {
case Right((a, left)) => loop(left, acc :+ a)
case _: Left[?, ?] => acc -> in
}
val (results, remaining) = loop(input, List.empty)
Right((results, remaining))
}
def many1: Parser[List[A]] =
this.zipWith(this.many)(_ :: _)
def many(sep: Parser[?]): Parser[List[A]] =
many1(sep) | const(Nil)
def many1(sep: Parser[?]): Parser[List[A]] =
this.zipWith((sep *> this).many)(_ :: _)
}
object Parser {
def apply[A](f: String => Either[String, (A, String)]): Parser[A] =
input => Eval.now(f(input))
def defer[A](f: => Parser[A]): Parser[A] =
f.eval(_)
def eval[A](f: String => Eval[Either[String, (A, String)]]): Parser[A] =
f(_)
extension [A](p: Parser[A]) {
infix def ~[B](that: => Parser[B]): Parser[Zipped.Type[A, B]] =
p.zip(that)
infix def ~~>[B](f: A => Parser[B]): Parser[B] =
p.flatMap(f)
infix def |[B >: A](that: => Parser[B]): Parser[B] =
p.or(that)
infix def ~>[B](b: => B): Parser[B] =
p.as(b)
infix def ~>[B](f: A => B): Parser[B] =
p.map(f)
infix def *>[B](that: Parser[B]): Parser[B] =
p.flatMap(_ => that)
infix def <*(that: Parser[?]): Parser[A] =
p.flatMap(l => that.as(l))
}
// Conversions
given Conversion[String, Parser[String]] = Parser.str
given Conversion[Char, Parser[Char]] = Parser.char
// DSL
def const[A](a: A): Parser[A] = Parser(input => Right(a -> input))
def str(str: String): Parser[String] = Parser { input =>
if (input.startsWith(str)) Right(str -> input.drop(str.length))
else Left(s"Expected: $str, got $input")
}
def char(c: Char): Parser[Char] =
str(c.toString).map(_.head)
def regex(pattern: String): Parser[String] = {
val regex = new scala.util.matching.Regex(s"^($pattern).*")
Parser { input =>
regex.findFirstMatchIn(input) match {
case None =>
Left(s"Expected regex: $pattern, got $input")
case Some(m) =>
val matchh = m.group(1)
Right(matchh -> input.drop(matchh.length))
}
}
}
def digit: Parser[Int] =
regex("[0-9]").map(_.toInt)
def oneOf[A](p: Parser[A], ps: Parser[A]*): Parser[A] =
oneOf(p +: ps)
def oneOf[A](ps: Iterable[Parser[A]]): Parser[A] = Parser { input =>
val results = ps.toVector.map(_.parse(input))
val success = results
.collect { case Right(a, left) => a -> left }
.sortBy(_._2.size)
.map(Right.apply)
success.headOption
.orElse(results.headOption)
.getOrElse(throw new IllegalStateException("oneOf: Must have at least one Parser supplied"))
}
}