diff --git a/org_mode_samples/comment/Makefile b/org_mode_samples/comment/Makefile new file mode 100644 index 00000000..c47a86c1 --- /dev/null +++ b/org_mode_samples/comment/Makefile @@ -0,0 +1,23 @@ +SHELL := bash +.ONESHELL: +.SHELLFLAGS := -eu -o pipefail -c +.DELETE_ON_ERROR: +MAKEFLAGS += --warn-undefined-variables +MAKEFLAGS += --no-builtin-rules +SRCFILES := $(wildcard *.org) +OUTFILES := $(patsubst %.org,%.tree.txt,$(SRCFILES)) + +ifeq ($(origin .RECIPEPREFIX), undefined) + $(error This Make does not support .RECIPEPREFIX. Please use GNU Make 4.0 or later) +endif +.RECIPEPREFIX = > + +.PHONY: all +all: $(OUTFILES) + +.PHONY: clean +clean: +> rm -rf $(OUTFILES) + +%.tree.txt: %.org ../common.el ../dump_org_ast.bash +> ../dump_org_ast.bash $< $@ diff --git a/org_mode_samples/comment/indented.org b/org_mode_samples/comment/indented.org new file mode 100644 index 00000000..b98916d5 --- /dev/null +++ b/org_mode_samples/comment/indented.org @@ -0,0 +1,8 @@ +# Comment + # indented line +# At the top of the file + + + +foo +# Another comment diff --git a/org_mode_samples/comment/multiline_comment.org b/org_mode_samples/comment/multiline_comment.org new file mode 100644 index 00000000..6c5fdeee --- /dev/null +++ b/org_mode_samples/comment/multiline_comment.org @@ -0,0 +1,5 @@ +# Comment +# +# At the top of the file +foo +# Another comment diff --git a/org_mode_samples/comment/require_whitespace_after_hash.org b/org_mode_samples/comment/require_whitespace_after_hash.org new file mode 100644 index 00000000..d06c06e1 --- /dev/null +++ b/org_mode_samples/comment/require_whitespace_after_hash.org @@ -0,0 +1,3 @@ +# Comment line +#not a comment +# Comment again diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 2520af29..89f600a5 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -1,5 +1,6 @@ use super::sexp::Token; use crate::compare::util::get_offsets; +use crate::parser::Comment; use crate::parser::Document; use crate::parser::DocumentElement; use crate::parser::Element; @@ -216,6 +217,7 @@ fn compare_element<'s>( Element::PlainList(obj) => compare_plain_list(source, emacs, obj), Element::GreaterBlock(obj) => compare_greater_block(source, emacs, obj), Element::FootnoteDefinition(obj) => compare_footnote_definition(source, emacs, obj), + Element::Comment(obj) => compare_comment(source, emacs, obj), } } @@ -453,3 +455,44 @@ fn compare_footnote_definition<'s>( children: child_status, }) } + +fn compare_comment<'s>( + source: &'s str, + emacs: &'s Token<'s>, + rust: &'s Comment<'s>, +) -> Result> { + let children = emacs.as_list()?; + let first_child = children + .first() + .ok_or("Should have at least one child.")? + .as_atom()?; + if first_child != "comment" { + return Err("Comment should correspond to a comment cell.".into()); + } + let mut child_status = Vec::new(); + let mut this_status = DiffStatus::Good; + + let attributes_child = children + .iter() + .nth(1) + .ok_or("Should have an attributes child.")?; + let attributes_map = attributes_child.as_map()?; + let begin = attributes_map + .get(":begin") + .ok_or("Missing :begin attribute.")? + .as_atom()?; + let end = attributes_map + .get(":end") + .ok_or("Missing :end attribute.")? + .as_atom()?; + let (rust_begin, rust_end) = get_offsets(source, rust); + if (rust_begin + 1).to_string() != begin || (rust_end + 1).to_string() != end { + this_status = DiffStatus::Bad; + } + + Ok(DiffResult { + status: this_status, + name: "comment".to_owned(), + children: child_status, + }) +} diff --git a/src/parser/comment.rs b/src/parser/comment.rs new file mode 100644 index 00000000..34c2ee0a --- /dev/null +++ b/src/parser/comment.rs @@ -0,0 +1,76 @@ +use nom::branch::alt; +use nom::bytes::complete::is_not; +use nom::bytes::complete::tag; +use nom::character::complete::line_ending; +use nom::character::complete::space0; +use nom::character::complete::space1; +use nom::combinator::eof; +use nom::combinator::not; +use nom::combinator::opt; +use nom::multi::many0; +use nom::sequence::preceded; +use nom::sequence::tuple; + +use super::util::get_consumed; +use super::Context; +use crate::parser::error::Res; +use crate::parser::parser_with_context::parser_with_context; +use crate::parser::util::exit_matcher_parser; +use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting; +use crate::parser::util::start_of_line; +use crate::parser::Comment; + +#[tracing::instrument(ret, level = "debug")] +pub fn comment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Comment<'s>> { + let comment_line_matcher = parser_with_context!(comment_line)(context); + let exit_matcher = parser_with_context!(exit_matcher_parser)(context); + let (remaining, first_line) = comment_line_matcher(input)?; + let (remaining, remaining_lines) = + many0(preceded(not(exit_matcher), comment_line_matcher))(remaining)?; + + let (remaining, _trailing_ws) = + maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; + + let source = get_consumed(input, remaining); + Ok((remaining, Comment { source })) +} + +#[tracing::instrument(ret, level = "debug")] +fn comment_line<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { + start_of_line(context, input)?; + let (remaining, _indent) = space0(input)?; + let (remaining, (_hash, _leading_whitespace_and_content, _line_ending)) = + tuple((tag("#"), opt(tuple((space1, is_not("\r\n")))), alt((line_ending, eof))))(remaining)?; + let source = get_consumed(input, remaining); + Ok((remaining, source)) +} + +#[cfg(test)] +mod tests { + use crate::parser::parser_context::ContextElement; + use crate::parser::parser_context::ContextTree; + use crate::parser::parser_with_context::parser_with_context; + + use super::*; + + #[test] + fn require_space_after_hash() { + let input = "# Comment line +#not a comment +# Comment again"; + let initial_context: ContextTree<'_, '_> = ContextTree::new(); + let document_context = + initial_context.with_additional_node(ContextElement::DocumentRoot(input)); + let comment_matcher = + parser_with_context!(comment)(&document_context); + let (remaining, first_comment) = + comment_matcher(input).expect("Parse first comment"); + assert_eq!(remaining, r#"#not a comment +# Comment again"#); + assert_eq!( + first_comment.source, + "# Comment line +" + ); + } +} diff --git a/src/parser/element.rs b/src/parser/element.rs index 9d2778bf..cb7cf19a 100644 --- a/src/parser/element.rs +++ b/src/parser/element.rs @@ -1,15 +1,17 @@ -use super::PlainListItem; +use super::comment::comment; use super::error::Res; use super::footnote_definition::footnote_definition; use super::greater_block::greater_block; use super::greater_element::FootnoteDefinition; use super::greater_element::GreaterBlock; use super::greater_element::PlainList; +use super::lesser_element::Comment; use super::lesser_element::Paragraph; use super::paragraph::paragraph; use super::plain_list::plain_list; use super::source::Source; use super::Context; +use super::PlainListItem; use crate::parser::parser_with_context::parser_with_context; use nom::branch::alt; use nom::combinator::map; @@ -20,6 +22,7 @@ pub enum Element<'s> { PlainList(PlainList<'s>), GreaterBlock(GreaterBlock<'s>), FootnoteDefinition(FootnoteDefinition<'s>), + Comment(Comment<'s>), } impl<'s> Source<'s> for Element<'s> { @@ -29,6 +32,7 @@ impl<'s> Source<'s> for Element<'s> { Element::PlainList(obj) => obj.source, Element::GreaterBlock(obj) => obj.source, Element::FootnoteDefinition(obj) => obj.source, + Element::Comment(obj) => obj.source, } } } @@ -63,6 +67,12 @@ impl<'s> Source<'s> for FootnoteDefinition<'s> { } } +impl<'s> Source<'s> for Comment<'s> { + fn get_source(&'s self) -> &'s str { + self.source + } +} + #[tracing::instrument(ret, level = "debug")] pub fn element<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Element<'s>> { let non_paragraph_matcher = parser_with_context!(non_paragraph_element)(context); @@ -81,9 +91,11 @@ pub fn non_paragraph_element<'r, 's>( let plain_list_matcher = parser_with_context!(plain_list)(context); let greater_block_matcher = parser_with_context!(greater_block)(context); let footnote_definition_matcher = parser_with_context!(footnote_definition)(context); + let comment_matcher = parser_with_context!(comment)(context); alt(( map(plain_list_matcher, Element::PlainList), map(greater_block_matcher, Element::GreaterBlock), map(footnote_definition_matcher, Element::FootnoteDefinition), + map(comment_matcher, Element::Comment), ))(input) } diff --git a/src/parser/lesser_element.rs b/src/parser/lesser_element.rs index 5abc4b38..110e647f 100644 --- a/src/parser/lesser_element.rs +++ b/src/parser/lesser_element.rs @@ -5,3 +5,8 @@ pub struct Paragraph<'s> { pub source: &'s str, pub children: Vec>, } + +#[derive(Debug)] +pub struct Comment<'s> { + pub source: &'s str, +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 169d5eac..c6dae3e6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,3 +1,4 @@ +mod comment; mod document; mod element; mod error; @@ -24,6 +25,7 @@ pub use greater_element::FootnoteDefinition; pub use greater_element::GreaterBlock; pub use greater_element::PlainList; pub use greater_element::PlainListItem; +pub use lesser_element::Comment; pub use lesser_element::Paragraph; pub use source::Source; type Context<'r, 's> = &'r parser_context::ContextTree<'r, 's>;