]> git.lizzy.rs Git - rust.git/blob - src/libsyntax/parse/comments.rs
libsyntax: Fix errors arising from the automated `~[T]` conversion
[rust.git] / src / libsyntax / parse / comments.rs
1 // Copyright 2012-2014 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};
13 use diagnostic;
14 use parse::lexer::{is_whitespace, with_str_from, Reader};
15 use parse::lexer::{StringReader, bump, is_eof, nextch_is, TokenAndSpan};
16 use parse::lexer::{is_line_non_doc_comment, is_block_non_doc_comment};
17 use parse::lexer;
18 use parse::token;
19
20 use std::io;
21 use std::str;
22 use std::uint;
23 use std::vec_ng::Vec;
24
25 #[deriving(Clone, Eq)]
26 pub enum CommentStyle {
27     Isolated, // No code on either side of each line of the comment
28     Trailing, // Code exists to the left of the comment
29     Mixed, // Code before /* foo */ and after the comment
30     BlankLine, // Just a manual blank line "\n\n", for layout
31 }
32
33 #[deriving(Clone)]
34 pub struct Comment {
35     style: CommentStyle,
36     lines: Vec<~str> ,
37     pos: BytePos
38 }
39
40 pub fn is_doc_comment(s: &str) -> bool {
41     (s.starts_with("///") && !is_line_non_doc_comment(s)) ||
42     s.starts_with("//!") ||
43     (s.starts_with("/**") && !is_block_non_doc_comment(s)) ||
44     s.starts_with("/*!")
45 }
46
47 pub fn doc_comment_style(comment: &str) -> ast::AttrStyle {
48     assert!(is_doc_comment(comment));
49     if comment.starts_with("//!") || comment.starts_with("/*!") {
50         ast::AttrInner
51     } else {
52         ast::AttrOuter
53     }
54 }
55
56 pub fn strip_doc_comment_decoration(comment: &str) -> ~str {
57     /// remove whitespace-only lines from the start/end of lines
58     fn vertical_trim(lines: Vec<~str> ) -> Vec<~str> {
59         let mut i = 0u;
60         let mut j = lines.len();
61         // first line of all-stars should be omitted
62         if lines.len() > 0 && lines.get(0).chars().all(|c| c == '*') {
63             i += 1;
64         }
65         while i < j && lines.get(i).trim().is_empty() {
66             i += 1;
67         }
68         // like the first, a last line of all stars should be omitted
69         if j > i && lines.get(j - 1).chars().skip(1).all(|c| c == '*') {
70             j -= 1;
71         }
72         while j > i && lines.get(j - 1).trim().is_empty() {
73             j -= 1;
74         }
75         return lines.slice(i, j).iter().map(|x| (*x).clone()).collect();
76     }
77
78     /// remove a "[ \t]*\*" block from each line, if possible
79     fn horizontal_trim(lines: Vec<~str> ) -> Vec<~str> {
80         let mut i = uint::MAX;
81         let mut can_trim = true;
82         let mut first = true;
83         for line in lines.iter() {
84             for (j, c) in line.chars().enumerate() {
85                 if j > i || !"* \t".contains_char(c) {
86                     can_trim = false;
87                     break;
88                 }
89                 if c == '*' {
90                     if first {
91                         i = j;
92                         first = false;
93                     } else if i != j {
94                         can_trim = false;
95                     }
96                     break;
97                 }
98             }
99             if i > line.len() {
100                 can_trim = false;
101             }
102             if !can_trim {
103                 break;
104             }
105         }
106
107         if can_trim {
108             lines.map(|line| line.slice(i + 1, line.len()).to_owned())
109         } else {
110             lines
111         }
112     }
113
114     // one-line comments lose their prefix
115     static ONLINERS: &'static [&'static str] = &["///!", "///", "//!", "//"];
116     for prefix in ONLINERS.iter() {
117         if comment.starts_with(*prefix) {
118             return comment.slice_from(prefix.len()).to_owned();
119         }
120     }
121
122     if comment.starts_with("/*") {
123         let lines = comment.slice(3u, comment.len() - 2u)
124             .lines_any()
125             .map(|s| s.to_owned())
126             .collect::<Vec<~str> >();
127
128         let lines = vertical_trim(lines);
129         let lines = horizontal_trim(lines);
130
131         return lines.connect("\n");
132     }
133
134     fail!("not a doc-comment: {}", comment);
135 }
136
137 fn read_to_eol(rdr: &StringReader) -> ~str {
138     let mut val = ~"";
139     while !rdr.curr_is('\n') && !is_eof(rdr) {
140         val.push_char(rdr.curr.get().unwrap());
141         bump(rdr);
142     }
143     if rdr.curr_is('\n') { bump(rdr); }
144     return val;
145 }
146
147 fn read_one_line_comment(rdr: &StringReader) -> ~str {
148     let val = read_to_eol(rdr);
149     assert!((val[0] == '/' as u8 && val[1] == '/' as u8) ||
150                  (val[0] == '#' as u8 && val[1] == '!' as u8));
151     return val;
152 }
153
154 fn consume_non_eol_whitespace(rdr: &StringReader) {
155     while is_whitespace(rdr.curr.get()) && !rdr.curr_is('\n') &&
156             !is_eof(rdr) {
157         bump(rdr);
158     }
159 }
160
161 fn push_blank_line_comment(rdr: &StringReader, comments: &mut Vec<Comment> ) {
162     debug!(">>> blank-line comment");
163     let v: Vec<~str> = Vec::new();
164     comments.push(Comment {
165         style: BlankLine,
166         lines: v,
167         pos: rdr.last_pos.get(),
168     });
169 }
170
171 fn consume_whitespace_counting_blank_lines(rdr: &StringReader,
172                                            comments: &mut Vec<Comment> ) {
173     while is_whitespace(rdr.curr.get()) && !is_eof(rdr) {
174         if rdr.col.get() == CharPos(0u) && rdr.curr_is('\n') {
175             push_blank_line_comment(rdr, &mut *comments);
176         }
177         bump(rdr);
178     }
179 }
180
181
182 fn read_shebang_comment(rdr: &StringReader, code_to_the_left: bool,
183                                             comments: &mut Vec<Comment> ) {
184     debug!(">>> shebang comment");
185     let p = rdr.last_pos.get();
186     debug!("<<< shebang comment");
187     comments.push(Comment {
188         style: if code_to_the_left { Trailing } else { Isolated },
189         lines: vec!(read_one_line_comment(rdr)),
190         pos: p
191     });
192 }
193
194 fn read_line_comments(rdr: &StringReader, code_to_the_left: bool,
195                                           comments: &mut Vec<Comment> ) {
196     debug!(">>> line comments");
197     let p = rdr.last_pos.get();
198     let mut lines: Vec<~str> = Vec::new();
199     while rdr.curr_is('/') && nextch_is(rdr, '/') {
200         let line = read_one_line_comment(rdr);
201         debug!("{}", line);
202         if is_doc_comment(line) { // doc-comments are not put in comments
203             break;
204         }
205         lines.push(line);
206         consume_non_eol_whitespace(rdr);
207     }
208     debug!("<<< line comments");
209     if !lines.is_empty() {
210         comments.push(Comment {
211             style: if code_to_the_left { Trailing } else { Isolated },
212             lines: lines,
213             pos: p
214         });
215     }
216 }
217
218 // Returns None if the first col chars of s contain a non-whitespace char.
219 // Otherwise returns Some(k) where k is first char offset after that leading
220 // whitespace.  Note k may be outside bounds of s.
221 fn all_whitespace(s: &str, col: CharPos) -> Option<uint> {
222     let len = s.len();
223     let mut col = col.to_uint();
224     let mut cursor: uint = 0;
225     while col > 0 && cursor < len {
226         let r: str::CharRange = s.char_range_at(cursor);
227         if !r.ch.is_whitespace() {
228             return None;
229         }
230         cursor = r.next;
231         col -= 1;
232     }
233     return Some(cursor);
234 }
235
236 fn trim_whitespace_prefix_and_push_line(lines: &mut Vec<~str> ,
237                                         s: ~str, col: CharPos) {
238     let len = s.len();
239     let s1 = match all_whitespace(s, col) {
240         Some(col) => {
241             if col < len {
242                 s.slice(col, len).to_owned()
243             } else {  ~"" }
244         }
245         None => s,
246     };
247     debug!("pushing line: {}", s1);
248     lines.push(s1);
249 }
250
251 fn read_block_comment(rdr: &StringReader,
252                       code_to_the_left: bool,
253                       comments: &mut Vec<Comment> ) {
254     debug!(">>> block comment");
255     let p = rdr.last_pos.get();
256     let mut lines: Vec<~str> = Vec::new();
257     let col: CharPos = rdr.col.get();
258     bump(rdr);
259     bump(rdr);
260
261     let mut curr_line = ~"/*";
262
263     // doc-comments are not really comments, they are attributes
264     if (rdr.curr_is('*') && !nextch_is(rdr, '*')) || rdr.curr_is('!') {
265         while !(rdr.curr_is('*') && nextch_is(rdr, '/')) && !is_eof(rdr) {
266             curr_line.push_char(rdr.curr.get().unwrap());
267             bump(rdr);
268         }
269         if !is_eof(rdr) {
270             curr_line.push_str("*/");
271             bump(rdr);
272             bump(rdr);
273         }
274         if !is_block_non_doc_comment(curr_line) { return; }
275         assert!(!curr_line.contains_char('\n'));
276         lines.push(curr_line);
277     } else {
278         let mut level: int = 1;
279         while level > 0 {
280             debug!("=== block comment level {}", level);
281             if is_eof(rdr) {
282                 rdr.fatal(~"unterminated block comment");
283             }
284             if rdr.curr_is('\n') {
285                 trim_whitespace_prefix_and_push_line(&mut lines, curr_line,
286                                                      col);
287                 curr_line = ~"";
288                 bump(rdr);
289             } else {
290                 curr_line.push_char(rdr.curr.get().unwrap());
291                 if rdr.curr_is('/') && nextch_is(rdr, '*') {
292                     bump(rdr);
293                     bump(rdr);
294                     curr_line.push_char('*');
295                     level += 1;
296                 } else {
297                     if rdr.curr_is('*') && nextch_is(rdr, '/') {
298                         bump(rdr);
299                         bump(rdr);
300                         curr_line.push_char('/');
301                         level -= 1;
302                     } else { bump(rdr); }
303                 }
304             }
305         }
306         if curr_line.len() != 0 {
307             trim_whitespace_prefix_and_push_line(&mut lines, curr_line, col);
308         }
309     }
310
311     let mut style = if code_to_the_left { Trailing } else { Isolated };
312     consume_non_eol_whitespace(rdr);
313     if !is_eof(rdr) && !rdr.curr_is('\n') && lines.len() == 1u {
314         style = Mixed;
315     }
316     debug!("<<< block comment");
317     comments.push(Comment {style: style, lines: lines, pos: p});
318 }
319
320 fn peeking_at_comment(rdr: &StringReader) -> bool {
321     return (rdr.curr_is('/') && nextch_is(rdr, '/')) ||
322          (rdr.curr_is('/') && nextch_is(rdr, '*')) ||
323          (rdr.curr_is('#') && nextch_is(rdr, '!'));
324 }
325
326 fn consume_comment(rdr: &StringReader,
327                    code_to_the_left: bool,
328                    comments: &mut Vec<Comment> ) {
329     debug!(">>> consume comment");
330     if rdr.curr_is('/') && nextch_is(rdr, '/') {
331         read_line_comments(rdr, code_to_the_left, comments);
332     } else if rdr.curr_is('/') && nextch_is(rdr, '*') {
333         read_block_comment(rdr, code_to_the_left, comments);
334     } else if rdr.curr_is('#') && nextch_is(rdr, '!') {
335         read_shebang_comment(rdr, code_to_the_left, comments);
336     } else { fail!(); }
337     debug!("<<< consume comment");
338 }
339
340 #[deriving(Clone)]
341 pub struct Literal {
342     lit: ~str,
343     pos: BytePos
344 }
345
346 // it appears this function is called only from pprust... that's
347 // probably not a good thing.
348 pub fn gather_comments_and_literals(span_diagnostic:
349                                         @diagnostic::SpanHandler,
350                                     path: ~str,
351                                     srdr: &mut io::Reader)
352                                  -> (Vec<Comment> , Vec<Literal> ) {
353     let src = srdr.read_to_end().unwrap();
354     let src = str::from_utf8_owned(src).unwrap();
355     let cm = CodeMap::new();
356     let filemap = cm.new_filemap(path, src);
357     let rdr = lexer::new_low_level_string_reader(span_diagnostic, filemap);
358
359     let mut comments: Vec<Comment> = Vec::new();
360     let mut literals: Vec<Literal> = Vec::new();
361     let mut first_read: bool = true;
362     while !is_eof(&rdr) {
363         loop {
364             let mut code_to_the_left = !first_read;
365             consume_non_eol_whitespace(&rdr);
366             if rdr.curr_is('\n') {
367                 code_to_the_left = false;
368                 consume_whitespace_counting_blank_lines(&rdr, &mut comments);
369             }
370             while peeking_at_comment(&rdr) {
371                 consume_comment(&rdr, code_to_the_left, &mut comments);
372                 consume_whitespace_counting_blank_lines(&rdr, &mut comments);
373             }
374             break;
375         }
376
377
378         let bstart = rdr.last_pos.get();
379         rdr.next_token();
380         //discard, and look ahead; we're working with internal state
381         let TokenAndSpan {tok: tok, sp: sp} = rdr.peek();
382         if token::is_lit(&tok) {
383             with_str_from(&rdr, bstart, |s| {
384                 debug!("tok lit: {}", s);
385                 literals.push(Literal {lit: s.to_owned(), pos: sp.lo});
386             })
387         } else {
388             debug!("tok: {}", token::to_str(&tok));
389         }
390         first_read = false;
391     }
392
393     (comments, literals)
394 }
395
396 #[cfg(test)]
397 mod test {
398     use super::*;
399
400     #[test] fn test_block_doc_comment_1() {
401         let comment = "/**\n * Test \n **  Test\n *   Test\n*/";
402         let stripped = strip_doc_comment_decoration(comment);
403         assert_eq!(stripped, ~" Test \n*  Test\n   Test");
404     }
405
406     #[test] fn test_block_doc_comment_2() {
407         let comment = "/**\n * Test\n *  Test\n*/";
408         let stripped = strip_doc_comment_decoration(comment);
409         assert_eq!(stripped, ~" Test\n  Test");
410     }
411
412     #[test] fn test_block_doc_comment_3() {
413         let comment = "/**\n let a: *int;\n *a = 5;\n*/";
414         let stripped = strip_doc_comment_decoration(comment);
415         assert_eq!(stripped, ~" let a: *int;\n *a = 5;");
416     }
417
418     #[test] fn test_block_doc_comment_4() {
419         let comment = "/*******************\n test\n *********************/";
420         let stripped = strip_doc_comment_decoration(comment);
421         assert_eq!(stripped, ~" test");
422     }
423
424     #[test] fn test_line_doc_comment() {
425         let stripped = strip_doc_comment_decoration("/// test");
426         assert_eq!(stripped, ~" test");
427         let stripped = strip_doc_comment_decoration("///! test");
428         assert_eq!(stripped, ~" test");
429         let stripped = strip_doc_comment_decoration("// test");
430         assert_eq!(stripped, ~" test");
431         let stripped = strip_doc_comment_decoration("// test");
432         assert_eq!(stripped, ~" test");
433         let stripped = strip_doc_comment_decoration("///test");
434         assert_eq!(stripped, ~"test");
435         let stripped = strip_doc_comment_decoration("///!test");
436         assert_eq!(stripped, ~"test");
437         let stripped = strip_doc_comment_decoration("//test");
438         assert_eq!(stripped, ~"test");
439     }
440 }