aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/util/Positions.scala
blob: c3890cc9a4638a3522119c39f4110db998d20ef4 (plain) (blame)
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
package dotty.tools.dotc
package util
import language.implicitConversions

/** Position format in little endian:
 *  Start: unsigned 26 Bits (works for source files up to 64M)
 *  End: unsigned 26 Bits
 *  Point: unsigned 12 Bits relative to start
 *  NoPosition encoded as -1L (this is a normally invalid position
 *  because point would lie beyond end.
 */
object Positions {

  private val StartEndBits = 26
   val StartEndMask: Long = (1L << StartEndBits) - 1
  private val SyntheticPointDelta = (1 << (64 - StartEndBits * 2)) - 1

  /** The maximal representable offset in a position */
  val MaxOffset = StartEndMask

  /** Convert offset `x` to an integer by sign extending the original
   *  field of `StartEndBits` width.
   */
  def offsetToInt(x: Int) =
    x << (32 - StartEndBits) >> (32 - StartEndBits)

  /** A position indicates a range between a start offset and an end offset.
   *  Positions can be synthetic or source-derived. A source-derived position
   *  has in addition a point lies somewhere between start and end. The point
   *  is roughly where the ^ would go if an error was diagnosed at that position.
   *  All quantities are encoded opaquely in a Long.
   */
  class Position(val coords: Long) extends AnyVal {

    /** Is this position different from NoPosition? */
    def exists = this != NoPosition

    /** The start of this position. */
    def start: Int = {
      assert(exists)
      (coords & StartEndMask).toInt
    }

    /** The end of this position */
    def end: Int = {
      assert(exists)
      ((coords >>> StartEndBits) & StartEndMask).toInt
    }

    /** The point of this position, returns start for synthetic positions */
    def point: Int = {
      assert(exists)
      val poff = pointDelta
      if (poff == SyntheticPointDelta) start else start + poff
    }

    /** The difference between point and start in this position */
    def pointDelta =
      (coords >>> (StartEndBits * 2)).toInt

    def orElse(that: Position) =
      if (this.exists) this else that

    /** The union of two positions. This is the least range that encloses
     *  both positions. It is always a synthetic position.
     */
    def union(that: Position) =
      if (!this.exists) that
      else if (!that.exists) this
      else Position(this.start min that.start, this.end max that.end, this.point)

    /** Does the range of this position contain the one of that position? */
    def contains(that: Position): Boolean =
      !that.exists || exists && (start <= that.start && end >= that.end)

    /** Is this position synthetic? */
    def isSynthetic = pointDelta == SyntheticPointDelta

    /** Is this position source-derived? */
    def isSourceDerived = !isSynthetic

     /** A position where all components are shifted by a given `offset`
     *  relative to this position.
     */
    def shift(offset: Int) =
      if (exists) fromOffsets(start + offset, end + offset, pointDelta)
      else this

    /** The zero-extent position with start and end at the point of this position */
    def focus = if (exists) Position(point) else NoPosition

    /** The zero-extent position with start and end at the start of this position */
    def startPos = if (exists) Position(start) else NoPosition

    /** The zero-extent position with start and end at the end of this position */
    def endPos = if (exists) Position(end) else NoPosition

    /** A copy of this position with a different start */
    def withStart(start: Int) =
      fromOffsets(start, this.end, if (isSynthetic) SyntheticPointDelta else this.point - start)

    /** A copy of this position with a different end */
    def withEnd(end: Int) = fromOffsets(this.start, end, pointDelta)

    /** A copy of this position with a different point */
    def withPoint(point: Int) = fromOffsets(this.start, this.end, point - this.start)

    /** A synthetic copy of this position */
    def toSynthetic = if (isSynthetic) this else Position(start, end)

    override def toString = {
      val (left, right) = if (isSynthetic) ("<", ">") else ("[", "]")
      if (exists)
        s"$left$start..${if (point == start) "" else s"$point.."}$end$right"
      else
        s"${left}no position${right}"
    }
  }

  private def fromOffsets(start: Int, end: Int, pointDelta: Int) = {
    //assert(start <= end || start == 1 && end == 0, s"$start..$end")
    new Position(
      (start & StartEndMask).toLong |
      ((end & StartEndMask).toLong << StartEndBits) |
      (pointDelta.toLong << (StartEndBits * 2)))
  }

  /** A synthetic position with given start and end */
  def Position(start: Int, end: Int): Position = {
    val pos = fromOffsets(start, end, SyntheticPointDelta)
    assert(pos.isSynthetic)
    pos
  }

  /** A source-derived position with given start, end, and point delta */
  def Position(start: Int, end: Int, point: Int): Position = {
    val pointDelta = (point - start) max 0
    val pos = fromOffsets(start, end, if (pointDelta >= SyntheticPointDelta) 0 else pointDelta)
    assert(pos.isSourceDerived)
    pos
  }

  /** A synthetic zero-extent position that starts and ends at given `start`. */
  def Position(start: Int): Position = Position(start, start)

  /** A sentinel for a non-existing position */
  val NoPosition = Position(1, 0)

  /** The coordinate of a symbol. This is either an index or
   *  a zero-range position.
   */
  class Coord(val encoding: Int) extends AnyVal {
    def isIndex = encoding > 0
    def isPosition = encoding <= 0
    def toIndex: Int = {
      assert(isIndex)
      encoding - 1
    }
    def toPosition = {
      assert(isPosition)
      if (this == NoCoord) NoPosition else Position(-1 - encoding)
    }
  }

  /** An index coordinate */
  implicit def indexCoord(n: Int): Coord = new Coord(n + 1)
  implicit def positionCoord(pos: Position): Coord =
    if (pos.exists) new Coord(-(pos.point + 1))
    else NoCoord

  /** A sentinel for a missing coordinate */
  val NoCoord = new Coord(0)
}