feat: support formulas in org table

This commit is contained in:
PoiScript 2023-11-24 16:15:11 +08:00
parent 3c2c8b28fd
commit f8fd1306e2
No known key found for this signature in database
GPG key ID: 22C2B1249D99985E
3 changed files with 129 additions and 24 deletions

View file

@ -1,6 +1,6 @@
use rowan::ast::AstNode;
use super::{OrgTable, OrgTableRow};
use super::{filter_token, OrgTable, OrgTableRow, Token};
use crate::syntax::SyntaxKind;
impl OrgTable {
@ -48,6 +48,37 @@ impl OrgTable {
.skip_while(|row| row.is_standard())
.any(|row| !row.is_rule())
}
/// Formulas associated to the table
///
/// ```rust
/// use orgize::{Org, ast::OrgTable};
///
/// let table = Org::parse("| a |").first_node::<OrgTable>().unwrap();
/// assert_eq!(table.tblfm().count(), 0);
///
/// let table = Org::parse("| a |\n#+tblfm: test").first_node::<OrgTable>().unwrap();
/// let tblfm = table.tblfm().collect::<Vec<_>>();
/// assert_eq!(tblfm.len(), 1);
/// assert_eq!(tblfm[0], " test");
///
/// let table = Org::parse("| a |\n#+TBLFM: test1\n#+TBLFM: test2").first_node::<OrgTable>().unwrap();
/// let tblfm = table.tblfm().collect::<Vec<_>>();
/// assert_eq!(tblfm.len(), 2);
/// assert_eq!(tblfm[0], " test1");
/// assert_eq!(tblfm[1], " test2");
/// ```
pub fn tblfm(&self) -> impl Iterator<Item = Token> {
self.syntax.children().filter_map(|n| {
if n.kind() == SyntaxKind::KEYWORD {
n.children_with_tokens()
.filter_map(filter_token(SyntaxKind::TEXT))
.last()
} else {
None
}
})
}
}
impl OrgTableRow {

View file

@ -8,10 +8,9 @@ use nom::{
sequence::tuple,
IResult, InputLength, InputTake,
};
use rowan::GreenNode;
use super::{
combinator::{blank_lines, hash_plus_token, trim_line_end, GreenElement},
combinator::{blank_lines, hash_plus_token, node, trim_line_end, GreenElement},
input::Input,
SyntaxKind,
};
@ -19,18 +18,19 @@ use super::{
#[tracing::instrument(level = "debug", skip(input), fields(input = input.s))]
pub fn keyword_node(input: Input) -> IResult<Input, GreenElement, ()> {
fn f(input: Input) -> IResult<Input, GreenElement, ()> {
let (input, (key, mut nodes, post_blank)) = keyword_node_base(input)?;
let (input, (key, mut nodes)) = keyword_node_base(input)?;
let (input, post_blank) = blank_lines(input)?;
nodes.extend(post_blank);
Ok((
input,
GreenElement::Node(GreenNode::new(
node(
if key == "CALL" {
SyntaxKind::BABEL_CALL.into()
SyntaxKind::BABEL_CALL
} else {
SyntaxKind::KEYWORD.into()
SyntaxKind::KEYWORD
},
nodes,
)),
),
))
}
crate::lossless_parser!(f, input)
@ -44,10 +44,12 @@ pub fn affiliated_keyword_nodes(input: Input) -> IResult<Input, Vec<GreenElement
let mut i = input;
while !i.is_empty() {
let Ok((input_, (key, nodes, post_blank))) = keyword_node_base(i) else {
let Ok((input_, (key, nodes))) = keyword_node_base(i) else {
break;
};
let (input_, post_blank) = blank_lines(input_)?;
// affiliated keyword can not followed by blank lines or eof
if !post_blank.is_empty() || input_.is_empty() {
return Ok((input, vec![]));
@ -64,24 +66,44 @@ pub fn affiliated_keyword_nodes(input: Input) -> IResult<Input, Vec<GreenElement
input_.input_len()
);
i = input_;
children.push(GreenElement::Node(GreenNode::new(
SyntaxKind::AFFILIATED_KEYWORD.into(),
nodes,
)));
children.push(node(SyntaxKind::AFFILIATED_KEYWORD, nodes));
}
Ok((i, children))
}
fn keyword_node_base(
input: Input,
) -> IResult<Input, (&str, Vec<GreenElement>, Vec<GreenElement>), ()> {
pub fn tblfm_keyword_nodes(input: Input) -> IResult<Input, Vec<GreenElement>, ()> {
let mut children = vec![];
let mut i = input;
while !i.is_empty() {
let Ok((input, (key, nodes))) = keyword_node_base(i) else {
break;
};
if !key.eq_ignore_ascii_case("TBLFM") {
break;
}
debug_assert!(
i.input_len() > input.input_len(),
"{} > {}",
i.input_len(),
input.input_len()
);
i = input;
children.push(node(SyntaxKind::KEYWORD, nodes));
}
Ok((i, children))
}
fn keyword_node_base(input: Input) -> IResult<Input, (&str, 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() {
@ -103,7 +125,7 @@ fn keyword_node_base(
children.push(nl.nl_token());
}
Ok((input, (key.s, children, post_blank)))
Ok((input, (key.s, children)))
}
fn key(input: Input) -> IResult<Input, (Input, Option<(Input, Input, Input)>, Input), ()> {

View file

@ -9,6 +9,7 @@ use nom::{
use super::{
combinator::{blank_lines, line_ends_iter, node, pipe_token, GreenElement, NodeBuilder},
input::Input,
keyword::tblfm_keyword_nodes,
object::standard_object_nodes,
SyntaxKind::*,
};
@ -23,11 +24,7 @@ fn org_table_node_base(input: Input) -> IResult<Input, GreenElement, ()> {
// Org tables end at the first line not starting with a vertical bar.
if !trimmed.starts_with('|') {
if start == 0 {
return Err(nom::Err::Error(()));
} else {
break;
}
break;
}
if trimmed.starts_with("|-") {
@ -39,8 +36,17 @@ fn org_table_node_base(input: Input) -> IResult<Input, GreenElement, ()> {
start = i;
}
let (input, post_blank) = blank_lines(input.slice(start..))?;
if start == 0 {
return Err(nom::Err::Error(()));
}
let input = input.slice(start..);
let (input, tblfm) = tblfm_keyword_nodes(input)?;
let (input, post_blank) = blank_lines(input)?;
children.extend(tblfm);
children.extend(post_blank);
Ok((input, node(ORG_TABLE, children)))
@ -176,6 +182,52 @@ r#"|
WHITESPACE@19..20 "\n"
"###
);
insta::assert_debug_snapshot!(
to_org_table("| a |\n#+tblfm: test").syntax,
@r###"
ORG_TABLE@0..19
ORG_TABLE_STANDARD_ROW@0..6
PIPE@0..1 "|"
WHITESPACE@1..2 " "
ORG_TABLE_CELL@2..3
TEXT@2..3 "a"
WHITESPACE@3..4 " "
PIPE@4..5 "|"
WHITESPACE@5..6 "\n"
KEYWORD@6..19
HASH_PLUS@6..8 "#+"
TEXT@8..13 "tblfm"
COLON@13..14 ":"
TEXT@14..19 " test"
"###
);
insta::assert_debug_snapshot!(
to_org_table("| a |\n#+TBLFM: test1\n#+TBLFM: test2").syntax,
@r###"
ORG_TABLE@0..35
ORG_TABLE_STANDARD_ROW@0..6
PIPE@0..1 "|"
WHITESPACE@1..2 " "
ORG_TABLE_CELL@2..3
TEXT@2..3 "a"
WHITESPACE@3..4 " "
PIPE@4..5 "|"
WHITESPACE@5..6 "\n"
KEYWORD@6..21
HASH_PLUS@6..8 "#+"
TEXT@8..13 "TBLFM"
COLON@13..14 ":"
TEXT@14..20 " test1"
NEW_LINE@20..21 "\n"
KEYWORD@21..35
HASH_PLUS@21..23 "#+"
TEXT@23..28 "TBLFM"
COLON@28..29 ":"
TEXT@29..35 " test2"
"###
);
}
#[test]