Appearance
| 1 | namespace Source is | |
| 2 | use System.IndexOutOfRangeException | |
| 3 | ||
| 4 | use Logging | |
| 5 | ||
| 6 | class LOCATION is | |
| 7 | _internal: LOCATION static | |
| 8 | _unknown: LOCATION static | |
| 9 | _reflected: LOCATION static | |
| 10 | ||
| 11 | internal: LOCATION static => _internal | |
| 12 | unknown: LOCATION static => _unknown | |
| 13 | reflected: LOCATION static => _reflected | |
| 14 | ||
| 15 | init_static() static is | |
| 16 | _internal = LOCATION("internal", 1, 1, 1, 1) | |
| 17 | _unknown = LOCATION("unknown", 1, 1, 1, 1) | |
| 18 | _reflected = LOCATION("reflected", 1, 1, 1, 1) | |
| 19 | si | |
| 20 | ||
| 21 | // A location spanning the whole of `file_name`, for a diagnostic | |
| 22 | // that applies to the file rather than to any one position in it | |
| 23 | // (a file-level `@@suppress`). The end is the widest position the | |
| 24 | // line/column packing can represent - not a real position in the | |
| 25 | // file - so this contains any location `contains`/`strictly_contains` | |
| 26 | // would compare it against, however long the file actually is. | |
| 27 | whole_file(file_name: string) -> LOCATION static => | |
| 28 | LOCATION(file_name, 1, 1, _MAX_LINE, _MAX_COLUMN) | |
| 29 | ||
| 30 | // The largest line and column pair() can hold without a real | |
| 31 | // position ever reaching it: the encoding gives 12 bits to the | |
| 32 | // column (0xFFF, matching column_of's mask), and a line count this | |
| 33 | // large stays clear of the top bits pair() shifts it into. | |
| 34 | _MAX_LINE: int static => 500000 | |
| 35 | _MAX_COLUMN: int static => 0xFFF | |
| 36 | ||
| 37 | file_name: string | |
| 38 | ||
| 39 | // line << 12 + column - easier to compare | |
| 40 | start: int | |
| 41 | end: int | |
| 42 | ||
| 43 | length: int => end - start | |
| 44 | ||
| 45 | is_internal: bool => self == internal | |
| 46 | is_reflected: bool => self == reflected | |
| 47 | is_unknown: bool => self == unknown | |
| 48 | ||
| 49 | init( | |
| 50 | file_name: string, | |
| 51 | ||
| 52 | start_line: int, | |
| 53 | start_column: int, | |
| 54 | ||
| 55 | end_line: int, | |
| 56 | end_column: int | |
| 57 | ) | |
| 58 | is | |
| 59 | self.file_name = file_name | |
| 60 | ||
| 61 | if | |
| 62 | start_line <= 0 \/ start_column < 0 \/ | |
| 63 | end_line < 0 \/ end_column < 0 | |
| 64 | then | |
| 65 | throw IndexOutOfRangeException( | |
| 66 | "invalid location {start_line},{start_column}..{end_line},{end_column}" | |
| 67 | ) | |
| 68 | fi | |
| 69 | ||
| 70 | start = pair(start_line, start_column) | |
| 71 | end = pair(end_line, end_column) | |
| 72 | ||
| 73 | if end == 0 then | |
| 74 | end = start | |
| 75 | elif end < start then | |
| 76 | let t = end | |
| 77 | end = start | |
| 78 | start = t | |
| 79 | fi | |
| 80 | si | |
| 81 | ||
| 82 | init( | |
| 83 | file_name: string, | |
| 84 | start: int, | |
| 85 | end: int | |
| 86 | ) | |
| 87 | is | |
| 88 | self.file_name = file_name | |
| 89 | self.start = start | |
| 90 | self.end = end | |
| 91 | si | |
| 92 | ||
| 93 | start_line: int => line_of(start) | |
| 94 | ||
| 95 | start_column: int => column_of(start) | |
| 96 | ||
| 97 | end_line: int => line_of(end) | |
| 98 | ||
| 99 | end_column: int => column_of(end) | |
| 100 | ||
| 101 | =~(other: LOCATION) -> bool => | |
| 102 | if self == other then | |
| 103 | true | |
| 104 | else | |
| 105 | self.start == other.start /\ self.end == other.end /\ self.file_name =~ other.file_name | |
| 106 | fi | |
| 107 | ||
| 108 | get_hash_code() -> int => start ^ (end << 1) ^ file_name.get_hash_code() | |
| 109 | ||
| 110 | pair(line: int, column: int) -> int static => (line << 12) | column | |
| 111 | ||
| 112 | line_of(line_column: int) -> int static => line_column >> 12 | |
| 113 | ||
| 114 | column_of(line_column: int) -> int static => line_column & 0xFFF | |
| 115 | ||
| 116 | contains(line_column: int) -> bool => | |
| 117 | self.start <= line_column /\ | |
| 118 | self.end >= line_column - 1 | |
| 119 | ||
| 120 | contains(line: int, column: int) -> bool => | |
| 121 | (self.start_line < line \/ (self.start_line == line /\ self.start_column <= column)) /\ | |
| 122 | (self.end_line > line \/ (self.end_line == line /\ self.end_column >= column - 1)) | |
| 123 | ||
| 124 | // Whether `other` is fully covered by `self`. Same file is | |
| 125 | // required — two locations with similar (line, column) coords | |
| 126 | // from different files don't contain each other. Non-strict: | |
| 127 | // a location contains itself. | |
| 128 | contains(other: LOCATION) -> bool => | |
| 129 | file_name =~ other.file_name /\ | |
| 130 | self.start <= other.start /\ | |
| 131 | self.end >= other.end | |
| 132 | ||
| 133 | // Strict containment: covers `other` and is wider on at least | |
| 134 | // one side. A location does NOT strictly contain itself, and | |
| 135 | // two locations with the same extent (e.g. an overload group | |
| 136 | // and its resolved member recorded at the same identifier) | |
| 137 | // don't strictly contain each other. | |
| 138 | strictly_contains(other: LOCATION) -> bool => | |
| 139 | contains(other) /\ | |
| 140 | (self.start < other.start \/ self.end > other.end) | |
| 141 | ||
| 142 | // The zero-width position at this location's start. Combined | |
| 143 | // with `::` it builds a span running from here to the end of | |
| 144 | // some inner location. | |
| 145 | start_position: LOCATION => LOCATION(file_name, start, start) | |
| 146 | ||
| 147 | to_string() -> string => | |
| 148 | "{file_name} {start_line},{start_column}..{end_line},{end_column}" | |
| 149 | ||
| 150 | ::(with: LOCATION) -> LOCATION => | |
| 151 | let new_start = if start < with.start then start else with.start fi in | |
| 152 | let new_end = if end > with.end then end else with.end fi in | |
| 153 | LOCATION(file_name, new_start, new_end) | |
| 154 | ||
| 155 | ..(with: LOCATION) -> LOCATION => | |
| 156 | let new_start = if start < with.start then start else with.start fi in | |
| 157 | let new_end_raw = if end > with.start then end else with.start fi in | |
| 158 | let new_end = | |
| 159 | if column_of(new_end_raw) > 1 then | |
| 160 | pair(column_of(new_end_raw) - 1, line_of(new_end_raw)) | |
| 161 | else | |
| 162 | new_end_raw | |
| 163 | fi | |
| 164 | in | |
| 165 | LOCATION(file_name, new_start, new_end) | |
| 166 | si | |
| 167 | ||
| 168 | class LOCATION_CURSOR is | |
| 169 | _file_name: string | |
| 170 | ||
| 171 | _previous_line: int | |
| 172 | _previous_column: int | |
| 173 | ||
| 174 | _start_line: int | |
| 175 | _start_column: int | |
| 176 | ||
| 177 | _current_line: int | |
| 178 | _current_column: int | |
| 179 | ||
| 180 | init(file_name: string) is | |
| 181 | _file_name = file_name | |
| 182 | ||
| 183 | _previous_line = 1 | |
| 184 | _previous_column = 0 | |
| 185 | ||
| 186 | _start_line = 1 | |
| 187 | _start_column = 0 | |
| 188 | ||
| 189 | _current_line = 1 | |
| 190 | _current_column = 0 | |
| 191 | si | |
| 192 | ||
| 193 | jump(line: int, column: int) is | |
| 194 | _start_line = line | |
| 195 | _start_column = column | |
| 196 | ||
| 197 | _current_line = line | |
| 198 | _current_column = column | |
| 199 | si | |
| 200 | ||
| 201 | save() is | |
| 202 | _previous_line = _current_line | |
| 203 | _previous_column = _current_column | |
| 204 | si | |
| 205 | ||
| 206 | restore() is | |
| 207 | _current_line = _previous_line | |
| 208 | _current_column = _previous_column | |
| 209 | si | |
| 210 | ||
| 211 | start() is | |
| 212 | _start_line = _current_line | |
| 213 | _start_column = _current_column | |
| 214 | si | |
| 215 | ||
| 216 | next_column() is | |
| 217 | _current_column = _current_column + 1 | |
| 218 | si | |
| 219 | ||
| 220 | next_line() is | |
| 221 | _current_column = 0 | |
| 222 | _current_line = _current_line + 1 | |
| 223 | si | |
| 224 | ||
| 225 | location: LOCATION => | |
| 226 | LOCATION( | |
| 227 | _file_name, | |
| 228 | ||
| 229 | _start_line, | |
| 230 | _start_column, | |
| 231 | ||
| 232 | _current_line, | |
| 233 | _current_column | |
| 234 | ) | |
| 235 | ||
| 236 | character_location: LOCATION => | |
| 237 | LOCATION( | |
| 238 | _file_name, | |
| 239 | ||
| 240 | // in practice if we report a character location, we | |
| 241 | // want to report the previous character because we | |
| 242 | // have one character lookahead | |
| 243 | _previous_line, | |
| 244 | _previous_column, | |
| 245 | ||
| 246 | _previous_line, | |
| 247 | _previous_column | |
| 248 | ) | |
| 249 | si | |
| 250 | ||
| 251 | class INTERNAL_LOCATION_CURSOR: LOCATION_CURSOR is | |
| 252 | location: LOCATION => LOCATION.internal | |
| 253 | ||
| 254 | init() is | |
| 255 | super.init("internal") | |
| 256 | si | |
| 257 | si | |
| 258 | si |