]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/parse/lexer/mod.rs
move error handling from libsyntax/diagnostics.rs to libsyntax/errors/*
[rust.git] / src / libsyntax / parse / lexer / mod.rs
1 // Copyright 2012-2013 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 use ast;
12 use codemap::{BytePos, CharPos, CodeMap, Pos, Span};
13 use codemap;
14 use errors::{FatalError, Handler};
15 use ext::tt::transcribe::tt_next_token;
16 use parse::token::str_to_ident;
17 use parse::token;
18 use str::char_at;
19
20 use std::borrow::Cow;
21 use std::char;
22 use std::mem::replace;
23 use std::rc::Rc;
24
25 pub use ext::tt::transcribe::{TtReader, new_tt_reader, new_tt_reader_with_doc_flag};
26
27 pub mod comments;
28 mod unicode_chars;
29
30 pub trait Reader {
31     fn is_eof(&self) -> bool;
32     fn next_token(&mut self) -> TokenAndSpan;
33     /// Report a fatal error with the current span.
34     fn fatal(&self, &str) -> FatalError;
35     /// Report a non-fatal error with the current span.
36     fn err(&self, &str);
37     fn peek(&self) -> TokenAndSpan;
38     /// Get a token the parser cares about.
39     fn real_token(&mut self) -> TokenAndSpan {
40         let mut t = self.next_token();
41         loop {
42             match t.tok {
43                 token::Whitespace | token::Comment | token::Shebang(_) => {
44                     t = self.next_token();
45                 },
46                 _ => break
47             }
48         }
49         t
50     }
51 }
52
53 #[derive(Clone, PartialEq, Eq, Debug)]
54 pub struct TokenAndSpan {
55     pub tok: token::Token,
56     pub sp: Span,
57 }
58
59 pub struct StringReader<'a> {
60     pub span_diagnostic: &'a Handler,
61     /// The absolute offset within the codemap of the next character to read
62     pub pos: BytePos,
63     /// The absolute offset within the codemap of the last character read(curr)
64     pub last_pos: BytePos,
65     /// The column of the next character to read
66     pub col: CharPos,
67     /// The last character to be read
68     pub curr: Option<char>,
69     pub filemap: Rc<codemap::FileMap>,
70     /* cached: */
71     pub peek_tok: token::Token,
72     pub peek_span: Span,
73
74     // cache a direct reference to the source text, so that we don't have to
75     // retrieve it via `self.filemap.src.as_ref().unwrap()` all the time.
76     source_text: Rc<String>
77 }
78
79 impl<'a> Reader for StringReader<'a> {
80     fn is_eof(&self) -> bool { self.curr.is_none() }
81     /// Return the next token. EFFECT: advances the string_reader.
82     fn next_token(&mut self) -> TokenAndSpan {
83         let ret_val = TokenAndSpan {
84             tok: replace(&mut self.peek_tok, token::Underscore),
85             sp: self.peek_span,
86         };
87         self.advance_token();
88         ret_val
89     }
90     fn fatal(&self, m: &str) -> FatalError {
91         self.fatal_span(self.peek_span, m)
92     }
93     fn err(&self, m: &str) {
94         self.err_span(self.peek_span, m)
95     }
96     fn peek(&self) -> TokenAndSpan {
97         // FIXME(pcwalton): Bad copy!
98         TokenAndSpan {
99             tok: self.peek_tok.clone(),
100             sp: self.peek_span,
101         }
102     }
103 }
104
105 impl<'a> Reader for TtReader<'a> {
106     fn is_eof(&self) -> bool {
107         self.cur_tok == token::Eof
108     }
109     fn next_token(&mut self) -> TokenAndSpan {
110         let r = tt_next_token(self);
111         debug!("TtReader: r={:?}", r);
112         r
113     }
114     fn fatal(&self, m: &str) -> FatalError {
115         self.sp_diag.span_fatal(self.cur_span, m)
116     }
117     fn err(&self, m: &str) {
118         self.sp_diag.span_err(self.cur_span, m);
119     }
120     fn peek(&self) -> TokenAndSpan {
121         TokenAndSpan {
122             tok: self.cur_tok.clone(),
123             sp: self.cur_span,
124         }
125     }
126 }
127
128 impl<'a> StringReader<'a> {
129     /// For comments.rs, which hackily pokes into pos and curr
130     pub fn new_raw<'b>(span_diagnostic: &'b Handler,
131                        filemap: Rc<codemap::FileMap>) -> StringReader<'b> {
132         if filemap.src.is_none() {
133             span_diagnostic.bug(&format!("Cannot lex filemap without source: {}",
134                                                  filemap.name)[..]);
135         }
136
137         let source_text = (*filemap.src.as_ref().unwrap()).clone();
138
139         let mut sr = StringReader {
140             span_diagnostic: span_diagnostic,
141             pos: filemap.start_pos,
142             last_pos: filemap.start_pos,
143             col: CharPos(0),
144             curr: Some('\n'),
145             filemap: filemap,
146             /* dummy values; not read */
147             peek_tok: token::Eof,
148             peek_span: codemap::DUMMY_SP,
149             source_text: source_text
150         };
151         sr.bump();
152         sr
153     }
154
155     pub fn new<'b>(span_diagnostic: &'b Handler,
156                    filemap: Rc<codemap::FileMap>) -> StringReader<'b> {
157         let mut sr = StringReader::new_raw(span_diagnostic, filemap);
158         sr.advance_token();
159         sr
160     }
161
162     pub fn curr_is(&self, c: char) -> bool {
163         self.curr == Some(c)
164     }
165
166     /// Report a fatal lexical error with a given span.
167     pub fn fatal_span(&self, sp: Span, m: &str) -> FatalError {
168         self.span_diagnostic.span_fatal(sp, m)
169     }
170
171     /// Report a lexical error with a given span.
172     pub fn err_span(&self, sp: Span, m: &str) {
173         self.span_diagnostic.span_err(sp, m)
174     }
175
176     /// Suggest some help with a given span.
177     pub fn help_span(&self, sp: Span, m: &str) {
178         self.span_diagnostic.span_help(sp, m)
179     }
180
181     /// Report a fatal error spanning [`from_pos`, `to_pos`).
182     fn fatal_span_(&self, from_pos: BytePos, to_pos: BytePos, m: &str) -> FatalError {
183         self.fatal_span(codemap::mk_sp(from_pos, to_pos), m)
184     }
185
186     /// Report a lexical error spanning [`from_pos`, `to_pos`).
187     fn err_span_(&self, from_pos: BytePos, to_pos: BytePos, m: &str) {
188         self.err_span(codemap::mk_sp(from_pos, to_pos), m)
189     }
190
191     /// Suggest some help spanning [`from_pos`, `to_pos`).
192     fn help_span_(&self, from_pos: BytePos, to_pos: BytePos, m: &str) {
193         self.help_span(codemap::mk_sp(from_pos, to_pos), m)
194     }
195
196     /// Report a lexical error spanning [`from_pos`, `to_pos`), appending an
197     /// escaped character to the error message
198     fn fatal_span_char(&self, from_pos: BytePos, to_pos: BytePos, m: &str, c: char) -> FatalError {
199         let mut m = m.to_string();
200         m.push_str(": ");
201         for c in c.escape_default() { m.push(c) }
202         self.fatal_span_(from_pos, to_pos, &m[..])
203     }
204
205     /// Report a lexical error spanning [`from_pos`, `to_pos`), appending an
206     /// escaped character to the error message
207     fn err_span_char(&self, from_pos: BytePos, to_pos: BytePos, m: &str, c: char) {
208         let mut m = m.to_string();
209         m.push_str(": ");
210         for c in c.escape_default() { m.push(c) }
211         self.err_span_(from_pos, to_pos, &m[..]);
212     }
213
214     /// Report a lexical error spanning [`from_pos`, `to_pos`), appending the
215     /// offending string to the error message
216     fn fatal_span_verbose(&self, from_pos: BytePos, to_pos: BytePos, mut m: String) -> FatalError {
217         m.push_str(": ");
218         let from = self.byte_offset(from_pos).to_usize();
219         let to = self.byte_offset(to_pos).to_usize();
220         m.push_str(&self.source_text[from..to]);
221         self.fatal_span_(from_pos, to_pos, &m[..])
222     }
223
224     /// Advance peek_tok and peek_span to refer to the next token, and
225     /// possibly update the interner.
226     fn advance_token(&mut self) {
227         match self.scan_whitespace_or_comment() {
228             Some(comment) => {
229                 self.peek_span = comment.sp;
230                 self.peek_tok = comment.tok;
231             },
232             None => {
233                 if self.is_eof() {
234                     self.peek_tok = token::Eof;
235                     self.peek_span = codemap::mk_sp(self.filemap.end_pos, self.filemap.end_pos);
236                 } else {
237                     let start_bytepos = self.last_pos;
238                     self.peek_tok = self.next_token_inner();
239                     self.peek_span = codemap::mk_sp(start_bytepos,
240                                                     self.last_pos);
241                 };
242             }
243         }
244     }
245
246     fn byte_offset(&self, pos: BytePos) -> BytePos {
247         (pos - self.filemap.start_pos)
248     }
249
250     /// Calls `f` with a string slice of the source text spanning from `start`
251     /// up to but excluding `self.last_pos`, meaning the slice does not include
252     /// the character `self.curr`.
253     pub fn with_str_from<T, F>(&self, start: BytePos, f: F) -> T where
254         F: FnOnce(&str) -> T,
255     {
256         self.with_str_from_to(start, self.last_pos, f)
257     }
258
259     /// Create a Name from a given offset to the current offset, each
260     /// adjusted 1 towards each other (assumes that on either side there is a
261     /// single-byte delimiter).
262     pub fn name_from(&self, start: BytePos) -> ast::Name {
263         debug!("taking an ident from {:?} to {:?}", start, self.last_pos);
264         self.with_str_from(start, token::intern)
265     }
266
267     /// As name_from, with an explicit endpoint.
268     pub fn name_from_to(&self, start: BytePos, end: BytePos) -> ast::Name {
269         debug!("taking an ident from {:?} to {:?}", start, end);
270         self.with_str_from_to(start, end, token::intern)
271     }
272
273     /// Calls `f` with a string slice of the source text spanning from `start`
274     /// up to but excluding `end`.
275     fn with_str_from_to<T, F>(&self, start: BytePos, end: BytePos, f: F) -> T where
276         F: FnOnce(&str) -> T,
277     {
278         f(&self.source_text[self.byte_offset(start).to_usize()..
279                             self.byte_offset(end).to_usize()])
280     }
281
282     /// Converts CRLF to LF in the given string, raising an error on bare CR.
283     fn translate_crlf<'b>(&self, start: BytePos,
284                           s: &'b str, errmsg: &'b str) -> Cow<'b, str> {
285         let mut i = 0;
286         while i < s.len() {
287             let ch = char_at(s, i);
288             let next = i + ch.len_utf8();
289             if ch == '\r' {
290                 if next < s.len() && char_at(s, next) == '\n' {
291                     return translate_crlf_(self, start, s, errmsg, i).into();
292                 }
293                 let pos = start + BytePos(i as u32);
294                 let end_pos = start + BytePos(next as u32);
295                 self.err_span_(pos, end_pos, errmsg);
296             }
297             i = next;
298         }
299         return s.into();
300
301         fn translate_crlf_(rdr: &StringReader, start: BytePos,
302                         s: &str, errmsg: &str, mut i: usize) -> String {
303             let mut buf = String::with_capacity(s.len());
304             let mut j = 0;
305             while i < s.len() {
306                 let ch = char_at(s, i);
307                 let next = i + ch.len_utf8();
308                 if ch == '\r' {
309                     if j < i { buf.push_str(&s[j..i]); }
310                     j = next;
311                     if next >= s.len() || char_at(s, next) != '\n' {
312                         let pos = start + BytePos(i as u32);
313                         let end_pos = start + BytePos(next as u32);
314                         rdr.err_span_(pos, end_pos, errmsg);
315                     }
316                 }
317                 i = next;
318             }
319             if j < s.len() { buf.push_str(&s[j..]); }
320             buf
321         }
322     }
323
324
325     /// Advance the StringReader by one character. If a newline is
326     /// discovered, add it to the FileMap's list of line start offsets.
327     pub fn bump(&mut self) {
328         self.last_pos = self.pos;
329         let current_byte_offset = self.byte_offset(self.pos).to_usize();
330         if current_byte_offset < self.source_text.len() {
331             assert!(self.curr.is_some());
332             let last_char = self.curr.unwrap();
333             let ch = char_at(&self.source_text, current_byte_offset);
334             let next = current_byte_offset + ch.len_utf8();
335             let byte_offset_diff = next - current_byte_offset;
336             self.pos = self.pos + Pos::from_usize(byte_offset_diff);
337             self.curr = Some(ch);
338             self.col = self.col + CharPos(1);
339             if last_char == '\n' {
340                 self.filemap.next_line(self.last_pos);
341                 self.col = CharPos(0);
342             }
343
344             if byte_offset_diff > 1 {
345                 self.filemap.record_multibyte_char(self.last_pos, byte_offset_diff);
346             }
347         } else {
348             self.curr = None;
349         }
350     }
351
352     pub fn nextch(&self) -> Option<char> {
353         let offset = self.byte_offset(self.pos).to_usize();
354         if offset < self.source_text.len() {
355             Some(char_at(&self.source_text, offset))
356         } else {
357             None
358         }
359     }
360
361     pub fn nextch_is(&self, c: char) -> bool {
362         self.nextch() == Some(c)
363     }
364
365     pub fn nextnextch(&self) -> Option<char> {
366         let offset = self.byte_offset(self.pos).to_usize();
367         let s = &self.source_text[..];
368         if offset >= s.len() { return None }
369         let next = offset + char_at(s, offset).len_utf8();
370         if next < s.len() {
371             Some(char_at(s, next))
372         } else {
373             None
374         }
375     }
376
377     pub fn nextnextch_is(&self, c: char) -> bool {
378         self.nextnextch() == Some(c)
379     }
380
381     /// Eats <XID_start><XID_continue>*, if possible.
382     fn scan_optional_raw_name(&mut self) -> Option<ast::Name> {
383         if !ident_start(self.curr) {
384             return None
385         }
386         let start = self.last_pos;
387         while ident_continue(self.curr) {
388             self.bump();
389         }
390
391         self.with_str_from(start, |string| {
392             if string == "_" {
393                 None
394             } else {
395                 Some(token::intern(string))
396             }
397         })
398     }
399
400     /// PRECONDITION: self.curr is not whitespace
401     /// Eats any kind of comment.
402     fn scan_comment(&mut self) -> Option<TokenAndSpan> {
403         match self.curr {
404             Some(c) => {
405                 if c.is_whitespace() {
406                     self.span_diagnostic.span_err(codemap::mk_sp(self.last_pos, self.last_pos),
407                                     "called consume_any_line_comment, but there was whitespace");
408                 }
409             },
410             None => { }
411         }
412
413         if self.curr_is('/') {
414             match self.nextch() {
415                 Some('/') => {
416                     self.bump();
417                     self.bump();
418
419                     // line comments starting with "///" or "//!" are doc-comments
420                     let doc_comment = self.curr_is('/') || self.curr_is('!');
421                     let start_bpos = if doc_comment {
422                         self.pos - BytePos(3)
423                     } else {
424                         self.last_pos - BytePos(2)
425                     };
426
427                     while !self.is_eof() {
428                         match self.curr.unwrap() {
429                             '\n' => break,
430                             '\r' => {
431                                 if self.nextch_is('\n') {
432                                     // CRLF
433                                     break
434                                 } else if doc_comment {
435                                     self.err_span_(self.last_pos, self.pos,
436                                                    "bare CR not allowed in doc-comment");
437                                 }
438                             }
439                             _ => ()
440                         }
441                         self.bump();
442                     }
443
444                     return if doc_comment {
445                         self.with_str_from(start_bpos, |string| {
446                             // comments with only more "/"s are not doc comments
447                             let tok = if is_doc_comment(string) {
448                                 token::DocComment(token::intern(string))
449                             } else {
450                                 token::Comment
451                             };
452
453                             Some(TokenAndSpan {
454                                 tok: tok,
455                                 sp: codemap::mk_sp(start_bpos, self.last_pos)
456                             })
457                         })
458                     } else {
459                         Some(TokenAndSpan {
460                             tok: token::Comment,
461                             sp: codemap::mk_sp(start_bpos, self.last_pos)
462                         })
463                     }
464                 }
465                 Some('*') => {
466                     self.bump(); self.bump();
467                     self.scan_block_comment()
468                 }
469                 _ => None
470             }
471         } else if self.curr_is('#') {
472             if self.nextch_is('!') {
473
474                 // Parse an inner attribute.
475                 if self.nextnextch_is('[') {
476                     return None;
477                 }
478
479                 // I guess this is the only way to figure out if
480                 // we're at the beginning of the file...
481                 let cmap = CodeMap::new();
482                 cmap.files.borrow_mut().push(self.filemap.clone());
483                 let loc = cmap.lookup_char_pos_adj(self.last_pos);
484                 debug!("Skipping a shebang");
485                 if loc.line == 1 && loc.col == CharPos(0) {
486                     // FIXME: Add shebang "token", return it
487                     let start = self.last_pos;
488                     while !self.curr_is('\n') && !self.is_eof() { self.bump(); }
489                     return Some(TokenAndSpan {
490                         tok: token::Shebang(self.name_from(start)),
491                         sp: codemap::mk_sp(start, self.last_pos)
492                     });
493                 }
494             }
495             None
496         } else {
497             None
498         }
499     }
500
501     /// If there is whitespace, shebang, or a comment, scan it. Otherwise,
502     /// return None.
503     fn scan_whitespace_or_comment(&mut self) -> Option<TokenAndSpan> {
504         match self.curr.unwrap_or('\0') {
505             // # to handle shebang at start of file -- this is the entry point
506             // for skipping over all "junk"
507             '/' | '#' => {
508                 let c = self.scan_comment();
509                 debug!("scanning a comment {:?}", c);
510                 c
511             },
512             c if is_whitespace(Some(c)) => {
513                 let start_bpos = self.last_pos;
514                 while is_whitespace(self.curr) { self.bump(); }
515                 let c = Some(TokenAndSpan {
516                     tok: token::Whitespace,
517                     sp: codemap::mk_sp(start_bpos, self.last_pos)
518                 });
519                 debug!("scanning whitespace: {:?}", c);
520                 c
521             },
522             _ => None
523         }
524     }
525
526     /// Might return a sugared-doc-attr
527     fn scan_block_comment(&mut self) -> Option<TokenAndSpan> {
528         // block comments starting with "/**" or "/*!" are doc-comments
529         let is_doc_comment = self.curr_is('*') || self.curr_is('!');
530         let start_bpos = self.last_pos - BytePos(2);
531
532         let mut level: isize = 1;
533         let mut has_cr = false;
534         while level > 0 {
535             if self.is_eof() {
536                 let msg = if is_doc_comment {
537                     "unterminated block doc-comment"
538                 } else {
539                     "unterminated block comment"
540                 };
541                 let last_bpos = self.last_pos;
542                 panic!(self.fatal_span_(start_bpos, last_bpos, msg));
543             }
544             let n = self.curr.unwrap();
545             match n {
546                 '/' if self.nextch_is('*') => {
547                     level += 1;
548                     self.bump();
549                 }
550                 '*' if self.nextch_is('/') => {
551                     level -= 1;
552                     self.bump();
553                 }
554                 '\r' => {
555                     has_cr = true;
556                 }
557                 _ => ()
558             }
559             self.bump();
560         }
561
562         self.with_str_from(start_bpos, |string| {
563             // but comments with only "*"s between two "/"s are not
564             let tok = if is_block_doc_comment(string) {
565                 let string = if has_cr {
566                     self.translate_crlf(start_bpos, string,
567                                         "bare CR not allowed in block doc-comment")
568                 } else { string.into() };
569                 token::DocComment(token::intern(&string[..]))
570             } else {
571                 token::Comment
572             };
573
574             Some(TokenAndSpan{
575                 tok: tok,
576                 sp: codemap::mk_sp(start_bpos, self.last_pos)
577             })
578         })
579     }
580
581     /// Scan through any digits (base `scan_radix`) or underscores,
582     /// and return how many digits there were.
583     ///
584     /// `real_radix` represents the true radix of the number we're
585     /// interested in, and errors will be emitted for any digits
586     /// between `real_radix` and `scan_radix`.
587     fn scan_digits(&mut self, real_radix: u32, scan_radix: u32) -> usize {
588         assert!(real_radix <= scan_radix);
589         let mut len = 0;
590         loop {
591             let c = self.curr;
592             if c == Some('_') { debug!("skipping a _"); self.bump(); continue; }
593             match c.and_then(|cc| cc.to_digit(scan_radix)) {
594                 Some(_) => {
595                     debug!("{:?} in scan_digits", c);
596                     // check that the hypothetical digit is actually
597                     // in range for the true radix
598                     if c.unwrap().to_digit(real_radix).is_none() {
599                         self.err_span_(self.last_pos, self.pos,
600                                        &format!("invalid digit for a base {} literal",
601                                                 real_radix));
602                     }
603                     len += 1;
604                     self.bump();
605                 }
606                 _ => return len
607             }
608         };
609     }
610
611     /// Lex a LIT_INTEGER or a LIT_FLOAT
612     fn scan_number(&mut self, c: char) -> token::Lit {
613         let num_digits;
614         let mut base = 10;
615         let start_bpos = self.last_pos;
616
617         self.bump();
618
619         if c == '0' {
620             match self.curr.unwrap_or('\0') {
621                 'b' => { self.bump(); base = 2; num_digits = self.scan_digits(2, 10); }
622                 'o' => { self.bump(); base = 8; num_digits = self.scan_digits(8, 10); }
623                 'x' => { self.bump(); base = 16; num_digits = self.scan_digits(16, 16); }
624                 '0'...'9' | '_' | '.' => {
625                     num_digits = self.scan_digits(10, 10) + 1;
626                 }
627                 _ => {
628                     // just a 0
629                     return token::Integer(self.name_from(start_bpos));
630                 }
631             }
632         } else if c.is_digit(10) {
633             num_digits = self.scan_digits(10, 10) + 1;
634         } else {
635             num_digits = 0;
636         }
637
638         if num_digits == 0 {
639             self.err_span_(start_bpos, self.last_pos, "no valid digits found for number");
640             return token::Integer(token::intern("0"));
641         }
642
643         // might be a float, but don't be greedy if this is actually an
644         // integer literal followed by field/method access or a range pattern
645         // (`0..2` and `12.foo()`)
646         if self.curr_is('.') && !self.nextch_is('.') && !self.nextch().unwrap_or('\0')
647                                                              .is_xid_start() {
648             // might have stuff after the ., and if it does, it needs to start
649             // with a number
650             self.bump();
651             if self.curr.unwrap_or('\0').is_digit(10) {
652                 self.scan_digits(10, 10);
653                 self.scan_float_exponent();
654             }
655             let last_pos = self.last_pos;
656             self.check_float_base(start_bpos, last_pos, base);
657             return token::Float(self.name_from(start_bpos));
658         } else {
659             // it might be a float if it has an exponent
660             if self.curr_is('e') || self.curr_is('E') {
661                 self.scan_float_exponent();
662                 let last_pos = self.last_pos;
663                 self.check_float_base(start_bpos, last_pos, base);
664                 return token::Float(self.name_from(start_bpos));
665             }
666             // but we certainly have an integer!
667             return token::Integer(self.name_from(start_bpos));
668         }
669     }
670
671     /// Scan over `n_digits` hex digits, stopping at `delim`, reporting an
672     /// error if too many or too few digits are encountered.
673     fn scan_hex_digits(&mut self,
674                        n_digits: usize,
675                        delim: char,
676                        below_0x7f_only: bool)
677                        -> bool {
678         debug!("scanning {} digits until {:?}", n_digits, delim);
679         let start_bpos = self.last_pos;
680         let mut accum_int = 0;
681
682         let mut valid = true;
683         for _ in 0..n_digits {
684             if self.is_eof() {
685                 let last_bpos = self.last_pos;
686                 panic!(self.fatal_span_(start_bpos,
687                                         last_bpos,
688                                         "unterminated numeric character escape"));
689             }
690             if self.curr_is(delim) {
691                 let last_bpos = self.last_pos;
692                 self.err_span_(start_bpos, last_bpos, "numeric character escape is too short");
693                 valid = false;
694                 break;
695             }
696             let c = self.curr.unwrap_or('\x00');
697             accum_int *= 16;
698             accum_int += c.to_digit(16).unwrap_or_else(|| {
699                 self.err_span_char(self.last_pos, self.pos,
700                               "invalid character in numeric character escape", c);
701
702                 valid = false;
703                 0
704             });
705             self.bump();
706         }
707
708         if below_0x7f_only && accum_int >= 0x80 {
709             self.err_span_(start_bpos,
710                            self.last_pos,
711                            "this form of character escape may only be used \
712                             with characters in the range [\\x00-\\x7f]");
713             valid = false;
714         }
715
716         match char::from_u32(accum_int) {
717             Some(_) => valid,
718             None => {
719                 let last_bpos = self.last_pos;
720                 self.err_span_(start_bpos, last_bpos, "invalid numeric character escape");
721                 false
722             }
723         }
724     }
725
726     /// Scan for a single (possibly escaped) byte or char
727     /// in a byte, (non-raw) byte string, char, or (non-raw) string literal.
728     /// `start` is the position of `first_source_char`, which is already consumed.
729     ///
730     /// Returns true if there was a valid char/byte, false otherwise.
731     fn scan_char_or_byte(&mut self, start: BytePos, first_source_char: char,
732                          ascii_only: bool, delim: char) -> bool {
733         match first_source_char {
734             '\\' => {
735                 // '\X' for some X must be a character constant:
736                 let escaped = self.curr;
737                 let escaped_pos = self.last_pos;
738                 self.bump();
739                 match escaped {
740                     None => {},  // EOF here is an error that will be checked later.
741                     Some(e) => {
742                         return match e {
743                             'n' | 'r' | 't' | '\\' | '\'' | '"' | '0' => true,
744                             'x' => self.scan_byte_escape(delim, !ascii_only),
745                             'u' => {
746                                 let valid = if self.curr_is('{') {
747                                     self.scan_unicode_escape(delim) && !ascii_only
748                                 } else {
749                                     self.err_span_(start, self.last_pos,
750                                         "incorrect unicode escape sequence");
751                                     self.help_span_(start, self.last_pos,
752                                         "format of unicode escape sequences is `\\u{…}`");
753                                     false
754                                 };
755                                 if ascii_only {
756                                     self.err_span_(start, self.last_pos,
757                                         "unicode escape sequences cannot be used as a byte or in \
758                                         a byte string"
759                                     );
760                                 }
761                                 valid
762
763                             }
764                             '\n' if delim == '"' => {
765                                 self.consume_whitespace();
766                                 true
767                             },
768                             '\r' if delim == '"' && self.curr_is('\n') => {
769                                 self.consume_whitespace();
770                                 true
771                             }
772                             c => {
773                                 let last_pos = self.last_pos;
774                                 self.err_span_char(
775                                     escaped_pos, last_pos,
776                                     if ascii_only { "unknown byte escape" }
777                                     else { "unknown character escape" },
778                                     c);
779                                 if e == '\r' {
780                                     self.help_span_(escaped_pos, last_pos,
781                                         "this is an isolated carriage return; consider checking \
782                                          your editor and version control settings")
783                                 }
784                                 if (e == '{' || e == '}') && !ascii_only {
785                                     self.help_span_(escaped_pos, last_pos,
786                                         "if used in a formatting string, \
787                                         curly braces are escaped with `{{` and `}}`")
788                                 }
789                                 false
790                             }
791                         }
792                     }
793                 }
794             }
795             '\t' | '\n' | '\r' | '\'' if delim == '\'' => {
796                 let last_pos = self.last_pos;
797                 self.err_span_char(
798                     start, last_pos,
799                     if ascii_only { "byte constant must be escaped" }
800                     else { "character constant must be escaped" },
801                     first_source_char);
802                 return false;
803             }
804             '\r' => {
805                 if self.curr_is('\n') {
806                     self.bump();
807                     return true;
808                 } else {
809                     self.err_span_(start, self.last_pos,
810                                    "bare CR not allowed in string, use \\r instead");
811                     return false;
812                 }
813             }
814             _ => if ascii_only && first_source_char > '\x7F' {
815                 let last_pos = self.last_pos;
816                 self.err_span_char(
817                     start, last_pos,
818                     "byte constant must be ASCII. \
819                      Use a \\xHH escape for a non-ASCII byte", first_source_char);
820                 return false;
821             }
822         }
823         true
824     }
825
826     /// Scan over a \u{...} escape
827     ///
828     /// At this point, we have already seen the \ and the u, the { is the current character. We
829     /// will read at least one digit, and up to 6, and pass over the }.
830     fn scan_unicode_escape(&mut self, delim: char) -> bool {
831         self.bump(); // past the {
832         let start_bpos = self.last_pos;
833         let mut count = 0;
834         let mut accum_int = 0;
835         let mut valid = true;
836
837         while !self.curr_is('}') && count <= 6 {
838             let c = match self.curr {
839                 Some(c) => c,
840                 None => {
841                     panic!(self.fatal_span_(start_bpos, self.last_pos,
842                                             "unterminated unicode escape (found EOF)"));
843                 }
844             };
845             accum_int *= 16;
846             accum_int += c.to_digit(16).unwrap_or_else(|| {
847                 if c == delim {
848                     panic!(self.fatal_span_(self.last_pos, self.pos,
849                                             "unterminated unicode escape (needed a `}`)"));
850                 } else {
851                     self.err_span_char(self.last_pos, self.pos,
852                                    "invalid character in unicode escape", c);
853                 }
854                 valid = false;
855                 0
856             });
857             self.bump();
858             count += 1;
859         }
860
861         if count > 6 {
862             self.err_span_(start_bpos, self.last_pos,
863                           "overlong unicode escape (can have at most 6 hex digits)");
864             valid = false;
865         }
866
867         if valid && (char::from_u32(accum_int).is_none() || count == 0) {
868             self.err_span_(start_bpos, self.last_pos, "invalid unicode character escape");
869             valid = false;
870         }
871
872         self.bump(); // past the ending }
873         valid
874     }
875
876     /// Scan over a float exponent.
877     fn scan_float_exponent(&mut self) {
878         if self.curr_is('e') || self.curr_is('E') {
879             self.bump();
880             if self.curr_is('-') || self.curr_is('+') {
881                 self.bump();
882             }
883             if self.scan_digits(10, 10) == 0 {
884                 self.err_span_(self.last_pos, self.pos, "expected at least one digit in exponent")
885             }
886         }
887     }
888
889     /// Check that a base is valid for a floating literal, emitting a nice
890     /// error if it isn't.
891     fn check_float_base(&mut self, start_bpos: BytePos, last_bpos: BytePos, base: usize) {
892         match base {
893             16 => self.err_span_(start_bpos, last_bpos, "hexadecimal float literal is not \
894                                    supported"),
895             8 => self.err_span_(start_bpos, last_bpos, "octal float literal is not supported"),
896             2 => self.err_span_(start_bpos, last_bpos, "binary float literal is not supported"),
897             _   => ()
898         }
899     }
900
901     fn binop(&mut self, op: token::BinOpToken) -> token::Token {
902         self.bump();
903         if self.curr_is('=') {
904             self.bump();
905             return token::BinOpEq(op);
906         } else {
907             return token::BinOp(op);
908         }
909     }
910
911     /// Return the next token from the string, advances the input past that
912     /// token, and updates the interner
913     fn next_token_inner(&mut self) -> token::Token {
914         let c = self.curr;
915         if ident_start(c) && match (c.unwrap(), self.nextch(), self.nextnextch()) {
916             // Note: r as in r" or r#" is part of a raw string literal,
917             // b as in b' is part of a byte literal.
918             // They are not identifiers, and are handled further down.
919            ('r', Some('"'), _) | ('r', Some('#'), _) |
920            ('b', Some('"'), _) | ('b', Some('\''), _) |
921            ('b', Some('r'), Some('"')) | ('b', Some('r'), Some('#')) => false,
922            _ => true
923         } {
924             let start = self.last_pos;
925             while ident_continue(self.curr) {
926                 self.bump();
927             }
928
929             return self.with_str_from(start, |string| {
930                 if string == "_" {
931                     token::Underscore
932                 } else {
933                     // FIXME: perform NFKC normalization here. (Issue #2253)
934                     if self.curr_is(':') && self.nextch_is(':') {
935                         token::Ident(str_to_ident(string), token::ModName)
936                     } else {
937                         token::Ident(str_to_ident(string), token::Plain)
938                     }
939                 }
940             });
941         }
942
943         if is_dec_digit(c) {
944             let num = self.scan_number(c.unwrap());
945             let suffix = self.scan_optional_raw_name();
946             debug!("next_token_inner: scanned number {:?}, {:?}", num, suffix);
947             return token::Literal(num, suffix)
948         }
949
950         match c.expect("next_token_inner called at EOF") {
951           // One-byte tokens.
952           ';' => { self.bump(); return token::Semi; }
953           ',' => { self.bump(); return token::Comma; }
954           '.' => {
955               self.bump();
956               return if self.curr_is('.') {
957                   self.bump();
958                   if self.curr_is('.') {
959                       self.bump();
960                       token::DotDotDot
961                   } else {
962                       token::DotDot
963                   }
964               } else {
965                   token::Dot
966               };
967           }
968           '(' => { self.bump(); return token::OpenDelim(token::Paren); }
969           ')' => { self.bump(); return token::CloseDelim(token::Paren); }
970           '{' => { self.bump(); return token::OpenDelim(token::Brace); }
971           '}' => { self.bump(); return token::CloseDelim(token::Brace); }
972           '[' => { self.bump(); return token::OpenDelim(token::Bracket); }
973           ']' => { self.bump(); return token::CloseDelim(token::Bracket); }
974           '@' => { self.bump(); return token::At; }
975           '#' => { self.bump(); return token::Pound; }
976           '~' => { self.bump(); return token::Tilde; }
977           '?' => { self.bump(); return token::Question; }
978           ':' => {
979             self.bump();
980             if self.curr_is(':') {
981                 self.bump();
982                 return token::ModSep;
983             } else {
984                 return token::Colon;
985             }
986           }
987
988           '$' => { self.bump(); return token::Dollar; }
989
990           // Multi-byte tokens.
991           '=' => {
992             self.bump();
993             if self.curr_is('=') {
994                 self.bump();
995                 return token::EqEq;
996             } else if self.curr_is('>') {
997                 self.bump();
998                 return token::FatArrow;
999             } else {
1000                 return token::Eq;
1001             }
1002           }
1003           '!' => {
1004             self.bump();
1005             if self.curr_is('=') {
1006                 self.bump();
1007                 return token::Ne;
1008             } else { return token::Not; }
1009           }
1010           '<' => {
1011             self.bump();
1012             match self.curr.unwrap_or('\x00') {
1013               '=' => { self.bump(); return token::Le; }
1014               '<' => { return self.binop(token::Shl); }
1015               '-' => {
1016                 self.bump();
1017                 match self.curr.unwrap_or('\x00') {
1018                   _ => { return token::LArrow; }
1019                 }
1020               }
1021               _ => { return token::Lt; }
1022             }
1023           }
1024           '>' => {
1025             self.bump();
1026             match self.curr.unwrap_or('\x00') {
1027               '=' => { self.bump(); return token::Ge; }
1028               '>' => { return self.binop(token::Shr); }
1029               _ => { return token::Gt; }
1030             }
1031           }
1032           '\'' => {
1033             // Either a character constant 'a' OR a lifetime name 'abc
1034             self.bump();
1035             let start = self.last_pos;
1036
1037             // the eof will be picked up by the final `'` check below
1038             let c2 = self.curr.unwrap_or('\x00');
1039             self.bump();
1040
1041             // If the character is an ident start not followed by another single
1042             // quote, then this is a lifetime name:
1043             if ident_start(Some(c2)) && !self.curr_is('\'') {
1044                 while ident_continue(self.curr) {
1045                     self.bump();
1046                 }
1047
1048                 // Include the leading `'` in the real identifier, for macro
1049                 // expansion purposes. See #12512 for the gory details of why
1050                 // this is necessary.
1051                 let ident = self.with_str_from(start, |lifetime_name| {
1052                     str_to_ident(&format!("'{}", lifetime_name))
1053                 });
1054
1055                 // Conjure up a "keyword checking ident" to make sure that
1056                 // the lifetime name is not a keyword.
1057                 let keyword_checking_ident =
1058                     self.with_str_from(start, |lifetime_name| {
1059                         str_to_ident(lifetime_name)
1060                     });
1061                 let keyword_checking_token =
1062                     &token::Ident(keyword_checking_ident, token::Plain);
1063                 let last_bpos = self.last_pos;
1064                 if keyword_checking_token.is_keyword(token::keywords::SelfValue) {
1065                     self.err_span_(start,
1066                                    last_bpos,
1067                                    "invalid lifetime name: 'self \
1068                                     is no longer a special lifetime");
1069                 } else if keyword_checking_token.is_any_keyword() &&
1070                     !keyword_checking_token.is_keyword(token::keywords::Static)
1071                 {
1072                     self.err_span_(start,
1073                                    last_bpos,
1074                                    "invalid lifetime name");
1075                 }
1076                 return token::Lifetime(ident);
1077             }
1078
1079             // Otherwise it is a character constant:
1080             let valid = self.scan_char_or_byte(start, c2, /* ascii_only = */ false, '\'');
1081             if !self.curr_is('\'') {
1082                 let last_bpos = self.last_pos;
1083                 panic!(self.fatal_span_verbose(
1084                         // Byte offsetting here is okay because the
1085                         // character before position `start` is an
1086                         // ascii single quote.
1087                         start - BytePos(1), last_bpos,
1088
1089                         String::from("character literal may only contain one codepoint")));
1090             }
1091             let id = if valid { self.name_from(start) } else { token::intern("0") };
1092             self.bump(); // advance curr past token
1093             let suffix = self.scan_optional_raw_name();
1094             return token::Literal(token::Char(id), suffix);
1095           }
1096           'b' => {
1097             self.bump();
1098             let lit = match self.curr {
1099                 Some('\'') => self.scan_byte(),
1100                 Some('"') => self.scan_byte_string(),
1101                 Some('r') => self.scan_raw_byte_string(),
1102                 _ => unreachable!()  // Should have been a token::Ident above.
1103             };
1104             let suffix = self.scan_optional_raw_name();
1105             return token::Literal(lit, suffix);
1106           }
1107           '"' => {
1108             let start_bpos = self.last_pos;
1109             let mut valid = true;
1110             self.bump();
1111             while !self.curr_is('"') {
1112                 if self.is_eof() {
1113                     let last_bpos = self.last_pos;
1114                     panic!(self.fatal_span_(start_bpos,
1115                                             last_bpos,
1116                                             "unterminated double quote string"));
1117                 }
1118
1119                 let ch_start = self.last_pos;
1120                 let ch = self.curr.unwrap();
1121                 self.bump();
1122                 valid &= self.scan_char_or_byte(ch_start, ch, /* ascii_only = */ false, '"');
1123             }
1124             // adjust for the ASCII " at the start of the literal
1125             let id = if valid { self.name_from(start_bpos + BytePos(1)) }
1126                      else { token::intern("??") };
1127             self.bump();
1128             let suffix = self.scan_optional_raw_name();
1129             return token::Literal(token::Str_(id), suffix);
1130           }
1131           'r' => {
1132             let start_bpos = self.last_pos;
1133             self.bump();
1134             let mut hash_count = 0;
1135             while self.curr_is('#') {
1136                 self.bump();
1137                 hash_count += 1;
1138             }
1139
1140             if self.is_eof() {
1141                 let last_bpos = self.last_pos;
1142                 panic!(self.fatal_span_(start_bpos, last_bpos, "unterminated raw string"));
1143             } else if !self.curr_is('"') {
1144                 let last_bpos = self.last_pos;
1145                 let curr_char = self.curr.unwrap();
1146                 panic!(self.fatal_span_char(start_bpos, last_bpos,
1147                                 "found invalid character; \
1148                                  only `#` is allowed in raw string delimitation",
1149                                 curr_char));
1150             }
1151             self.bump();
1152             let content_start_bpos = self.last_pos;
1153             let mut content_end_bpos;
1154             let mut valid = true;
1155             'outer: loop {
1156                 if self.is_eof() {
1157                     let last_bpos = self.last_pos;
1158                     panic!(self.fatal_span_(start_bpos, last_bpos, "unterminated raw string"));
1159                 }
1160                 //if self.curr_is('"') {
1161                     //content_end_bpos = self.last_pos;
1162                     //for _ in 0..hash_count {
1163                         //self.bump();
1164                         //if !self.curr_is('#') {
1165                             //continue 'outer;
1166                 let c = self.curr.unwrap();
1167                 match c {
1168                     '"' => {
1169                         content_end_bpos = self.last_pos;
1170                         for _ in 0..hash_count {
1171                             self.bump();
1172                             if !self.curr_is('#') {
1173                                 continue 'outer;
1174                             }
1175                         }
1176                         break;
1177                     },
1178                     '\r' => {
1179                         if !self.nextch_is('\n') {
1180                             let last_bpos = self.last_pos;
1181                             self.err_span_(start_bpos, last_bpos, "bare CR not allowed in raw \
1182                                            string, use \\r instead");
1183                             valid = false;
1184                         }
1185                     }
1186                     _ => ()
1187                 }
1188                 self.bump();
1189             }
1190             self.bump();
1191             let id = if valid {
1192                 self.name_from_to(content_start_bpos, content_end_bpos)
1193             } else {
1194                 token::intern("??")
1195             };
1196             let suffix = self.scan_optional_raw_name();
1197             return token::Literal(token::StrRaw(id, hash_count), suffix);
1198           }
1199           '-' => {
1200             if self.nextch_is('>') {
1201                 self.bump();
1202                 self.bump();
1203                 return token::RArrow;
1204             } else { return self.binop(token::Minus); }
1205           }
1206           '&' => {
1207             if self.nextch_is('&') {
1208                 self.bump();
1209                 self.bump();
1210                 return token::AndAnd;
1211             } else { return self.binop(token::And); }
1212           }
1213           '|' => {
1214             match self.nextch() {
1215               Some('|') => { self.bump(); self.bump(); return token::OrOr; }
1216               _ => { return self.binop(token::Or); }
1217             }
1218           }
1219           '+' => { return self.binop(token::Plus); }
1220           '*' => { return self.binop(token::Star); }
1221           '/' => { return self.binop(token::Slash); }
1222           '^' => { return self.binop(token::Caret); }
1223           '%' => { return self.binop(token::Percent); }
1224           c => {
1225               let last_bpos = self.last_pos;
1226               let bpos = self.pos;
1227               unicode_chars::check_for_substitution(&self, c);
1228               panic!(self.fatal_span_char(last_bpos, bpos, "unknown start of token", c))
1229           }
1230         }
1231     }
1232
1233     fn consume_whitespace(&mut self) {
1234         while is_whitespace(self.curr) && !self.is_eof() { self.bump(); }
1235     }
1236
1237     fn read_to_eol(&mut self) -> String {
1238         let mut val = String::new();
1239         while !self.curr_is('\n') && !self.is_eof() {
1240             val.push(self.curr.unwrap());
1241             self.bump();
1242         }
1243         if self.curr_is('\n') { self.bump(); }
1244         return val
1245     }
1246
1247     fn read_one_line_comment(&mut self) -> String {
1248         let val = self.read_to_eol();
1249         assert!((val.as_bytes()[0] == b'/' && val.as_bytes()[1] == b'/')
1250              || (val.as_bytes()[0] == b'#' && val.as_bytes()[1] == b'!'));
1251         return val;
1252     }
1253
1254     fn consume_non_eol_whitespace(&mut self) {
1255         while is_whitespace(self.curr) && !self.curr_is('\n') && !self.is_eof() {
1256             self.bump();
1257         }
1258     }
1259
1260     fn peeking_at_comment(&self) -> bool {
1261         (self.curr_is('/') && self.nextch_is('/'))
1262      || (self.curr_is('/') && self.nextch_is('*'))
1263      // consider shebangs comments, but not inner attributes
1264      || (self.curr_is('#') && self.nextch_is('!') && !self.nextnextch_is('['))
1265     }
1266
1267     fn scan_byte(&mut self) -> token::Lit {
1268         self.bump();
1269         let start = self.last_pos;
1270
1271         // the eof will be picked up by the final `'` check below
1272         let c2 = self.curr.unwrap_or('\x00');
1273         self.bump();
1274
1275         let valid = self.scan_char_or_byte(start, c2, /* ascii_only = */ true, '\'');
1276         if !self.curr_is('\'') {
1277             // Byte offsetting here is okay because the
1278             // character before position `start` are an
1279             // ascii single quote and ascii 'b'.
1280             let last_pos = self.last_pos;
1281             panic!(self.fatal_span_verbose(
1282                 start - BytePos(2), last_pos,
1283                 "unterminated byte constant".to_string()));
1284         }
1285
1286         let id = if valid { self.name_from(start) } else { token::intern("?") };
1287         self.bump(); // advance curr past token
1288         return token::Byte(id);
1289     }
1290
1291     fn scan_byte_escape(&mut self, delim: char, below_0x7f_only: bool) -> bool {
1292         self.scan_hex_digits(2, delim, below_0x7f_only)
1293     }
1294
1295     fn scan_byte_string(&mut self) -> token::Lit {
1296         self.bump();
1297         let start = self.last_pos;
1298         let mut valid = true;
1299
1300         while !self.curr_is('"') {
1301             if self.is_eof() {
1302                 let last_pos = self.last_pos;
1303                 panic!(self.fatal_span_(start, last_pos, "unterminated double quote byte string"));
1304             }
1305
1306             let ch_start = self.last_pos;
1307             let ch = self.curr.unwrap();
1308             self.bump();
1309             valid &= self.scan_char_or_byte(ch_start, ch, /* ascii_only = */ true, '"');
1310         }
1311         let id = if valid { self.name_from(start) } else { token::intern("??") };
1312         self.bump();
1313         return token::ByteStr(id);
1314     }
1315
1316     fn scan_raw_byte_string(&mut self) -> token::Lit {
1317         let start_bpos = self.last_pos;
1318         self.bump();
1319         let mut hash_count = 0;
1320         while self.curr_is('#') {
1321             self.bump();
1322             hash_count += 1;
1323         }
1324
1325         if self.is_eof() {
1326             let last_pos = self.last_pos;
1327             panic!(self.fatal_span_(start_bpos, last_pos, "unterminated raw string"));
1328         } else if !self.curr_is('"') {
1329             let last_pos = self.last_pos;
1330             let ch = self.curr.unwrap();
1331             panic!(self.fatal_span_char(start_bpos, last_pos,
1332                             "found invalid character; \
1333                              only `#` is allowed in raw string delimitation",
1334                             ch));
1335         }
1336         self.bump();
1337         let content_start_bpos = self.last_pos;
1338         let mut content_end_bpos;
1339         'outer: loop {
1340             match self.curr {
1341                 None => {
1342                     let last_pos = self.last_pos;
1343                     panic!(self.fatal_span_(start_bpos, last_pos, "unterminated raw string"))
1344                 },
1345                 Some('"') => {
1346                     content_end_bpos = self.last_pos;
1347                     for _ in 0..hash_count {
1348                         self.bump();
1349                         if !self.curr_is('#') {
1350                             continue 'outer;
1351                         }
1352                     }
1353                     break;
1354                 },
1355                 Some(c) => if c > '\x7F' {
1356                     let last_pos = self.last_pos;
1357                     self.err_span_char(
1358                         last_pos, last_pos, "raw byte string must be ASCII", c);
1359                 }
1360             }
1361             self.bump();
1362         }
1363         self.bump();
1364         return token::ByteStrRaw(self.name_from_to(content_start_bpos,
1365                                                   content_end_bpos),
1366                                 hash_count);
1367     }
1368 }
1369
1370 pub fn is_whitespace(c: Option<char>) -> bool {
1371     match c.unwrap_or('\x00') { // None can be null for now... it's not whitespace
1372         ' ' | '\n' | '\t' | '\r' => true,
1373         _ => false
1374     }
1375 }
1376
1377 fn in_range(c: Option<char>, lo: char, hi: char) -> bool {
1378     match c {
1379         Some(c) => lo <= c && c <= hi,
1380         _ => false
1381     }
1382 }
1383
1384 fn is_dec_digit(c: Option<char>) -> bool { return in_range(c, '0', '9'); }
1385
1386 pub fn is_doc_comment(s: &str) -> bool {
1387     let res = (s.starts_with("///") && *s.as_bytes().get(3).unwrap_or(&b' ') != b'/')
1388               || s.starts_with("//!");
1389     debug!("is {:?} a doc comment? {}", s, res);
1390     res
1391 }
1392
1393 pub fn is_block_doc_comment(s: &str) -> bool {
1394     let res = ((s.starts_with("/**") && *s.as_bytes().get(3).unwrap_or(&b' ') != b'*')
1395                || s.starts_with("/*!"))
1396               && s.len() >= 5; // Prevent `/**/` from being parsed as a doc comment
1397     debug!("is {:?} a doc comment? {}", s, res);
1398     res
1399 }
1400
1401 fn ident_start(c: Option<char>) -> bool {
1402     let c = match c { Some(c) => c, None => return false };
1403
1404     (c >= 'a' && c <= 'z')
1405         || (c >= 'A' && c <= 'Z')
1406         || c == '_'
1407         || (c > '\x7f' && c.is_xid_start())
1408 }
1409
1410 fn ident_continue(c: Option<char>) -> bool {
1411     let c = match c { Some(c) => c, None => return false };
1412
1413     (c >= 'a' && c <= 'z')
1414         || (c >= 'A' && c <= 'Z')
1415         || (c >= '0' && c <= '9')
1416         || c == '_'
1417         || (c > '\x7f' && c.is_xid_continue())
1418 }
1419
1420 #[cfg(test)]
1421 mod tests {
1422     use super::*;
1423
1424     use codemap::{BytePos, CodeMap, Span, NO_EXPANSION};
1425     use diagnostic;
1426     use parse::token;
1427     use parse::token::{str_to_ident};
1428     use std::io;
1429
1430     fn mk_sh() -> diagnostic::Handler {
1431         // FIXME (#22405): Replace `Box::new` with `box` here when/if possible.
1432         let emitter = diagnostic::EmitterWriter::new(Box::new(io::sink()), None);
1433         let handler = diagnostic::Handler::with_emitter(true, Box::new(emitter));
1434         diagnostic::Handler::new(handler, CodeMap::new())
1435     }
1436
1437     // open a string reader for the given string
1438     fn setup<'a>(span_handler: &'a diagnostic::Handler,
1439                  teststr: String) -> StringReader<'a> {
1440         let fm = span_handler.cm.new_filemap("zebra.rs".to_string(), teststr);
1441         StringReader::new(span_handler, fm)
1442     }
1443
1444     #[test] fn t1 () {
1445         let span_handler = mk_sh();
1446         let mut string_reader = setup(&span_handler,
1447             "/* my source file */ \
1448              fn main() { println!(\"zebra\"); }\n".to_string());
1449         let id = str_to_ident("fn");
1450         assert_eq!(string_reader.next_token().tok, token::Comment);
1451         assert_eq!(string_reader.next_token().tok, token::Whitespace);
1452         let tok1 = string_reader.next_token();
1453         let tok2 = TokenAndSpan{
1454             tok:token::Ident(id, token::Plain),
1455             sp:Span {lo:BytePos(21),hi:BytePos(23),expn_id: NO_EXPANSION}};
1456         assert_eq!(tok1,tok2);
1457         assert_eq!(string_reader.next_token().tok, token::Whitespace);
1458         // the 'main' id is already read:
1459         assert_eq!(string_reader.last_pos.clone(), BytePos(28));
1460         // read another token:
1461         let tok3 = string_reader.next_token();
1462         let tok4 = TokenAndSpan{
1463             tok:token::Ident(str_to_ident("main"), token::Plain),
1464             sp:Span {lo:BytePos(24),hi:BytePos(28),expn_id: NO_EXPANSION}};
1465         assert_eq!(tok3,tok4);
1466         // the lparen is already read:
1467         assert_eq!(string_reader.last_pos.clone(), BytePos(29))
1468     }
1469
1470     // check that the given reader produces the desired stream
1471     // of tokens (stop checking after exhausting the expected vec)
1472     fn check_tokenization (mut string_reader: StringReader, expected: Vec<token::Token> ) {
1473         for expected_tok in &expected {
1474             assert_eq!(&string_reader.next_token().tok, expected_tok);
1475         }
1476     }
1477
1478     // make the identifier by looking up the string in the interner
1479     fn mk_ident(id: &str, style: token::IdentStyle) -> token::Token {
1480         token::Ident(str_to_ident(id), style)
1481     }
1482
1483     #[test] fn doublecolonparsing () {
1484         check_tokenization(setup(&mk_sh(), "a b".to_string()),
1485                            vec![mk_ident("a", token::Plain),
1486                                 token::Whitespace,
1487                                 mk_ident("b", token::Plain)]);
1488     }
1489
1490     #[test] fn dcparsing_2 () {
1491         check_tokenization(setup(&mk_sh(), "a::b".to_string()),
1492                            vec![mk_ident("a",token::ModName),
1493                                 token::ModSep,
1494                                 mk_ident("b", token::Plain)]);
1495     }
1496
1497     #[test] fn dcparsing_3 () {
1498         check_tokenization(setup(&mk_sh(), "a ::b".to_string()),
1499                            vec![mk_ident("a", token::Plain),
1500                                 token::Whitespace,
1501                                 token::ModSep,
1502                                 mk_ident("b", token::Plain)]);
1503     }
1504
1505     #[test] fn dcparsing_4 () {
1506         check_tokenization(setup(&mk_sh(), "a:: b".to_string()),
1507                            vec![mk_ident("a",token::ModName),
1508                                 token::ModSep,
1509                                 token::Whitespace,
1510                                 mk_ident("b", token::Plain)]);
1511     }
1512
1513     #[test] fn character_a() {
1514         assert_eq!(setup(&mk_sh(), "'a'".to_string()).next_token().tok,
1515                    token::Literal(token::Char(token::intern("a")), None));
1516     }
1517
1518     #[test] fn character_space() {
1519         assert_eq!(setup(&mk_sh(), "' '".to_string()).next_token().tok,
1520                    token::Literal(token::Char(token::intern(" ")), None));
1521     }
1522
1523     #[test] fn character_escaped() {
1524         assert_eq!(setup(&mk_sh(), "'\\n'".to_string()).next_token().tok,
1525                    token::Literal(token::Char(token::intern("\\n")), None));
1526     }
1527
1528     #[test] fn lifetime_name() {
1529         assert_eq!(setup(&mk_sh(), "'abc".to_string()).next_token().tok,
1530                    token::Lifetime(token::str_to_ident("'abc")));
1531     }
1532
1533     #[test] fn raw_string() {
1534         assert_eq!(setup(&mk_sh(),
1535                          "r###\"\"#a\\b\x00c\"\"###".to_string()).next_token()
1536                                                                  .tok,
1537                    token::Literal(token::StrRaw(token::intern("\"#a\\b\x00c\""), 3), None));
1538     }
1539
1540     #[test] fn literal_suffixes() {
1541         macro_rules! test {
1542             ($input: expr, $tok_type: ident, $tok_contents: expr) => {{
1543                 assert_eq!(setup(&mk_sh(), format!("{}suffix", $input)).next_token().tok,
1544                            token::Literal(token::$tok_type(token::intern($tok_contents)),
1545                                           Some(token::intern("suffix"))));
1546                 // with a whitespace separator:
1547                 assert_eq!(setup(&mk_sh(), format!("{} suffix", $input)).next_token().tok,
1548                            token::Literal(token::$tok_type(token::intern($tok_contents)),
1549                                           None));
1550             }}
1551         }
1552
1553         test!("'a'", Char, "a");
1554         test!("b'a'", Byte, "a");
1555         test!("\"a\"", Str_, "a");
1556         test!("b\"a\"", ByteStr, "a");
1557         test!("1234", Integer, "1234");
1558         test!("0b101", Integer, "0b101");
1559         test!("0xABC", Integer, "0xABC");
1560         test!("1.0", Float, "1.0");
1561         test!("1.0e10", Float, "1.0e10");
1562
1563         assert_eq!(setup(&mk_sh(), "2us".to_string()).next_token().tok,
1564                    token::Literal(token::Integer(token::intern("2")),
1565                                   Some(token::intern("us"))));
1566         assert_eq!(setup(&mk_sh(), "r###\"raw\"###suffix".to_string()).next_token().tok,
1567                    token::Literal(token::StrRaw(token::intern("raw"), 3),
1568                                   Some(token::intern("suffix"))));
1569         assert_eq!(setup(&mk_sh(), "br###\"raw\"###suffix".to_string()).next_token().tok,
1570                    token::Literal(token::ByteStrRaw(token::intern("raw"), 3),
1571                                   Some(token::intern("suffix"))));
1572     }
1573
1574     #[test] fn line_doc_comments() {
1575         assert!(is_doc_comment("///"));
1576         assert!(is_doc_comment("/// blah"));
1577         assert!(!is_doc_comment("////"));
1578     }
1579
1580     #[test] fn nested_block_comments() {
1581         let sh = mk_sh();
1582         let mut lexer = setup(&sh, "/* /* */ */'a'".to_string());
1583         match lexer.next_token().tok {
1584             token::Comment => { },
1585             _ => panic!("expected a comment!")
1586         }
1587         assert_eq!(lexer.next_token().tok, token::Literal(token::Char(token::intern("a")), None));
1588     }
1589
1590     #[test] fn crlf_comments() {
1591         let sh = mk_sh();
1592         let mut lexer = setup(&sh, "// test\r\n/// test\r\n".to_string());
1593         let comment = lexer.next_token();
1594         assert_eq!(comment.tok, token::Comment);
1595         assert_eq!(comment.sp, ::codemap::mk_sp(BytePos(0), BytePos(7)));
1596         assert_eq!(lexer.next_token().tok, token::Whitespace);
1597         assert_eq!(lexer.next_token().tok, token::DocComment(token::intern("/// test")));
1598     }
1599 }