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 | file_name: string; | |
| 22 | ||
| 23 | // line << 12 + column - easier to compare | |
| 24 | start: int; | |
| 25 | end: int; | |
| 26 | ||
| 27 | length: int => end - start; | |
| 28 | ||
| 29 | is_internal: bool => self == internal; | |
| 30 | is_reflected: bool => self == reflected; | |
| 31 | ||
| 32 | init( | |
| 33 | file_name: string, | |
| 34 | ||
| 35 | start_line: int, | |
| 36 | start_column: int, | |
| 37 | ||
| 38 | end_line: int, | |
| 39 | end_column: int | |
| 40 | ) | |
| 41 | is | |
| 42 | self.file_name = file_name; | |
| 43 | ||
| 44 | if | |
| 45 | start_line <= 0 \/ start_column < 0 \/ | |
| 46 | end_line < 0 \/ end_column < 0 | |
| 47 | then | |
| 48 | throw IndexOutOfRangeException( | |
| 49 | "invalid location {start_line},{start_column}..{end_line},{end_column}" | |
| 50 | ); | |
| 51 | fi | |
| 52 | ||
| 53 | start = pair(start_line, start_column); | |
| 54 | end = pair(end_line, end_column); | |
| 55 | ||
| 56 | if end == 0 then | |
| 57 | end = start; | |
| 58 | elif end < start then | |
| 59 | let t = end; | |
| 60 | end = start; | |
| 61 | start = t; | |
| 62 | fi | |
| 63 | si | |
| 64 | ||
| 65 | init( | |
| 66 | file_name: string, | |
| 67 | start: int, | |
| 68 | end: int | |
| 69 | ) | |
| 70 | is | |
| 71 | self.file_name = file_name; | |
| 72 | self.start = start; | |
| 73 | self.end = end; | |
| 74 | si | |
| 75 | ||
| 76 | start_line: int => line_of(start); | |
| 77 | ||
| 78 | start_column: int => column_of(start); | |
| 79 | ||
| 80 | end_line: int => line_of(end); | |
| 81 | ||
| 82 | end_column: int => column_of(end); | |
| 83 | ||
| 84 | =~(other: LOCATION) -> bool => | |
| 85 | if self == other then | |
| 86 | true; | |
| 87 | else | |
| 88 | self.start == other.start /\ self.end == other.end /\ self.file_name =~ other.file_name; | |
| 89 | fi; | |
| 90 | ||
| 91 | pair(line: int, column: int) -> int static => (line << 12) | column; | |
| 92 | ||
| 93 | line_of(line_column: int) -> int static => line_column >> 12; | |
| 94 | ||
| 95 | column_of(line_column: int) -> int static => line_column & 0xFFF; | |
| 96 | ||
| 97 | contains(line_column: int) -> bool => | |
| 98 | self.start <= line_column /\ | |
| 99 | self.end >= line_column - 1; | |
| 100 | ||
| 101 | contains(line: int, column: int) -> bool => | |
| 102 | (self.start_line < line \/ (self.start_line == line /\ self.start_column <= column)) /\ | |
| 103 | (self.end_line > line \/ (self.end_line == line /\ self.end_column >= column - 1)); | |
| 104 | ||
| 105 | // Whether `other` is fully covered by `self`. Same file is | |
| 106 | // required — two locations with similar (line, column) coords | |
| 107 | // from different files don't contain each other. Non-strict: | |
| 108 | // a location contains itself. | |
| 109 | contains(other: LOCATION) -> bool => | |
| 110 | file_name =~ other.file_name /\ | |
| 111 | self.start <= other.start /\ | |
| 112 | self.end >= other.end; | |
| 113 | ||
| 114 | // Strict containment: covers `other` and is wider on at least | |
| 115 | // one side. A location does NOT strictly contain itself, and | |
| 116 | // two locations with the same extent (e.g. an overload group | |
| 117 | // and its resolved member recorded at the same identifier) | |
| 118 | // don't strictly contain each other. | |
| 119 | strictly_contains(other: LOCATION) -> bool => | |
| 120 | contains(other) /\ | |
| 121 | (self.start < other.start \/ self.end > other.end); | |
| 122 | ||
| 123 | to_string() -> string => | |
| 124 | "{file_name} {start_line},{start_column}..{end_line},{end_column}"; | |
| 125 | ||
| 126 | ::(with: LOCATION) -> LOCATION => | |
| 127 | let new_start = if start < with.start then start else with.start fi in | |
| 128 | let new_end = if end > with.end then end else with.end fi in | |
| 129 | LOCATION(file_name, new_start, new_end); | |
| 130 | ||
| 131 | ..(with: LOCATION) -> LOCATION => | |
| 132 | let new_start = if start < with.start then start else with.start fi in | |
| 133 | let new_end_raw = if end > with.start then end else with.start fi in | |
| 134 | let new_end = | |
| 135 | if column_of(new_end_raw) > 1 then | |
| 136 | pair(column_of(new_end_raw) - 1, line_of(new_end_raw)) | |
| 137 | else | |
| 138 | new_end_raw | |
| 139 | fi | |
| 140 | in | |
| 141 | LOCATION(file_name, new_start, new_end); | |
| 142 | si | |
| 143 | ||
| 144 | class LOCATION_CURSOR is | |
| 145 | _file_name: string; | |
| 146 | ||
| 147 | _previous_line: int; | |
| 148 | _previous_column: int; | |
| 149 | ||
| 150 | _start_line: int; | |
| 151 | _start_column: int; | |
| 152 | ||
| 153 | _current_line: int; | |
| 154 | _current_column: int; | |
| 155 | ||
| 156 | init(file_name: string) is | |
| 157 | _file_name = file_name; | |
| 158 | ||
| 159 | _previous_line = 1; | |
| 160 | _previous_column = 0; | |
| 161 | ||
| 162 | _start_line = 1; | |
| 163 | _start_column = 0; | |
| 164 | ||
| 165 | _current_line = 1; | |
| 166 | _current_column = 0; | |
| 167 | si | |
| 168 | ||
| 169 | jump(line: int, column: int) is | |
| 170 | _start_line = line; | |
| 171 | _start_column = column; | |
| 172 | ||
| 173 | _current_line = line; | |
| 174 | _current_column = column; | |
| 175 | si | |
| 176 | ||
| 177 | save() is | |
| 178 | _previous_line = _current_line; | |
| 179 | _previous_column = _current_column; | |
| 180 | si | |
| 181 | ||
| 182 | restore() is | |
| 183 | _current_line = _previous_line; | |
| 184 | _current_column = _previous_column; | |
| 185 | si | |
| 186 | ||
| 187 | start() is | |
| 188 | _start_line = _current_line; | |
| 189 | _start_column = _current_column; | |
| 190 | si | |
| 191 | ||
| 192 | next_column() is | |
| 193 | _current_column = _current_column + 1; | |
| 194 | si | |
| 195 | ||
| 196 | next_line() is | |
| 197 | _current_column = 0; | |
| 198 | _current_line = _current_line + 1; | |
| 199 | si | |
| 200 | ||
| 201 | location: LOCATION => | |
| 202 | LOCATION( | |
| 203 | _file_name, | |
| 204 | ||
| 205 | _start_line, | |
| 206 | _start_column, | |
| 207 | ||
| 208 | _current_line, | |
| 209 | _current_column | |
| 210 | ); | |
| 211 | ||
| 212 | character_location: LOCATION => | |
| 213 | LOCATION( | |
| 214 | _file_name, | |
| 215 | ||
| 216 | // in practice if we report a character location, we | |
| 217 | // want to report the previous character because we | |
| 218 | // have one character lookahead | |
| 219 | _previous_line, | |
| 220 | _previous_column, | |
| 221 | ||
| 222 | _previous_line, | |
| 223 | _previous_column | |
| 224 | ); | |
| 225 | si | |
| 226 | ||
| 227 | class INTERNAL_LOCATION_CURSOR: LOCATION_CURSOR is | |
| 228 | location: LOCATION => LOCATION.internal; | |
| 229 | ||
| 230 | init() is | |
| 231 | super.init("internal"); | |
| 232 | si | |
| 233 | si | |
| 234 | si |