Parse doc comments

This commit is contained in:
Richard Feldman 2020-05-23 21:42:30 -04:00
parent 4d8cbc4811
commit 3dac73d8d0
5 changed files with 138 additions and 67 deletions

View file

@ -407,7 +407,7 @@ pub fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool {
has_at_least_one_newline = true; has_at_least_one_newline = true;
} }
} }
CommentOrNewline::LineComment(_) => {} CommentOrNewline::LineComment(_) | CommentOrNewline::DocComment(_) => {}
} }
} }

View file

@ -47,6 +47,11 @@ where
LineComment(comment) => { LineComment(comment) => {
fmt_comment(buf, comment, indent); fmt_comment(buf, comment, indent);
encountered_comment = true;
}
DocComment(docs) => {
fmt_docs(buf, docs, indent);
encountered_comment = true; encountered_comment = true;
} }
} }
@ -70,12 +75,16 @@ where
buf.push('#'); buf.push('#');
buf.push_str(comment); buf.push_str(comment);
} }
DocComment(docs) => {
buf.push_str("##");
buf.push_str(docs);
}
} }
match iter.peek() { match iter.peek() {
None => {} None => {}
Some(next_space) => match next_space { Some(next_space) => match next_space {
Newline => {} Newline => {}
LineComment(_) => { LineComment(_) | DocComment(_) => {
newline(buf, indent); newline(buf, indent);
} }
}, },
@ -96,6 +105,9 @@ where
LineComment(comment) => { LineComment(comment) => {
fmt_comment(buf, comment, indent); fmt_comment(buf, comment, indent);
} }
DocComment(docs) => {
fmt_docs(buf, docs, indent);
}
} }
} }
} }
@ -107,9 +119,17 @@ fn fmt_comment<'a>(buf: &mut String<'a>, comment: &'a str, indent: u16) {
newline(buf, indent); newline(buf, indent);
} }
fn fmt_docs<'a>(buf: &mut String<'a>, docs: &'a str, indent: u16) {
buf.push_str("##");
buf.push_str(docs);
newline(buf, indent);
}
pub fn is_comment<'a>(space: &'a CommentOrNewline<'a>) -> bool { pub fn is_comment<'a>(space: &'a CommentOrNewline<'a>) -> bool {
match space { match space {
CommentOrNewline::Newline => false, CommentOrNewline::Newline => false,
CommentOrNewline::LineComment(_) => true, CommentOrNewline::LineComment(_) => true,
CommentOrNewline::DocComment(_) => true,
} }
} }

View file

@ -1,6 +1,5 @@
use bumpalo::Bump; use bumpalo::Bump;
use roc_builtins::std::Mode; use roc_builtins::std::{Mode, StdLib};
use roc_builtins::std::StdLib;
use roc_can::constraint::Constraint; use roc_can::constraint::Constraint;
use roc_can::def::Declaration; use roc_can::def::Declaration;
use roc_can::module::{canonicalize_module_defs, ModuleOutput}; use roc_can::module::{canonicalize_module_defs, ModuleOutput};

View file

