]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/parse/lexer.rs
auto merge of #13600 : brandonw/rust/master, r=brson
[rust.git] / src / libsyntax / parse / lexer.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 diagnostic::SpanHandler;
15 use ext::tt::transcribe::tt_next_token;
16 use parse::token;
17 use parse::token::{str_to_ident};
18
19 use std::char;
20 use std::mem::replace;
21 use std::num::from_str_radix;
22 use std::rc::Rc;
23 use std::str;
24 use std::strbuf::StrBuf;
25
26 pub use ext::tt::transcribe::{TtReader, new_tt_reader};
27
28 pub trait Reader {
29     fn is_eof(&self) -> bool;
30     fn next_token(&mut self) -> TokenAndSpan;
31     fn fatal(&self, ~str) -> !;
32     fn span_diag<'a>(&'a self) -> &'a SpanHandler;
33     fn peek(&self) -> TokenAndSpan;
34 }
35
36 #[deriving(Clone, Eq, Show)]
37 pub struct TokenAndSpan {
38     pub tok: token::Token,
39     pub sp: Span,
40 }
41
42 pub struct StringReader<'a> {
43     pub span_diagnostic: &'a SpanHandler,
44     // The absolute offset within the codemap of the next character to read
45     pub pos: BytePos,
46     // The absolute offset within the codemap of the last character read(curr)
47     pub last_pos: BytePos,
48     // The column of the next character to read
49     pub col: CharPos,
50     // The last character to be read
51     pub curr: Option<char>,
52     pub filemap: Rc<codemap::FileMap>,
53     /* cached: */
54     pub peek_tok: token::Token,
55     pub peek_span: Span,
56 }
57
58 impl<'a> StringReader<'a> {
59     pub fn curr_is(&self, c: char) -> bool {
60         self.curr == Some(c)
61     }
62 }
63
64 pub fn new_string_reader<'a>(span_diagnostic: &'a SpanHandler,
65                              filemap: Rc<codemap::FileMap>)
66                              -> StringReader<'a> {
67     let mut r = new_low_level_string_reader(span_diagnostic, filemap);
68     string_advance_token(&mut r); /* fill in peek_* */
69     r
70 }
71
72 /* For comments.rs, which hackily pokes into 'pos' and 'curr' */
73 pub fn new_low_level_string_reader<'a>(span_diagnostic: &'a SpanHandler,
74                                        filemap: Rc<codemap::FileMap>)
75                                        -> StringReader<'a> {
76     // Force the initial reader bump to start on a fresh line
77     let initial_char = '\n';
78     let mut r = StringReader {
79         span_diagnostic: span_diagnostic,
80         pos: filemap.start_pos,
81         last_pos: filemap.start_pos,
82         col: CharPos(0),
83         curr: Some(initial_char),
84         filemap: filemap,
85         /* dummy values; not read */
86         peek_tok: token::EOF,
87         peek_span: codemap::DUMMY_SP,
88     };
89     bump(&mut r);
90     r
91 }
92
93 impl<'a> Reader for StringReader<'a> {
94     fn is_eof(&self) -> bool { is_eof(self) }
95     // return the next token. EFFECT: advances the string_reader.
96     fn next_token(&mut self) -> TokenAndSpan {
97         let ret_val = TokenAndSpan {
98             tok: replace(&mut self.peek_tok, token::UNDERSCORE),
99             sp: self.peek_span,
100         };
101         string_advance_token(self);
102         ret_val
103     }
104     fn fatal(&self, m: ~str) -> ! {
105         self.span_diagnostic.span_fatal(self.peek_span, m)
106     }
107     fn span_diag<'a>(&'a self) -> &'a SpanHandler { self.span_diagnostic }
108     fn peek(&self) -> TokenAndSpan {
109         // FIXME(pcwalton): Bad copy!
110         TokenAndSpan {
111             tok: self.peek_tok.clone(),
112             sp: self.peek_span.clone(),
113         }
114     }
115 }
116
117 impl<'a> Reader for TtReader<'a> {
118     fn is_eof(&self) -> bool {
119         self.cur_tok == token::EOF
120     }
121     fn next_token(&mut self) -> TokenAndSpan {
122         let r = tt_next_token(self);
123         debug!("TtReader: r={:?}", r);
124         r
125     }
126     fn fatal(&self, m: ~str) -> ! {
127         self.sp_diag.span_fatal(self.cur_span, m);
128     }
129     fn span_diag<'a>(&'a self) -> &'a SpanHandler { self.sp_diag }
130     fn peek(&self) -> TokenAndSpan {
131         TokenAndSpan {
132             tok: self.cur_tok.clone(),
133             sp: self.cur_span.clone(),
134         }
135     }
136 }
137
138 // report a lexical error spanning [`from_pos`, `to_pos`)
139 fn fatal_span(rdr: &mut StringReader,
140               from_pos: BytePos,
141               to_pos: BytePos,
142               m: ~str)
143            -> ! {
144     rdr.peek_span = codemap::mk_sp(from_pos, to_pos);
145     rdr.fatal(m);
146 }
147
148 // report a lexical error spanning [`from_pos`, `to_pos`), appending an
149 // escaped character to the error message
150 fn fatal_span_char(rdr: &mut StringReader,
151                    from_pos: BytePos,
152                    to_pos: BytePos,
153                    m: ~str,
154                    c: char)
155                 -> ! {
156     let mut m = StrBuf::from_owned_str(m);
157     m.push_str(": ");
158     char::escape_default(c, |c| m.push_char(c));
159     fatal_span(rdr, from_pos, to_pos, m.into_owned());
160 }
161
162 // report a lexical error spanning [`from_pos`, `to_pos`), appending the
163 // offending string to the error message
164 fn fatal_span_verbose(rdr: &mut StringReader,
165                       from_pos: BytePos,
166                       to_pos: BytePos,
167                       m: ~str)
168                    -> ! {
169     let mut m = StrBuf::from_owned_str(m);
170     m.push_str(": ");
171     let from = byte_offset(rdr, from_pos).to_uint();
172     let to = byte_offset(rdr, to_pos).to_uint();
173     m.push_str(rdr.filemap.src.slice(from, to));
174     fatal_span(rdr, from_pos, to_pos, m.into_owned());
175 }
176
177 // EFFECT: advance peek_tok and peek_span to refer to the next token.
178 // EFFECT: update the interner, maybe.
179 fn string_advance_token(r: &mut StringReader) {
180     match consume_whitespace_and_comments(r) {
181         Some(comment) => {
182             r.peek_span = comment.sp;
183             r.peek_tok = comment.tok;
184         },
185         None => {
186             if is_eof(r) {
187                 r.peek_tok = token::EOF;
188             } else {
189                 let start_bytepos = r.last_pos;
190                 r.peek_tok = next_token_inner(r);
191                 r.peek_span = codemap::mk_sp(start_bytepos,
192                                              r.last_pos);
193             };
194         }
195     }
196 }
197
198 fn byte_offset(rdr: &StringReader, pos: BytePos) -> BytePos {
199     (pos - rdr.filemap.start_pos)
200 }
201
202 /// Calls `f` with a string slice of the source text spanning from `start`
203 /// up to but excluding `rdr.last_pos`, meaning the slice does not include
204 /// the character `rdr.curr`.
205 pub fn with_str_from<T>(
206                      rdr: &StringReader,
207                      start: BytePos,
208                      f: |s: &str| -> T)
209                      -> T {
210     with_str_from_to(rdr, start, rdr.last_pos, f)
211 }
212
213 /// Calls `f` with astring slice of the source text spanning from `start`
214 /// up to but excluding `end`.
215 fn with_str_from_to<T>(
216                     rdr: &StringReader,
217                     start: BytePos,
218                     end: BytePos,
219                     f: |s: &str| -> T)
220                     -> T {
221     f(rdr.filemap.src.slice(
222             byte_offset(rdr, start).to_uint(),
223             byte_offset(rdr, end).to_uint()))
224 }
225
226 // EFFECT: advance the StringReader by one character. If a newline is
227 // discovered, add it to the FileMap's list of line start offsets.
228 pub fn bump(rdr: &mut StringReader) {
229     rdr.last_pos = rdr.pos;
230     let current_byte_offset = byte_offset(rdr, rdr.pos).to_uint();
231     if current_byte_offset < rdr.filemap.src.len() {
232         assert!(rdr.curr.is_some());
233         let last_char = rdr.curr.unwrap();
234         let next = rdr.filemap.src.char_range_at(current_byte_offset);
235         let byte_offset_diff = next.next - current_byte_offset;
236         rdr.pos = rdr.pos + Pos::from_uint(byte_offset_diff);
237         rdr.curr = Some(next.ch);
238         rdr.col = rdr.col + CharPos(1u);
239         if last_char == '\n' {
240             rdr.filemap.next_line(rdr.last_pos);
241             rdr.col = CharPos(0u);
242         }
243
244         if byte_offset_diff > 1 {
245             rdr.filemap.record_multibyte_char(rdr.last_pos, byte_offset_diff);
246         }
247     } else {
248         rdr.curr = None;
249     }
250 }
251
252 pub fn is_eof(rdr: &StringReader) -> bool {
253     rdr.curr.is_none()
254 }
255
256 pub fn nextch(rdr: &StringReader) -> Option<char> {
257     let offset = byte_offset(rdr, rdr.pos).to_uint();
258     if offset < rdr.filemap.src.len() {
259         Some(rdr.filemap.src.char_at(offset))
260     } else {
261         None
262     }
263 }
264 pub fn nextch_is(rdr: &StringReader, c: char) -> bool {
265     nextch(rdr) == Some(c)
266 }
267
268 pub fn nextnextch(rdr: &StringReader) -> Option<char> {
269     let offset = byte_offset(rdr, rdr.pos).to_uint();
270     let s = rdr.filemap.deref().src.as_slice();
271     if offset >= s.len() { return None }
272     let str::CharRange { next, .. } = s.char_range_at(offset);
273     if next < s.len() {
274         Some(s.char_at(next))
275     } else {
276         None
277     }
278 }
279 pub fn nextnextch_is(rdr: &StringReader, c: char) -> bool {
280     nextnextch(rdr) == Some(c)
281 }
282
283 fn hex_digit_val(c: Option<char>) -> int {
284     let d = c.unwrap_or('\x00');
285
286     if in_range(c, '0', '9') { return (d as int) - ('0' as int); }
287     if in_range(c, 'a', 'f') { return (d as int) - ('a' as int) + 10; }
288     if in_range(c, 'A', 'F') { return (d as int) - ('A' as int) + 10; }
289     fail!();
290 }
291
292 pub fn is_whitespace(c: Option<char>) -> bool {
293     match c.unwrap_or('\x00') { // None can be null for now... it's not whitespace
294         ' ' | '\n' | '\t' | '\r' => true,
295         _ => false
296     }
297 }
298
299 fn in_range(c: Option<char>, lo: char, hi: char) -> bool {
300     match c {
301         Some(c) => lo <= c && c <= hi,
302         _ => false
303     }
304 }
305
306 fn is_dec_digit(c: Option<char>) -> bool { return in_range(c, '0', '9'); }
307
308 fn is_hex_digit(c: Option<char>) -> bool {
309     return in_range(c, '0', '9') || in_range(c, 'a', 'f') ||
310             in_range(c, 'A', 'F');
311 }
312
313 // EFFECT: eats whitespace and comments.
314 // returns a Some(sugared-doc-attr) if one exists, None otherwise.
315 fn consume_whitespace_and_comments(rdr: &mut StringReader)
316                                 -> Option<TokenAndSpan> {
317     while is_whitespace(rdr.curr) { bump(rdr); }
318     return consume_any_line_comment(rdr);
319 }
320
321 pub fn is_line_non_doc_comment(s: &str) -> bool {
322     s.starts_with("////")
323 }
324
325 // PRECONDITION: rdr.curr is not whitespace
326 // EFFECT: eats any kind of comment.
327 // returns a Some(sugared-doc-attr) if one exists, None otherwise
328 fn consume_any_line_comment(rdr: &mut StringReader)
329                          -> Option<TokenAndSpan> {
330     if rdr.curr_is('/') {
331         match nextch(rdr) {
332             Some('/') => {
333                 bump(rdr);
334                 bump(rdr);
335                 // line comments starting with "///" or "//!" are doc-comments
336                 if rdr.curr_is('/') || rdr.curr_is('!') {
337                     let start_bpos = rdr.pos - BytePos(3);
338                     while !rdr.curr_is('\n') && !is_eof(rdr) {
339                         bump(rdr);
340                     }
341                     let ret = with_str_from(rdr, start_bpos, |string| {
342                         // but comments with only more "/"s are not
343                         if !is_line_non_doc_comment(string) {
344                             Some(TokenAndSpan{
345                                 tok: token::DOC_COMMENT(str_to_ident(string)),
346                                 sp: codemap::mk_sp(start_bpos, rdr.pos)
347                             })
348                         } else {
349                             None
350                         }
351                     });
352
353                     if ret.is_some() {
354                         return ret;
355                     }
356                 } else {
357                     while !rdr.curr_is('\n') && !is_eof(rdr) { bump(rdr); }
358                 }
359                 // Restart whitespace munch.
360                 consume_whitespace_and_comments(rdr)
361             }
362             Some('*') => { bump(rdr); bump(rdr); consume_block_comment(rdr) }
363             _ => None
364         }
365     } else if rdr.curr_is('#') {
366         if nextch_is(rdr, '!') {
367
368             // Parse an inner attribute.
369             if nextnextch_is(rdr, '[') {
370                 return None;
371             }
372
373             // I guess this is the only way to figure out if
374             // we're at the beginning of the file...
375             let cmap = CodeMap::new();
376             cmap.files.borrow_mut().push(rdr.filemap.clone());
377             let loc = cmap.lookup_char_pos_adj(rdr.last_pos);
378             if loc.line == 1u && loc.col == CharPos(0u) {
379                 while !rdr.curr_is('\n') && !is_eof(rdr) { bump(rdr); }
380                 return consume_whitespace_and_comments(rdr);
381             }
382         }
383         None
384     } else {
385         None
386     }
387 }
388
389 pub fn is_block_non_doc_comment(s: &str) -> bool {
390     s.starts_with("/***")
391 }
392
393 // might return a sugared-doc-attr
394 fn consume_block_comment(rdr: &mut StringReader) -> Option<TokenAndSpan> {
395     // block comments starting with "/**" or "/*!" are doc-comments
396     let is_doc_comment = rdr.curr_is('*') || rdr.curr_is('!');
397     let start_bpos = rdr.pos - BytePos(if is_doc_comment {3} else {2});
398
399     let mut level: int = 1;
400     while level > 0 {
401         if is_eof(rdr) {
402             let msg = if is_doc_comment {
403                 ~"unterminated block doc-comment"
404             } else {
405                 ~"unterminated block comment"
406             };
407             fatal_span(rdr, start_bpos, rdr.last_pos, msg);
408         } else if rdr.curr_is('/') && nextch_is(rdr, '*') {
409             level += 1;
410             bump(rdr);
411             bump(rdr);
412         } else if rdr.curr_is('*') && nextch_is(rdr, '/') {
413             level -= 1;
414             bump(rdr);
415             bump(rdr);
416         } else {
417             bump(rdr);
418         }
419     }
420
421     let res = if is_doc_comment {
422         with_str_from(rdr, start_bpos, |string| {
423             // but comments with only "*"s between two "/"s are not
424             if !is_block_non_doc_comment(string) {
425                 Some(TokenAndSpan{
426                         tok: token::DOC_COMMENT(str_to_ident(string)),
427                         sp: codemap::mk_sp(start_bpos, rdr.pos)
428                     })
429             } else {
430                 None
431             }
432         })
433     } else {
434         None
435     };
436
437     // restart whitespace munch.
438     if res.is_some() { res } else { consume_whitespace_and_comments(rdr) }
439 }
440
441 fn scan_exponent(rdr: &mut StringReader, start_bpos: BytePos) -> Option<~str> {
442     // \x00 hits the `return None` case immediately, so this is fine.
443     let mut c = rdr.curr.unwrap_or('\x00');
444     let mut rslt = StrBuf::new();
445     if c == 'e' || c == 'E' {
446         rslt.push_char(c);
447         bump(rdr);
448         c = rdr.curr.unwrap_or('\x00');
449         if c == '-' || c == '+' {
450             rslt.push_char(c);
451             bump(rdr);
452         }
453         let exponent = scan_digits(rdr, 10u);
454         if exponent.len() > 0u {
455             rslt.push_str(exponent);
456             return Some(rslt.into_owned());
457         } else {
458             fatal_span(rdr, start_bpos, rdr.last_pos,
459                        ~"scan_exponent: bad fp literal");
460         }
461     } else { return None::<~str>; }
462 }
463
464 fn scan_digits(rdr: &mut StringReader, radix: uint) -> ~str {
465     let mut rslt = StrBuf::new();
466     loop {
467         let c = rdr.curr;
468         if c == Some('_') { bump(rdr); continue; }
469         match c.and_then(|cc| char::to_digit(cc, radix)) {
470           Some(_) => {
471             rslt.push_char(c.unwrap());
472             bump(rdr);
473           }
474           _ => return rslt.into_owned()
475         }
476     };
477 }
478
479 fn check_float_base(rdr: &mut StringReader, start_bpos: BytePos, last_bpos: BytePos,
480                     base: uint) {
481     match base {
482       16u => fatal_span(rdr, start_bpos, last_bpos,
483                       ~"hexadecimal float literal is not supported"),
484       8u => fatal_span(rdr, start_bpos, last_bpos,
485                      ~"octal float literal is not supported"),
486       2u => fatal_span(rdr, start_bpos, last_bpos,
487                      ~"binary float literal is not supported"),
488       _ => ()
489     }
490 }
491
492 fn scan_number(c: char, rdr: &mut StringReader) -> token::Token {
493     let mut num_str;
494     let mut base = 10u;
495     let mut c = c;
496     let mut n = nextch(rdr).unwrap_or('\x00');
497     let start_bpos = rdr.last_pos;
498     if c == '0' && n == 'x' {
499         bump(rdr);
500         bump(rdr);
501         base = 16u;
502     } else if c == '0' && n == 'o' {
503         bump(rdr);
504         bump(rdr);
505         base = 8u;
506     } else if c == '0' && n == 'b' {
507         bump(rdr);
508         bump(rdr);
509         base = 2u;
510     }
511     num_str = StrBuf::from_owned_str(scan_digits(rdr, base));
512     c = rdr.curr.unwrap_or('\x00');
513     nextch(rdr);
514     if c == 'u' || c == 'i' {
515         enum Result { Signed(ast::IntTy), Unsigned(ast::UintTy) }
516         let signed = c == 'i';
517         let mut tp = {
518             if signed { Signed(ast::TyI) }
519             else { Unsigned(ast::TyU) }
520         };
521         bump(rdr);
522         c = rdr.curr.unwrap_or('\x00');
523         if c == '8' {
524             bump(rdr);
525             tp = if signed { Signed(ast::TyI8) }
526                       else { Unsigned(ast::TyU8) };
527         }
528         n = nextch(rdr).unwrap_or('\x00');
529         if c == '1' && n == '6' {
530             bump(rdr);
531             bump(rdr);
532             tp = if signed { Signed(ast::TyI16) }
533                       else { Unsigned(ast::TyU16) };
534         } else if c == '3' && n == '2' {
535             bump(rdr);
536             bump(rdr);
537             tp = if signed { Signed(ast::TyI32) }
538                       else { Unsigned(ast::TyU32) };
539         } else if c == '6' && n == '4' {
540             bump(rdr);
541             bump(rdr);
542             tp = if signed { Signed(ast::TyI64) }
543                       else { Unsigned(ast::TyU64) };
544         }
545         if num_str.len() == 0u {
546             fatal_span(rdr, start_bpos, rdr.last_pos,
547                        ~"no valid digits found for number");
548         }
549         let parsed = match from_str_radix::<u64>(num_str.as_slice(),
550                                                  base as uint) {
551             Some(p) => p,
552             None => fatal_span(rdr, start_bpos, rdr.last_pos,
553                                ~"int literal is too large")
554         };
555
556         match tp {
557           Signed(t) => return token::LIT_INT(parsed as i64, t),
558           Unsigned(t) => return token::LIT_UINT(parsed, t)
559         }
560     }
561     let mut is_float = false;
562     if rdr.curr_is('.') && !(ident_start(nextch(rdr)) || nextch_is(rdr, '.')) {
563         is_float = true;
564         bump(rdr);
565         let dec_part = scan_digits(rdr, 10u);
566         num_str.push_char('.');
567         num_str.push_str(dec_part);
568     }
569     match scan_exponent(rdr, start_bpos) {
570       Some(ref s) => {
571         is_float = true;
572         num_str.push_str(*s);
573       }
574       None => ()
575     }
576
577     if rdr.curr_is('f') {
578         bump(rdr);
579         c = rdr.curr.unwrap_or('\x00');
580         n = nextch(rdr).unwrap_or('\x00');
581         if c == '3' && n == '2' {
582             bump(rdr);
583             bump(rdr);
584             check_float_base(rdr, start_bpos, rdr.last_pos, base);
585             return token::LIT_FLOAT(str_to_ident(num_str.into_owned()),
586                                     ast::TyF32);
587         } else if c == '6' && n == '4' {
588             bump(rdr);
589             bump(rdr);
590             check_float_base(rdr, start_bpos, rdr.last_pos, base);
591             return token::LIT_FLOAT(str_to_ident(num_str.into_owned()),
592                                     ast::TyF64);
593             /* FIXME (#2252): if this is out of range for either a
594             32-bit or 64-bit float, it won't be noticed till the
595             back-end.  */
596         } else {
597             fatal_span(rdr, start_bpos, rdr.last_pos,
598                        ~"expected `f32` or `f64` suffix");
599         }
600     }
601     if is_float {
602         check_float_base(rdr, start_bpos, rdr.last_pos, base);
603         return token::LIT_FLOAT_UNSUFFIXED(str_to_ident(
604                 num_str.into_owned()));
605     } else {
606         if num_str.len() == 0u {
607             fatal_span(rdr, start_bpos, rdr.last_pos,
608                        ~"no valid digits found for number");
609         }
610         let parsed = match from_str_radix::<u64>(num_str.as_slice(),
611                                                  base as uint) {
612             Some(p) => p,
613             None => fatal_span(rdr, start_bpos, rdr.last_pos,
614                                ~"int literal is too large")
615         };
616
617         debug!("lexing {} as an unsuffixed integer literal",
618                num_str.as_slice());
619         return token::LIT_INT_UNSUFFIXED(parsed as i64);
620     }
621 }
622
623 fn scan_numeric_escape(rdr: &mut StringReader, n_hex_digits: uint) -> char {
624     let mut accum_int = 0;
625     let mut i = n_hex_digits;
626     let start_bpos = rdr.last_pos;
627     while i != 0u && !is_eof(rdr) {
628         let n = rdr.curr;
629         if !is_hex_digit(n) {
630             fatal_span_char(rdr, rdr.last_pos, rdr.pos,
631                             ~"illegal character in numeric character escape",
632                             n.unwrap());
633         }
634         bump(rdr);
635         accum_int *= 16;
636         accum_int += hex_digit_val(n);
637         i -= 1u;
638     }
639     if i != 0 && is_eof(rdr) {
640         fatal_span(rdr, start_bpos, rdr.last_pos,
641                    ~"unterminated numeric character escape");
642     }
643
644     match char::from_u32(accum_int as u32) {
645         Some(x) => x,
646         None => fatal_span(rdr, start_bpos, rdr.last_pos,
647                            ~"illegal numeric character escape")
648     }
649 }
650
651 fn ident_start(c: Option<char>) -> bool {
652     let c = match c { Some(c) => c, None => return false };
653
654     (c >= 'a' && c <= 'z')
655         || (c >= 'A' && c <= 'Z')
656         || c == '_'
657         || (c > '\x7f' && char::is_XID_start(c))
658 }
659
660 fn ident_continue(c: Option<char>) -> bool {
661     let c = match c { Some(c) => c, None => return false };
662
663     (c >= 'a' && c <= 'z')
664         || (c >= 'A' && c <= 'Z')
665         || (c >= '0' && c <= '9')
666         || c == '_'
667         || (c > '\x7f' && char::is_XID_continue(c))
668 }
669
670 // return the next token from the string
671 // EFFECT: advances the input past that token
672 // EFFECT: updates the interner
673 fn next_token_inner(rdr: &mut StringReader) -> token::Token {
674     let c = rdr.curr;
675     if ident_start(c) && !nextch_is(rdr, '"') && !nextch_is(rdr, '#') {
676         // Note: r as in r" or r#" is part of a raw string literal,
677         // not an identifier, and is handled further down.
678
679         let start = rdr.last_pos;
680         while ident_continue(rdr.curr) {
681             bump(rdr);
682         }
683
684         return with_str_from(rdr, start, |string| {
685             if string == "_" {
686                 token::UNDERSCORE
687             } else {
688                 let is_mod_name = rdr.curr_is(':') && nextch_is(rdr, ':');
689
690                 // FIXME: perform NFKC normalization here. (Issue #2253)
691                 token::IDENT(str_to_ident(string), is_mod_name)
692             }
693         })
694     }
695     if is_dec_digit(c) {
696         return scan_number(c.unwrap(), rdr);
697     }
698     fn binop(rdr: &mut StringReader, op: token::BinOp) -> token::Token {
699         bump(rdr);
700         if rdr.curr_is('=') {
701             bump(rdr);
702             return token::BINOPEQ(op);
703         } else { return token::BINOP(op); }
704     }
705     match c.expect("next_token_inner called at EOF") {
706
707
708
709
710
711       // One-byte tokens.
712       ';' => { bump(rdr); return token::SEMI; }
713       ',' => { bump(rdr); return token::COMMA; }
714       '.' => {
715           bump(rdr);
716           return if rdr.curr_is('.') {
717               bump(rdr);
718               if rdr.curr_is('.') {
719                   bump(rdr);
720                   token::DOTDOTDOT
721               } else {
722                   token::DOTDOT
723               }
724           } else {
725               token::DOT
726           };
727       }
728       '(' => { bump(rdr); return token::LPAREN; }
729       ')' => { bump(rdr); return token::RPAREN; }
730       '{' => { bump(rdr); return token::LBRACE; }
731       '}' => { bump(rdr); return token::RBRACE; }
732       '[' => { bump(rdr); return token::LBRACKET; }
733       ']' => { bump(rdr); return token::RBRACKET; }
734       '@' => { bump(rdr); return token::AT; }
735       '#' => { bump(rdr); return token::POUND; }
736       '~' => { bump(rdr); return token::TILDE; }
737       ':' => {
738         bump(rdr);
739         if rdr.curr_is(':') {
740             bump(rdr);
741             return token::MOD_SEP;
742         } else { return token::COLON; }
743       }
744
745       '$' => { bump(rdr); return token::DOLLAR; }
746
747
748
749
750
751       // Multi-byte tokens.
752       '=' => {
753         bump(rdr);
754         if rdr.curr_is('=') {
755             bump(rdr);
756             return token::EQEQ;
757         } else if rdr.curr_is('>') {
758             bump(rdr);
759             return token::FAT_ARROW;
760         } else {
761             return token::EQ;
762         }
763       }
764       '!' => {
765         bump(rdr);
766         if rdr.curr_is('=') {
767             bump(rdr);
768             return token::NE;
769         } else { return token::NOT; }
770       }
771       '<' => {
772         bump(rdr);
773         match rdr.curr.unwrap_or('\x00') {
774           '=' => { bump(rdr); return token::LE; }
775           '<' => { return binop(rdr, token::SHL); }
776           '-' => {
777             bump(rdr);
778             match rdr.curr.unwrap_or('\x00') {
779               '>' => { bump(rdr); return token::DARROW; }
780               _ => { return token::LARROW; }
781             }
782           }
783           _ => { return token::LT; }
784         }
785       }
786       '>' => {
787         bump(rdr);
788         match rdr.curr.unwrap_or('\x00') {
789           '=' => { bump(rdr); return token::GE; }
790           '>' => { return binop(rdr, token::SHR); }
791           _ => { return token::GT; }
792         }
793       }
794       '\'' => {
795         // Either a character constant 'a' OR a lifetime name 'abc
796         bump(rdr);
797         let start = rdr.last_pos;
798
799         // the eof will be picked up by the final `'` check below
800         let mut c2 = rdr.curr.unwrap_or('\x00');
801         bump(rdr);
802
803         // If the character is an ident start not followed by another single
804         // quote, then this is a lifetime name:
805         if ident_start(Some(c2)) && !rdr.curr_is('\'') {
806             while ident_continue(rdr.curr) {
807                 bump(rdr);
808             }
809             let ident = with_str_from(rdr, start, |lifetime_name| {
810                 str_to_ident(lifetime_name)
811             });
812             let tok = &token::IDENT(ident, false);
813
814             if token::is_keyword(token::keywords::Self, tok) {
815                 fatal_span(rdr, start, rdr.last_pos,
816                            ~"invalid lifetime name: 'self is no longer a special lifetime");
817             } else if token::is_any_keyword(tok) &&
818                 !token::is_keyword(token::keywords::Static, tok) {
819                 fatal_span(rdr, start, rdr.last_pos,
820                            ~"invalid lifetime name");
821             } else {
822                 return token::LIFETIME(ident);
823             }
824         }
825
826         // Otherwise it is a character constant:
827         match c2 {
828             '\\' => {
829                 // '\X' for some X must be a character constant:
830                 let escaped = rdr.curr;
831                 let escaped_pos = rdr.last_pos;
832                 bump(rdr);
833                 match escaped {
834                     None => {}
835                     Some(e) => {
836                         c2 = match e {
837                             'n' => '\n',
838                             'r' => '\r',
839                             't' => '\t',
840                             '\\' => '\\',
841                             '\'' => '\'',
842                             '"' => '"',
843                             '0' => '\x00',
844                             'x' => scan_numeric_escape(rdr, 2u),
845                             'u' => scan_numeric_escape(rdr, 4u),
846                             'U' => scan_numeric_escape(rdr, 8u),
847                             c2 => {
848                                 fatal_span_char(rdr, escaped_pos, rdr.last_pos,
849                                                 ~"unknown character escape", c2)
850                             }
851                         }
852                     }
853                 }
854             }
855             '\t' | '\n' | '\r' | '\'' => {
856                 fatal_span_char(rdr, start, rdr.last_pos,
857                                 ~"character constant must be escaped", c2);
858             }
859             _ => {}
860         }
861         if !rdr.curr_is('\'') {
862             fatal_span_verbose(rdr,
863                                // Byte offsetting here is okay because the
864                                // character before position `start` is an
865                                // ascii single quote.
866                                start - BytePos(1),
867                                rdr.last_pos,
868                                ~"unterminated character constant");
869         }
870         bump(rdr); // advance curr past token
871         return token::LIT_CHAR(c2 as u32);
872       }
873       '"' => {
874         let mut accum_str = StrBuf::new();
875         let start_bpos = rdr.last_pos;
876         bump(rdr);
877         while !rdr.curr_is('"') {
878             if is_eof(rdr) {
879                 fatal_span(rdr, start_bpos, rdr.last_pos,
880                            ~"unterminated double quote string");
881             }
882
883             let ch = rdr.curr.unwrap();
884             bump(rdr);
885             match ch {
886               '\\' => {
887                 if is_eof(rdr) {
888                     fatal_span(rdr, start_bpos, rdr.last_pos,
889                            ~"unterminated double quote string");
890                 }
891
892                 let escaped = rdr.curr.unwrap();
893                 let escaped_pos = rdr.last_pos;
894                 bump(rdr);
895                 match escaped {
896                   'n' => accum_str.push_char('\n'),
897                   'r' => accum_str.push_char('\r'),
898                   't' => accum_str.push_char('\t'),
899                   '\\' => accum_str.push_char('\\'),
900                   '\'' => accum_str.push_char('\''),
901                   '"' => accum_str.push_char('"'),
902                   '\n' => consume_whitespace(rdr),
903                   '0' => accum_str.push_char('\x00'),
904                   'x' => {
905                     accum_str.push_char(scan_numeric_escape(rdr, 2u));
906                   }
907                   'u' => {
908                     accum_str.push_char(scan_numeric_escape(rdr, 4u));
909                   }
910                   'U' => {
911                     accum_str.push_char(scan_numeric_escape(rdr, 8u));
912                   }
913                   c2 => {
914                     fatal_span_char(rdr, escaped_pos, rdr.last_pos,
915                                     ~"unknown string escape", c2);
916                   }
917                 }
918               }
919               _ => accum_str.push_char(ch)
920             }
921         }
922         bump(rdr);
923         return token::LIT_STR(str_to_ident(accum_str.as_slice()));
924       }
925       'r' => {
926         let start_bpos = rdr.last_pos;
927         bump(rdr);
928         let mut hash_count = 0u;
929         while rdr.curr_is('#') {
930             bump(rdr);
931             hash_count += 1;
932         }
933
934         if is_eof(rdr) {
935             fatal_span(rdr, start_bpos, rdr.last_pos,
936                        ~"unterminated raw string");
937         } else if !rdr.curr_is('"') {
938             fatal_span_char(rdr, start_bpos, rdr.last_pos,
939                             ~"only `#` is allowed in raw string delimitation; \
940                               found illegal character",
941                             rdr.curr.unwrap());
942         }
943         bump(rdr);
944         let content_start_bpos = rdr.last_pos;
945         let mut content_end_bpos;
946         'outer: loop {
947             if is_eof(rdr) {
948                 fatal_span(rdr, start_bpos, rdr.last_pos,
949                            ~"unterminated raw string");
950             }
951             if rdr.curr_is('"') {
952                 content_end_bpos = rdr.last_pos;
953                 for _ in range(0, hash_count) {
954                     bump(rdr);
955                     if !rdr.curr_is('#') {
956                         continue 'outer;
957                     }
958                 }
959                 break;
960             }
961             bump(rdr);
962         }
963         bump(rdr);
964         let str_content = with_str_from_to(rdr,
965                                            content_start_bpos,
966                                            content_end_bpos,
967                                            str_to_ident);
968         return token::LIT_STR_RAW(str_content, hash_count);
969       }
970       '-' => {
971         if nextch_is(rdr, '>') {
972             bump(rdr);
973             bump(rdr);
974             return token::RARROW;
975         } else { return binop(rdr, token::MINUS); }
976       }
977       '&' => {
978         if nextch_is(rdr, '&') {
979             bump(rdr);
980             bump(rdr);
981             return token::ANDAND;
982         } else { return binop(rdr, token::AND); }
983       }
984       '|' => {
985         match nextch(rdr) {
986           Some('|') => { bump(rdr); bump(rdr); return token::OROR; }
987           _ => { return binop(rdr, token::OR); }
988         }
989       }
990       '+' => { return binop(rdr, token::PLUS); }
991       '*' => { return binop(rdr, token::STAR); }
992       '/' => { return binop(rdr, token::SLASH); }
993       '^' => { return binop(rdr, token::CARET); }
994       '%' => { return binop(rdr, token::PERCENT); }
995       c => {
996           fatal_span_char(rdr, rdr.last_pos, rdr.pos,
997                           ~"unknown start of token", c);
998       }
999     }
1000 }
1001
1002 fn consume_whitespace(rdr: &mut StringReader) {
1003     while is_whitespace(rdr.curr) && !is_eof(rdr) { bump(rdr); }
1004 }
1005
1006 #[cfg(test)]
1007 mod test {
1008     use super::*;
1009
1010     use codemap::{BytePos, CodeMap, Span};
1011     use diagnostic;
1012     use parse::token;
1013     use parse::token::{str_to_ident};
1014     use std::io::util;
1015
1016     fn mk_sh() -> diagnostic::SpanHandler {
1017         let emitter = diagnostic::EmitterWriter::new(~util::NullWriter);
1018         let handler = diagnostic::mk_handler(~emitter);
1019         diagnostic::mk_span_handler(handler, CodeMap::new())
1020     }
1021
1022     // open a string reader for the given string
1023     fn setup<'a>(span_handler: &'a diagnostic::SpanHandler,
1024                  teststr: ~str) -> StringReader<'a> {
1025         let fm = span_handler.cm.new_filemap(~"zebra.rs", teststr);
1026         new_string_reader(span_handler, fm)
1027     }
1028
1029     #[test] fn t1 () {
1030         let span_handler = mk_sh();
1031         let mut string_reader = setup(&span_handler,
1032             ~"/* my source file */ \
1033               fn main() { println!(\"zebra\"); }\n");
1034         let id = str_to_ident("fn");
1035         let tok1 = string_reader.next_token();
1036         let tok2 = TokenAndSpan{
1037             tok:token::IDENT(id, false),
1038             sp:Span {lo:BytePos(21),hi:BytePos(23),expn_info: None}};
1039         assert_eq!(tok1,tok2);
1040         // the 'main' id is already read:
1041         assert_eq!(string_reader.last_pos.clone(), BytePos(28));
1042         // read another token:
1043         let tok3 = string_reader.next_token();
1044         let tok4 = TokenAndSpan{
1045             tok:token::IDENT(str_to_ident("main"), false),
1046             sp:Span {lo:BytePos(24),hi:BytePos(28),expn_info: None}};
1047         assert_eq!(tok3,tok4);
1048         // the lparen is already read:
1049         assert_eq!(string_reader.last_pos.clone(), BytePos(29))
1050     }
1051
1052     // check that the given reader produces the desired stream
1053     // of tokens (stop checking after exhausting the expected vec)
1054     fn check_tokenization (mut string_reader: StringReader, expected: Vec<token::Token> ) {
1055         for expected_tok in expected.iter() {
1056             assert_eq!(&string_reader.next_token().tok, expected_tok);
1057         }
1058     }
1059
1060     // make the identifier by looking up the string in the interner
1061     fn mk_ident (id: &str, is_mod_name: bool) -> token::Token {
1062         token::IDENT (str_to_ident(id),is_mod_name)
1063     }
1064
1065     #[test] fn doublecolonparsing () {
1066         check_tokenization(setup(&mk_sh(), ~"a b"),
1067                            vec!(mk_ident("a",false),
1068                              mk_ident("b",false)));
1069     }
1070
1071     #[test] fn dcparsing_2 () {
1072         check_tokenization(setup(&mk_sh(), ~"a::b"),
1073                            vec!(mk_ident("a",true),
1074                              token::MOD_SEP,
1075                              mk_ident("b",false)));
1076     }
1077
1078     #[test] fn dcparsing_3 () {
1079         check_tokenization(setup(&mk_sh(), ~"a ::b"),
1080                            vec!(mk_ident("a",false),
1081                              token::MOD_SEP,
1082                              mk_ident("b",false)));
1083     }
1084
1085     #[test] fn dcparsing_4 () {
1086         check_tokenization(setup(&mk_sh(), ~"a:: b"),
1087                            vec!(mk_ident("a",true),
1088                              token::MOD_SEP,
1089                              mk_ident("b",false)));
1090     }
1091
1092     #[test] fn character_a() {
1093         assert_eq!(setup(&mk_sh(), ~"'a'").next_token().tok,
1094                    token::LIT_CHAR('a' as u32));
1095     }
1096
1097     #[test] fn character_space() {
1098         assert_eq!(setup(&mk_sh(), ~"' '").next_token().tok,
1099                    token::LIT_CHAR(' ' as u32));
1100     }
1101
1102     #[test] fn character_escaped() {
1103         assert_eq!(setup(&mk_sh(), ~"'\\n'").next_token().tok,
1104                    token::LIT_CHAR('\n' as u32));
1105     }
1106
1107     #[test] fn lifetime_name() {
1108         assert_eq!(setup(&mk_sh(), ~"'abc").next_token().tok,
1109                    token::LIFETIME(token::str_to_ident("abc")));
1110     }
1111
1112     #[test] fn raw_string() {
1113         assert_eq!(setup(&mk_sh(), ~"r###\"\"#a\\b\x00c\"\"###").next_token().tok,
1114                    token::LIT_STR_RAW(token::str_to_ident("\"#a\\b\x00c\""), 3));
1115     }
1116
1117     #[test] fn line_doc_comments() {
1118         assert!(!is_line_non_doc_comment("///"));
1119         assert!(!is_line_non_doc_comment("/// blah"));
1120         assert!(is_line_non_doc_comment("////"));
1121     }
1122
1123     #[test] fn nested_block_comments() {
1124         assert_eq!(setup(&mk_sh(), ~"/* /* */ */'a'").next_token().tok,
1125                    token::LIT_CHAR('a' as u32));
1126     }
1127
1128 }