]>
Commit | Line | Data |
---|---|---|
223e47cc LB |
1 | // Copyright 2012 The Rust Project Developers. See the COPYRIGHT |
2 | // file at the top-level directory of this distribution and at | |
3 | // http://rust-lang.org/COPYRIGHT. | |
4 | // | |
5 | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | |
6 | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | |
7 | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | |
8 | // option. This file may not be copied, modified, or distributed | |
9 | // except according to those terms. | |
10 | ||
1a4d82fc JJ |
11 | //! The CodeMap tracks all the source code used within a single crate, mapping |
12 | //! from integer byte positions to the original source code location. Each bit | |
13 | //! of source parsed during crate parsing (typically files, in-memory strings, | |
14 | //! or various bits of macro expansion) cover a continuous range of bytes in the | |
15 | //! CodeMap and are represented by FileMaps. Byte positions are stored in | |
16 | //! `spans` and used pervasively in the compiler. They are absolute positions | |
17 | //! within the CodeMap, which upon request can be converted to line and column | |
18 | //! information, source code snippets, etc. | |
223e47cc | 19 | |
d9579d0f | 20 | pub use self::ExpnFormat::*; |
223e47cc | 21 | |
1a4d82fc | 22 | use std::cell::RefCell; |
1a4d82fc JJ |
23 | use std::ops::{Add, Sub}; |
24 | use std::rc::Rc; | |
223e47cc | 25 | |
9346a6ac AL |
26 | use std::fmt; |
27 | ||
1a4d82fc | 28 | use serialize::{Encodable, Decodable, Encoder, Decoder}; |
223e47cc | 29 | |
c34b1796 AL |
30 | |
31 | // _____________________________________________________________________________ | |
32 | // Pos, BytePos, CharPos | |
33 | // | |
34 | ||
223e47cc | 35 | pub trait Pos { |
85aaf69f SL |
36 | fn from_usize(n: usize) -> Self; |
37 | fn to_usize(&self) -> usize; | |
223e47cc LB |
38 | } |
39 | ||
1a4d82fc JJ |
40 | /// A byte offset. Keep this small (currently 32-bits), as AST contains |
41 | /// a lot of them. | |
85aaf69f | 42 | #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Debug)] |
1a4d82fc JJ |
43 | pub struct BytePos(pub u32); |
44 | ||
223e47cc LB |
45 | /// A character offset. Because of multibyte utf8 characters, a byte offset |
46 | /// is not equivalent to a character offset. The CodeMap will convert BytePos | |
47 | /// values to CharPos values as necessary. | |
9346a6ac | 48 | #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Debug)] |
85aaf69f | 49 | pub struct CharPos(pub usize); |
223e47cc | 50 | |
1a4d82fc | 51 | // FIXME: Lots of boilerplate in these impls, but so far my attempts to fix |
223e47cc LB |
52 | // have been unsuccessful |
53 | ||
54 | impl Pos for BytePos { | |
85aaf69f SL |
55 | fn from_usize(n: usize) -> BytePos { BytePos(n as u32) } |
56 | fn to_usize(&self) -> usize { let BytePos(n) = *self; n as usize } | |
223e47cc LB |
57 | } |
58 | ||
1a4d82fc JJ |
59 | impl Add for BytePos { |
60 | type Output = BytePos; | |
223e47cc | 61 | |
1a4d82fc | 62 | fn add(self, rhs: BytePos) -> BytePos { |
85aaf69f | 63 | BytePos((self.to_usize() + rhs.to_usize()) as u32) |
223e47cc LB |
64 | } |
65 | } | |
66 | ||
1a4d82fc JJ |
67 | impl Sub for BytePos { |
68 | type Output = BytePos; | |
69 | ||
70 | fn sub(self, rhs: BytePos) -> BytePos { | |
85aaf69f | 71 | BytePos((self.to_usize() - rhs.to_usize()) as u32) |
223e47cc LB |
72 | } |
73 | } | |
74 | ||
c34b1796 AL |
75 | impl Encodable for BytePos { |
76 | fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> { | |
77 | s.emit_u32(self.0) | |
78 | } | |
79 | } | |
80 | ||
81 | impl Decodable for BytePos { | |
82 | fn decode<D: Decoder>(d: &mut D) -> Result<BytePos, D::Error> { | |
83 | Ok(BytePos(try!{ d.read_u32() })) | |
84 | } | |
85 | } | |
86 | ||
223e47cc | 87 | impl Pos for CharPos { |
85aaf69f SL |
88 | fn from_usize(n: usize) -> CharPos { CharPos(n) } |
89 | fn to_usize(&self) -> usize { let CharPos(n) = *self; n } | |
223e47cc LB |
90 | } |
91 | ||
1a4d82fc JJ |
92 | impl Add for CharPos { |
93 | type Output = CharPos; | |
223e47cc | 94 | |
1a4d82fc | 95 | fn add(self, rhs: CharPos) -> CharPos { |
85aaf69f | 96 | CharPos(self.to_usize() + rhs.to_usize()) |
223e47cc LB |
97 | } |
98 | } | |
99 | ||
1a4d82fc JJ |
100 | impl Sub for CharPos { |
101 | type Output = CharPos; | |
102 | ||
103 | fn sub(self, rhs: CharPos) -> CharPos { | |
85aaf69f | 104 | CharPos(self.to_usize() - rhs.to_usize()) |
223e47cc LB |
105 | } |
106 | } | |
107 | ||
c34b1796 AL |
108 | // _____________________________________________________________________________ |
109 | // Span, Spanned | |
110 | // | |
111 | ||
1a4d82fc JJ |
112 | /// Spans represent a region of code, used for error reporting. Positions in spans |
113 | /// are *absolute* positions from the beginning of the codemap, not positions | |
114 | /// relative to FileMaps. Methods on the CodeMap can be used to relate spans back | |
115 | /// to the original source. | |
85aaf69f | 116 | #[derive(Clone, Copy, Debug, Hash)] |
1a4d82fc JJ |
117 | pub struct Span { |
118 | pub lo: BytePos, | |
119 | pub hi: BytePos, | |
120 | /// Information about where the macro came from, if this piece of | |
121 | /// code was created by a macro expansion. | |
122 | pub expn_id: ExpnId | |
223e47cc LB |
123 | } |
124 | ||
1a4d82fc JJ |
125 | pub const DUMMY_SP: Span = Span { lo: BytePos(0), hi: BytePos(0), expn_id: NO_EXPANSION }; |
126 | ||
127 | // Generic span to be used for code originating from the command line | |
128 | pub const COMMAND_LINE_SP: Span = Span { lo: BytePos(0), | |
129 | hi: BytePos(0), | |
130 | expn_id: COMMAND_LINE_EXPN }; | |
131 | ||
85aaf69f | 132 | #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)] |
1a4d82fc JJ |
133 | pub struct Spanned<T> { |
134 | pub node: T, | |
135 | pub span: Span, | |
136 | } | |
223e47cc | 137 | |
1a4d82fc JJ |
138 | impl PartialEq for Span { |
139 | fn eq(&self, other: &Span) -> bool { | |
223e47cc LB |
140 | return (*self).lo == (*other).lo && (*self).hi == (*other).hi; |
141 | } | |
1a4d82fc | 142 | fn ne(&self, other: &Span) -> bool { !(*self).eq(other) } |
223e47cc LB |
143 | } |
144 | ||
1a4d82fc JJ |
145 | impl Eq for Span {} |
146 | ||
147 | impl Encodable for Span { | |
1a4d82fc | 148 | fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> { |
c34b1796 AL |
149 | // Encode spans as a single u64 in order to cut down on tagging overhead |
150 | // added by the RBML metadata encoding. The should be solved differently | |
151 | // altogether some time (FIXME #21482) | |
152 | s.emit_u64( (self.lo.0 as u64) | ((self.hi.0 as u64) << 32) ) | |
970d7e83 | 153 | } |
223e47cc LB |
154 | } |
155 | ||
1a4d82fc | 156 | impl Decodable for Span { |
c34b1796 AL |
157 | fn decode<D: Decoder>(d: &mut D) -> Result<Span, D::Error> { |
158 | let lo_hi: u64 = try! { d.read_u64() }; | |
159 | let lo = BytePos(lo_hi as u32); | |
160 | let hi = BytePos((lo_hi >> 32) as u32); | |
161 | Ok(mk_sp(lo, hi)) | |
223e47cc LB |
162 | } |
163 | } | |
164 | ||
1a4d82fc | 165 | pub fn spanned<T>(lo: BytePos, hi: BytePos, t: T) -> Spanned<T> { |
223e47cc LB |
166 | respan(mk_sp(lo, hi), t) |
167 | } | |
168 | ||
1a4d82fc JJ |
169 | pub fn respan<T>(sp: Span, t: T) -> Spanned<T> { |
170 | Spanned {node: t, span: sp} | |
223e47cc LB |
171 | } |
172 | ||
1a4d82fc JJ |
173 | pub fn dummy_spanned<T>(t: T) -> Spanned<T> { |
174 | respan(DUMMY_SP, t) | |
223e47cc LB |
175 | } |
176 | ||
177 | /* assuming that we're not in macro expansion */ | |
1a4d82fc JJ |
178 | pub fn mk_sp(lo: BytePos, hi: BytePos) -> Span { |
179 | Span {lo: lo, hi: hi, expn_id: NO_EXPANSION} | |
223e47cc LB |
180 | } |
181 | ||
1a4d82fc JJ |
182 | /// Return the span itself if it doesn't come from a macro expansion, |
183 | /// otherwise return the call site span up to the `enclosing_sp` by | |
184 | /// following the `expn_info` chain. | |
185 | pub fn original_sp(cm: &CodeMap, sp: Span, enclosing_sp: Span) -> Span { | |
186 | let call_site1 = cm.with_expn_info(sp.expn_id, |ei| ei.map(|ei| ei.call_site)); | |
187 | let call_site2 = cm.with_expn_info(enclosing_sp.expn_id, |ei| ei.map(|ei| ei.call_site)); | |
188 | match (call_site1, call_site2) { | |
189 | (None, _) => sp, | |
190 | (Some(call_site1), Some(call_site2)) if call_site1 == call_site2 => sp, | |
191 | (Some(call_site1), _) => original_sp(cm, call_site1, enclosing_sp), | |
192 | } | |
193 | } | |
223e47cc | 194 | |
c34b1796 AL |
195 | // _____________________________________________________________________________ |
196 | // Loc, LocWithOpt, FileMapAndLine, FileMapAndBytePos | |
197 | // | |
198 | ||
223e47cc | 199 | /// A source code location used for error reporting |
9346a6ac | 200 | #[derive(Debug)] |
223e47cc LB |
201 | pub struct Loc { |
202 | /// Information about the original source | |
1a4d82fc | 203 | pub file: Rc<FileMap>, |
223e47cc | 204 | /// The (1-based) line number |
85aaf69f | 205 | pub line: usize, |
223e47cc | 206 | /// The (0-based) column offset |
1a4d82fc | 207 | pub col: CharPos |
223e47cc LB |
208 | } |
209 | ||
210 | /// A source code location used as the result of lookup_char_pos_adj | |
211 | // Actually, *none* of the clients use the filename *or* file field; | |
212 | // perhaps they should just be removed. | |
9346a6ac | 213 | #[derive(Debug)] |
223e47cc | 214 | pub struct LocWithOpt { |
1a4d82fc | 215 | pub filename: FileName, |
85aaf69f | 216 | pub line: usize, |
1a4d82fc JJ |
217 | pub col: CharPos, |
218 | pub file: Option<Rc<FileMap>>, | |
223e47cc LB |
219 | } |
220 | ||
221 | // used to be structural records. Better names, anyone? | |
9346a6ac | 222 | #[derive(Debug)] |
85aaf69f | 223 | pub struct FileMapAndLine { pub fm: Rc<FileMap>, pub line: usize } |
9346a6ac | 224 | #[derive(Debug)] |
1a4d82fc JJ |
225 | pub struct FileMapAndBytePos { pub fm: Rc<FileMap>, pub pos: BytePos } |
226 | ||
c34b1796 AL |
227 | |
228 | // _____________________________________________________________________________ | |
d9579d0f | 229 | // ExpnFormat, NameAndSpan, ExpnInfo, ExpnId |
c34b1796 AL |
230 | // |
231 | ||
d9579d0f AL |
232 | /// The source of expansion. |
233 | #[derive(Clone, Copy, Hash, Debug, PartialEq, Eq)] | |
234 | pub enum ExpnFormat { | |
1a4d82fc JJ |
235 | /// e.g. #[derive(...)] <item> |
236 | MacroAttribute, | |
237 | /// e.g. `format!()` | |
d9579d0f AL |
238 | MacroBang, |
239 | /// Syntax sugar expansion performed by the compiler (libsyntax::expand). | |
240 | CompilerExpansion, | |
1a4d82fc JJ |
241 | } |
242 | ||
85aaf69f | 243 | #[derive(Clone, Hash, Debug)] |
1a4d82fc JJ |
244 | pub struct NameAndSpan { |
245 | /// The name of the macro that was invoked to create the thing | |
246 | /// with this Span. | |
247 | pub name: String, | |
248 | /// The format with which the macro was invoked. | |
d9579d0f | 249 | pub format: ExpnFormat, |
c34b1796 AL |
250 | /// Whether the macro is allowed to use #[unstable]/feature-gated |
251 | /// features internally without forcing the whole crate to opt-in | |
252 | /// to them. | |
253 | pub allow_internal_unstable: bool, | |
1a4d82fc JJ |
254 | /// The span of the macro definition itself. The macro may not |
255 | /// have a sensible definition span (e.g. something defined | |
256 | /// completely inside libsyntax) in which case this is None. | |
257 | pub span: Option<Span> | |
223e47cc LB |
258 | } |
259 | ||
d9579d0f | 260 | /// Extra information for tracking spans of macro and syntax sugar expansion |
85aaf69f | 261 | #[derive(Hash, Debug)] |
1a4d82fc | 262 | pub struct ExpnInfo { |
d9579d0f AL |
263 | /// The location of the actual macro invocation or syntax sugar , e.g. |
264 | /// `let x = foo!();` or `if let Some(y) = x {}` | |
1a4d82fc JJ |
265 | /// |
266 | /// This may recursively refer to other macro invocations, e.g. if | |
267 | /// `foo!()` invoked `bar!()` internally, and there was an | |
268 | /// expression inside `bar!`; the call_site of the expression in | |
269 | /// the expansion would point to the `bar!` invocation; that | |
270 | /// call_site span would have its own ExpnInfo, with the call_site | |
271 | /// pointing to the `foo!` invocation. | |
272 | pub call_site: Span, | |
d9579d0f | 273 | /// Information about the expansion. |
1a4d82fc | 274 | pub callee: NameAndSpan |
223e47cc LB |
275 | } |
276 | ||
85aaf69f | 277 | #[derive(PartialEq, Eq, Clone, Debug, Hash, RustcEncodable, RustcDecodable, Copy)] |
1a4d82fc | 278 | pub struct ExpnId(u32); |
223e47cc | 279 | |
c34b1796 | 280 | pub const NO_EXPANSION: ExpnId = ExpnId(!0); |
1a4d82fc | 281 | // For code appearing from the command line |
c34b1796 | 282 | pub const COMMAND_LINE_EXPN: ExpnId = ExpnId(!1); |
1a4d82fc JJ |
283 | |
284 | impl ExpnId { | |
d9579d0f AL |
285 | pub fn from_u32(id: u32) -> ExpnId { |
286 | ExpnId(id) | |
1a4d82fc JJ |
287 | } |
288 | ||
d9579d0f AL |
289 | pub fn into_u32(self) -> u32 { |
290 | self.0 | |
1a4d82fc | 291 | } |
223e47cc LB |
292 | } |
293 | ||
c34b1796 AL |
294 | // _____________________________________________________________________________ |
295 | // FileMap, MultiByteChar, FileName, FileLines | |
296 | // | |
297 | ||
1a4d82fc JJ |
298 | pub type FileName = String; |
299 | ||
9346a6ac AL |
300 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] |
301 | pub struct LineInfo { | |
302 | /// Index of line, starting from 0. | |
303 | pub line_index: usize, | |
304 | ||
305 | /// Column in line where span begins, starting from 0. | |
306 | pub start_col: CharPos, | |
307 | ||
308 | /// Column in line where span ends, starting from 0, exclusive. | |
309 | pub end_col: CharPos, | |
310 | } | |
311 | ||
1a4d82fc JJ |
312 | pub struct FileLines { |
313 | pub file: Rc<FileMap>, | |
9346a6ac | 314 | pub lines: Vec<LineInfo> |
223e47cc LB |
315 | } |
316 | ||
317 | /// Identifies an offset of a multi-byte character in a FileMap | |
c34b1796 | 318 | #[derive(Copy, Clone, RustcEncodable, RustcDecodable, Eq, PartialEq)] |
223e47cc LB |
319 | pub struct MultiByteChar { |
320 | /// The absolute offset of the character in the CodeMap | |
1a4d82fc | 321 | pub pos: BytePos, |
223e47cc | 322 | /// The number of bytes, >=2 |
85aaf69f | 323 | pub bytes: usize, |
223e47cc LB |
324 | } |
325 | ||
326 | /// A single source in the CodeMap | |
327 | pub struct FileMap { | |
328 | /// The name of the file that the source came from, source that doesn't | |
329 | /// originate from files has names between angle brackets by convention, | |
330 | /// e.g. `<anon>` | |
1a4d82fc | 331 | pub name: FileName, |
223e47cc | 332 | /// The complete source code |
c34b1796 | 333 | pub src: Option<Rc<String>>, |
223e47cc | 334 | /// The start position of this source in the CodeMap |
1a4d82fc | 335 | pub start_pos: BytePos, |
c34b1796 AL |
336 | /// The end position of this source in the CodeMap |
337 | pub end_pos: BytePos, | |
223e47cc | 338 | /// Locations of lines beginnings in the source code |
c34b1796 | 339 | pub lines: RefCell<Vec<BytePos>>, |
223e47cc | 340 | /// Locations of multi-byte characters in the source code |
c34b1796 AL |
341 | pub multibyte_chars: RefCell<Vec<MultiByteChar>>, |
342 | } | |
343 | ||
344 | impl Encodable for FileMap { | |
345 | fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> { | |
346 | s.emit_struct("FileMap", 5, |s| { | |
347 | try! { s.emit_struct_field("name", 0, |s| self.name.encode(s)) }; | |
348 | try! { s.emit_struct_field("start_pos", 1, |s| self.start_pos.encode(s)) }; | |
349 | try! { s.emit_struct_field("end_pos", 2, |s| self.end_pos.encode(s)) }; | |
350 | try! { s.emit_struct_field("lines", 3, |s| { | |
351 | let lines = self.lines.borrow(); | |
352 | // store the length | |
353 | try! { s.emit_u32(lines.len() as u32) }; | |
354 | ||
9346a6ac | 355 | if !lines.is_empty() { |
c34b1796 AL |
356 | // In order to preserve some space, we exploit the fact that |
357 | // the lines list is sorted and individual lines are | |
358 | // probably not that long. Because of that we can store lines | |
359 | // as a difference list, using as little space as possible | |
360 | // for the differences. | |
361 | let max_line_length = if lines.len() == 1 { | |
362 | 0 | |
363 | } else { | |
364 | lines.windows(2) | |
365 | .map(|w| w[1] - w[0]) | |
366 | .map(|bp| bp.to_usize()) | |
367 | .max() | |
368 | .unwrap() | |
369 | }; | |
370 | ||
371 | let bytes_per_diff: u8 = match max_line_length { | |
372 | 0 ... 0xFF => 1, | |
373 | 0x100 ... 0xFFFF => 2, | |
374 | _ => 4 | |
375 | }; | |
376 | ||
377 | // Encode the number of bytes used per diff. | |
378 | try! { bytes_per_diff.encode(s) }; | |
379 | ||
380 | // Encode the first element. | |
381 | try! { lines[0].encode(s) }; | |
382 | ||
383 | let diff_iter = (&lines[..]).windows(2) | |
384 | .map(|w| (w[1] - w[0])); | |
385 | ||
386 | match bytes_per_diff { | |
387 | 1 => for diff in diff_iter { try! { (diff.0 as u8).encode(s) } }, | |
388 | 2 => for diff in diff_iter { try! { (diff.0 as u16).encode(s) } }, | |
389 | 4 => for diff in diff_iter { try! { diff.0.encode(s) } }, | |
390 | _ => unreachable!() | |
391 | } | |
392 | } | |
393 | ||
394 | Ok(()) | |
395 | }) | |
396 | }; | |
397 | s.emit_struct_field("multibyte_chars", 4, |s| { | |
398 | (*self.multibyte_chars.borrow()).encode(s) | |
399 | }) | |
400 | }) | |
401 | } | |
402 | } | |
403 | ||
404 | impl Decodable for FileMap { | |
405 | fn decode<D: Decoder>(d: &mut D) -> Result<FileMap, D::Error> { | |
406 | ||
407 | d.read_struct("FileMap", 5, |d| { | |
408 | let name: String = try! { | |
409 | d.read_struct_field("name", 0, |d| Decodable::decode(d)) | |
410 | }; | |
411 | let start_pos: BytePos = try! { | |
412 | d.read_struct_field("start_pos", 1, |d| Decodable::decode(d)) | |
413 | }; | |
414 | let end_pos: BytePos = try! { | |
415 | d.read_struct_field("end_pos", 2, |d| Decodable::decode(d)) | |
416 | }; | |
417 | let lines: Vec<BytePos> = try! { | |
418 | d.read_struct_field("lines", 3, |d| { | |
419 | let num_lines: u32 = try! { Decodable::decode(d) }; | |
420 | let mut lines = Vec::with_capacity(num_lines as usize); | |
421 | ||
422 | if num_lines > 0 { | |
423 | // Read the number of bytes used per diff. | |
424 | let bytes_per_diff: u8 = try! { Decodable::decode(d) }; | |
425 | ||
426 | // Read the first element. | |
427 | let mut line_start: BytePos = try! { Decodable::decode(d) }; | |
428 | lines.push(line_start); | |
429 | ||
430 | for _ in 1..num_lines { | |
431 | let diff = match bytes_per_diff { | |
432 | 1 => try! { d.read_u8() } as u32, | |
433 | 2 => try! { d.read_u16() } as u32, | |
434 | 4 => try! { d.read_u32() }, | |
435 | _ => unreachable!() | |
436 | }; | |
437 | ||
438 | line_start = line_start + BytePos(diff); | |
439 | ||
440 | lines.push(line_start); | |
441 | } | |
442 | } | |
443 | ||
444 | Ok(lines) | |
445 | }) | |
446 | }; | |
447 | let multibyte_chars: Vec<MultiByteChar> = try! { | |
448 | d.read_struct_field("multibyte_chars", 4, |d| Decodable::decode(d)) | |
449 | }; | |
450 | Ok(FileMap { | |
451 | name: name, | |
452 | start_pos: start_pos, | |
453 | end_pos: end_pos, | |
454 | src: None, | |
455 | lines: RefCell::new(lines), | |
456 | multibyte_chars: RefCell::new(multibyte_chars) | |
457 | }) | |
458 | }) | |
459 | } | |
223e47cc LB |
460 | } |
461 | ||
9346a6ac AL |
462 | impl fmt::Debug for FileMap { |
463 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { | |
464 | write!(fmt, "FileMap({})", self.name) | |
465 | } | |
466 | } | |
467 | ||
970d7e83 | 468 | impl FileMap { |
1a4d82fc JJ |
469 | /// EFFECT: register a start-of-line offset in the |
470 | /// table of line-beginnings. | |
471 | /// UNCHECKED INVARIANT: these offsets must be added in the right | |
472 | /// order and must be in the right places; there is shared knowledge | |
473 | /// about what ends a line between this file and parse.rs | |
474 | /// WARNING: pos param here is the offset relative to start of CodeMap, | |
475 | /// and CodeMap will append a newline when adding a filemap without a newline at the end, | |
476 | /// so the safe way to call this is with value calculated as | |
477 | /// filemap.start_pos + newline_offset_relative_to_the_start_of_filemap. | |
970d7e83 | 478 | pub fn next_line(&self, pos: BytePos) { |
223e47cc | 479 | // the new charpos must be > the last one (or it's the first one). |
1a4d82fc JJ |
480 | let mut lines = self.lines.borrow_mut(); |
481 | let line_len = lines.len(); | |
482 | assert!(line_len == 0 || ((*lines)[line_len - 1] < pos)); | |
970d7e83 | 483 | lines.push(pos); |
223e47cc LB |
484 | } |
485 | ||
9346a6ac AL |
486 | /// get a line from the list of pre-computed line-beginnings. |
487 | /// line-number here is 0-based. | |
488 | pub fn get_line(&self, line_number: usize) -> Option<&str> { | |
c34b1796 AL |
489 | match self.src { |
490 | Some(ref src) => { | |
491 | let lines = self.lines.borrow(); | |
492 | lines.get(line_number).map(|&line| { | |
493 | let begin: BytePos = line - self.start_pos; | |
494 | let begin = begin.to_usize(); | |
495 | let slice = &src[begin..]; | |
496 | match slice.find('\n') { | |
497 | Some(e) => &slice[..e], | |
498 | None => slice | |
9346a6ac | 499 | } |
c34b1796 AL |
500 | }) |
501 | } | |
502 | None => None | |
503 | } | |
223e47cc LB |
504 | } |
505 | ||
85aaf69f | 506 | pub fn record_multibyte_char(&self, pos: BytePos, bytes: usize) { |
223e47cc LB |
507 | assert!(bytes >=2 && bytes <= 4); |
508 | let mbc = MultiByteChar { | |
509 | pos: pos, | |
510 | bytes: bytes, | |
511 | }; | |
1a4d82fc JJ |
512 | self.multibyte_chars.borrow_mut().push(mbc); |
513 | } | |
514 | ||
515 | pub fn is_real_file(&self) -> bool { | |
516 | !(self.name.starts_with("<") && | |
517 | self.name.ends_with(">")) | |
223e47cc | 518 | } |
c34b1796 AL |
519 | |
520 | pub fn is_imported(&self) -> bool { | |
521 | self.src.is_none() | |
522 | } | |
223e47cc LB |
523 | } |
524 | ||
c34b1796 AL |
525 | |
526 | // _____________________________________________________________________________ | |
527 | // CodeMap | |
528 | // | |
529 | ||
223e47cc | 530 | pub struct CodeMap { |
1a4d82fc JJ |
531 | pub files: RefCell<Vec<Rc<FileMap>>>, |
532 | expansions: RefCell<Vec<ExpnInfo>> | |
223e47cc LB |
533 | } |
534 | ||
970d7e83 | 535 | impl CodeMap { |
223e47cc LB |
536 | pub fn new() -> CodeMap { |
537 | CodeMap { | |
1a4d82fc JJ |
538 | files: RefCell::new(Vec::new()), |
539 | expansions: RefCell::new(Vec::new()), | |
223e47cc LB |
540 | } |
541 | } | |
542 | ||
d9579d0f | 543 | pub fn new_filemap(&self, filename: FileName, mut src: String) -> Rc<FileMap> { |
1a4d82fc JJ |
544 | let mut files = self.files.borrow_mut(); |
545 | let start_pos = match files.last() { | |
546 | None => 0, | |
c34b1796 | 547 | Some(last) => last.end_pos.to_usize(), |
1a4d82fc | 548 | }; |
223e47cc | 549 | |
1a4d82fc | 550 | // Remove utf-8 BOM if any. |
d9579d0f AL |
551 | if src.starts_with("\u{feff}") { |
552 | src.drain(..3); | |
553 | } | |
223e47cc | 554 | |
1a4d82fc | 555 | // Append '\n' in case it's not already there. |
85aaf69f SL |
556 | // This is a workaround to prevent CodeMap.lookup_filemap_idx from |
557 | // accidentally overflowing into the next filemap in case the last byte | |
558 | // of span is also the last byte of filemap, which leads to incorrect | |
559 | // results from CodeMap.span_to_*. | |
9346a6ac | 560 | if !src.is_empty() && !src.ends_with("\n") { |
1a4d82fc JJ |
561 | src.push('\n'); |
562 | } | |
223e47cc | 563 | |
c34b1796 AL |
564 | let end_pos = start_pos + src.len(); |
565 | ||
1a4d82fc JJ |
566 | let filemap = Rc::new(FileMap { |
567 | name: filename, | |
c34b1796 | 568 | src: Some(Rc::new(src)), |
85aaf69f | 569 | start_pos: Pos::from_usize(start_pos), |
c34b1796 | 570 | end_pos: Pos::from_usize(end_pos), |
1a4d82fc JJ |
571 | lines: RefCell::new(Vec::new()), |
572 | multibyte_chars: RefCell::new(Vec::new()), | |
573 | }); | |
223e47cc | 574 | |
1a4d82fc JJ |
575 | files.push(filemap.clone()); |
576 | ||
577 | filemap | |
223e47cc LB |
578 | } |
579 | ||
c34b1796 AL |
580 | /// Allocates a new FileMap representing a source file from an external |
581 | /// crate. The source code of such an "imported filemap" is not available, | |
582 | /// but we still know enough to generate accurate debuginfo location | |
583 | /// information for things inlined from other crates. | |
584 | pub fn new_imported_filemap(&self, | |
585 | filename: FileName, | |
586 | source_len: usize, | |
d9579d0f AL |
587 | mut file_local_lines: Vec<BytePos>, |
588 | mut file_local_multibyte_chars: Vec<MultiByteChar>) | |
c34b1796 AL |
589 | -> Rc<FileMap> { |
590 | let mut files = self.files.borrow_mut(); | |
591 | let start_pos = match files.last() { | |
592 | None => 0, | |
593 | Some(last) => last.end_pos.to_usize(), | |
594 | }; | |
595 | ||
596 | let end_pos = Pos::from_usize(start_pos + source_len); | |
597 | let start_pos = Pos::from_usize(start_pos); | |
598 | ||
d9579d0f AL |
599 | for pos in &mut file_local_lines { |
600 | *pos = *pos + start_pos; | |
601 | } | |
602 | ||
603 | for mbc in &mut file_local_multibyte_chars { | |
604 | mbc.pos = mbc.pos + start_pos; | |
605 | } | |
c34b1796 AL |
606 | |
607 | let filemap = Rc::new(FileMap { | |
608 | name: filename, | |
609 | src: None, | |
610 | start_pos: start_pos, | |
611 | end_pos: end_pos, | |
d9579d0f AL |
612 | lines: RefCell::new(file_local_lines), |
613 | multibyte_chars: RefCell::new(file_local_multibyte_chars), | |
c34b1796 AL |
614 | }); |
615 | ||
616 | files.push(filemap.clone()); | |
617 | ||
618 | filemap | |
619 | } | |
620 | ||
1a4d82fc | 621 | pub fn mk_substr_filename(&self, sp: Span) -> String { |
223e47cc | 622 | let pos = self.lookup_char_pos(sp.lo); |
1a4d82fc JJ |
623 | (format!("<{}:{}:{}>", |
624 | pos.file.name, | |
625 | pos.line, | |
85aaf69f | 626 | pos.col.to_usize() + 1)).to_string() |
223e47cc LB |
627 | } |
628 | ||
629 | /// Lookup source information about a BytePos | |
970d7e83 | 630 | pub fn lookup_char_pos(&self, pos: BytePos) -> Loc { |
d9579d0f AL |
631 | let FileMapAndLine {fm: f, line: a} = self.lookup_line(pos); |
632 | let line = a + 1; // Line numbers start at 1 | |
633 | let chpos = self.bytepos_to_file_charpos(pos); | |
634 | let linebpos = (*f.lines.borrow())[a]; | |
635 | let linechpos = self.bytepos_to_file_charpos(linebpos); | |
636 | debug!("byte pos {:?} is on the line at byte pos {:?}", | |
637 | pos, linebpos); | |
638 | debug!("char pos {:?} is on the line at char pos {:?}", | |
639 | chpos, linechpos); | |
640 | debug!("byte is on line: {}", line); | |
641 | assert!(chpos >= linechpos); | |
642 | Loc { | |
643 | file: f, | |
644 | line: line, | |
645 | col: chpos - linechpos | |
646 | } | |
647 | } | |
648 | ||
649 | fn lookup_line(&self, pos: BytePos) -> FileMapAndLine { | |
650 | let idx = self.lookup_filemap_idx(pos); | |
651 | ||
652 | let files = self.files.borrow(); | |
653 | let f = (*files)[idx].clone(); | |
654 | let mut a = 0; | |
655 | { | |
656 | let lines = f.lines.borrow(); | |
657 | let mut b = lines.len(); | |
658 | while b - a > 1 { | |
659 | let m = (a + b) / 2; | |
660 | if (*lines)[m] > pos { b = m; } else { a = m; } | |
661 | } | |
662 | } | |
663 | FileMapAndLine {fm: f, line: a} | |
223e47cc LB |
664 | } |
665 | ||
970d7e83 | 666 | pub fn lookup_char_pos_adj(&self, pos: BytePos) -> LocWithOpt { |
223e47cc | 667 | let loc = self.lookup_char_pos(pos); |
1a4d82fc JJ |
668 | LocWithOpt { |
669 | filename: loc.file.name.to_string(), | |
670 | line: loc.line, | |
671 | col: loc.col, | |
672 | file: Some(loc.file) | |
223e47cc LB |
673 | } |
674 | } | |
675 | ||
1a4d82fc | 676 | pub fn span_to_string(&self, sp: Span) -> String { |
9346a6ac | 677 | if self.files.borrow().is_empty() && sp == DUMMY_SP { |
1a4d82fc | 678 | return "no-location".to_string(); |
223e47cc LB |
679 | } |
680 | ||
681 | let lo = self.lookup_char_pos_adj(sp.lo); | |
682 | let hi = self.lookup_char_pos_adj(sp.hi); | |
1a4d82fc JJ |
683 | return (format!("{}:{}:{}: {}:{}", |
684 | lo.filename, | |
685 | lo.line, | |
85aaf69f | 686 | lo.col.to_usize() + 1, |
1a4d82fc | 687 | hi.line, |
85aaf69f | 688 | hi.col.to_usize() + 1)).to_string() |
223e47cc LB |
689 | } |
690 | ||
1a4d82fc JJ |
691 | pub fn span_to_filename(&self, sp: Span) -> FileName { |
692 | self.lookup_char_pos(sp.lo).file.name.to_string() | |
223e47cc LB |
693 | } |
694 | ||
d9579d0f AL |
695 | pub fn span_to_lines(&self, sp: Span) -> FileLinesResult { |
696 | if sp.lo > sp.hi { | |
697 | return Err(SpanLinesError::IllFormedSpan(sp)); | |
698 | } | |
699 | ||
223e47cc LB |
700 | let lo = self.lookup_char_pos(sp.lo); |
701 | let hi = self.lookup_char_pos(sp.hi); | |
d9579d0f AL |
702 | |
703 | if lo.file.start_pos != hi.file.start_pos { | |
704 | return Err(SpanLinesError::DistinctSources(DistinctSources { | |
705 | begin: (lo.file.name.clone(), lo.file.start_pos), | |
706 | end: (hi.file.name.clone(), hi.file.start_pos), | |
707 | })); | |
708 | } | |
709 | assert!(hi.line >= lo.line); | |
710 | ||
9346a6ac AL |
711 | let mut lines = Vec::with_capacity(hi.line - lo.line + 1); |
712 | ||
713 | // The span starts partway through the first line, | |
714 | // but after that it starts from offset 0. | |
715 | let mut start_col = lo.col; | |
716 | ||
717 | // For every line but the last, it extends from `start_col` | |
718 | // and to the end of the line. Be careful because the line | |
719 | // numbers in Loc are 1-based, so we subtract 1 to get 0-based | |
720 | // lines. | |
721 | for line_index in lo.line-1 .. hi.line-1 { | |
722 | let line_len = lo.file.get_line(line_index).map(|s| s.len()).unwrap_or(0); | |
723 | lines.push(LineInfo { line_index: line_index, | |
724 | start_col: start_col, | |
725 | end_col: CharPos::from_usize(line_len) }); | |
726 | start_col = CharPos::from_usize(0); | |
727 | } | |
728 | ||
729 | // For the last line, it extends from `start_col` to `hi.col`: | |
730 | lines.push(LineInfo { line_index: hi.line - 1, | |
731 | start_col: start_col, | |
732 | end_col: hi.col }); | |
733 | ||
d9579d0f | 734 | Ok(FileLines {file: lo.file, lines: lines}) |
223e47cc LB |
735 | } |
736 | ||
85aaf69f SL |
737 | pub fn span_to_snippet(&self, sp: Span) -> Result<String, SpanSnippetError> { |
738 | if sp.lo > sp.hi { | |
739 | return Err(SpanSnippetError::IllFormedSpan(sp)); | |
740 | } | |
741 | ||
c34b1796 AL |
742 | let local_begin = self.lookup_byte_offset(sp.lo); |
743 | let local_end = self.lookup_byte_offset(sp.hi); | |
1a4d82fc | 744 | |
c34b1796 | 745 | if local_begin.fm.start_pos != local_end.fm.start_pos { |
85aaf69f | 746 | return Err(SpanSnippetError::DistinctSources(DistinctSources { |
c34b1796 AL |
747 | begin: (local_begin.fm.name.clone(), |
748 | local_begin.fm.start_pos), | |
749 | end: (local_end.fm.name.clone(), | |
750 | local_end.fm.start_pos) | |
85aaf69f | 751 | })); |
1a4d82fc | 752 | } else { |
c34b1796 AL |
753 | match local_begin.fm.src { |
754 | Some(ref src) => { | |
755 | let start_index = local_begin.pos.to_usize(); | |
756 | let end_index = local_end.pos.to_usize(); | |
757 | let source_len = (local_begin.fm.end_pos - | |
758 | local_begin.fm.start_pos).to_usize(); | |
759 | ||
760 | if start_index > end_index || end_index > source_len { | |
761 | return Err(SpanSnippetError::MalformedForCodemap( | |
762 | MalformedCodemapPositions { | |
763 | name: local_begin.fm.name.clone(), | |
764 | source_len: source_len, | |
765 | begin_pos: local_begin.pos, | |
766 | end_pos: local_end.pos, | |
767 | })); | |
768 | } | |
85aaf69f | 769 | |
c34b1796 AL |
770 | return Ok((&src[start_index..end_index]).to_string()) |
771 | } | |
772 | None => { | |
773 | return Err(SpanSnippetError::SourceNotAvailable { | |
774 | filename: local_begin.fm.name.clone() | |
775 | }); | |
776 | } | |
777 | } | |
1a4d82fc | 778 | } |
223e47cc LB |
779 | } |
780 | ||
1a4d82fc | 781 | pub fn get_filemap(&self, filename: &str) -> Rc<FileMap> { |
85aaf69f | 782 | for fm in &*self.files.borrow() { |
1a4d82fc JJ |
783 | if filename == fm.name { |
784 | return fm.clone(); | |
785 | } | |
786 | } | |
787 | panic!("asking for {} which we don't know about", filename); | |
788 | } | |
789 | ||
c34b1796 | 790 | /// For a global BytePos compute the local offset within the containing FileMap |
1a4d82fc JJ |
791 | pub fn lookup_byte_offset(&self, bpos: BytePos) -> FileMapAndBytePos { |
792 | let idx = self.lookup_filemap_idx(bpos); | |
793 | let fm = (*self.files.borrow())[idx].clone(); | |
794 | let offset = bpos - fm.start_pos; | |
795 | FileMapAndBytePos {fm: fm, pos: offset} | |
796 | } | |
797 | ||
798 | /// Converts an absolute BytePos to a CharPos relative to the filemap and above. | |
799 | pub fn bytepos_to_file_charpos(&self, bpos: BytePos) -> CharPos { | |
800 | let idx = self.lookup_filemap_idx(bpos); | |
801 | let files = self.files.borrow(); | |
802 | let map = &(*files)[idx]; | |
803 | ||
804 | // The number of extra bytes due to multibyte chars in the FileMap | |
805 | let mut total_extra_bytes = 0; | |
806 | ||
85aaf69f | 807 | for mbc in &*map.multibyte_chars.borrow() { |
1a4d82fc JJ |
808 | debug!("{}-byte char at {:?}", mbc.bytes, mbc.pos); |
809 | if mbc.pos < bpos { | |
810 | // every character is at least one byte, so we only | |
811 | // count the actual extra bytes. | |
812 | total_extra_bytes += mbc.bytes - 1; | |
813 | // We should never see a byte position in the middle of a | |
814 | // character | |
85aaf69f | 815 | assert!(bpos.to_usize() >= mbc.pos.to_usize() + mbc.bytes); |
1a4d82fc JJ |
816 | } else { |
817 | break; | |
818 | } | |
819 | } | |
820 | ||
85aaf69f SL |
821 | assert!(map.start_pos.to_usize() + total_extra_bytes <= bpos.to_usize()); |
822 | CharPos(bpos.to_usize() - map.start_pos.to_usize() - total_extra_bytes) | |
223e47cc | 823 | } |
223e47cc | 824 | |
85aaf69f | 825 | fn lookup_filemap_idx(&self, pos: BytePos) -> usize { |
1a4d82fc JJ |
826 | let files = self.files.borrow(); |
827 | let files = &*files; | |
223e47cc | 828 | let len = files.len(); |
85aaf69f | 829 | let mut a = 0; |
223e47cc | 830 | let mut b = len; |
85aaf69f SL |
831 | while b - a > 1 { |
832 | let m = (a + b) / 2; | |
1a4d82fc | 833 | if files[m].start_pos > pos { |
223e47cc LB |
834 | b = m; |
835 | } else { | |
836 | a = m; | |
837 | } | |
838 | } | |
1a4d82fc JJ |
839 | // There can be filemaps with length 0. These have the same start_pos as |
840 | // the previous filemap, but are not the filemaps we want (because they | |
841 | // are length 0, they cannot contain what we are looking for). So, | |
842 | // rewind until we find a useful filemap. | |
843 | loop { | |
844 | let lines = files[a].lines.borrow(); | |
845 | let lines = lines; | |
9346a6ac | 846 | if !lines.is_empty() { |
1a4d82fc JJ |
847 | break; |
848 | } | |
849 | if a == 0 { | |
850 | panic!("position {} does not resolve to a source location", | |
85aaf69f | 851 | pos.to_usize()); |
1a4d82fc JJ |
852 | } |
853 | a -= 1; | |
854 | } | |
855 | if a >= len { | |
856 | panic!("position {} does not resolve to a source location", | |
85aaf69f | 857 | pos.to_usize()) |
223e47cc LB |
858 | } |
859 | ||
860 | return a; | |
861 | } | |
862 | ||
1a4d82fc JJ |
863 | pub fn record_expansion(&self, expn_info: ExpnInfo) -> ExpnId { |
864 | let mut expansions = self.expansions.borrow_mut(); | |
865 | expansions.push(expn_info); | |
9346a6ac AL |
866 | let len = expansions.len(); |
867 | if len > u32::max_value() as usize { | |
868 | panic!("too many ExpnInfo's!"); | |
869 | } | |
870 | ExpnId(len as u32 - 1) | |
223e47cc LB |
871 | } |
872 | ||
1a4d82fc JJ |
873 | pub fn with_expn_info<T, F>(&self, id: ExpnId, f: F) -> T where |
874 | F: FnOnce(Option<&ExpnInfo>) -> T, | |
875 | { | |
876 | match id { | |
9346a6ac | 877 | NO_EXPANSION | COMMAND_LINE_EXPN => f(None), |
85aaf69f | 878 | ExpnId(i) => f(Some(&(*self.expansions.borrow())[i as usize])) |
1a4d82fc | 879 | } |
223e47cc LB |
880 | } |
881 | ||
c34b1796 AL |
882 | /// Check if a span is "internal" to a macro in which #[unstable] |
883 | /// items can be used (that is, a macro marked with | |
884 | /// `#[allow_internal_unstable]`). | |
885 | pub fn span_allows_unstable(&self, span: Span) -> bool { | |
886 | debug!("span_allows_unstable(span = {:?})", span); | |
887 | let mut allows_unstable = false; | |
888 | let mut expn_id = span.expn_id; | |
889 | loop { | |
890 | let quit = self.with_expn_info(expn_id, |expninfo| { | |
891 | debug!("span_allows_unstable: expninfo = {:?}", expninfo); | |
892 | expninfo.map_or(/* hit the top level */ true, |info| { | |
893 | ||
894 | let span_comes_from_this_expansion = | |
895 | info.callee.span.map_or(span == info.call_site, |mac_span| { | |
896 | mac_span.lo <= span.lo && span.hi <= mac_span.hi | |
897 | }); | |
898 | ||
899 | debug!("span_allows_unstable: from this expansion? {}, allows unstable? {}", | |
900 | span_comes_from_this_expansion, | |
901 | info.callee.allow_internal_unstable); | |
902 | if span_comes_from_this_expansion { | |
903 | allows_unstable = info.callee.allow_internal_unstable; | |
904 | // we've found the right place, stop looking | |
905 | true | |
1a4d82fc | 906 | } else { |
c34b1796 AL |
907 | // not the right place, keep looking |
908 | expn_id = info.call_site.expn_id; | |
909 | false | |
1a4d82fc | 910 | } |
c34b1796 AL |
911 | }) |
912 | }); | |
913 | if quit { | |
914 | break | |
223e47cc | 915 | } |
c34b1796 AL |
916 | } |
917 | debug!("span_allows_unstable? {}", allows_unstable); | |
918 | allows_unstable | |
223e47cc LB |
919 | } |
920 | } | |
921 | ||
c34b1796 | 922 | // _____________________________________________________________________________ |
d9579d0f | 923 | // SpanLinesError, SpanSnippetError, DistinctSources, MalformedCodemapPositions |
c34b1796 AL |
924 | // |
925 | ||
d9579d0f AL |
926 | pub type FileLinesResult = Result<FileLines, SpanLinesError>; |
927 | ||
928 | #[derive(Clone, PartialEq, Eq, Debug)] | |
929 | pub enum SpanLinesError { | |
930 | IllFormedSpan(Span), | |
931 | DistinctSources(DistinctSources), | |
932 | } | |
933 | ||
85aaf69f SL |
934 | #[derive(Clone, PartialEq, Eq, Debug)] |
935 | pub enum SpanSnippetError { | |
936 | IllFormedSpan(Span), | |
937 | DistinctSources(DistinctSources), | |
938 | MalformedForCodemap(MalformedCodemapPositions), | |
c34b1796 | 939 | SourceNotAvailable { filename: String } |
85aaf69f SL |
940 | } |
941 | ||
942 | #[derive(Clone, PartialEq, Eq, Debug)] | |
943 | pub struct DistinctSources { | |
944 | begin: (String, BytePos), | |
945 | end: (String, BytePos) | |
946 | } | |
947 | ||
948 | #[derive(Clone, PartialEq, Eq, Debug)] | |
949 | pub struct MalformedCodemapPositions { | |
950 | name: String, | |
951 | source_len: usize, | |
952 | begin_pos: BytePos, | |
953 | end_pos: BytePos | |
954 | } | |
955 | ||
c34b1796 AL |
956 | |
957 | // _____________________________________________________________________________ | |
958 | // Tests | |
959 | // | |
960 | ||
223e47cc | 961 | #[cfg(test)] |
d9579d0f | 962 | mod tests { |
223e47cc | 963 | use super::*; |
9346a6ac | 964 | use std::rc::Rc; |
223e47cc LB |
965 | |
966 | #[test] | |
967 | fn t1 () { | |
968 | let cm = CodeMap::new(); | |
1a4d82fc JJ |
969 | let fm = cm.new_filemap("blork.rs".to_string(), |
970 | "first line.\nsecond line".to_string()); | |
223e47cc | 971 | fm.next_line(BytePos(0)); |
9346a6ac | 972 | assert_eq!(fm.get_line(0), Some("first line.")); |
223e47cc LB |
973 | // TESTING BROKEN BEHAVIOR: |
974 | fm.next_line(BytePos(10)); | |
9346a6ac | 975 | assert_eq!(fm.get_line(1), Some(".")); |
223e47cc LB |
976 | } |
977 | ||
978 | #[test] | |
c34b1796 | 979 | #[should_panic] |
223e47cc LB |
980 | fn t2 () { |
981 | let cm = CodeMap::new(); | |
1a4d82fc JJ |
982 | let fm = cm.new_filemap("blork.rs".to_string(), |
983 | "first line.\nsecond line".to_string()); | |
223e47cc LB |
984 | // TESTING *REALLY* BROKEN BEHAVIOR: |
985 | fm.next_line(BytePos(0)); | |
986 | fm.next_line(BytePos(10)); | |
987 | fm.next_line(BytePos(2)); | |
988 | } | |
1a4d82fc JJ |
989 | |
990 | fn init_code_map() -> CodeMap { | |
991 | let cm = CodeMap::new(); | |
992 | let fm1 = cm.new_filemap("blork.rs".to_string(), | |
993 | "first line.\nsecond line".to_string()); | |
994 | let fm2 = cm.new_filemap("empty.rs".to_string(), | |
995 | "".to_string()); | |
996 | let fm3 = cm.new_filemap("blork2.rs".to_string(), | |
997 | "first line.\nsecond line".to_string()); | |
998 | ||
999 | fm1.next_line(BytePos(0)); | |
1000 | fm1.next_line(BytePos(12)); | |
1001 | fm2.next_line(BytePos(24)); | |
1002 | fm3.next_line(BytePos(24)); | |
1003 | fm3.next_line(BytePos(34)); | |
1004 | ||
1005 | cm | |
1006 | } | |
1007 | ||
1008 | #[test] | |
1009 | fn t3() { | |
1010 | // Test lookup_byte_offset | |
1011 | let cm = init_code_map(); | |
1012 | ||
1013 | let fmabp1 = cm.lookup_byte_offset(BytePos(22)); | |
1014 | assert_eq!(fmabp1.fm.name, "blork.rs"); | |
1015 | assert_eq!(fmabp1.pos, BytePos(22)); | |
1016 | ||
1017 | let fmabp2 = cm.lookup_byte_offset(BytePos(24)); | |
1018 | assert_eq!(fmabp2.fm.name, "blork2.rs"); | |
1019 | assert_eq!(fmabp2.pos, BytePos(0)); | |
1020 | } | |
1021 | ||
1022 | #[test] | |
1023 | fn t4() { | |
1024 | // Test bytepos_to_file_charpos | |
1025 | let cm = init_code_map(); | |
1026 | ||
1027 | let cp1 = cm.bytepos_to_file_charpos(BytePos(22)); | |
1028 | assert_eq!(cp1, CharPos(22)); | |
1029 | ||
1030 | let cp2 = cm.bytepos_to_file_charpos(BytePos(24)); | |
1031 | assert_eq!(cp2, CharPos(0)); | |
1032 | } | |
1033 | ||
1034 | #[test] | |
1035 | fn t5() { | |
1036 | // Test zero-length filemaps. | |
1037 | let cm = init_code_map(); | |
1038 | ||
1039 | let loc1 = cm.lookup_char_pos(BytePos(22)); | |
1040 | assert_eq!(loc1.file.name, "blork.rs"); | |
1041 | assert_eq!(loc1.line, 2); | |
1042 | assert_eq!(loc1.col, CharPos(10)); | |
1043 | ||
1044 | let loc2 = cm.lookup_char_pos(BytePos(24)); | |
1045 | assert_eq!(loc2.file.name, "blork2.rs"); | |
1046 | assert_eq!(loc2.line, 1); | |
1047 | assert_eq!(loc2.col, CharPos(0)); | |
1048 | } | |
1049 | ||
1050 | fn init_code_map_mbc() -> CodeMap { | |
1051 | let cm = CodeMap::new(); | |
1052 | // € is a three byte utf8 char. | |
1053 | let fm1 = | |
1054 | cm.new_filemap("blork.rs".to_string(), | |
1055 | "fir€st €€€€ line.\nsecond line".to_string()); | |
1056 | let fm2 = cm.new_filemap("blork2.rs".to_string(), | |
1057 | "first line€€.\n€ second line".to_string()); | |
1058 | ||
1059 | fm1.next_line(BytePos(0)); | |
1060 | fm1.next_line(BytePos(22)); | |
1061 | fm2.next_line(BytePos(40)); | |
1062 | fm2.next_line(BytePos(58)); | |
1063 | ||
1064 | fm1.record_multibyte_char(BytePos(3), 3); | |
1065 | fm1.record_multibyte_char(BytePos(9), 3); | |
1066 | fm1.record_multibyte_char(BytePos(12), 3); | |
1067 | fm1.record_multibyte_char(BytePos(15), 3); | |
1068 | fm1.record_multibyte_char(BytePos(18), 3); | |
1069 | fm2.record_multibyte_char(BytePos(50), 3); | |
1070 | fm2.record_multibyte_char(BytePos(53), 3); | |
1071 | fm2.record_multibyte_char(BytePos(58), 3); | |
1072 | ||
1073 | cm | |
1074 | } | |
1075 | ||
1076 | #[test] | |
1077 | fn t6() { | |
1078 | // Test bytepos_to_file_charpos in the presence of multi-byte chars | |
1079 | let cm = init_code_map_mbc(); | |
1080 | ||
1081 | let cp1 = cm.bytepos_to_file_charpos(BytePos(3)); | |
1082 | assert_eq!(cp1, CharPos(3)); | |
1083 | ||
1084 | let cp2 = cm.bytepos_to_file_charpos(BytePos(6)); | |
1085 | assert_eq!(cp2, CharPos(4)); | |
1086 | ||
1087 | let cp3 = cm.bytepos_to_file_charpos(BytePos(56)); | |
1088 | assert_eq!(cp3, CharPos(12)); | |
1089 | ||
1090 | let cp4 = cm.bytepos_to_file_charpos(BytePos(61)); | |
1091 | assert_eq!(cp4, CharPos(15)); | |
1092 | } | |
1093 | ||
1094 | #[test] | |
1095 | fn t7() { | |
1096 | // Test span_to_lines for a span ending at the end of filemap | |
1097 | let cm = init_code_map(); | |
1098 | let span = Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}; | |
d9579d0f | 1099 | let file_lines = cm.span_to_lines(span).unwrap(); |
1a4d82fc JJ |
1100 | |
1101 | assert_eq!(file_lines.file.name, "blork.rs"); | |
1102 | assert_eq!(file_lines.lines.len(), 1); | |
9346a6ac AL |
1103 | assert_eq!(file_lines.lines[0].line_index, 1); |
1104 | } | |
1105 | ||
1106 | /// Given a string like " ^~~~~~~~~~~~ ", produces a span | |
1107 | /// coverting that range. The idea is that the string has the same | |
1108 | /// length as the input, and we uncover the byte positions. Note | |
1109 | /// that this can span lines and so on. | |
1110 | fn span_from_selection(input: &str, selection: &str) -> Span { | |
1111 | assert_eq!(input.len(), selection.len()); | |
1112 | let left_index = selection.find('^').unwrap() as u32; | |
1113 | let right_index = selection.rfind('~').unwrap() as u32; | |
1114 | Span { lo: BytePos(left_index), hi: BytePos(right_index + 1), expn_id: NO_EXPANSION } | |
1115 | } | |
1116 | ||
1117 | fn new_filemap_and_lines(cm: &CodeMap, filename: &str, input: &str) -> Rc<FileMap> { | |
1118 | let fm = cm.new_filemap(filename.to_string(), input.to_string()); | |
1119 | let mut byte_pos: u32 = 0; | |
1120 | for line in input.lines() { | |
1121 | // register the start of this line | |
1122 | fm.next_line(BytePos(byte_pos)); | |
1123 | ||
1124 | // update byte_pos to include this line and the \n at the end | |
1125 | byte_pos += line.len() as u32 + 1; | |
1126 | } | |
1127 | fm | |
1128 | } | |
1129 | ||
1130 | /// Test span_to_snippet and span_to_lines for a span coverting 3 | |
1131 | /// lines in the middle of a file. | |
1132 | #[test] | |
1133 | fn span_to_snippet_and_lines_spanning_multiple_lines() { | |
1134 | let cm = CodeMap::new(); | |
1135 | let inputtext = "aaaaa\nbbbbBB\nCCC\nDDDDDddddd\neee\n"; | |
1136 | let selection = " \n ^~\n~~~\n~~~~~ \n \n"; | |
1137 | new_filemap_and_lines(&cm, "blork.rs", inputtext); | |
1138 | let span = span_from_selection(inputtext, selection); | |
1139 | ||
1140 | // check that we are extracting the text we thought we were extracting | |
1141 | assert_eq!(&cm.span_to_snippet(span).unwrap(), "BB\nCCC\nDDDDD"); | |
1142 | ||
1143 | // check that span_to_lines gives us the complete result with the lines/cols we expected | |
d9579d0f | 1144 | let lines = cm.span_to_lines(span).unwrap(); |
9346a6ac AL |
1145 | let expected = vec![ |
1146 | LineInfo { line_index: 1, start_col: CharPos(4), end_col: CharPos(6) }, | |
1147 | LineInfo { line_index: 2, start_col: CharPos(0), end_col: CharPos(3) }, | |
1148 | LineInfo { line_index: 3, start_col: CharPos(0), end_col: CharPos(5) } | |
1149 | ]; | |
1150 | assert_eq!(lines.lines, expected); | |
1a4d82fc JJ |
1151 | } |
1152 | ||
1153 | #[test] | |
1154 | fn t8() { | |
1155 | // Test span_to_snippet for a span ending at the end of filemap | |
1156 | let cm = init_code_map(); | |
1157 | let span = Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}; | |
1158 | let snippet = cm.span_to_snippet(span); | |
1159 | ||
85aaf69f | 1160 | assert_eq!(snippet, Ok("second line".to_string())); |
1a4d82fc JJ |
1161 | } |
1162 | ||
1163 | #[test] | |
1164 | fn t9() { | |
1165 | // Test span_to_str for a span ending at the end of filemap | |
1166 | let cm = init_code_map(); | |
1167 | let span = Span {lo: BytePos(12), hi: BytePos(23), expn_id: NO_EXPANSION}; | |
1168 | let sstr = cm.span_to_string(span); | |
1169 | ||
1170 | assert_eq!(sstr, "blork.rs:2:1: 2:12"); | |
1171 | } | |
223e47cc | 1172 | } |