]> git.proxmox.com Git - rustc.git/blob - src/libsyntax/codemap.rs
ef9b14739a5ed188f15fc05f97d77faf1ebd61ef
[rustc.git] / src / libsyntax / codemap.rs
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
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.
19
20 pub use self::ExpnFormat::*;
21
22 use std::cell::RefCell;
23 use std::ops::{Add, Sub};
24 use std::rc::Rc;
25
26 use std::fmt;
27
28 use serialize::{Encodable, Decodable, Encoder, Decoder};
29
30
31 // _____________________________________________________________________________
32 // Pos, BytePos, CharPos
33 //
34
35 pub trait Pos {
36 fn from_usize(n: usize) -> Self;
37 fn to_usize(&self) -> usize;
38 }
39
40 /// A byte offset. Keep this small (currently 32-bits), as AST contains
41 /// a lot of them.
42 #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Debug)]
43 pub struct BytePos(pub u32);
44
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.
48 #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Debug)]
49 pub struct CharPos(pub usize);
50
51 // FIXME: Lots of boilerplate in these impls, but so far my attempts to fix
52 // have been unsuccessful
53
54 impl Pos for BytePos {
55 fn from_usize(n: usize) -> BytePos { BytePos(n as u32) }
56 fn to_usize(&self) -> usize { let BytePos(n) = *self; n as usize }
57 }
58
59 impl Add for BytePos {
60 type Output = BytePos;
61
62 fn add(self, rhs: BytePos) -> BytePos {
63 BytePos((self.to_usize() + rhs.to_usize()) as u32)
64 }
65 }
66
67 impl Sub for BytePos {
68 type Output = BytePos;
69
70 fn sub(self, rhs: BytePos) -> BytePos {
71 BytePos((self.to_usize() - rhs.to_usize()) as u32)
72 }
73 }
74
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
87 impl Pos for CharPos {
88 fn from_usize(n: usize) -> CharPos { CharPos(n) }
89 fn to_usize(&self) -> usize { let CharPos(n) = *self; n }
90 }
91
92 impl Add for CharPos {
93 type Output = CharPos;
94
95 fn add(self, rhs: CharPos) -> CharPos {
96 CharPos(self.to_usize() + rhs.to_usize())
97 }
98 }
99
100 impl Sub for CharPos {
101 type Output = CharPos;
102
103 fn sub(self, rhs: CharPos) -> CharPos {
104 CharPos(self.to_usize() - rhs.to_usize())
105 }
106 }
107
108 // _____________________________________________________________________________
109 // Span, Spanned
110 //
111
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.
116 #[derive(Clone, Copy, Debug, Hash)]
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
123 }
124
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
132 #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
133 pub struct Spanned<T> {
134 pub node: T,
135 pub span: Span,
136 }
137
138 impl PartialEq for Span {
139 fn eq(&self, other: &Span) -> bool {
140 return (*self).lo == (*other).lo && (*self).hi == (*other).hi;
141 }
142 fn ne(&self, other: &Span) -> bool { !(*self).eq(other) }
143 }
144
145 impl Eq for Span {}
146
147 impl Encodable for Span {
148 fn encode<S: Encoder>(&self, s: &mut S) -> Result<(), S::Error> {
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) )
153 }
154 }
155
156 impl Decodable for Span {
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))
162 }
163 }
164
165 pub fn spanned<T>(lo: BytePos, hi: BytePos, t: T) -> Spanned<T> {
166 respan(mk_sp(lo, hi), t)
167 }
168
169 pub fn respan<T>(sp: Span, t: T) -> Spanned<T> {
170 Spanned {node: t, span: sp}
171 }
172
173 pub fn dummy_spanned<T>(t: T) -> Spanned<T> {
174 respan(DUMMY_SP, t)
175 }
176
177 /* assuming that we're not in macro expansion */
178 pub fn mk_sp(lo: BytePos, hi: BytePos) -> Span {
179 Span {lo: lo, hi: hi, expn_id: NO_EXPANSION}
180 }
181
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 }
194
195 // _____________________________________________________________________________
196 // Loc, LocWithOpt, FileMapAndLine, FileMapAndBytePos
197 //
198
199 /// A source code location used for error reporting
200 #[derive(Debug)]
201 pub struct Loc {
202 /// Information about the original source
203 pub file: Rc<FileMap>,
204 /// The (1-based) line number
205 pub line: usize,
206 /// The (0-based) column offset
207 pub col: CharPos
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.
213 #[derive(Debug)]
214 pub struct LocWithOpt {
215 pub filename: FileName,
216 pub line: usize,
217 pub col: CharPos,
218 pub file: Option<Rc<FileMap>>,
219 }
220
221 // used to be structural records. Better names, anyone?
222 #[derive(Debug)]
223 pub struct FileMapAndLine { pub fm: Rc<FileMap>, pub line: usize }
224 #[derive(Debug)]
225 pub struct FileMapAndBytePos { pub fm: Rc<FileMap>, pub pos: BytePos }
226
227
228 // _____________________________________________________________________________
229 // ExpnFormat, NameAndSpan, ExpnInfo, ExpnId
230 //
231
232 /// The source of expansion.
233 #[derive(Clone, Copy, Hash, Debug, PartialEq, Eq)]
234 pub enum ExpnFormat {
235 /// e.g. #[derive(...)] <item>
236 MacroAttribute,
237 /// e.g. `format!()`
238 MacroBang,
239 /// Syntax sugar expansion performed by the compiler (libsyntax::expand).
240 CompilerExpansion,
241 }
242
243 #[derive(Clone, Hash, Debug)]
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.
249 pub format: ExpnFormat,
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,
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>
258 }
259
260 /// Extra information for tracking spans of macro and syntax sugar expansion
261 #[derive(Hash, Debug)]
262 pub struct ExpnInfo {
263 /// The location of the actual macro invocation or syntax sugar , e.g.
264 /// `let x = foo!();` or `if let Some(y) = x {}`
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,
273 /// Information about the expansion.
274 pub callee: NameAndSpan
275 }
276
277 #[derive(PartialEq, Eq, Clone, Debug, Hash, RustcEncodable, RustcDecodable, Copy)]
278 pub struct ExpnId(u32);
279
280 pub const NO_EXPANSION: ExpnId = ExpnId(!0);
281 // For code appearing from the command line
282 pub const COMMAND_LINE_EXPN: ExpnId = ExpnId(!1);
283
284 impl ExpnId {
285 pub fn from_u32(id: u32) -> ExpnId {
286 ExpnId(id)
287 }
288
289 pub fn into_u32(self) -> u32 {
290 self.0
291 }
292 }
293
294 // _____________________________________________________________________________
295 // FileMap, MultiByteChar, FileName, FileLines
296 //
297
298 pub type FileName = String;
299
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
312 pub struct FileLines {
313 pub file: Rc<FileMap>,
314 pub lines: Vec<LineInfo>
315 }
316
317 /// Identifies an offset of a multi-byte character in a FileMap
318 #[derive(Copy, Clone, RustcEncodable, RustcDecodable, Eq, PartialEq)]
319 pub struct MultiByteChar {
320 /// The absolute offset of the character in the CodeMap
321 pub pos: BytePos,
322 /// The number of bytes, >=2
323 pub bytes: usize,
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>`
331 pub name: FileName,
332 /// The complete source code
333 pub src: Option<Rc<String>>,
334 /// The start position of this source in the CodeMap
335 pub start_pos: BytePos,
336 /// The end position of this source in the CodeMap
337 pub end_pos: BytePos,
338 /// Locations of lines beginnings in the source code
339 pub lines: RefCell<Vec<BytePos>>,
340 /// Locations of multi-byte characters in the source code
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
355 if !lines.is_empty() {
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 }
460 }
461
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
468 impl FileMap {
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.
478 pub fn next_line(&self, pos: BytePos) {
479 // the new charpos must be > the last one (or it's the first one).
480 let mut lines = self.lines.borrow_mut();
481 let line_len = lines.len();
482 assert!(line_len == 0 || ((*lines)[line_len - 1] < pos));
483 lines.push(pos);
484 }
485
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> {
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
499 }
500 })
501 }
502 None => None
503 }
504 }
505
506 pub fn record_multibyte_char(&self, pos: BytePos, bytes: usize) {
507 assert!(bytes >=2 && bytes <= 4);
508 let mbc = MultiByteChar {
509 pos: pos,
510 bytes: bytes,
511 };
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(">"))
518 }
519
520 pub fn is_imported(&self) -> bool {
521 self.src.is_none()
522 }
523 }
524
525
526 // _____________________________________________________________________________
527 // CodeMap
528 //
529
530 pub struct CodeMap {
531 pub files: RefCell<Vec<Rc<FileMap>>>,
532 expansions: RefCell<Vec<ExpnInfo>>
533 }
534
535 impl CodeMap {
536 pub fn new() -> CodeMap {
537 CodeMap {
538 files: RefCell::new(Vec::new()),
539 expansions: RefCell::new(Vec::new()),
540 }
541 }
542
543 pub fn new_filemap(&self, filename: FileName, mut src: String) -> Rc<FileMap> {
544 let mut files = self.files.borrow_mut();
545 let start_pos = match files.last() {
546 None => 0,
547 Some(last) => last.end_pos.to_usize(),
548 };
549
550 // Remove utf-8 BOM if any.
551 if src.starts_with("\u{feff}") {
552 src.drain(..3);
553 }
554
555 // Append '\n' in case it's not already there.
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_*.
560 if !src.is_empty() && !src.ends_with("\n") {
561 src.push('\n');
562 }
563
564 let end_pos = start_pos + src.len();
565
566 let filemap = Rc::new(FileMap {
567 name: filename,
568 src: Some(Rc::new(src)),
569 start_pos: Pos::from_usize(start_pos),
570 end_pos: Pos::from_usize(end_pos),
571 lines: RefCell::new(Vec::new()),
572 multibyte_chars: RefCell::new(Vec::new()),
573 });
574
575 files.push(filemap.clone());
576
577 filemap
578 }
579
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,
587 mut file_local_lines: Vec<BytePos>,
588 mut file_local_multibyte_chars: Vec<MultiByteChar>)
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
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 }
606
607 let filemap = Rc::new(FileMap {
608 name: filename,
609 src: None,
610 start_pos: start_pos,
611 end_pos: end_pos,
612 lines: RefCell::new(file_local_lines),
613 multibyte_chars: RefCell::new(file_local_multibyte_chars),
614 });
615
616 files.push(filemap.clone());
617
618 filemap
619 }
620
621 pub fn mk_substr_filename(&self, sp: Span) -> String {
622 let pos = self.lookup_char_pos(sp.lo);
623 (format!("<{}:{}:{}>",
624 pos.file.name,
625 pos.line,
626 pos.col.to_usize() + 1)).to_string()
627 }
628
629 /// Lookup source information about a BytePos
630 pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
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}
664 }
665
666 pub fn lookup_char_pos_adj(&self, pos: BytePos) -> LocWithOpt {
667 let loc = self.lookup_char_pos(pos);
668 LocWithOpt {
669 filename: loc.file.name.to_string(),
670 line: loc.line,
671 col: loc.col,
672 file: Some(loc.file)
673 }
674 }
675
676 pub fn span_to_string(&self, sp: Span) -> String {
677 if self.files.borrow().is_empty() && sp == DUMMY_SP {
678 return "no-location".to_string();
679 }
680
681 let lo = self.lookup_char_pos_adj(sp.lo);
682 let hi = self.lookup_char_pos_adj(sp.hi);
683 return (format!("{}:{}:{}: {}:{}",
684 lo.filename,
685 lo.line,
686 lo.col.to_usize() + 1,
687 hi.line,
688 hi.col.to_usize() + 1)).to_string()
689 }
690
691 pub fn span_to_filename(&self, sp: Span) -> FileName {
692 self.lookup_char_pos(sp.lo).file.name.to_string()
693 }
694
695 pub fn span_to_lines(&self, sp: Span) -> FileLinesResult {
696 if sp.lo > sp.hi {
697 return Err(SpanLinesError::IllFormedSpan(sp));
698 }
699
700 let lo = self.lookup_char_pos(sp.lo);
701 let hi = self.lookup_char_pos(sp.hi);
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
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
734 Ok(FileLines {file: lo.file, lines: lines})
735 }
736
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
742 let local_begin = self.lookup_byte_offset(sp.lo);
743 let local_end = self.lookup_byte_offset(sp.hi);
744
745 if local_begin.fm.start_pos != local_end.fm.start_pos {
746 return Err(SpanSnippetError::DistinctSources(DistinctSources {
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)
751 }));
752 } else {
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 }
769
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 }
778 }
779 }
780
781 pub fn get_filemap(&self, filename: &str) -> Rc<FileMap> {
782 for fm in &*self.files.borrow() {
783 if filename == fm.name {
784 return fm.clone();
785 }
786 }
787 panic!("asking for {} which we don't know about", filename);
788 }
789
790 /// For a global BytePos compute the local offset within the containing FileMap
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
807 for mbc in &*map.multibyte_chars.borrow() {
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
815 assert!(bpos.to_usize() >= mbc.pos.to_usize() + mbc.bytes);
816 } else {
817 break;
818 }
819 }
820
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)
823 }
824
825 fn lookup_filemap_idx(&self, pos: BytePos) -> usize {
826 let files = self.files.borrow();
827 let files = &*files;
828 let len = files.len();
829 let mut a = 0;
830 let mut b = len;
831 while b - a > 1 {
832 let m = (a + b) / 2;
833 if files[m].start_pos > pos {
834 b = m;
835 } else {
836 a = m;
837 }
838 }
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;
846 if !lines.is_empty() {
847 break;
848 }
849 if a == 0 {
850 panic!("position {} does not resolve to a source location",
851 pos.to_usize());
852 }
853 a -= 1;
854 }
855 if a >= len {
856 panic!("position {} does not resolve to a source location",
857 pos.to_usize())
858 }
859
860 return a;
861 }
862
863 pub fn record_expansion(&self, expn_info: ExpnInfo) -> ExpnId {
864 let mut expansions = self.expansions.borrow_mut();
865 expansions.push(expn_info);
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)
871 }
872
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 {
877 NO_EXPANSION | COMMAND_LINE_EXPN => f(None),
878 ExpnId(i) => f(Some(&(*self.expansions.borrow())[i as usize]))
879 }
880 }
881
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
906 } else {
907 // not the right place, keep looking
908 expn_id = info.call_site.expn_id;
909 false
910 }
911 })
912 });
913 if quit {
914 break
915 }
916 }
917 debug!("span_allows_unstable? {}", allows_unstable);
918 allows_unstable
919 }
920 }
921
922 // _____________________________________________________________________________
923 // SpanLinesError, SpanSnippetError, DistinctSources, MalformedCodemapPositions
924 //
925
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
934 #[derive(Clone, PartialEq, Eq, Debug)]
935 pub enum SpanSnippetError {
936 IllFormedSpan(Span),
937 DistinctSources(DistinctSources),
938 MalformedForCodemap(MalformedCodemapPositions),
939 SourceNotAvailable { filename: String }
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
956
957 // _____________________________________________________________________________
958 // Tests
959 //
960
961 #[cfg(test)]
962 mod tests {
963 use super::*;
964 use std::rc::Rc;
965
966 #[test]
967 fn t1 () {
968 let cm = CodeMap::new();
969 let fm = cm.new_filemap("blork.rs".to_string(),
970 "first line.\nsecond line".to_string());
971 fm.next_line(BytePos(0));
972 assert_eq!(fm.get_line(0), Some("first line."));
973 // TESTING BROKEN BEHAVIOR:
974 fm.next_line(BytePos(10));
975 assert_eq!(fm.get_line(1), Some("."));
976 }
977
978 #[test]
979 #[should_panic]
980 fn t2 () {
981 let cm = CodeMap::new();
982 let fm = cm.new_filemap("blork.rs".to_string(),
983 "first line.\nsecond line".to_string());
984 // TESTING *REALLY* BROKEN BEHAVIOR:
985 fm.next_line(BytePos(0));
986 fm.next_line(BytePos(10));
987 fm.next_line(BytePos(2));
988 }
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};
1099 let file_lines = cm.span_to_lines(span).unwrap();
1100
1101 assert_eq!(file_lines.file.name, "blork.rs");
1102 assert_eq!(file_lines.lines.len(), 1);
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
1144 let lines = cm.span_to_lines(span).unwrap();
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);
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
1160 assert_eq!(snippet, Ok("second line".to_string()));
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 }
1172 }