-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathparser.scala
131 lines (98 loc) · 3.64 KB
/
parser.scala
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
import cats.parse.Parser as P
import cats.parse.Parser0 as P0
import cats.parse.Caret
import cats.parse.Rfc5234.{sp, alpha, digit, crlf, lf}
import cats.syntax.all.*
case class Operator(raw: Char)
enum Expr[F[_]]:
case Add(l: Expr[F], r: Expr[F], operator: F[Operator])
case Mul(l: Expr[F], r: Expr[F], operator: F[Operator])
case Name(value: F[String])
case Lit(value: F[Int])
def map[G[_]](f: [A] => F[A] => G[A]): Expr[G] =
this match
case Lit(num) => Lit(f(num))
case Name(num) => Name(f(num))
case Add(e1, e2, o) => Add(e1.map(f), e2.map(f), f(o))
case Mul(e1, e2, o) => Mul(e1.map(f), e2.map(f), f(o))
end Expr
enum Statement[F[_]]:
case Ass(name: Expr.Name[F], e: Expr[F])
def map[G[_]](f: [A] => F[A] => G[A]): Statement[G] =
this match
case Ass(nm, e) =>
// TODO: how to preserve this?
Ass(nm.map(f).asInstanceOf[Expr.Name[G]], e.map(f))
case class Program[F[_]](statements: Vector[F[Statement[F]]], text: String)
case class Span(from: Caret, to: Caret):
def contains(c: Caret) =
val before =
(from.line < c.line) || (from.line == c.line && from.col <= c.col)
val after = (to.line > c.line) || (to.line == c.line && to.col >= c.col)
before && after
case class WithSpan[A](span: Span, value: A)
object QuickmaffsParser:
case class ParsingError(caret: Caret)
def parse(s: String): Either[ParsingError, Program[WithSpan]] =
parseWithPosition(statements.map(_.toVector).map(Program.apply(_, s)), s)
def parseExpr(s: String): Either[ParsingError, Expr[WithSpan]] =
parseWithPosition(expr, s)
def parseStatement(
s: String
): Either[ParsingError, WithSpan[Statement[WithSpan]]] =
parseWithPosition(statement, s)
private def withSpan[A](p: P[A]): P[WithSpan[A]] =
(P.caret.with1 ~ p ~ P.caret).map { case ((start, a), end) =>
WithSpan(Span(start, end), a)
}
private val name: P[Expr.Name[WithSpan]] =
withSpan(
(P.charWhere(_.isLetter) ~ P.charsWhile0(_.isLetter)).map(_.toString + _)
).map(Expr.Name.apply)
val litNum =
withSpan(P.charsWhile(_.isDigit).map(_.toInt)).map(Expr.Lit.apply)
inline def operator(ch: Char): P[WithSpan[Operator]] =
withSpan(P.char(ch).as(Operator(ch))).surroundedBy(sp.rep0)
val LB = operator('(')
val RB = operator(')')
val PLUS = operator('+')
val MUL = operator('*')
val ASS = operator('=')
val SEMICOLON = operator(';')
private val expr = P.recursive[Expr[WithSpan]] { recurse =>
inline def pair[F[_]](
p: P[Expr[F]],
sym: P[F[Operator]]
): P[(Expr[F], Expr[F], F[Operator])] =
(p, sym, p).tupled.map { case (l, o, r) => (l, r, o) }
val e = recurse.surroundedBy(sp.rep0)
val add = pair(e, PLUS).between(LB, RB).map(Expr.Add.apply)
val mul = pair(e, MUL).between(LB, RB).map(Expr.Mul.apply)
P.oneOf(add.backtrack :: mul :: litNum :: name :: Nil)
}
private val assignment =
((name <* sp.? <* ASS) ~ expr)
.map(Statement.Ass.apply)
.surroundedBy(sp.rep0)
private val sep = SEMICOLON orElse crlf orElse lf
private val statement = withSpan(assignment)
private val statements = statement
.repSep0(sep.rep)
.surroundedBy(sep.rep0)
private def parseWithPosition[A](
p: P0[A] | P[A],
s: String
): Either[ParsingError, A] =
p.parseAll(s).left.map { err =>
val offset = err.failedAtOffset
var line = 0
var col = 0
(0 to (offset min s.length)).foreach { i =>
if (s(i) == '\n') then
line += 1
col = 0
else col += 1
}
ParsingError(Caret(line, col, offset))
}
end QuickmaffsParser