feat: support affiliated keyword
This commit is contained in:
parent
a269f2f258
commit
1362624083
34 changed files with 946 additions and 399 deletions
58
src/ast/affiliated_keyword.rs
Normal file
58
src/ast/affiliated_keyword.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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`;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
mod generated;
|
||||
|
||||
|
||||
mod affiliated_keyword;
|
||||
mod drawer;
|
||||
mod headline;
|
||||
mod inline_call;
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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, ()> {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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."
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, ()> {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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 ":"
|
||||
|
|
|
|||
|
|
@ -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, ()> {
|
||||
|
|
|
|||
|
|
@ -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 " "
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
"###
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ pub enum SyntaxKind {
|
|||
DRAWER_END,
|
||||
KEYWORD,
|
||||
BABEL_CALL,
|
||||
AFFILIATED_KEYWORD,
|
||||
TABLE_EL,
|
||||
CLOCK,
|
||||
FN_DEF,
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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, ()> {
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue