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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
|
/* NSC -- new Scala compiler
* Copyright 2005-2013 LAMP/EPFL
* @author Martin Odersky
*/
package scala
package reflect
package internal
package util
/** @inheritdoc */
class Position extends scala.reflect.api.Position with InternalPositionImpl with DeprecatedPosition {
type Pos = Position
def pos: Position = this
def withPos(newPos: Position): macros.Attachments { type Pos = Position.this.Pos } = newPos
protected def fail(what: String) = throw new UnsupportedOperationException(s"Position.$what on $this")
// If scala-refactoring extends Position directly it seems I have no
// choice but to offer all the concrete methods.
def isDefined = false
def isRange = false
def source: SourceFile = NoSourceFile
def start: Int = fail("start")
def point: Int = fail("point")
def end: Int = fail("end")
}
object Position {
val tabInc = 8
private def validate[T <: Position](pos: T): T = {
if (pos.isRange)
assert(pos.start <= pos.end, s"bad position: ${pos.show}")
pos
}
/** Prints the message with the given position indication. */
def formatMessage(posIn: Position, msg: String, shortenFile: Boolean): String = {
val pos = if (posIn eq null) NoPosition else posIn
val prefix = pos.source match {
case NoSourceFile => ""
case s if shortenFile => s.file.name + ":"
case s => s.file.path + ":"
}
prefix + (pos showError msg)
}
def offset(source: SourceFile, point: Int): Position = validate(new OffsetPosition(source, point))
def range(source: SourceFile, start: Int, point: Int, end: Int): Position = validate(new RangePosition(source, start, point, end))
def transparent(source: SourceFile, start: Int, point: Int, end: Int): Position = validate(new TransparentPosition(source, start, point, end))
}
class OffsetPosition(sourceIn: SourceFile, pointIn: Int) extends DefinedPosition {
override def isRange = false
override def source = sourceIn
override def point = pointIn
override def start = point
override def end = point
}
class RangePosition(sourceIn: SourceFile, startIn: Int, pointIn: Int, endIn: Int) extends OffsetPosition(sourceIn, pointIn) {
override def isRange = true
override def start = startIn
override def end = endIn
}
class TransparentPosition(sourceIn: SourceFile, startIn: Int, pointIn: Int, endIn: Int) extends RangePosition(sourceIn, startIn, pointIn, endIn) {
override def isTransparent = true
}
case object NoPosition extends UndefinedPosition
case class FakePos(msg: String) extends UndefinedPosition {
override def toString = msg
}
sealed abstract class DefinedPosition extends Position {
final override def isDefined = true
override def equals(that: Any) = that match {
case that: DefinedPosition => source.file == that.source.file && start == that.start && point == that.point && end == that.end
case _ => false
}
override def hashCode = Seq[Any](source.file, start, point, end).##
override def toString = (
if (isRange) s"RangePosition($canonicalPath, $start, $point, $end)"
else s"source-$canonicalPath,line-$line,$pointMessage$point"
)
private def pointMessage = if (point > source.length) "out-of-bounds-" else "offset="
private def canonicalPath = source.file.canonicalPath
}
sealed abstract class UndefinedPosition extends Position {
final override def isDefined = false
override def isRange = false
override def source = NoSourceFile
override def start = fail("start")
override def point = fail("point")
override def end = fail("end")
}
private[util] trait InternalPositionImpl {
self: Position =>
// The methods which would be abstract in Position if it were
// possible to change Position.
def isDefined: Boolean
def isRange: Boolean
def source: SourceFile
def start: Int
def point: Int
def end: Int
/** Map this position to its position in the original source file
* (which may be this position unchanged.)
*/
def finalPosition: Pos = source positionInUltimateSource this
def isTransparent = false
def isOffset = isDefined && !isRange
def isOpaqueRange = isRange && !isTransparent
def pointOrElse(alt: Int): Int = if (isDefined) point else alt
def makeTransparent: Position = if (isOpaqueRange) Position.transparent(source, start, point, end) else this
/** Copy a range position with a changed value.
*/
def withStart(start: Int): Position = copyRange(start = start)
def withPoint(point: Int): Position = if (isRange) copyRange(point = point) else Position.offset(source, point)
def withEnd(end: Int): Position = copyRange(end = end)
def withSource(source: SourceFile): Position = copyRange(source = source)
def withShift(shift: Int): Position = Position.range(source, start + shift, point + shift, end + shift)
/** Convert a range position to a simple offset.
*/
def focusStart: Position = if (this.isRange) asOffset(start) else this
def focus: Position = if (this.isRange) asOffset(point) else this
def focusEnd: Position = if (this.isRange) asOffset(end) else this
/** If you have it in for punctuation you might not like these methods.
* However I think they're aptly named.
*
* | means union
* ^ means "the point" (look, it's a caret)
* |^ means union, taking the point of the rhs
* ^| means union, taking the point of the lhs
*/
def |(that: Position, poses: Position*): Position = poses.foldLeft(this | that)(_ | _)
def |(that: Position): Position = this union that
def ^(point: Int): Position = this withPoint point
def |^(that: Position): Position = (this | that) ^ that.point
def ^|(that: Position): Position = (this | that) ^ this.point
def union(pos: Position): Position = (
if (!pos.isRange) this
else if (this.isRange) copyRange(start = start min pos.start, end = end max pos.end)
else pos
)
def includes(pos: Position): Boolean = isRange && pos.isDefined && start <= pos.start && pos.end <= end
def properlyIncludes(pos: Position): Boolean = includes(pos) && (start < pos.start || pos.end < end)
def precedes(pos: Position): Boolean = bothDefined(pos) && end <= pos.start
def properlyPrecedes(pos: Position): Boolean = bothDefined(pos) && end < pos.start
def sameRange(pos: Position): Boolean = bothRanges(pos) && start == pos.start && end == pos.end
// This works because it's a range position invariant that S1 < E1 and S2 < E2.
// So if S1 < E2 and S2 < E1, then both starts precede both ends, which is the
// necessary condition to establish that there is overlap.
def overlaps(pos: Position): Boolean = bothRanges(pos) && start < pos.end && pos.start < end
def line: Int = if (hasSource) source.offsetToLine(point) + 1 else 0
def column: Int = if (hasSource) calculateColumn() else 0
def lineContent: String = if (hasSource) source.lineToString(line - 1) else ""
def lineCaret: String = if (hasSource) " " * (column - 1) + "^" else ""
@deprecated("use `lineCaret`", since="2.11.0")
def lineCarat: String = lineCaret
def showError(msg: String): String = {
def escaped(s: String) = {
def u(c: Int) = f"\\u$c%04x"
def uable(c: Int) = (c < 0x20 && c != '\t') || c == 0x7F
if (s exists (c => uable(c))) {
val sb = new StringBuilder
s foreach (c => sb append (if (uable(c)) u(c) else c))
sb.toString
} else s
}
def errorAt(p: Pos) = {
def where = p.line
def content = escaped(p.lineContent)
def indicator = p.lineCaret
f"$where: $msg%n$content%n$indicator"
}
finalPosition match {
case FakePos(fmsg) => s"$fmsg $msg"
case NoPosition => msg
case pos => errorAt(pos)
}
}
def showDebug: String = toString
def show = (
if (isOpaqueRange) s"[$start:$end]"
else if (isTransparent) s"<$start:$end>"
else if (isDefined) s"[$point]"
else "[NoPosition]"
)
private def asOffset(point: Int): Position = Position.offset(source, point)
private def copyRange(source: SourceFile = source, start: Int = start, point: Int = point, end: Int = end): Position =
Position.range(source, start, point, end)
private def calculateColumn(): Int = {
var idx = source.lineToOffset(source.offsetToLine(point))
var col = 0
while (idx != point) {
col += (if (source.content(idx) == '\t') Position.tabInc - col % Position.tabInc else 1)
idx += 1
}
col + 1
}
private def hasSource = source ne NoSourceFile
private def bothRanges(that: Position) = isRange && that.isRange
private def bothDefined(that: Position) = isDefined && that.isDefined
}
/** Holding cell for methods unused and/or unnecessary. */
private[util] trait DeprecatedPosition {
self: Position =>
@deprecated("use `point`", "2.9.0") // Used in sbt 0.12.4
def offset: Option[Int] = if (isDefined) Some(point) else None
@deprecated("use `focus`", "2.11.0")
def toSingleLine: Position = this
@deprecated("use `line`", "2.11.0")
def safeLine: Int = line
@deprecated("use `showDebug`", "2.11.0")
def dbgString: String = showDebug
@deprecated("use `finalPosition`", "2.11.0")
def inUltimateSource(source: SourceFile): Position = source positionInUltimateSource this
@deprecated("use `lineCaret`", since="2.11.0")
def lineWithCarat(maxWidth: Int): (String, String) = ("", "")
@deprecated("use `withSource(source)` and `withShift`", "2.11.0")
def withSource(source: SourceFile, shift: Int): Position = this withSource source withShift shift
@deprecated("use `start` instead", "2.11.0")
def startOrPoint: Int = if (isRange) start else point
@deprecated("use `end` instead", "2.11.0")
def endOrPoint: Int = if (isRange) end else point
}
|