diff --git a/Makefile b/Makefile index dfad1d3..0728dd8 100644 --- a/Makefile +++ b/Makefile @@ -6,9 +6,14 @@ MAKEFLAGS += --warn-undefined-variables MAKEFLAGS += --no-builtin-rules TESTJOBS := 4 OS:=$(shell uname -s) +RELEASEFLAGS := ifeq ($(OS),Linux) TESTJOBS:=$(shell nproc) + RELEASEFLAGS=--target x86_64-unknown-linux-musl +endif +ifeq ($(OS),FreeBSD) + TESTJOBS:=$(shell sysctl -n hw.ncpu) endif ifeq ($(origin .RECIPEPREFIX), undefined) @@ -22,7 +27,7 @@ build: .PHONY: release release: -> cargo build --release --target x86_64-unknown-linux-musl +> cargo build --release $(RELEASEFLAGS) .PHONY: clean clean: diff --git a/build.rs b/build.rs index 5994fbd..94c9502 100644 --- a/build.rs +++ b/build.rs @@ -76,8 +76,7 @@ fn is_expect_fail(name: &str) -> Option<&str> { "element_container_priority_footnote_definition_dynamic_block" => Some("Keyword needs to be implemented."), "element_container_priority_greater_block_dynamic_block" => Some("Keyword needs to be implemented."), "element_container_priority_section_dynamic_block" => Some("Keyword needs to be implemented."), - "exit_matcher_investigation_table_list" => Some("Table needs to be implemented."), - "element_container_priority_readme" => Some("Table needs to be implemented."), + "element_container_priority_readme" => Some("A lot needs to be implemented."), _ => None, } } diff --git a/org_mode_samples/table/simple.org b/org_mode_samples/table/simple.org new file mode 100644 index 0000000..8f7fcaf --- /dev/null +++ b/org_mode_samples/table/simple.org @@ -0,0 +1,3 @@ +| foo | bar | +|-----+-------| +| baz | lorem | diff --git a/scripts/run_integration_test.bash b/scripts/run_integration_test.bash index f1feb0f..9185c65 100755 --- a/scripts/run_integration_test.bash +++ b/scripts/run_integration_test.bash @@ -21,5 +21,5 @@ function get_test_names { } get_test_names "$@" | while read test; do - (cd "$DIR/../" && cargo test --no-fail-fast --test test_loader "$test") + (cd "$DIR/../" && cargo test --no-fail-fast --test test_loader "$test" -- --show-output) done diff --git a/src/compare/diff.rs b/src/compare/diff.rs index 4920fb3..11975d2 100644 --- a/src/compare/diff.rs +++ b/src/compare/diff.rs @@ -14,6 +14,9 @@ use crate::parser::PlainList; use crate::parser::PlainListItem; use crate::parser::PropertyDrawer; use crate::parser::Section; +use crate::parser::Table; +use crate::parser::TableCell; +use crate::parser::TableRow; use crate::DynamicBlock; #[derive(Debug)] @@ -198,6 +201,7 @@ fn compare_element<'s>( Element::Comment(obj) => compare_comment(source, emacs, obj), Element::Drawer(obj) => compare_drawer(source, emacs, obj), Element::PropertyDrawer(obj) => compare_property_drawer(source, emacs, obj), + Element::Table(obj) => compare_table(source, emacs, obj), } } @@ -459,3 +463,88 @@ fn compare_property_drawer<'s>( children: child_status, }) } + +fn compare_table<'s>( + source: &'s str, + emacs: &'s Token<'s>, + rust: &'s Table<'s>, +) -> Result> { + let children = emacs.as_list()?; + let mut child_status = Vec::new(); + let mut this_status = DiffStatus::Good; + let emacs_name = "table"; + if assert_name(emacs, emacs_name).is_err() { + this_status = DiffStatus::Bad; + } + + if assert_bounds(source, emacs, rust).is_err() { + this_status = DiffStatus::Bad; + } + + for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { + child_status.push(compare_table_row(source, emacs_child, rust_child)?); + } + + Ok(DiffResult { + status: this_status, + name: emacs_name.to_owned(), + message: None, + children: child_status, + }) +} + +fn compare_table_row<'s>( + source: &'s str, + emacs: &'s Token<'s>, + rust: &'s TableRow<'s>, +) -> Result> { + let children = emacs.as_list()?; + let mut child_status = Vec::new(); + let mut this_status = DiffStatus::Good; + let emacs_name = "table-row"; + if assert_name(emacs, emacs_name).is_err() { + this_status = DiffStatus::Bad; + } + + if assert_bounds(source, emacs, rust).is_err() { + this_status = DiffStatus::Bad; + } + + for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) { + child_status.push(compare_table_cell(source, emacs_child, rust_child)?); + } + + Ok(DiffResult { + status: this_status, + name: emacs_name.to_owned(), + message: None, + children: child_status, + }) +} + +fn compare_table_cell<'s>( + source: &'s str, + emacs: &'s Token<'s>, + rust: &'s TableCell<'s>, +) -> Result> { + let children = emacs.as_list()?; + let mut child_status = Vec::new(); + let mut this_status = DiffStatus::Good; + let emacs_name = "table-cell"; + if assert_name(emacs, emacs_name).is_err() { + this_status = DiffStatus::Bad; + } + + if assert_bounds(source, emacs, rust).is_err() { + this_status = DiffStatus::Bad; + } + + for (emacs_child, rust_child) in children.iter().skip(2).zip(rust.children.iter()) {} + + Ok(DiffResult { + status: this_status, + name: emacs_name.to_owned(), + message: None, + children: child_status, + }) +} diff --git a/src/init_tracing.rs b/src/init_tracing.rs index 171de8d..3a89c49 100644 --- a/src/init_tracing.rs +++ b/src/init_tracing.rs @@ -3,7 +3,7 @@ use tracing_subscriber::util::SubscriberInitExt; use tracing_subscriber::EnvFilter; pub fn init_telemetry() -> Result<(), Box> { - let env_filter = EnvFilter::try_from_default_env().unwrap_or(EnvFilter::new("WARN")); + // let env_filter = EnvFilter::try_from_default_env().unwrap_or(EnvFilter::new("warn")); // let stdout = tracing_subscriber::fmt::Layer::new() // .pretty() @@ -20,7 +20,7 @@ pub fn init_telemetry() -> Result<(), Box> { let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer); tracing_subscriber::registry() - .with(env_filter) + // .with(env_filter) .with(opentelemetry) // .with(stdout) .try_init()?; diff --git a/src/lib.rs b/src/lib.rs index fbcf791..bea65b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ #![feature(round_char_boundary)] #![feature(exit_status_error)] -mod parser; mod compare; -pub use parser::*; +mod parser; +pub use compare::compare_document; pub use compare::emacs_parse_org_document; pub use compare::sexp; -pub use compare::compare_document; +pub use parser::*; diff --git a/src/main.rs b/src/main.rs index 6201ac9..f82e263 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,8 @@ #![feature(round_char_boundary)] use crate::init_tracing::init_telemetry; use crate::init_tracing::shutdown_telemetry; -use crate::parser::document; +use ::organic::document; mod init_tracing; -mod parser; const TEST_DOC: &'static str = include_str!("../toy_language.txt"); diff --git a/src/parser/comment.rs b/src/parser/comment.rs index 92f79f6..558af0b 100644 --- a/src/parser/comment.rs +++ b/src/parser/comment.rs @@ -49,8 +49,11 @@ pub fn comment<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, 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 (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)) } @@ -71,12 +74,13 @@ mod tests { 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"#); + 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 e3a2c4e..abfaf4b 100644 --- a/src/parser/element.rs +++ b/src/parser/element.rs @@ -9,8 +9,11 @@ use super::greater_element::FootnoteDefinition; use super::greater_element::GreaterBlock; use super::greater_element::PlainList; use super::greater_element::PropertyDrawer; +use super::greater_element::Table; +use super::greater_element::TableRow; use super::lesser_element::Comment; use super::lesser_element::Paragraph; +use super::lesser_element::TableCell; use super::paragraph::paragraph; use super::plain_list::plain_list; use super::source::Source; @@ -18,6 +21,8 @@ use super::Context; use super::Drawer; use super::PlainListItem; use crate::parser::parser_with_context::parser_with_context; +use crate::parser::table; +use crate::parser::table::org_mode_table; use nom::branch::alt; use nom::combinator::map; @@ -31,6 +36,7 @@ pub enum Element<'s> { Comment(Comment<'s>), Drawer(Drawer<'s>), PropertyDrawer(PropertyDrawer<'s>), + Table(Table<'s>), } impl<'s> Source<'s> for Element<'s> { @@ -44,6 +50,7 @@ impl<'s> Source<'s> for Element<'s> { Element::Comment(obj) => obj.source, Element::Drawer(obj) => obj.source, Element::PropertyDrawer(obj) => obj.source, + Element::Table(obj) => obj.source, } } } @@ -102,6 +109,24 @@ impl<'s> Source<'s> for PropertyDrawer<'s> { } } +impl<'s> Source<'s> for Table<'s> { + fn get_source(&'s self) -> &'s str { + self.source + } +} + +impl<'s> Source<'s> for TableRow<'s> { + fn get_source(&'s self) -> &'s str { + self.source + } +} + +impl<'s> Source<'s> for TableCell<'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); @@ -123,6 +148,7 @@ pub fn non_paragraph_element<'r, 's>( let footnote_definition_matcher = parser_with_context!(footnote_definition)(context); let comment_matcher = parser_with_context!(comment)(context); let drawer_matcher = parser_with_context!(drawer)(context); + let table_matcher = parser_with_context!(org_mode_table)(context); alt(( map(plain_list_matcher, Element::PlainList), map(greater_block_matcher, Element::GreaterBlock), @@ -130,5 +156,6 @@ pub fn non_paragraph_element<'r, 's>( map(footnote_definition_matcher, Element::FootnoteDefinition), map(comment_matcher, Element::Comment), map(drawer_matcher, Element::Drawer), + map(table_matcher, Element::Table), ))(input) } diff --git a/src/parser/greater_element.rs b/src/parser/greater_element.rs index 78b05c2..8af70e4 100644 --- a/src/parser/greater_element.rs +++ b/src/parser/greater_element.rs @@ -1,4 +1,5 @@ use super::element::Element; +use super::lesser_element::TableCell; #[derive(Debug)] pub struct PlainList<'s> { @@ -55,3 +56,15 @@ pub struct NodeProperty<'s> { pub source: &'s str, pub value: Option<&'s str>, } + +#[derive(Debug)] +pub struct Table<'s> { + pub source: &'s str, + pub children: Vec>, +} + +#[derive(Debug)] +pub struct TableRow<'s> { + pub source: &'s str, + pub children: Vec>, +} diff --git a/src/parser/lesser_element.rs b/src/parser/lesser_element.rs index 6d041b4..44b4f44 100644 --- a/src/parser/lesser_element.rs +++ b/src/parser/lesser_element.rs @@ -1,4 +1,5 @@ -use super::object::{Object, TextMarkup}; +use super::object::Object; +use super::object::TextMarkup; #[derive(Debug)] pub struct Paragraph<'s> { @@ -11,6 +12,12 @@ pub struct Comment<'s> { pub source: &'s str, } +#[derive(Debug)] +pub struct TableCell<'s> { + pub source: &'s str, + pub children: Vec>, +} + impl<'s> Paragraph<'s> { pub fn of_text(input: &'s str) -> Self { let mut objects = Vec::with_capacity(1); diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6359a5c..33eb7de 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -18,6 +18,7 @@ mod plain_list; mod plain_text; mod property_drawer; mod source; +mod table; mod util; pub use document::document; pub use document::Document; @@ -32,7 +33,10 @@ pub use greater_element::GreaterBlock; pub use greater_element::PlainList; pub use greater_element::PlainListItem; pub use greater_element::PropertyDrawer; +pub use greater_element::Table; +pub use greater_element::TableRow; pub use lesser_element::Comment; pub use lesser_element::Paragraph; +pub use lesser_element::TableCell; pub use source::Source; type Context<'r, 's> = &'r parser_context::ContextTree<'r, 's>; diff --git a/src/parser/object.rs b/src/parser/object.rs index 36162a6..7a493a1 100644 --- a/src/parser/object.rs +++ b/src/parser/object.rs @@ -48,6 +48,20 @@ pub fn standard_set_object<'r, 's>( context: Context<'r, 's>, input: &'s str, ) -> Res<&'s str, Object<'s>> { + // TODO: add entities, LaTeX fragments, export snippets, footnote references, citations (NOT citation references), inline babel calls, inline source blocks, line breaks, links, macros, targets and radio targets, statistics cookies, subscript and superscript, timestamps, and text markup. + not(|i| context.check_exit_matcher(i))(input)?; + + let plain_text_matcher = parser_with_context!(plain_text)(context); + + map(plain_text_matcher, Object::PlainText)(input) +} + +#[tracing::instrument(ret, level = "debug")] +pub fn minimal_set_object<'r, 's>( + context: Context<'r, 's>, + input: &'s str, +) -> Res<&'s str, Object<'s>> { + // TODO: add text markup, entities, LaTeX fragments, superscripts and subscripts not(|i| context.check_exit_matcher(i))(input)?; let plain_text_matcher = parser_with_context!(plain_text)(context); diff --git a/src/parser/parser_context.rs b/src/parser/parser_context.rs index e113d21..c97f666 100644 --- a/src/parser/parser_context.rs +++ b/src/parser/parser_context.rs @@ -89,7 +89,6 @@ impl<'r, 's> ContextTree<'r, 's> { return local_result; } } - } _ => {} }; diff --git a/src/parser/table.rs b/src/parser/table.rs new file mode 100644 index 0000000..44f9616 --- /dev/null +++ b/src/parser/table.rs @@ -0,0 +1,151 @@ +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::combinator::eof; +use nom::combinator::not; +use nom::combinator::peek; +use nom::combinator::recognize; +use nom::combinator::verify; +use nom::multi::many1; +use nom::multi::many_till; +use nom::sequence::tuple; + +use super::Context; +use crate::parser::error::Res; +use crate::parser::exiting::ExitClass; +use crate::parser::greater_element::TableRow; +use crate::parser::lesser_element::TableCell; +use crate::parser::object::minimal_set_object; +use crate::parser::object::Object; +use crate::parser::parser_context::ContextElement; +use crate::parser::parser_context::ExitMatcherNode; +use crate::parser::parser_with_context::parser_with_context; +use crate::parser::util::exit_matcher_parser; +use crate::parser::util::get_consumed; +use crate::parser::util::maybe_consume_trailing_whitespace_if_not_exiting; +use crate::parser::util::start_of_line; +use crate::parser::Table; + +/// Parse an org-mode-style table +/// +/// This is not the table.el style. +#[tracing::instrument(ret, level = "debug")] +pub fn org_mode_table<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Table<'s>> { + start_of_line(context, input)?; + peek(tuple((space0, tag("|"))))(input)?; + + let parser_context = context + .with_additional_node(ContextElement::ConsumeTrailingWhitespace(true)) + .with_additional_node(ContextElement::Context("table")) + .with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { + class: ExitClass::Alpha, + exit_matcher: &table_end, + })); + + let org_mode_table_row_matcher = parser_with_context!(org_mode_table_row)(&parser_context); + let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); + + let (remaining, (children, _exit_contents)) = + many_till(org_mode_table_row_matcher, exit_matcher)(input)?; + + // TODO: Consume trailing formulas + let (remaining, _trailing_ws) = + maybe_consume_trailing_whitespace_if_not_exiting(context, remaining)?; + let source = get_consumed(input, remaining); + + Ok((remaining, Table { source, children })) +} + +#[tracing::instrument(ret, level = "debug")] +fn table_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> { + start_of_line(context, input)?; + recognize(tuple((space0, not(tag("|")))))(input) +} + +#[tracing::instrument(ret, level = "debug")] +pub fn org_mode_table_row<'r, 's>( + context: Context<'r, 's>, + input: &'s str, +) -> Res<&'s str, TableRow<'s>> { + alt(( + parser_with_context!(org_mode_table_row_rule)(context), + parser_with_context!(org_mode_table_row_regular)(context), + ))(input) +} + +#[tracing::instrument(ret, level = "debug")] +pub fn org_mode_table_row_rule<'r, 's>( + context: Context<'r, 's>, + input: &'s str, +) -> Res<&'s str, TableRow<'s>> { + start_of_line(context, input)?; + let (remaining, _) = tuple((space0, tag("|-"), is_not("\r\n"), line_ending))(input)?; + let source = get_consumed(input, remaining); + Ok(( + remaining, + TableRow { + source, + children: Vec::new(), + }, + )) +} + +#[tracing::instrument(ret, level = "debug")] +pub fn org_mode_table_row_regular<'r, 's>( + context: Context<'r, 's>, + input: &'s str, +) -> Res<&'s str, TableRow<'s>> { + start_of_line(context, input)?; + let (remaining, _) = tuple((space0, tag("|")))(input)?; + let (remaining, children) = + many1(parser_with_context!(org_mode_table_cell)(context))(remaining)?; + let (remaining, _tail) = recognize(tuple((space0, alt((line_ending, eof)))))(remaining)?; + let source = get_consumed(input, remaining); + Ok((remaining, TableRow { source, children })) +} + +#[tracing::instrument(ret, level = "debug")] +pub fn org_mode_table_cell<'r, 's>( + context: Context<'r, 's>, + input: &'s str, +) -> Res<&'s str, TableCell<'s>> { + let parser_context = + context.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode { + class: ExitClass::Beta, + exit_matcher: &org_mode_table_cell_end, + })); + let table_cell_set_object_matcher = + parser_with_context!(table_cell_set_object)(&parser_context); + let exit_matcher = parser_with_context!(exit_matcher_parser)(&parser_context); + let (remaining, (children, _exit_contents)) = verify( + many_till(table_cell_set_object_matcher, exit_matcher), + |(children, exit_contents)| !children.is_empty() || exit_contents.ends_with("|"), + )(input)?; + + let (remaining, _tail) = org_mode_table_cell_end(&parser_context, remaining)?; + + let source = get_consumed(input, remaining); + + Ok((remaining, TableCell { source, children })) +} + +#[tracing::instrument(ret, level = "debug")] +fn org_mode_table_cell_end<'r, 's>( + context: Context<'r, 's>, + input: &'s str, +) -> Res<&'s str, &'s str> { + recognize(tuple((space0, alt((tag("|"), peek(line_ending))))))(input) +} + +#[tracing::instrument(ret, level = "debug")] +pub fn table_cell_set_object<'r, 's>( + context: Context<'r, 's>, + input: &'s str, +) -> Res<&'s str, Object<'s>> { + not(|i| context.check_exit_matcher(i))(input)?; + + parser_with_context!(minimal_set_object)(context)(input) + // TODO: add citations, export snippets, footnote references, links, macros, radio targets, targets, and timestamps. +} diff --git a/toy_language.txt b/toy_language.txt index 2538054..8f7fcaf 100644 --- a/toy_language.txt +++ b/toy_language.txt @@ -1,25 +1,3 @@ - - - - -# Blank lines and comments can come before property drawers in the zeroth section -:PROPERTIES: -:FOO: bar -:END: - - - -* Spaces turn property drawers into regular drawers - -:PROPERTIES: -:FOO: bar -:END: -* Comments turn property drawers into regular drawers -# Comment -:PROPERTIES: -:FOO: bar -:END: -* Baseline -:PROPERTIES: -:FOO: bar -:END: +| foo | bar | +|-----+-------| +| baz | lorem |