-
Notifications
You must be signed in to change notification settings - Fork 44
/
Parsed.scala
190 lines (138 loc) · 5.76 KB
/
Parsed.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/*
* Copyright 2012-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package laika.parse
/** Represents the result of a `Parser`, a value of type `T` in case of success,
* a message in case of failure as well as the `SourceCursor` for the remaining input.
*
* @author Jens Halm
*/
sealed abstract class Parsed[+T] {
/** The context representing the remaining input
* left over by the parser that produced this result.
*/
val next: SourceCursor
/** Indicates whether this results represents a successful
* parser invocation.
*/
val isSuccess: Boolean
/** Indicates whether this results represents an unsuccessful
* parser invocation.
*/
def isFailure: Boolean = !isSuccess
/** The result as an Option, empty in case of failure.
*/
def toOption: Option[T]
/** The result as an Either, a Left in case of failure.
*/
def toEither: Either[String, T]
/** Returns the result value from the parser invocation if the
* parser succeeded or otherwise the specified fallback value.
*/
def getOrElse[B >: T](default: => B): B = toOption.getOrElse(default)
/** Returns this `Parsed` instance if the parser succeeded or
* otherwise the specified fallback instance.
*/
def orElse[U >: T](default: => Parsed[U]): Parsed[U]
/** Builds a new `Parsed` instance by applying the specified function
* to the result of this instance.
*/
def map[U](f: T => U): Parsed[U]
}
/** The success case of `Parsed` containing the result and the remaining input.
*/
case class Success[+T](result: T, next: SourceCursor) extends Parsed[T] {
val isSuccess = true
def toOption: Option[T] = Some(result)
def toEither: Either[String, T] = Right(result)
def orElse[U >: T](default: => Parsed[U]): Parsed[U] = this
def map[U](f: T => U) = Success(f(result), next)
override def toString = s"[${next.position}] parsed: $result"
}
/** The failure case of `Parsed` containing an error message and the remaining input.
*
* Implementation note:
* The message property is of type `Message`, to allow for lazy message creation.
* The former SDK parser combinators which this API is partially inspired by contained
* a lot of unnecessary string concatenations for messages which were then never read.
* This implementation avoids this extra cost and the result is measurable
* (about 15% performance gain for a typical Markdown document for example).
*
* @param msgProvider A provider that produces an error message for this failure based on its SourceCursor
* @param next The unconsumed input at the point where the failing parser started
* @param maxOffset The offset position the parser could successfully read to before failing
*/
case class Failure(msgProvider: Message, next: SourceCursor, maxOffset: Int)
extends Parsed[Nothing] {
private lazy val failureContext = next.consume(maxOffset - next.offset)
/** The message specifying the cause of the failure.
*/
lazy val message: String = msgProvider.message(failureContext)
val isSuccess = false
def toOption: Option[Nothing] = None
def toEither: Either[String, Nothing] = Left(message)
def orElse[U >: Nothing](default: => Parsed[U]): Parsed[U] = default match {
case s: Success[_] => s
case f: Failure => if (f.maxOffset > maxOffset) f else this
}
def map[U](f: Nothing => U): Failure = this
override def toString: String = {
val pointOfFailure = failureContext.position
s"[$pointOfFailure] failure: $message\n\n${pointOfFailure.lineContentWithCaret}"
}
}
object Failure {
@inline def apply(msgProvider: Message, next: SourceCursor): Failure =
apply(msgProvider, next, next.offset)
}
/** Represents a lazy failure message.
* Implementations can use the specified `SourceCursor` to construct
* the actual message, e.g. for adding parts of the input to the message.
*/
trait Message {
def message(source: SourceCursor): String
}
/** Message companion providing several pre-built messages
* and factory methods.
*/
object Message {
val UnexpectedEOF: Message = fixed("Unexpected end of input")
val ExpectedFailure: Message = fixed("Expected failure, but parser succeeded")
val ExpectedEOF: Message = fixed("Expected end of input")
val ExpectedStart: Message = fixed("Expected start of input")
val ExpectedEOL: Message = fixed("Expected end of line")
private class MessageFactory[T](f: T => String) extends (T => Message) {
def apply(value: T): Message = new Message {
def message(source: SourceCursor): String = f(value)
}
}
/** Builds a message instance for a fixed string,
* independent of the parser context.
*/
def fixed(msg: String): Message = new Message {
def message(source: SourceCursor): String = msg
}
/** Builds a message instance for the specified factory function.
*/
def forContext(f: SourceCursor => String): Message = new Message {
def message(source: SourceCursor): String = f(source)
}
/** Builds a factory function that produces new messages
* based on some arbitrary input type.
* This allows to pre-capture some context for the message
* that does not relate to the parser context.
*/
def forRuntimeValue[T](f: T => String): T => Message = new MessageFactory(f)
}