feat: support affiliated keyword

This commit is contained in:
PoiScript 2023-11-15 00:03:16 +08:00
parent a269f2f258
commit 1362624083
No known key found for this signature in database
GPG key ID: 22C2B1249D99985E
34 changed files with 946 additions and 399 deletions

View file

@ -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::<AffiliatedKeyword>().unwrap();
/// assert_eq!(keyword.key().unwrap().text(), "CAPTION");
/// ```
pub fn key(&self) -> Option<SyntaxToken> {
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::<AffiliatedKeyword>().unwrap();
/// assert!(keyword.optional().is_none());
/// let keyword = Org::parse("#+CAPTION[OPTIONAL]: VALUE\nabc").first_node::<AffiliatedKeyword>().unwrap();
/// assert_eq!(keyword.optional().unwrap().text(), "OPTIONAL");
/// ```
pub fn optional(&self) -> Option<SyntaxToken> {
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::<AffiliatedKeyword>().unwrap();
/// assert_eq!(keyword.value().unwrap().text(), " VALUE");
/// let keyword = Org::parse("#+CAPTION[OPTIONAL]:VALUE\nabc").first_node::<AffiliatedKeyword>().unwrap();
/// assert_eq!(keyword.value().unwrap().text(), "VALUE");
/// ```
pub fn value(&self) -> Option<SyntaxToken> {
self.syntax
.children_with_tokens()
.filter_map(|it| match it {
SyntaxElement::Token(t) if t.kind() == SyntaxKind::TEXT => Some(t),
_ => None,
})
.last()
}
}

View file

@ -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<AffiliatedKeyword> {
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<AffiliatedKeyword> { affiliated_keyword(&self.syntax, |k| k == "CAPTION") }\n`;
content += ` pub fn header(&self) -> Option<AffiliatedKeyword> { affiliated_keyword(&self.syntax, |k| k == "HEADER") }\n`;
content += ` pub fn name(&self) -> Option<AffiliatedKeyword> { affiliated_keyword(&self.syntax, |k| k == "NAME") }\n`;
content += ` pub fn plot(&self) -> Option<AffiliatedKeyword> { affiliated_keyword(&self.syntax, |k| k == "PLOT") }\n`;
content += ` pub fn results(&self) -> Option<AffiliatedKeyword> { affiliated_keyword(&self.syntax, |k| k == "RESULTS") }\n`;
content += ` pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> { affiliated_keyword(&self.syntax, |k| k.starts_with("ATTR_") && &k[5..] == backend) }\n`;
}
content += `}\n`;
}

View file

@ -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<AffiliatedKeyword> {
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<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
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<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
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<ListItem> {
support::children(&self.syntax)
}
pub fn caption(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
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<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
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<AffiliatedKeyword> {
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<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
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<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
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<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
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<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
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<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
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<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
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<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
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<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
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<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
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<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
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<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "CAPTION")
}
pub fn header(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "HEADER")
}
pub fn name(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "NAME")
}
pub fn plot(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "PLOT")
}
pub fn results(&self) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| k == "RESULTS")
}
pub fn attr(&self, backend: &str) -> Option<AffiliatedKeyword> {
affiliated_keyword(&self.syntax, |k| {
k.starts_with("ATTR_") && &k[5..] == backend
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct InlineCall {

View file

@ -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::<Link>().unwrap();
/// assert!(!link.is_image());
/// let link = Org::parse("[[file:/home/dominik/images/jupiter.jpg]]").first_node::<Link>().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::<Link>().unwrap();
/// assert_eq!(link.caption().unwrap().value().unwrap().text(), " image link");
/// ```
pub fn caption(&self) -> Option<AffiliatedKeyword> {
// TODO: support other element type
Paragraph::cast(self.syntax.parent()?.clone())?.caption()
}
}

View file

@ -2,6 +2,7 @@
mod generated;
mod affiliated_keyword;
mod drawer;
mod headline;
mod inline_call;

