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.
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.
12 use codemap::{BytePos, CharPos, CodeMap, Pos};
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};
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
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)) ||
47 pub fn doc_comment_style(comment: &str) -> ast::AttrStyle {
48 assert!(is_doc_comment(comment));
49 if comment.starts_with("//!") || comment.starts_with("/*!") {
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> {
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 == '*') {
65 while i < j && lines.get(i).trim().is_empty() {
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 == '*') {
72 while j > i && lines.get(j - 1).trim().is_empty() {
75 return lines.slice(i, j).iter().map(|x| (*x).clone()).collect();
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;
83 for line in lines.iter() {
84 for (j, c) in line.chars().enumerate() {
85 if j > i || !"* \t".contains_char(c) {
108 lines.map(|line| line.slice(i + 1, line.len()).to_owned())
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();
122 if comment.starts_with("/*") {
123 let lines = comment.slice(3u, comment.len() - 2u)
125 .map(|s| s.to_owned())
126 .collect::<Vec<~str> >();
128 let lines = vertical_trim(lines);
129 let lines = horizontal_trim(lines);
131 return lines.connect("\n");
134 fail!("not a doc-comment: {}", comment);
137 fn read_to_eol(rdr: &StringReader) -> ~str {
139 while !rdr.curr_is('\n') && !is_eof(rdr) {
140 val.push_char(rdr.curr.get().unwrap());
143 if rdr.curr_is('\n') { bump(rdr); }
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));
154 fn consume_non_eol_whitespace(rdr: &StringReader) {
155 while is_whitespace(rdr.curr.get()) && !rdr.curr_is('\n') &&
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 {
167 pos: rdr.last_pos.get(),
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);
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)),
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);
202 if is_doc_comment(line) { // doc-comments are not put in comments
206 consume_non_eol_whitespace(rdr);
208 debug!("<<< line comments");
209 if !lines.is_empty() {
210 comments.push(Comment {
211 style: if code_to_the_left { Trailing } else { Isolated },
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> {
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() {
236 fn trim_whitespace_prefix_and_push_line(lines: &mut Vec<~str> ,
237 s: ~str, col: CharPos) {
239 let s1 = match all_whitespace(s, col) {
242 s.slice(col, len).to_owned()
247 debug!("pushing line: {}", s1);
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();
261 let mut curr_line = ~"/*";
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());
270 curr_line.push_str("*/");
274 if !is_block_non_doc_comment(curr_line) { return; }
275 assert!(!curr_line.contains_char('\n'));
276 lines.push(curr_line);
278 let mut level: int = 1;
280 debug!("=== block comment level {}", level);
282 rdr.fatal(~"unterminated block comment");
284 if rdr.curr_is('\n') {
285 trim_whitespace_prefix_and_push_line(&mut lines, curr_line,
290 curr_line.push_char(rdr.curr.get().unwrap());
291 if rdr.curr_is('/') && nextch_is(rdr, '*') {
294 curr_line.push_char('*');
297 if rdr.curr_is('*') && nextch_is(rdr, '/') {
300 curr_line.push_char('/');
302 } else { bump(rdr); }
306 if curr_line.len() != 0 {
307 trim_whitespace_prefix_and_push_line(&mut lines, curr_line, col);
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 {
316 debug!("<<< block comment");
317 comments.push(Comment {style: style, lines: lines, pos: p});
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, '!'));
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);
337 debug!("<<< consume comment");
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,
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);
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) {
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);
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);
378 let bstart = rdr.last_pos.get();
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});
388 debug!("tok: {}", token::to_str(&tok));
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");
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");
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;");
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");
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");