@ -294,6 +294,7 @@ pub enum AssignedField<'a, Val> {
pub enum CommentOrNewline<'a> { pub enum CommentOrNewline<'a> {
Newline, Newline,
LineComment(&'a str), LineComment(&'a str),
DocComment(&'a str),
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]

View file

@ -202,6 +202,13 @@ pub fn space1<'a>(min_indent: u16) -> impl Parser<'a, &'a [CommentOrNewline<'a>]
spaces(true, min_indent) spaces(true, min_indent)
} }
#[derive(Debug, Clone, Copy)]
enum LineState {
Normal,
Comment,
DocComment,
}
#[inline(always)] #[inline(always)]
fn spaces<'a>( fn spaces<'a>(
require_at_least_one: bool, require_at_least_one: bool,
@ -213,85 +220,129 @@ fn spaces<'a>(
let mut space_list = Vec::new_in(arena); let mut space_list = Vec::new_in(arena);
let mut chars_parsed = 0; let mut chars_parsed = 0;
let mut comment_line_buf = String::new_in(arena); let mut comment_line_buf = String::new_in(arena);
let mut is_parsing_comment = false; let mut line_state = LineState::Normal;
let mut state = state; let mut state = state;
let mut any_newlines = false; let mut any_newlines = false;
for ch in chars { for ch in chars {
chars_parsed += 1; chars_parsed += 1;
if is_parsing_comment { match line_state {
match ch { LineState::Normal => {
' ' => { match ch {
// If we're in a line comment, this won't affect indentation anyway. ' ' => {
state = state.advance_without_indenting(1)?; // Don't check indentation here; it might not be enough
// indentation yet, but maybe it will be after more spaces happen!
state = state.advance_spaces(1)?;
}
'\n' => {
// No need to check indentation because we're about to reset it anyway.
state = state.newline()?;
comment_line_buf.push(ch); // Newlines only get added to the list when they're outside comments.
} space_list.push(Newline);
'\n' => {
state = state.newline()?;
// This was a newline, so end this line comment. any_newlines = true;
space_list.push(LineComment(comment_line_buf.into_bump_str())); }
comment_line_buf = String::new_in(arena); '#' => {
// Check indentation to make sure we were indented enough
// before this comment began.
state = state
.check_indent(min_indent)
.map_err(|(fail, _)| (fail, original_state.clone()))?
.advance_without_indenting(1)?;
is_parsing_comment = false; // We're now parsing a line comment!
} line_state = LineState::Comment;
nonblank => { }
// Chars can have btye lengths of more than 1! nonblank => {
state = state.advance_without_indenting(nonblank.len_utf8())?; return if require_at_least_one && chars_parsed <= 1 {
// We've parsed 1 char and it was not a space,
// but we require parsing at least one space!
Err(unexpected(nonblank, 0, state.clone(), state.attempting))
} else {
// First make sure we were indented enough!
//
// (We only do this if we've encountered any newlines.
// Otherwise, we assume indentation is already correct.
// It's actively important for correctness that we skip
// this check if there are no newlines, because otherwise
// we would have false positives for single-line defs.)
if any_newlines {
state = state
.check_indent(min_indent)
.map_err(|(fail, _)| (fail, original_state))?;
}
comment_line_buf.push(nonblank); Ok((space_list.into_bump_slice(), state))
};
}
} }
} }
} else { LineState::Comment => {
match ch { match ch {
' ' => { ' ' => {
// Don't check indentation here; it might not be enough // If we're in a line comment, this won't affect indentation anyway.
// indentation yet, but maybe it will be after more spaces happen! state = state.advance_without_indenting(1)?;
state = state.advance_spaces(1)?;
}
'\n' => {
// No need to check indentation because we're about to reset it anyway.
state = state.newline()?;
// Newlines only get added to the list when they're outside comments. match comment_line_buf.chars().next() {
space_list.push(Newline); Some('#') => {
// This is a comment begining with `## ` - that is,
// a doc comment.
//
// (The space is important; otherwise, this is not
// a doc comment, but rather something like a
// big separator block, e.g. ############)
line_state = LineState::DocComment;
any_newlines = true; // This is now the beginning of the doc comment.
} comment_line_buf.clear();
'#' => { }
// Check indentation to make sure we were indented enough _ => {
// before this comment began. comment_line_buf.push(ch);
state = state }
.check_indent(min_indent)
.map_err(|(fail, _)| (fail, original_state.clone()))?
.advance_without_indenting(1)?;
// We're now parsing a line comment!
is_parsing_comment = true;
}
nonblank => {
return if require_at_least_one && chars_parsed <= 1 {
// We've parsed 1 char and it was not a space,
// but we require parsing at least one space!
Err(unexpected(nonblank, 0, state.clone(), state.attempting))
} else {
// First make sure we were indented enough!
//
// (We only do this if we've encountered any newlines.
// Otherwise, we assume indentation is already correct.
// It's actively important for correctness that we skip
// this check if there are no newlines, because otherwise
// we would have false positives for single-line defs.)
if any_newlines {
state = state
.check_indent(min_indent)
.map_err(|(fail, _)| (fail, original_state))?;
} }
}
'\n' => {
state = state.newline()?;
Ok((space_list.into_bump_slice(), state)) // This was a newline, so end this line comment.
}; space_list.push(LineComment(comment_line_buf.into_bump_str()));
comment_line_buf = String::new_in(arena);
line_state = LineState::Normal;
}
nonblank => {
// Chars can have btye lengths of more than 1!
state = state.advance_without_indenting(nonblank.len_utf8())?;
comment_line_buf.push(nonblank);
}
}
}
LineState::DocComment => {
match ch {
' ' => {
// If we're in a doc comment, this won't affect indentation anyway.
state = state.advance_without_indenting(1)?;
comment_line_buf.push(ch);
}
'\n' => {
state = state.newline()?;
// This was a newline, so end this doc comment.
space_list.push(DocComment(comment_line_buf.into_bump_str()));
comment_line_buf = String::new_in(arena);
line_state = LineState::Normal;
}
nonblank => {
// Chars can have btye lengths of more than 1!
state = state.advance_without_indenting(nonblank.len_utf8())?;
comment_line_buf.push(nonblank);
}
} }
} }
} }