View file

@ -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<GreenElement> {
nodes
}
#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))]
pub fn block_node(input: Input) -> IResult<Input, GreenElement, ()> {
debug_assert_lossless(block_node_base)(input)
crate::lossless_parser!(block_node_base, input)
}
#[test]

View file

@ -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<Input, GreenElement, ()> {
debug_assert_lossless(map(
let mut parser = map(
tuple((
space0,
tag("CLOCK:"),
@ -56,7 +54,8 @@ pub fn clock_node(input: Input) -> IResult<Input, GreenElement, ()> {
b.children.extend(post_blank);
b.finish(SyntaxKind::CLOCK)
},
))(input)
);
crate::lossless_parser!(parser, input)
}
#[test]

View file

@ -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<Input<'a>, GreenElement, ()>
where
F: Parser<Input<'a>, 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

View file

@ -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<Input, GreenElement, ()> {
Ok((input, node(SyntaxKind::COMMENT, children)))
}
#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))]
pub fn comment_node(input: Input) -> IResult<Input, GreenElement, ()> {
debug_assert_lossless(comment_node_base)(input)
crate::lossless_parser!(comment_node_base, input)
}
#[test]

View file

@ -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<Input, GreenElement, ()> {
debug_assert_lossless(map(
let mut parser = map(
tuple((
l_bracket_token,
alt((
@ -42,7 +41,8 @@ pub fn cookie_node(input: Input) -> IResult<Input, GreenElement, ()> {
node(COOKIE, children)
},
))(input)
);
crate::lossless_parser!(parser, input)
}
#[test]

View file

@ -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<Input, GreenElement, ()> {
debug_assert_lossless(document_node_base)(input)
crate::lossless_parser!(document_node_base, input)
}
fn document_node_base(input: Input) -> IResult<Input, GreenElement, ()> {

View file

@ -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, GreenElement, ()> {
)(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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
debug_assert_lossless(drawer_node_base)(input)
crate::lossless_parser!(drawer_node_base, input)
}
#[test]

View file

@ -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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
debug_assert_lossless(dyn_block_node_base)(input)
crate::lossless_parser!(dyn_block_node_base, input)
}
#[test]

View file

@ -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<Vec<GreenElement>, 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<Vec<GreenElement>, 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<Input, GreenElement, ()> {
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."
"###
);
}

View file

@ -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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
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<Input, Input, ()> {

View file

@ -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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
debug_assert_lossless(fixed_width_node_base)(input)
crate::lossless_parser!(fixed_width_node_base, input)
}
#[test]

View file

@ -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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
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 ":"

View file

@ -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<Input, GreenElement, ()> {
debug_assert_lossless(fn_ref_node_base)(input)
crate::lossless_parser!(fn_ref_node_base, input)
}
fn fn_ref_node_base(input: Input) -> IResult<Input, GreenElement, ()> {

View file

@ -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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
let (input, stars) = headline_stars(input)?;
@ -90,7 +89,7 @@ fn headline_node_base(input: Input) -> IResult<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
let (input, section) = section_text(input)?;
Ok((input, node(SECTION, element_nodes(section)?)))
@ -114,7 +113,7 @@ pub fn section_text(input: Input) -> IResult<Input, Input, ()> {
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<Input, Input, ()> {
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<Input, Input, ()> {
}
}
#[instrument(skip(input), fields(input = input.s))]
#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))]
fn headline_tags_node(input: Input) -> IResult<Input, GreenElement, ()> {
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 " "

View file

@ -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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
}
node(SyntaxKind::INLINE_CALL, children)
},
))(input)
);
crate::lossless_parser!(parser, input)
}
#[test]

View file

@ -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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
children.push(r_curly);
node(SyntaxKind::INLINE_SRC, children)
},
))(input)
);
crate::lossless_parser!(parser, input)
}
#[test]

View file

