From 1362624083f10f0759f7a45b8a22a8fe99f13282 Mon Sep 17 00:00:00 2001 From: PoiScript Date: Wed, 15 Nov 2023 00:03:16 +0800 Subject: [PATCH] feat: support affiliated keyword --- src/ast/affiliated_keyword.rs | 58 ++++++ src/ast/generate.js | 34 ++++ src/ast/generated.rs | 357 ++++++++++++++++++++++++++++++++- src/ast/link.rs | 17 +- src/ast/mod.rs | 1 + src/syntax/block.rs | 6 +- src/syntax/clock.rs | 11 +- src/syntax/combinator.rs | 23 +-- src/syntax/comment.rs | 5 +- src/syntax/cookie.rs | 10 +- src/syntax/document.rs | 5 +- src/syntax/drawer.rs | 12 +- src/syntax/dyn_block.rs | 8 +- src/syntax/element.rs | 361 +++++++++++++++++++--------------- src/syntax/emphasis.rs | 38 ++-- src/syntax/fixed_width.rs | 5 +- src/syntax/fn_def.rs | 13 +- src/syntax/fn_ref.rs | 7 +- src/syntax/headline.rs | 18 +- src/syntax/inline_call.rs | 9 +- src/syntax/inline_src.rs | 9 +- src/syntax/keyword.rs | 212 ++++++++++++-------- src/syntax/link.rs | 9 +- src/syntax/list.rs | 19 +- src/syntax/macros.rs | 9 +- src/syntax/mod.rs | 1 + src/syntax/paragraph.rs | 25 +-- src/syntax/planning.rs | 4 +- src/syntax/radio_target.rs | 7 +- src/syntax/rule.rs | 7 +- src/syntax/snippet.rs | 7 +- src/syntax/table.rs | 11 +- src/syntax/target.rs | 8 +- src/syntax/timestamp.rs | 19 +- 34 files changed, 946 insertions(+), 399 deletions(-) create mode 100644 src/ast/affiliated_keyword.rs diff --git a/src/ast/affiliated_keyword.rs b/src/ast/affiliated_keyword.rs new file mode 100644 index 0000000..07ed569 --- /dev/null +++ b/src/ast/affiliated_keyword.rs @@ -0,0 +1,58 @@ +use crate::syntax::{SyntaxElement, SyntaxKind, SyntaxToken}; + +use super::AffiliatedKeyword; + +impl AffiliatedKeyword { + /// + /// ```rust + /// use orgize::{Org, ast::AffiliatedKeyword}; + /// + /// let keyword = Org::parse("#+CAPTION: VALUE\nabc").first_node::().unwrap(); + /// assert_eq!(keyword.key().unwrap().text(), "CAPTION"); + /// ``` + pub fn key(&self) -> Option { + self.syntax.children_with_tokens().find_map(|it| match it { + SyntaxElement::Token(t) if t.kind() == SyntaxKind::TEXT => Some(t), + _ => None, + }) + } + + /// + /// ```rust + /// use orgize::{Org, ast::AffiliatedKeyword}; + /// + /// let keyword = Org::parse("#+CAPTION: VALUE\nabc").first_node::().unwrap(); + /// assert!(keyword.optional().is_none()); + /// let keyword = Org::parse("#+CAPTION[OPTIONAL]: VALUE\nabc").first_node::().unwrap(); + /// assert_eq!(keyword.optional().unwrap().text(), "OPTIONAL"); + /// ``` + pub fn optional(&self) -> Option { + self.syntax + .children_with_tokens() + .skip_while(|it| it.kind() != SyntaxKind::L_BRACKET) + .nth(1) + .and_then(|it| match it { + SyntaxElement::Token(t) if t.kind() == SyntaxKind::TEXT => Some(t), + _ => None, + }) + } + + /// + /// ```rust + /// use orgize::{Org, ast::AffiliatedKeyword}; + /// + /// let keyword = Org::parse("#+CAPTION: VALUE\nabc").first_node::().unwrap(); + /// assert_eq!(keyword.value().unwrap().text(), " VALUE"); + /// let keyword = Org::parse("#+CAPTION[OPTIONAL]:VALUE\nabc").first_node::().unwrap(); + /// assert_eq!(keyword.value().unwrap().text(), "VALUE"); + /// ``` + pub fn value(&self) -> Option { + self.syntax + .children_with_tokens() + .filter_map(|it| match it { + SyntaxElement::Token(t) if t.kind() == SyntaxKind::TEXT => Some(t), + _ => None, + }) + .last() + } +} diff --git a/src/ast/generate.js b/src/ast/generate.js index bc98eaa..12145b2 100644 --- a/src/ast/generate.js +++ b/src/ast/generate.js @@ -19,6 +19,7 @@ const nodes = [ struct: "Paragraph", kind: ["PARAGRAPH"], post_blank: true, + affiliated_keywords: true, }, { struct: "Headline", @@ -97,6 +98,7 @@ const nodes = [ struct: "OrgTable", kind: ["ORG_TABLE"], post_blank: true, + affiliated_keywords: true, }, { struct: "OrgTableRow", @@ -110,6 +112,7 @@ const nodes = [ struct: "List", kind: ["LIST"], children: [["items", "ListItem"]], + affiliated_keywords: true, }, { struct: "ListItem", @@ -143,6 +146,7 @@ const nodes = [ { struct: "DynBlock", kind: ["DYN_BLOCK"], + affiliated_keywords: true, }, { struct: "Keyword", @@ -152,6 +156,10 @@ const nodes = [ struct: "BabelCall", kind: ["BABEL_CALL"], }, + { + struct: "AffiliatedKeyword", + kind: ["AFFILIATED_KEYWORD"], + }, { struct: "TableEl", kind: ["TABLE_EL"], @@ -166,12 +174,14 @@ const nodes = [ struct: "FnDef", kind: ["FN_DEF"], post_blank: true, + affiliated_keywords: true, }, { struct: "Comment", kind: ["COMMENT"], post_blank: true, token: [["text", "TEXT"]], + affiliated_keywords: true, }, { struct: "Rule", @@ -183,38 +193,47 @@ const nodes = [ kind: ["FIXED_WIDTH"], post_blank: true, token: [["text", "TEXT"]], + affiliated_keywords: true, }, { struct: "SpecialBlock", kind: ["SPECIAL_BLOCK"], + affiliated_keywords: true, }, { struct: "QuoteBlock", kind: ["QUOTE_BLOCK"], + affiliated_keywords: true, }, { struct: "CenterBlock", kind: ["CENTER_BLOCK"], + affiliated_keywords: true, }, { struct: "VerseBlock", kind: ["VERSE_BLOCK"], + affiliated_keywords: true, }, { struct: "CommentBlock", kind: ["COMMENT_BLOCK"], + affiliated_keywords: true, }, { struct: "ExampleBlock", kind: ["EXAMPLE_BLOCK"], + affiliated_keywords: true, }, { struct: "ExportBlock", kind: ["EXPORT_BLOCK"], + affiliated_keywords: true, }, { struct: "SourceBlock", kind: ["SOURCE_BLOCK"], + affiliated_keywords: true, }, { struct: "InlineCall", @@ -313,6 +332,13 @@ let content = `//! generated file, do not modify it directly use rowan::ast::{support, AstChildren, AstNode}; use crate::syntax::{OrgLanguage, SyntaxKind, SyntaxKind::*, SyntaxNode, SyntaxToken}; + +fn affiliated_keyword(node: &SyntaxNode, filter: impl Fn(&str) -> bool) -> Option { + node.children() + .take_while(|n| n.kind() == SyntaxKind::AFFILIATED_KEYWORD) + .filter_map(AffiliatedKeyword::cast) + .find(|k| matches!(k.key(), Some(k) if filter(k.text()))) +} `; for (const node of nodes) { @@ -356,6 +382,14 @@ impl ${node.struct} {\n`; if (node.pre_blank) { content += ` pub fn pre_blank(&self) -> usize { super::blank_lines(&self.syntax) }\n`; } + if (node.affiliated_keywords) { + content += ` pub fn caption(&self) -> Option { affiliated_keyword(&self.syntax, |k| k == "CAPTION") }\n`; + content += ` pub fn header(&self) -> Option { affiliated_keyword(&self.syntax, |k| k == "HEADER") }\n`; + content += ` pub fn name(&self) -> Option { affiliated_keyword(&self.syntax, |k| k == "NAME") }\n`; + content += ` pub fn plot(&self) -> Option { affiliated_keyword(&self.syntax, |k| k == "PLOT") }\n`; + content += ` pub fn results(&self) -> Option { affiliated_keyword(&self.syntax, |k| k == "RESULTS") }\n`; + content += ` pub fn attr(&self, backend: &str) -> Option { affiliated_keyword(&self.syntax, |k| k.starts_with("ATTR_") && &k[5..] == backend) }\n`; + } content += `}\n`; } diff --git a/src/ast/generated.rs b/src/ast/generated.rs index 97031e0..d52c64a 100644 --- a/src/ast/generated.rs +++ b/src/ast/generated.rs @@ -3,7 +3,17 @@ #![allow(unused)] use crate::syntax::{OrgLanguage, SyntaxKind, SyntaxKind::*, SyntaxNode, SyntaxToken}; -use rowan::ast::{support::{self, token}, AstChildren, AstNode}; +use rowan::ast::{support, AstChildren, AstNode}; + +fn affiliated_keyword( + node: &SyntaxNode, + filter: impl Fn(&str) -> bool, +) -> Option { + node.children() + .take_while(|n| n.kind() == SyntaxKind::AFFILIATED_KEYWORD) + .filter_map(AffiliatedKeyword::cast) + .find(|k| matches!(k.key(), Some(k) if filter(k.text()))) +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Document { @@ -81,6 +91,26 @@ impl Paragraph { pub fn post_blank(&self) -> usize { super::blank_lines(&self.syntax) } + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -384,6 +414,26 @@ impl OrgTable { pub fn post_blank(&self) -> usize { super::blank_lines(&self.syntax) } + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -442,6 +492,26 @@ impl List { pub fn items(&self) -> AstChildren { support::children(&self.syntax) } + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -578,7 +648,28 @@ impl AstNode for DynBlock { &self.syntax } } -impl DynBlock {} +impl DynBlock { + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct Keyword { @@ -616,6 +707,24 @@ impl AstNode for BabelCall { } impl BabelCall {} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct AffiliatedKeyword { + pub(crate) syntax: SyntaxNode, +} +impl AstNode for AffiliatedKeyword { + type Language = OrgLanguage; + fn can_cast(kind: SyntaxKind) -> bool { + kind == AFFILIATED_KEYWORD + } + fn cast(node: SyntaxNode) -> Option { + Self::can_cast(node.kind()).then(|| AffiliatedKeyword { syntax: node }) + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl AffiliatedKeyword {} + #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct TableEl { pub(crate) syntax: SyntaxNode, @@ -680,6 +789,26 @@ impl FnDef { pub fn post_blank(&self) -> usize { super::blank_lines(&self.syntax) } + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -705,6 +834,26 @@ impl Comment { pub fn post_blank(&self) -> usize { super::blank_lines(&self.syntax) } + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -752,6 +901,26 @@ impl FixedWidth { pub fn post_blank(&self) -> usize { super::blank_lines(&self.syntax) } + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -770,7 +939,28 @@ impl AstNode for SpecialBlock { &self.syntax } } -impl SpecialBlock {} +impl SpecialBlock { + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct QuoteBlock { @@ -788,7 +978,28 @@ impl AstNode for QuoteBlock { &self.syntax } } -impl QuoteBlock {} +impl QuoteBlock { + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct CenterBlock { @@ -806,7 +1017,28 @@ impl AstNode for CenterBlock { &self.syntax } } -impl CenterBlock {} +impl CenterBlock { + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VerseBlock { @@ -824,7 +1056,28 @@ impl AstNode for VerseBlock { &self.syntax } } -impl VerseBlock {} +impl VerseBlock { + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct CommentBlock { @@ -842,7 +1095,28 @@ impl AstNode for CommentBlock { &self.syntax } } -impl CommentBlock {} +impl CommentBlock { + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExampleBlock { @@ -860,7 +1134,28 @@ impl AstNode for ExampleBlock { &self.syntax } } -impl ExampleBlock {} +impl ExampleBlock { + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ExportBlock { @@ -878,7 +1173,28 @@ impl AstNode for ExportBlock { &self.syntax } } -impl ExportBlock {} +impl ExportBlock { + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct SourceBlock { @@ -896,7 +1212,28 @@ impl AstNode for SourceBlock { &self.syntax } } -impl SourceBlock {} +impl SourceBlock { + pub fn caption(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "CAPTION") + } + pub fn header(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "HEADER") + } + pub fn name(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "NAME") + } + pub fn plot(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "PLOT") + } + pub fn results(&self) -> Option { + affiliated_keyword(&self.syntax, |k| k == "RESULTS") + } + pub fn attr(&self, backend: &str) -> Option { + affiliated_keyword(&self.syntax, |k| { + k.starts_with("ATTR_") && &k[5..] == backend + }) + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct InlineCall { diff --git a/src/ast/link.rs b/src/ast/link.rs index 2e85101..f2aa87e 100644 --- a/src/ast/link.rs +++ b/src/ast/link.rs @@ -1,6 +1,6 @@ use rowan::ast::{support, AstNode}; -use super::Link; +use super::{AffiliatedKeyword, Link, Paragraph}; use crate::syntax::SyntaxKind; impl Link { @@ -23,6 +23,8 @@ impl Link { /// ```rust /// use orgize::{Org, ast::Link}; /// + /// let link = Org::parse("[[https://google.com]]").first_node::().unwrap(); + /// assert!(!link.is_image()); /// let link = Org::parse("[[file:/home/dominik/images/jupiter.jpg]]").first_node::().unwrap(); /// assert!(link.is_image()); /// ``` @@ -38,4 +40,17 @@ impl Link { .unwrap_or_default() && !self.has_description() } + + /// Returns caption keyword in this link + /// + /// ```rust + /// use orgize::{Org, ast::Link}; + /// + /// let link = Org::parse("#+CAPTION: image link\n[[file:/home/dominik/images/jupiter.jpg]]").first_node::().unwrap(); + /// assert_eq!(link.caption().unwrap().value().unwrap().text(), " image link"); + /// ``` + pub fn caption(&self) -> Option { + // TODO: support other element type + Paragraph::cast(self.syntax.parent()?.clone())?.caption() + } } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 9f1f9a0..dadd2cf 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -2,6 +2,7 @@ mod generated; +mod affiliated_keyword; mod drawer; mod headline; mod inline_call; diff --git a/src/syntax/block.rs b/src/syntax/block.rs index b47a98b..cabc90b 100644 --- a/src/syntax/block.rs +++ b/src/syntax/block.rs @@ -9,8 +9,7 @@ use nom::{ use super::{ combinator::{ - blank_lines, debug_assert_lossless, line_starts_iter, node, token, trim_line_end, - GreenElement, NodeBuilder, + blank_lines, line_starts_iter, node, token, trim_line_end, GreenElement, NodeBuilder, }, element::element_nodes, input::Input, @@ -115,8 +114,9 @@ fn comma_quoted_text_nodes(input: Input) -> Vec { nodes } +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn block_node(input: Input) -> IResult { - debug_assert_lossless(block_node_base)(input) + crate::lossless_parser!(block_node_base, input) } #[test] diff --git a/src/syntax/clock.rs b/src/syntax/clock.rs index cf3f01b..de9cc20 100644 --- a/src/syntax/clock.rs +++ b/src/syntax/clock.rs @@ -8,17 +8,15 @@ use nom::{ }; use super::{ - combinator::{ - blank_lines, colon_token, debug_assert_lossless, double_arrow_token, GreenElement, - NodeBuilder, - }, + combinator::{blank_lines, colon_token, double_arrow_token, GreenElement, NodeBuilder}, input::Input, timestamp::{timestamp_active_node, timestamp_inactive_node}, SyntaxKind, }; +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn clock_node(input: Input) -> IResult { - debug_assert_lossless(map( + let mut parser = map( tuple(( space0, tag("CLOCK:"), @@ -56,7 +54,8 @@ pub fn clock_node(input: Input) -> IResult { b.children.extend(post_blank); b.finish(SyntaxKind::CLOCK) }, - ))(input) + ); + crate::lossless_parser!(parser, input) } #[test] diff --git a/src/syntax/combinator.rs b/src/syntax/combinator.rs index a86c33f..2d48f5b 100644 --- a/src/syntax/combinator.rs +++ b/src/syntax/combinator.rs @@ -3,7 +3,6 @@ use std::iter::once; use memchr::{memchr, memchr2_iter, memchr_iter}; use nom::{ bytes::complete::tag, character::complete::space0, AsBytes, IResult, InputLength, InputTake, - Parser, }; use rowan::{GreenNode, GreenToken, Language, NodeOrToken}; @@ -72,23 +71,19 @@ token_parser!(hash_plus_token, "#+", HASH_PLUS); token_parser!(hash_token, "#", HASH); token_parser!(double_arrow_token, "=>", DOUBLE_ARROW); -pub fn debug_assert_lossless<'a, F>( - mut f: F, -) -> impl FnMut(Input<'a>) -> IResult, GreenElement, ()> -where - F: Parser, GreenElement, ()>, -{ - move |input: Input| { - let (i, o) = f.parse(input)?; - +#[macro_export] +macro_rules! lossless_parser { + ($parser:expr, $input:expr) => {{ + let i_ = $input; + let (i, o) = $parser($input)?; + tracing::info!(consumed = o.to_string()); debug_assert_eq!( - &input.as_str()[0..(input.input_len() - i.input_len())], + &i_.as_str()[0..(i_.s.len() - i.s.len())], &o.to_string(), - "parser must be lossless" + stringify!("parser must be lossless") ); - Ok((i, o)) - } + }}; } /// Takes all blank lines diff --git a/src/syntax/comment.rs b/src/syntax/comment.rs index bf67a10..434a2ea 100644 --- a/src/syntax/comment.rs +++ b/src/syntax/comment.rs @@ -1,7 +1,7 @@ use nom::{IResult, InputTake}; use super::{ - combinator::{blank_lines, debug_assert_lossless, line_ends_iter, node, GreenElement}, + combinator::{blank_lines, line_ends_iter, node, GreenElement}, input::Input, SyntaxKind, }; @@ -33,8 +33,9 @@ fn comment_node_base(input: Input) -> IResult { Ok((input, node(SyntaxKind::COMMENT, children))) } +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn comment_node(input: Input) -> IResult { - debug_assert_lossless(comment_node_base)(input) + crate::lossless_parser!(comment_node_base, input) } #[test] diff --git a/src/syntax/cookie.rs b/src/syntax/cookie.rs index e62e66d..4000f09 100644 --- a/src/syntax/cookie.rs +++ b/src/syntax/cookie.rs @@ -8,15 +8,14 @@ use nom::{ }; use super::{ - combinator::{ - debug_assert_lossless, l_bracket_token, node, r_bracket_token, token, GreenElement, - }, + combinator::{l_bracket_token, node, r_bracket_token, token, GreenElement}, input::Input, SyntaxKind::*, }; +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn cookie_node(input: Input) -> IResult { - debug_assert_lossless(map( + let mut parser = map( tuple(( l_bracket_token, alt(( @@ -42,7 +41,8 @@ pub fn cookie_node(input: Input) -> IResult { node(COOKIE, children) }, - ))(input) + ); + crate::lossless_parser!(parser, input) } #[test] diff --git a/src/syntax/document.rs b/src/syntax/document.rs index a317592..fb687cf 100644 --- a/src/syntax/document.rs +++ b/src/syntax/document.rs @@ -4,14 +4,15 @@ use nom::{ }; use super::{ - combinator::{blank_lines, debug_assert_lossless, node, GreenElement}, + combinator::{blank_lines, node, GreenElement}, headline::{headline_node, section_node}, input::Input, SyntaxKind::*, }; +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn document_node(input: Input) -> IResult { - debug_assert_lossless(document_node_base)(input) + crate::lossless_parser!(document_node_base, input) } fn document_node_base(input: Input) -> IResult { diff --git a/src/syntax/drawer.rs b/src/syntax/drawer.rs index 92833c1..77967da 100644 --- a/src/syntax/drawer.rs +++ b/src/syntax/drawer.rs @@ -9,8 +9,8 @@ use nom::{ use super::{ combinator::{ - blank_lines, colon_token, debug_assert_lossless, line_starts_iter, node, plus_token, - trim_line_end, GreenElement, NodeBuilder, + blank_lines, colon_token, line_starts_iter, node, plus_token, trim_line_end, GreenElement, + NodeBuilder, }, input::Input, SyntaxKind::*, @@ -126,14 +126,14 @@ fn node_property_node(input: Input) -> IResult { )(input) } -#[tracing::instrument(skip(input), fields(input = input.s))] +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn property_drawer_node(input: Input) -> IResult { - debug_assert_lossless(property_drawer_node_base)(input) + crate::lossless_parser!(property_drawer_node_base, input) } -#[tracing::instrument(skip(input), fields(input = input.s))] +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn drawer_node(input: Input) -> IResult { - debug_assert_lossless(drawer_node_base)(input) + crate::lossless_parser!(drawer_node_base, input) } #[test] diff --git a/src/syntax/dyn_block.rs b/src/syntax/dyn_block.rs index ab18df1..5e01397 100644 --- a/src/syntax/dyn_block.rs +++ b/src/syntax/dyn_block.rs @@ -8,10 +8,7 @@ use nom::{ }; use super::{ - combinator::{ - blank_lines, debug_assert_lossless, line_starts_iter, node, trim_line_end, GreenElement, - NodeBuilder, - }, + combinator::{blank_lines, line_starts_iter, node, trim_line_end, GreenElement, NodeBuilder}, input::Input, SyntaxKind::*, }; @@ -74,8 +71,9 @@ fn dyn_block_end_node(input: Input) -> IResult { Ok((input, b.finish(DYN_BLOCK_END))) } +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn dyn_block_node(input: Input) -> IResult { - debug_assert_lossless(dyn_block_node_base)(input) + crate::lossless_parser!(dyn_block_node_base, input) } #[test] diff --git a/src/syntax/element.rs b/src/syntax/element.rs index 95791ae..736b51e 100644 --- a/src/syntax/element.rs +++ b/src/syntax/element.rs @@ -1,192 +1,86 @@ -use nom::{AsBytes, IResult, InputTake}; +use nom::IResult; use super::{ block::block_node, clock::clock_node, - combinator::{line_starts_iter, GreenElement}, + combinator::GreenElement, comment::comment_node, drawer::drawer_node, dyn_block::dyn_block_node, fixed_width::fixed_width_node, fn_def::fn_def_node, input::Input, - keyword::keyword_node, + keyword::{affiliated_keyword_nodes, keyword_node}, list::list_node, - paragraph::paragraph_nodes, + paragraph::paragraph_node, rule::rule_node, table::{org_table_node, table_el_node}, }; /// Parses input into multiple element -#[tracing::instrument(skip(input), fields(input = input.s))] +/// +/// input must not contains blank line in the beginning +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn element_nodes(input: Input) -> Result, nom::Err<()>> { - // TODO: - // debug_assert!(!input.is_empty()); - let nodes = element_nodes_base(input)?; + debug_assert!(!input.is_empty()); + + let mut i = input; + let mut nodes = vec![]; + + while !i.is_empty() { + let result = element_node(i); + debug_assert!(result.is_ok(), "element_node() always returns Ok()"); + let (input, node) = result?; + i = input; + nodes.push(node); + } + debug_assert_eq!( input.as_str(), nodes.iter().fold(String::new(), |s, n| s + &n.to_string()), "parser must be lossless" ); + Ok(nodes) } -/// Parses input into multiple elements -/// -/// input must not contains blank line in the beginning -fn element_nodes_base(input: Input) -> Result, nom::Err<()>> { - #[derive(PartialEq, Eq)] - enum PreviousLine { - None, - BlankLine, - AffiliatedKeyword, - Other, - } - - let mut children = vec![]; - - let mut i = input; - - let mut previous_line = PreviousLine::None; - - 'l: loop { - for (input, head) in line_starts_iter(i.as_str()).map(|idx| i.take_split(idx)) { - // find the first byte that's not a whitespace - let trimmed = input.as_str().trim_start_matches(|c| c == ' ' || c == '\t'); - - // if this line is an affiliated keyword, that skip it - if is_affiliated_keyword(trimmed) { - if previous_line == PreviousLine::BlankLine { - children.extend(paragraph_nodes(head)?); - } - previous_line = PreviousLine::AffiliatedKeyword; - continue; - } - - // if this line is a blank line - if is_blank_line(trimmed) { - if previous_line == PreviousLine::AffiliatedKeyword { - previous_line = PreviousLine::BlankLine; - if let Ok((input, node)) = keyword_node(input) { - if !head.is_empty() { - children.extend(paragraph_nodes(head)?); - } - children.push(node); - i = input; - continue 'l; - } - } - continue; - } - - if let Ok((input, node)) = match trimmed.bytes().next() { - Some(b'[') => fn_def_node(input), - Some(b'0'..=b'9') | Some(b'*') => list_node(input), - Some(b'C') => clock_node(input), - Some(b'-') => rule_node(input).or_else(|_| list_node(input)), - Some(b':') => drawer_node(input).or_else(|_| fixed_width_node(input)), - Some(b'|') => org_table_node(input), - Some(b'+') => table_el_node(input).or_else(|_| list_node(input)), - Some(b'#') => block_node(input) - .or_else(|_| keyword_node(input)) - .or_else(|_| dyn_block_node(input)) - .or_else(|_| comment_node(input)), - _ => Err(nom::Err::Error(())), - } { - if !head.is_empty() { - children.extend(paragraph_nodes(head)?); - } - children.push(node); - i = input; - continue 'l; - } - } - - break; - } - - if !i.is_empty() { - children.extend(paragraph_nodes(i)?); - } - - Ok(children) -} - -pub fn is_affiliated_keyword(line: &str) -> bool { - line.starts_with("#+CAPTION:") - || line.starts_with("#+DATA:") - || line.starts_with("#+HEADER:") - || line.starts_with("#+HEADERS:") - || line.starts_with("#+LABEL:") - || line.starts_with("#+NAME:") - || line.starts_with("#+PLOT:") - || line.starts_with("#+RESNAME:") - || line.starts_with("#+RESULT:") - || line.starts_with("#+RESULTS:") - || line.starts_with("#+SOURCE:") - || line.starts_with("#+SRCNAME:") - || line.starts_with("#+TBLNAME:") - || line.starts_with("#+ATTR_") -} - -pub fn is_blank_line(line: &str) -> bool { - matches!(line.bytes().next(), None | Some(b'\n') | Some(b'\r')) -} - +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn element_node(input: Input) -> IResult { - let mut has_affiliated_keyword = false; + // skip affiliated keyword first + let (i, nodes) = affiliated_keyword_nodes(input)?; - for offset in line_starts_iter(input.as_str()) { - // find the first byte that's not a whitespace - let Some(idx) = input.as_bytes()[offset..] - .iter() - .position(|b| *b != b' ' && *b != b'\t') - else { - break; - }; + let has_affiliated_keyword = !nodes.is_empty(); - let line = &input.as_str()[(idx + offset)..]; + // find first non-whitespace character + let byte = i + .as_str() + .trim_start_matches(|c| c == ' ' || c == '\t') + .bytes() + .next(); - // if this line is an affiliated keyword, that we skip it - if line.starts_with("#+CAPTION:") - || line.starts_with("#+DATA:") - || line.starts_with("#+HEADER:") - || line.starts_with("#+HEADERS:") - || line.starts_with("#+LABEL:") - || line.starts_with("#+NAME:") - || line.starts_with("#+PLOT:") - || line.starts_with("#+RESNAME:") - || line.starts_with("#+RESULT:") - || line.starts_with("#+RESULTS:") - || line.starts_with("#+SOURCE:") - || line.starts_with("#+SRCNAME:") - || line.starts_with("#+TBLNAME:") - || line.starts_with("#+ATTR_") - { - has_affiliated_keyword = true; - continue; - } + debug_assert!( + !(has_affiliated_keyword && matches!(byte, None | Some(b'\n') | Some(b'\r'))), + "affiliated_keyword must not followed by blank lines: {:?}", + input.s + ); - return match input.as_bytes()[idx + offset] { - b'[' => fn_def_node(input), - b'0'..=b'9' | b'*' => list_node(input), - b'C' => clock_node(input), - b'-' => rule_node(input).or_else(|_| list_node(input)), - b':' => drawer_node(input).or_else(|_| fixed_width_node(input)), - b'|' => org_table_node(input), - b'+' => table_el_node(input).or_else(|_| list_node(input)), - b'#' => block_node(input) - .or_else(|_| keyword_node(input)) - .or_else(|_| dyn_block_node(input)) - .or_else(|_| comment_node(input)), - _ => Err(nom::Err::Error(())), - }; - } + let result = match byte { + Some(b'[') => fn_def_node(input), + Some(b'0'..=b'9') | Some(b'*') => list_node(input), + // clock doesn't have affiliated keywords + Some(b'C') if !has_affiliated_keyword => clock_node(input), + Some(b'-') => rule_node(input).or_else(|_| list_node(input)), + Some(b':') => drawer_node(input).or_else(|_| fixed_width_node(input)), + Some(b'|') => org_table_node(input), + Some(b'+') => table_el_node(input).or_else(|_| list_node(input)), + Some(b'#') => block_node(input) + .or_else(|_| keyword_node(input)) + .or_else(|_| dyn_block_node(input)) + .or_else(|_| comment_node(input)), + _ => Err(nom::Err::Error(())), + }; - // we find an affiliated keyword, but it's not followed by any element - // in this case, we treat it as a simple keyword - - return Err(nom::Err::Error(())); + result.or_else(|_| paragraph_node(input)) } #[test] @@ -218,17 +112,156 @@ b"#), t("#+ATTR_HTML: :width 300px\n[[./img/a.jpg]]"), @r###" SECTION@0..41 + PARAGRAPH@0..41 + AFFILIATED_KEYWORD@0..26 + HASH_PLUS@0..2 "#+" + TEXT@2..11 "ATTR_HTML" + COLON@11..12 ":" + TEXT@12..25 " :width 300px" + NEW_LINE@25..26 "\n" + LINK@26..41 + L_BRACKET2@26..28 "[[" + LINK_PATH@28..39 "./img/a.jpg" + R_BRACKET2@39..41 "]]" + "### + ); + + insta::assert_debug_snapshot!( + t("#+ATTR_HTML: :width 300px\n[[./img/a.jpg]]"), + @r###" + SECTION@0..41 + PARAGRAPH@0..41 + AFFILIATED_KEYWORD@0..26 + HASH_PLUS@0..2 "#+" + TEXT@2..11 "ATTR_HTML" + COLON@11..12 ":" + TEXT@12..25 " :width 300px" + NEW_LINE@25..26 "\n" + LINK@26..41 + L_BRACKET2@26..28 "[[" + LINK_PATH@28..39 "./img/a.jpg" + R_BRACKET2@39..41 "]]" + "### + ); +} + +#[test] +fn affiliated_keywords() { + use crate::syntax::{SyntaxKind, SyntaxNode}; + use crate::{syntax::combinator::node, ParseConfig}; + + let t = |input: &str| { + let config = &ParseConfig::default(); + let children = element_nodes((input, config).into()).unwrap(); + SyntaxNode::new_root(node(SyntaxKind::SECTION, children).into_node().unwrap()) + }; + + // affiliated keywords + paragraph + insta::assert_debug_snapshot!( + t("#+ATTR_HTML: :width 300px\n[[./img/a.jpg]]"), + @r###" + SECTION@0..41 + PARAGRAPH@0..41 + AFFILIATED_KEYWORD@0..26 + HASH_PLUS@0..2 "#+" + TEXT@2..11 "ATTR_HTML" + COLON@11..12 ":" + TEXT@12..25 " :width 300px" + NEW_LINE@25..26 "\n" + LINK@26..41 + L_BRACKET2@26..28 "[[" + LINK_PATH@28..39 "./img/a.jpg" + R_BRACKET2@39..41 "]]" + "### + ); + + // affiliated keywords + blank lines, fallback to normal keyword + insta::assert_debug_snapshot!( + t("#+ATTR_HTML: :width 300px\n#+CAPTION: abc\n\n[[./img/a.jpg]]"), + @r###" + SECTION@0..57 KEYWORD@0..26 HASH_PLUS@0..2 "#+" TEXT@2..11 "ATTR_HTML" COLON@11..12 ":" TEXT@12..25 " :width 300px" NEW_LINE@25..26 "\n" - PARAGRAPH@26..41 - LINK@26..41 - L_BRACKET2@26..28 "[[" - LINK_PATH@28..39 "./img/a.jpg" - R_BRACKET2@39..41 "]]" + KEYWORD@26..42 + HASH_PLUS@26..28 "#+" + TEXT@28..35 "CAPTION" + COLON@35..36 ":" + TEXT@36..40 " abc" + NEW_LINE@40..41 "\n" + BLANK_LINE@41..42 "\n" + PARAGRAPH@42..57 + LINK@42..57 + L_BRACKET2@42..44 "[[" + LINK_PATH@44..55 "./img/a.jpg" + R_BRACKET2@55..57 "]]" "### - ) + ); + + // affiliated keywords + special element + insta::assert_debug_snapshot!( + t("#+CAPTION: a footnote def\n[fn:WORD] https://orgmode.org"), + @r###" + SECTION@0..55 + FN_DEF@0..55 + AFFILIATED_KEYWORD@0..26 + HASH_PLUS@0..2 "#+" + TEXT@2..9 "CAPTION" + COLON@9..10 ":" + TEXT@10..25 " a footnote def" + NEW_LINE@25..26 "\n" + L_BRACKET@26..27 "[" + TEXT@27..29 "fn" + COLON@29..30 ":" + TEXT@30..34 "WORD" + R_BRACKET@34..35 "]" + TEXT@35..55 " https://orgmode.org" + "### + ); + + // affiliated keywords + clock + insta::assert_debug_snapshot!( + t("#+CAPTION: a footnote def\nCLOCK: [2003-09-16 Tue 09:39]"), + @r###" + SECTION@0..55 + PARAGRAPH@0..55 + AFFILIATED_KEYWORD@0..26 + HASH_PLUS@0..2 "#+" + TEXT@2..9 "CAPTION" + COLON@9..10 ":" + TEXT@10..25 " a footnote def" + NEW_LINE@25..26 "\n" + TEXT@26..33 "CLOCK: " + TIMESTAMP_INACTIVE@33..55 + L_BRACKET@33..34 "[" + TIMESTAMP_YEAR@34..38 "2003" + MINUS@38..39 "-" + TIMESTAMP_MONTH@39..41 "09" + MINUS@41..42 "-" + TIMESTAMP_DAY@42..44 "16" + WHITESPACE@44..45 " " + TIMESTAMP_DAYNAME@45..48 "Tue" + WHITESPACE@48..49 " " + TIMESTAMP_HOUR@49..51 "09" + COLON@51..52 ":" + TIMESTAMP_MINUTE@52..54 "39" + R_BRACKET@54..55 "]" + "### + ); + + // affiliated keywords + eof + insta::assert_debug_snapshot!( + t("#+CAPTION: Longer caption."), + @r###" + SECTION@0..26 + KEYWORD@0..26 + HASH_PLUS@0..2 "#+" + TEXT@2..9 "CAPTION" + COLON@9..10 ":" + TEXT@10..26 " Longer caption." + "### + ); } diff --git a/src/syntax/emphasis.rs b/src/syntax/emphasis.rs index 06c45a3..3c680e3 100644 --- a/src/syntax/emphasis.rs +++ b/src/syntax/emphasis.rs @@ -3,64 +3,76 @@ use memchr::memchr_iter; use nom::{combinator::map, AsBytes, IResult, Slice}; use super::{ - combinator::{debug_assert_lossless, node, token, GreenElement}, + combinator::{node, token, GreenElement}, input::Input, object::object_nodes, SyntaxKind::*, }; +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn bold_node(input: Input) -> IResult { - debug_assert_lossless(map(emphasis(b'*'), |contents| { + let mut parser = map(emphasis(b'*'), |contents| { let mut children = vec![token(STAR, "*")]; children.extend(object_nodes(contents)); children.push(token(STAR, "*")); node(BOLD, children) - }))(input) + }); + crate::lossless_parser!(parser, input) } +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn code_node(input: Input) -> IResult { - debug_assert_lossless(map(emphasis(b'~'), |contents| { + let mut parser = map(emphasis(b'~'), |contents| { node( CODE, [token(TILDE, "~"), contents.text_token(), token(TILDE, "~")], ) - }))(input) + }); + crate::lossless_parser!(parser, input) } +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn strike_node(input: Input) -> IResult { - debug_assert_lossless(map(emphasis(b'+'), |contents| { + let mut parser = map(emphasis(b'+'), |contents| { let mut children = vec![token(PLUS, "+")]; children.extend(object_nodes(contents)); children.push(token(PLUS, "+")); node(STRIKE, children) - }))(input) + }); + crate::lossless_parser!(parser, input) } +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn verbatim_node(input: Input) -> IResult { - debug_assert_lossless(map(emphasis(b'='), |contents| { + let mut parser = map(emphasis(b'='), |contents| { node( VERBATIM, [token(EQUAL, "="), contents.text_token(), token(EQUAL, "=")], ) - }))(input) + }); + crate::lossless_parser!(parser, input) } +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn underline_node(input: Input) -> IResult { - debug_assert_lossless(map(emphasis(b'_'), |contents| { + let mut parser = map(emphasis(b'_'), |contents| { let mut children = vec![token(UNDERSCORE, "_")]; children.extend(object_nodes(contents)); children.push(token(UNDERSCORE, "_")); node(UNDERLINE, children) - }))(input) + }); + crate::lossless_parser!(parser, input) } +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn italic_node(input: Input) -> IResult { - debug_assert_lossless(map(emphasis(b'/'), |contents| { + let mut parser = map(emphasis(b'/'), |contents| { let mut children = vec![token(SLASH, "/")]; children.extend(object_nodes(contents)); children.push(token(SLASH, "/")); node(ITALIC, children) - }))(input) + }); + crate::lossless_parser!(parser, input) } fn emphasis(marker: u8) -> impl Fn(Input) -> IResult { diff --git a/src/syntax/fixed_width.rs b/src/syntax/fixed_width.rs index 44f26d5..c16bc4b 100644 --- a/src/syntax/fixed_width.rs +++ b/src/syntax/fixed_width.rs @@ -1,7 +1,7 @@ use nom::{IResult, InputTake}; use super::{ - combinator::{blank_lines, debug_assert_lossless, line_ends_iter, node, GreenElement}, + combinator::{blank_lines, line_ends_iter, node, GreenElement}, input::Input, SyntaxKind, }; @@ -33,8 +33,9 @@ fn fixed_width_node_base(input: Input) -> IResult { Ok((input, node(SyntaxKind::FIXED_WIDTH, children))) } +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn fixed_width_node(input: Input) -> IResult { - debug_assert_lossless(fixed_width_node_base)(input) + crate::lossless_parser!(fixed_width_node_base, input) } #[test] diff --git a/src/syntax/fn_def.rs b/src/syntax/fn_def.rs index 6014dc5..adcc69b 100644 --- a/src/syntax/fn_def.rs +++ b/src/syntax/fn_def.rs @@ -7,17 +7,17 @@ use nom::{ use super::{ combinator::{ - blank_lines, colon_token, debug_assert_lossless, l_bracket_token, r_bracket_token, - trim_line_end, GreenElement, NodeBuilder, + blank_lines, colon_token, l_bracket_token, r_bracket_token, trim_line_end, GreenElement, + NodeBuilder, }, input::Input, keyword::affiliated_keyword_nodes, SyntaxKind, }; -#[tracing::instrument(skip(input), fields(input = input.s))] +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn fn_def_node(input: Input) -> IResult { - debug_assert_lossless(map( + let mut parser = map( tuple(( affiliated_keyword_nodes, l_bracket_token, @@ -51,7 +51,8 @@ pub fn fn_def_node(input: Input) -> IResult { b.children.extend(post_blank); b.finish(SyntaxKind::FN_DEF) }, - ))(input) + ); + crate::lossless_parser!(parser, input) } #[test] @@ -137,7 +138,7 @@ fn parse() { to_fn_def("#+ATTR_poi: 1\n[fn:WORD-1] https://orgmode.org").syntax, @r###" FN_DEF@0..45 - KEYWORD@0..14 + AFFILIATED_KEYWORD@0..14 HASH_PLUS@0..2 "#+" TEXT@2..10 "ATTR_poi" COLON@10..11 ":" diff --git a/src/syntax/fn_ref.rs b/src/syntax/fn_ref.rs index 6902101..8ba1c69 100644 --- a/src/syntax/fn_ref.rs +++ b/src/syntax/fn_ref.rs @@ -7,16 +7,15 @@ use nom::{ }; use super::{ - combinator::{ - colon_token, debug_assert_lossless, l_bracket_token, node, r_bracket_token, GreenElement, - }, + combinator::{colon_token, l_bracket_token, node, r_bracket_token, GreenElement}, input::Input, object::object_nodes, SyntaxKind::*, }; +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn fn_ref_node(input: Input) -> IResult { - debug_assert_lossless(fn_ref_node_base)(input) + crate::lossless_parser!(fn_ref_node_base, input) } fn fn_ref_node_base(input: Input) -> IResult { diff --git a/src/syntax/headline.rs b/src/syntax/headline.rs index afd26aa..347fbf4 100644 --- a/src/syntax/headline.rs +++ b/src/syntax/headline.rs @@ -6,12 +6,11 @@ use nom::{ sequence::tuple, AsBytes, IResult, InputLength, InputTake, Slice, }; -use tracing::instrument; use super::{ combinator::{ - debug_assert_lossless, hash_token, l_bracket_token, line_starts_iter, node, - r_bracket_token, token, trim_line_end, GreenElement, NodeBuilder, + hash_token, l_bracket_token, line_starts_iter, node, r_bracket_token, token, trim_line_end, + GreenElement, NodeBuilder, }, drawer::property_drawer_node, element::element_nodes, @@ -21,11 +20,11 @@ use super::{ SyntaxKind::*, }; +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn headline_node(input: Input) -> IResult { - debug_assert_lossless(headline_node_base)(input) + crate::lossless_parser!(headline_node_base, input) } -#[instrument(skip(input), fields(input = input.s))] fn headline_node_base(input: Input) -> IResult { let (input, stars) = headline_stars(input)?; @@ -90,7 +89,7 @@ fn headline_node_base(input: Input) -> IResult { Ok((i, b.finish(HEADLINE))) } -#[instrument(skip(input), fields(input = input.s))] +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn section_node(input: Input) -> IResult { let (input, section) = section_text(input)?; Ok((input, node(SECTION, element_nodes(section)?))) @@ -114,7 +113,7 @@ pub fn section_text(input: Input) -> IResult { Ok(input.take_split(input.input_len())) } -#[instrument(skip(input), fields(input = input.s))] +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] fn headline_stars(input: Input) -> IResult { let bytes = input.as_bytes(); let level = bytes.iter().take_while(|&&c| c == b'*').count(); @@ -130,7 +129,7 @@ fn headline_stars(input: Input) -> IResult { } } -#[instrument(skip(input), fields(input = input.s))] +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] fn headline_tags_node(input: Input) -> IResult { if !input.s.ends_with(':') { return Err(nom::Err::Error(())); @@ -245,8 +244,7 @@ fn parse() { NEW_LINE@5..6 "\n" SECTION@6..7 PARAGRAPH@6..7 - BLANK_LINE@6..7 - NEW_LINE@6..7 "\n" + BLANK_LINE@6..7 "\n" HEADLINE@7..13 HEADLINE_STARS@7..9 "**" WHITESPACE@9..10 " " diff --git a/src/syntax/inline_call.rs b/src/syntax/inline_call.rs index 15c07a2..3078315 100644 --- a/src/syntax/inline_call.rs +++ b/src/syntax/inline_call.rs @@ -7,15 +7,15 @@ use nom::{ use super::{ combinator::{ - debug_assert_lossless, l_bracket_token, l_parens_token, node, r_bracket_token, - r_parens_token, GreenElement, + l_bracket_token, l_parens_token, node, r_bracket_token, r_parens_token, GreenElement, }, input::Input, SyntaxKind, }; +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn inline_call_node(input: Input) -> IResult { - debug_assert_lossless(map( + let mut parser = map( tuple(( tag("call_"), take_till(|c| c == '[' || c == '\n' || c == '(' || c == ')'), @@ -51,7 +51,8 @@ pub fn inline_call_node(input: Input) -> IResult { } node(SyntaxKind::INLINE_CALL, children) }, - ))(input) + ); + crate::lossless_parser!(parser, input) } #[test] diff --git a/src/syntax/inline_src.rs b/src/syntax/inline_src.rs index 6d6c98a..4fc32b5 100644 --- a/src/syntax/inline_src.rs +++ b/src/syntax/inline_src.rs @@ -7,15 +7,15 @@ use nom::{ use super::{ combinator::{ - debug_assert_lossless, l_bracket_token, l_curly_token, node, r_bracket_token, - r_curly_token, GreenElement, + l_bracket_token, l_curly_token, node, r_bracket_token, r_curly_token, GreenElement, }, input::Input, SyntaxKind, }; +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn inline_src_node(input: Input) -> IResult { - debug_assert_lossless(map( + let mut parser = map( tuple(( tag("src_"), take_while1(|c: char| !c.is_ascii_whitespace() && c != '[' && c != '{'), @@ -40,7 +40,8 @@ pub fn inline_src_node(input: Input) -> IResult { children.push(r_curly); node(SyntaxKind::INLINE_SRC, children) }, - ))(input) + ); + crate::lossless_parser!(parser, input) } #[test] diff --git a/src/syntax/keyword.rs b/src/syntax/keyword.rs index 9757c6c..8039331 100644 --- a/src/syntax/keyword.rs +++ b/src/syntax/keyword.rs @@ -1,106 +1,128 @@ use nom::{ - bytes::complete::take_till, + branch::alt, + bytes::complete::{tag, take_till, take_while1}, character::complete::space0, - combinator::{cond, opt}, + combinator::{recognize, verify}, sequence::tuple, - IResult, + IResult, InputLength, InputTake, }; +use rowan::GreenNode; use super::{ - combinator::{ - blank_lines, colon_token, debug_assert_lossless, hash_plus_token, l_bracket_token, - r_bracket_token, trim_line_end, GreenElement, NodeBuilder, - }, + combinator::{blank_lines, hash_plus_token, trim_line_end, GreenElement}, input::Input, SyntaxKind, }; +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn keyword_node(input: Input) -> IResult { - debug_assert_lossless(keyword_node_base)(input) -} - -fn keyword_node_base(input: Input) -> IResult { - let (input, (ws, hash_plus, key)) = tuple(( - space0, - hash_plus_token, - take_till(|c: char| c.is_ascii_whitespace() || c == ':' || c == '['), - ))(input)?; - - let is_babel_call = key.s.eq_ignore_ascii_case("CALL"); - - let (input, optional) = cond( - !is_babel_call, - opt(tuple(( - l_bracket_token, - take_till(|c| c == ']' || c == '\n'), - r_bracket_token, - ))), - )(input)?; - - let (input, (colon, (value, ws_, nl), post_blank)) = - tuple((colon_token, trim_line_end, blank_lines))(input)?; - - let mut b = NodeBuilder::new(); - - b.ws(ws); - b.push(hash_plus); - b.text(key); - if let Some(Some((l_bracket, optional, r_bracket))) = optional { - b.children - .extend([l_bracket, optional.text_token(), r_bracket]); + fn f(input: Input) -> IResult { + let (input, (key, mut nodes, post_blank)) = keyword_node_base(input)?; + nodes.extend(post_blank); + Ok(( + input, + GreenElement::Node(GreenNode::new( + if key == "CALL" { + SyntaxKind::BABEL_CALL.into() + } else { + SyntaxKind::KEYWORD.into() + }, + nodes, + )), + )) } - b.push(colon); - b.ws(ws_); - b.text(value); - b.nl(nl); - b.children.extend(post_blank); - - Ok(( - input, - b.finish(if is_babel_call { - SyntaxKind::BABEL_CALL - } else { - SyntaxKind::KEYWORD - }), - )) + crate::lossless_parser!(f, input) } +/// Return empty vector if input doesn't contain affiliated keyword, or affiliated keyword is +/// followed by blank lines. +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn affiliated_keyword_nodes(input: Input) -> IResult, ()> { - use rowan::NodeOrToken; - let mut children = vec![]; let mut i = input; while !i.is_empty() { - let Ok((input, keyword)) = keyword_node(i) else { + let Ok((input_, (key, nodes, post_blank))) = keyword_node_base(i) else { break; }; - i = input; - let Some(node) = keyword.as_node() else { - return Err(nom::Err::Error(())); - }; - - // find the first text token in children - let Some(NodeOrToken::Token(token)) = node - .children() - .find(|t| t.kind() == SyntaxKind::TEXT.into()) - else { - return Err(nom::Err::Error(())); - }; - - let text = token.text(); - - if input.c.affiliated_keywords.iter().all(|w| w != text) && !text.starts_with("ATTR_") { - return Err(nom::Err::Error(())); + // affiliated keyword can not followed by blank lines or eof + if !post_blank.is_empty() || input_.is_empty() { + return Ok((input, vec![])); } - children.push(keyword); + if input_.c.affiliated_keywords.iter().all(|w| w != key) && !key.starts_with("ATTR_") { + break; + } + + i = input_; + children.push(GreenElement::Node(GreenNode::new( + SyntaxKind::AFFILIATED_KEYWORD.into(), + nodes, + ))); } Ok((i, children)) } +fn keyword_node_base( + input: Input, +) -> IResult, Vec), ()> { + let (input, (ws, hash_plus)) = tuple((space0, hash_plus_token))(input)?; + + let (input, (key, optional, colon)) = alt((key_with_optional, key))(input)?; + + let (input, (value, ws_, nl)) = trim_line_end(input)?; + let (input, post_blank) = blank_lines(input)?; + + let mut children = vec![]; + if !ws.is_empty() { + children.push(ws.ws_token()); + } + children.push(hash_plus); + children.push(key.text_token()); + if let Some((l_bracket, optional, r_bracket)) = optional { + children.push(l_bracket.token(SyntaxKind::L_BRACKET)); + children.push(optional.text_token()); + children.push(r_bracket.token(SyntaxKind::R_BRACKET)); + } + children.push(colon.token(SyntaxKind::COLON)); + children.push(value.text_token()); + if !ws_.is_empty() { + children.push(ws_.ws_token()); + } + if !nl.is_empty() { + children.push(nl.nl_token()); + } + + Ok((input, (key.s, children, post_blank))) +} + +fn key(input: Input) -> IResult, Input), ()> { + let (input, output) = verify( + recognize(tuple(( + take_till(|c: char| c.is_ascii_whitespace() || c == ':'), + take_while1(|c: char| c == ':'), + ))), + |i: &Input| i.input_len() >= 2, + )(input)?; + let (colon, key) = output.take_split(output.input_len() - 1); + Ok((input, (key, None, colon))) +} + +fn key_with_optional( + input: Input, +) -> IResult, Input), ()> { + let (input, (key, r_backer, optional, l_backer, colon)) = tuple(( + alt((tag("CAPTION"), tag("RESULTS"))), + tag("["), + take_till(|c| c == '\r' || c == '\n' || c == ']'), + tag("]"), + tag(":"), + ))(input)?; + Ok((input, (key, Some((r_backer, optional, l_backer)), colon))) +} + #[test] fn parse() { use crate::{ @@ -113,6 +135,13 @@ fn parse() { let to_babel_call = to_ast::(keyword_node); + to_keyword("#+KEY:"); + to_keyword("#+::"); + to_keyword("#+::"); + to_keyword("#+:: "); + to_keyword("#+:: \n"); + to_keyword("#+::\n"); + insta::assert_debug_snapshot!( to_keyword("#+KEY:").syntax, @r###" @@ -193,22 +222,43 @@ fn parse() { ); insta::assert_debug_snapshot!( - to_keyword("#+CAPTION[Short caption]: Longer caption.").syntax, + to_keyword("#+ABC[OPTIONAL]: Longer value.").syntax, @r###" - KEYWORD@0..41 + KEYWORD@0..30 + HASH_PLUS@0..2 "#+" + TEXT@2..15 "ABC[OPTIONAL]" + COLON@15..16 ":" + TEXT@16..30 " Longer value." + "### + ); + + insta::assert_debug_snapshot!( + to_keyword("#+CAPTION: value").syntax, + @r###" + KEYWORD@0..16 + HASH_PLUS@0..2 "#+" + TEXT@2..9 "CAPTION" + COLON@9..10 ":" + TEXT@10..16 " value" + "### + ); + + insta::assert_debug_snapshot!( + to_keyword("#+CAPTION[caption optional]: value").syntax, + @r###" + KEYWORD@0..34 HASH_PLUS@0..2 "#+" TEXT@2..9 "CAPTION" L_BRACKET@9..10 "[" - TEXT@10..23 "Short caption" - R_BRACKET@23..24 "]" - COLON@24..25 ":" - TEXT@25..41 " Longer caption." + TEXT@10..26 "caption optional" + R_BRACKET@26..27 "]" + COLON@27..28 ":" + TEXT@28..34 " value" "### ); let config = &ParseConfig::default(); assert!(keyword_node(("#+KE Y: VALUE", config).into()).is_err()); - assert!(keyword_node(("#+CALL[option]: VALUE", config).into()).is_err()); assert!(keyword_node(("#+ KEY: VALUE", config).into()).is_err()); } diff --git a/src/syntax/link.rs b/src/syntax/link.rs index 85b1824..680529a 100644 --- a/src/syntax/link.rs +++ b/src/syntax/link.rs @@ -7,15 +7,15 @@ use nom::{ use super::{ combinator::{ - debug_assert_lossless, l_bracket2_token, l_bracket_token, node, r_bracket2_token, - r_bracket_token, GreenElement, + l_bracket2_token, l_bracket_token, node, r_bracket2_token, r_bracket_token, GreenElement, }, input::Input, SyntaxKind::*, }; +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn link_node(input: Input) -> IResult { - debug_assert_lossless(map( + let mut parser = map( tuple(( l_bracket2_token, take_while(|c: char| c != '<' && c != '>' && c != '\n' && c != ']'), @@ -37,7 +37,8 @@ pub fn link_node(input: Input) -> IResult { node(LINK, children) }, - ))(input) + ); + crate::lossless_parser!(parser, input) } #[test] diff --git a/src/syntax/list.rs b/src/syntax/list.rs index 45890b6..098d2f0 100644 --- a/src/syntax/list.rs +++ b/src/syntax/list.rs @@ -10,24 +10,29 @@ use nom::{ use super::{ combinator::{ - at_token, blank_lines, colon2_token, debug_assert_lossless, l_bracket_token, - line_starts_iter, node, r_bracket_token, GreenElement, + at_token, blank_lines, colon2_token, l_bracket_token, line_starts_iter, node, + r_bracket_token, GreenElement, }, element::element_node, input::Input, + keyword::affiliated_keyword_nodes, object::object_nodes, SyntaxKind::*, }; +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn list_node(input: Input) -> IResult { - debug_assert_lossless(list_node_base)(input) + crate::lossless_parser!(list_node_base, input) } fn list_node_base(input: Input) -> IResult { + let (input, affiliated_keywords) = affiliated_keyword_nodes(input)?; let (input, first_indent) = space0(input)?; let (input, first_item) = list_item_node(first_indent, input)?; - let mut children = vec![first_item]; + let mut children = vec![]; + children.extend(affiliated_keywords); + children.push(first_item); let mut input = input; while !input.is_empty() { @@ -422,8 +427,10 @@ fn parse() { LIST_ITEM_INDENT@0..0 "" LIST_ITEM_BULLET@0..2 "* " LIST_ITEM_CONTENT@2..23 - PARAGRAPH@2..23 - TEXT@2..23 "item1\n\n still item 1" + PARAGRAPH@2..9 + TEXT@2..9 "item1\n\n" + PARAGRAPH@9..23 + TEXT@9..23 " still item 1" "### ); diff --git a/src/syntax/macros.rs b/src/syntax/macros.rs index 1c187f3..2568bf9 100644 --- a/src/syntax/macros.rs +++ b/src/syntax/macros.rs @@ -7,15 +7,15 @@ use nom::{ use super::{ combinator::{ - debug_assert_lossless, l_curly3_token, l_parens_token, node, r_curly3_token, - r_parens_token, GreenElement, + l_curly3_token, l_parens_token, node, r_curly3_token, r_parens_token, GreenElement, }, input::Input, SyntaxKind::*, }; +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn macros_node(input: Input) -> IResult { - debug_assert_lossless(map( + let mut parser = map( tuple(( l_curly3_token, verify( @@ -38,7 +38,8 @@ pub fn macros_node(input: Input) -> IResult { children.push(r_curly3); node(MACROS, children) }, - ))(input) + ); + crate::lossless_parser!(parser, input) } #[test] diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 83522a7..e6625c4 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -144,6 +144,7 @@ pub enum SyntaxKind { DRAWER_END, KEYWORD, BABEL_CALL, + AFFILIATED_KEYWORD, TABLE_EL, CLOCK, FN_DEF, diff --git a/src/syntax/paragraph.rs b/src/syntax/paragraph.rs index d723ae3..27a59ca 100644 --- a/src/syntax/paragraph.rs +++ b/src/syntax/paragraph.rs @@ -1,15 +1,22 @@ use nom::{IResult, InputTake}; use super::{ - combinator::{blank_lines, debug_assert_lossless, line_ends_iter, node, GreenElement}, + combinator::{blank_lines, line_ends_iter, node, GreenElement}, input::Input, + keyword::affiliated_keyword_nodes, object::object_nodes, SyntaxKind, }; +pub fn paragraph_node(input: Input) -> IResult { + crate::lossless_parser!(paragraph_node_base, input) +} + fn paragraph_node_base(input: Input) -> IResult { debug_assert!(!input.is_empty()); + let (input, keywords) = affiliated_keyword_nodes(input)?; + let mut start = 0; for idx in line_ends_iter(input.as_str()) { // stops at blank line @@ -24,27 +31,13 @@ fn paragraph_node_base(input: Input) -> IResult { let (input, post_blank) = blank_lines(input)?; let mut children = vec![]; + children.extend(keywords); children.extend(object_nodes(contents)); children.extend(post_blank); Ok((input, node(SyntaxKind::PARAGRAPH, children))) } -pub fn paragraph_node(input: Input) -> IResult { - debug_assert_lossless(paragraph_node_base)(input) -} - -pub fn paragraph_nodes(input: Input) -> Result, nom::Err<()>> { - let mut i = input; - let mut children = vec![]; - while !i.is_empty() { - let (input, node) = paragraph_node(i)?; - children.push(node); - i = input; - } - Ok(children) -} - #[test] fn parse() { use crate::{ast::Paragraph, tests::to_ast}; diff --git a/src/syntax/planning.rs b/src/syntax/planning.rs index a93cf91..284d9e3 100644 --- a/src/syntax/planning.rs +++ b/src/syntax/planning.rs @@ -8,14 +8,14 @@ use nom::{ }; use super::{ - combinator::{debug_assert_lossless, GreenElement, NodeBuilder}, + combinator::{GreenElement, NodeBuilder}, input::Input, timestamp::{timestamp_active_node, timestamp_inactive_node}, SyntaxKind::*, }; pub fn planning_node(input: Input) -> IResult { - debug_assert_lossless(planning_node_base)(input) + crate::lossless_parser!(planning_node_base, input) } fn planning_node_base(input: Input) -> IResult { diff --git a/src/syntax/radio_target.rs b/src/syntax/radio_target.rs index 6787ad7..3fc39f5 100644 --- a/src/syntax/radio_target.rs +++ b/src/syntax/radio_target.rs @@ -6,7 +6,7 @@ use nom::{ }; use super::{ - combinator::{debug_assert_lossless, l_angle3_token, node, r_angle3_token, GreenElement}, + combinator::{l_angle3_token, node, r_angle3_token, GreenElement}, input::Input, SyntaxKind::*, }; @@ -14,7 +14,7 @@ use super::{ // TODO: text-markup, entities, latex-fragments, subscript and superscript pub fn radio_target_node(input: Input) -> IResult { - debug_assert_lossless(map( + let mut parser = map( tuple(( l_angle3_token, verify( @@ -28,7 +28,8 @@ pub fn radio_target_node(input: Input) -> IResult { |(l_angle3, contents, r_angle3)| { node(RADIO_TARGET, [l_angle3, contents.text_token(), r_angle3]) }, - ))(input) + ); + crate::lossless_parser!(parser, input) } #[test] diff --git a/src/syntax/rule.rs b/src/syntax/rule.rs index 2a54ad1..9dacd19 100644 --- a/src/syntax/rule.rs +++ b/src/syntax/rule.rs @@ -8,13 +8,13 @@ use nom::{ }; use super::{ - combinator::{blank_lines, debug_assert_lossless, GreenElement, NodeBuilder}, + combinator::{blank_lines, GreenElement, NodeBuilder}, input::Input, SyntaxKind::*, }; pub fn rule_node(input: Input) -> IResult { - debug_assert_lossless(map( + let mut parser = map( tuple(( space0, take_while_m_n(5, usize::max_value(), |c| c == '-'), @@ -31,7 +31,8 @@ pub fn rule_node(input: Input) -> IResult { b.children.extend(post_blank); b.finish(RULE) }, - ))(input) + ); + crate::lossless_parser!(parser, input) } #[test] diff --git a/src/syntax/snippet.rs b/src/syntax/snippet.rs index e09e914..5f55f44 100644 --- a/src/syntax/snippet.rs +++ b/src/syntax/snippet.rs @@ -6,13 +6,13 @@ use nom::{ }; use super::{ - combinator::{at2_token, colon_token, debug_assert_lossless, node, GreenElement}, + combinator::{at2_token, colon_token, node, GreenElement}, input::Input, SyntaxKind::*, }; pub fn snippet_node(input: Input) -> IResult { - debug_assert_lossless(map( + let mut parser = map( tuple(( at2_token, take_while1(|c: char| c.is_ascii_alphanumeric() || c == '-'), @@ -26,7 +26,8 @@ pub fn snippet_node(input: Input) -> IResult { [at2, name.text_token(), colon, value.text_token(), at2_], ) }, - ))(input) + ); + crate::lossless_parser!(parser, input) } #[test] diff --git a/src/syntax/table.rs b/src/syntax/table.rs index 9097349..cb14403 100644 --- a/src/syntax/table.rs +++ b/src/syntax/table.rs @@ -7,10 +7,7 @@ use nom::{ }; use super::{ - combinator::{ - blank_lines, debug_assert_lossless, line_ends_iter, node, pipe_token, GreenElement, - NodeBuilder, - }, + combinator::{blank_lines, line_ends_iter, node, pipe_token, GreenElement, NodeBuilder}, input::Input, object::object_nodes, SyntaxKind::*, @@ -120,12 +117,14 @@ fn table_el_node_base(input: Input) -> IResult { Ok((input, node(TABLE_EL, children))) } +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn org_table_node(input: Input) -> IResult { - debug_assert_lossless(org_table_node_base)(input) + crate::lossless_parser!(org_table_node_base, input) } +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn table_el_node(input: Input) -> IResult { - debug_assert_lossless(table_el_node_base)(input) + crate::lossless_parser!(table_el_node_base, input) } #[test] diff --git a/src/syntax/target.rs b/src/syntax/target.rs index 2368d9b..f42b90f 100644 --- a/src/syntax/target.rs +++ b/src/syntax/target.rs @@ -6,13 +6,14 @@ use nom::{ }; use super::{ - combinator::{debug_assert_lossless, l_angle2_token, node, r_angle2_token, GreenElement}, + combinator::{l_angle2_token, node, r_angle2_token, GreenElement}, input::Input, SyntaxKind::*, }; +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn target_node(input: Input) -> IResult { - debug_assert_lossless(map( + let mut parser = map( tuple(( l_angle2_token, verify( @@ -24,7 +25,8 @@ pub fn target_node(input: Input) -> IResult { r_angle2_token, )), |(l_angle2, target, r_angle2)| node(TARGET, [l_angle2, target.text_token(), r_angle2]), - ))(input) + ); + crate::lossless_parser!(parser, input) } #[test] diff --git a/src/syntax/timestamp.rs b/src/syntax/timestamp.rs index 4188069..63c673a 100644 --- a/src/syntax/timestamp.rs +++ b/src/syntax/timestamp.rs @@ -8,16 +8,17 @@ use nom::{ use super::{ combinator::{ - colon_token, debug_assert_lossless, l_angle_token, l_bracket_token, l_parens_token, - minus2_token, minus_token, node, percent2_token, r_angle_token, r_bracket_token, - r_parens_token, GreenElement, NodeBuilder, + colon_token, l_angle_token, l_bracket_token, l_parens_token, minus2_token, minus_token, + node, percent2_token, r_angle_token, r_bracket_token, r_parens_token, GreenElement, + NodeBuilder, }, input::Input, SyntaxKind::*, }; +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn timestamp_diary_node(input: Input) -> IResult { - debug_assert_lossless(map( + let mut parser = map( tuple(( l_angle_token, percent2_token, @@ -39,7 +40,8 @@ pub fn timestamp_diary_node(input: Input) -> IResult { ], ) }, - ))(input) + ); + crate::lossless_parser!(parser, input) } fn is_digit_str(s: &Input) -> bool { @@ -231,11 +233,14 @@ fn timestamp_inactive_node_base(input: Input) -> IResult IResult { - debug_assert_lossless(timestamp_active_node_base)(input) + crate::lossless_parser!(timestamp_active_node_base, input) } + +#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))] pub fn timestamp_inactive_node(input: Input) -> IResult { - debug_assert_lossless(timestamp_inactive_node_base)(input) + crate::lossless_parser!(timestamp_inactive_node_base, input) } #[test]