@ -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<Input, GreenElement, ()> {
debug_assert_lossless(keyword_node_base)(input)
}
fn keyword_node_base(input: Input) -> IResult<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
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<Input, Vec<GreenElement>, ()> {
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<Input, (&str, Vec<GreenElement>, Vec<GreenElement>), ()> {
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, (Input, Option<(Input, Input, Input)>, 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, (Input, Option<(Input, Input, Input)>, 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::<BabelCall>(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());
}

View file

@ -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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
node(LINK, children)
},
))(input)
);
crate::lossless_parser!(parser, input)
}
#[test]

View file

@ -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<Input, GreenElement, ()> {
debug_assert_lossless(list_node_base)(input)
crate::lossless_parser!(list_node_base, input)
}
fn list_node_base(input: Input) -> IResult<Input, GreenElement, ()> {
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"
"###
);

View file

@ -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<Input, GreenElement, ()> {
debug_assert_lossless(map(
let mut parser = map(
tuple((
l_curly3_token,
verify(
@ -38,7 +38,8 @@ pub fn macros_node(input: Input) -> IResult<Input, GreenElement, ()> {
children.push(r_curly3);
node(MACROS, children)
},
))(input)
);
crate::lossless_parser!(parser, input)
}
#[test]

View file

@ -144,6 +144,7 @@ pub enum SyntaxKind {
DRAWER_END,
KEYWORD,
BABEL_CALL,
AFFILIATED_KEYWORD,
TABLE_EL,
CLOCK,
FN_DEF,

View file

@ -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<Input, GreenElement, ()> {
crate::lossless_parser!(paragraph_node_base, input)
}
fn paragraph_node_base(input: Input) -> IResult<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
debug_assert_lossless(paragraph_node_base)(input)
}
pub fn paragraph_nodes(input: Input) -> Result<Vec<GreenElement>, 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};

View file

@ -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<Input, GreenElement, ()> {
debug_assert_lossless(planning_node_base)(input)
crate::lossless_parser!(planning_node_base, input)
}
fn planning_node_base(input: Input) -> IResult<Input, GreenElement, ()> {

View file

@ -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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
|(l_angle3, contents, r_angle3)| {
node(RADIO_TARGET, [l_angle3, contents.text_token(), r_angle3])
},
))(input)
);
crate::lossless_parser!(parser, input)
}
#[test]

View file

@ -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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
b.children.extend(post_blank);
b.finish(RULE)
},
))(input)
);
crate::lossless_parser!(parser, input)
}
#[test]

View file

@ -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<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
[at2, name.text_token(), colon, value.text_token(), at2_],
)
},
))(input)
);
crate::lossless_parser!(parser, input)
}
#[test]

View file

@ -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<Input, GreenElement, ()> {
Ok((input, node(TABLE_EL, children)))
}
#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))]
pub fn org_table_node(input: Input) -> IResult<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
debug_assert_lossless(table_el_node_base)(input)
crate::lossless_parser!(table_el_node_base, input)
}
#[test]

View file

@ -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<Input, GreenElement, ()> {
debug_assert_lossless(map(
let mut parser = map(
tuple((
l_angle2_token,
verify(
@ -24,7 +25,8 @@ pub fn target_node(input: Input) -> IResult<Input, GreenElement, ()> {
r_angle2_token,
)),
|(l_angle2, target, r_angle2)| node(TARGET, [l_angle2, target.text_token(), r_angle2]),
))(input)
);
crate::lossless_parser!(parser, input)
}
#[test]

View file

@ -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<Input, GreenElement, ()> {
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, GreenElement, ()> {
],
)
},
))(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<Input, GreenElement, ()
}
}
#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))]
pub fn timestamp_active_node(input: Input) -> IResult<Input, GreenElement, ()> {
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<Input, GreenElement, ()> {
debug_assert_lossless(timestamp_inactive_node_base)(input)
crate::lossless_parser!(timestamp_inactive_node_base, input)
}
#[test]