Scaffold table token

This commit is contained in:
Juno Takano 2026-02-16 01:06:24 -03:00
commit b890eb93f1
6 changed files with 217 additions and 4 deletions

View file

@ -8,6 +8,7 @@ pub mod inline;
pub mod anchor;
pub mod list;
pub mod quote;
pub mod table;
#[derive(Clone, Default, Debug)]
pub struct Context {
@ -22,6 +23,7 @@ pub enum Block {
List,
PreFormat,
Quote,
Table,
Verse,
#[default]
None,
@ -54,6 +56,9 @@ pub fn close(state: &State, tokens: &mut Vec<Token>) {
Block::Quote => {
panic!("End of input with open quote")
},
Block::Table => {
panic!("End of input with open table")
},
Block::Verse => {
tokens.push(Token::Verse(Verse::new(false)));
},

View file

@ -9,7 +9,7 @@ use crate::{
Block, Lexeme, State, Token,
token::{
Header, List, LineBreak, Literal, Paragraph, PreFormat, Quote,
Verse,
Table, Verse,
},
},
},
@ -59,6 +59,11 @@ pub fn parse(
iterator.next();
iterator.next();
return true;
} else if Table::probe(lexeme) {
log!(VERBOSE, "Block Context: None -> Table on {lexeme}");
state.context.block = Block::Table;
iterator.next();
return true;
} else if Paragraph::probe(lexeme) {
log!(VERBOSE, "Block Context: None -> Paragraph on {lexeme}");
state.context.block = Block::Paragraph;
@ -95,6 +100,9 @@ pub fn parse(
Block::Quote => {
return super::quote::parse(lexeme, state, tokens, iterator, graph);
},
Block::Table => {
return super::table::parse(lexeme, state, tokens, iterator, graph);
},
Block::Verse => {
if Verse::probe_end(lexeme) {
tokens.push(Token::Verse(Verse::new(false)));

View file

@ -0,0 +1,114 @@
use std::{iter::Peekable, slice::Iter};
use crate::{
graph::Graph,
prelude::*,
syntax::content::parser::{
Lexeme, State, Token, context::Block, format, state, token::Table,
},
};
/// Handles open table contexts until a table is fully parsed.
///
/// A return of `true` will trigger a continue in the outer parser,
/// skipping any further parsing of the current lexeme.
///
/// # Panics
/// This parser can handle only the Table context, and will panic if passed an
/// unrelated context since it has no knowledge on how to handle them.
pub fn parse(
lexeme: &Lexeme,
state: &mut State,
tokens: &mut Vec<Token>,
iterator: &mut Peekable<Iter<'_, Lexeme>>,
graph: &Graph,
) -> bool {
let buffer = &mut state.buffers.table;
let candidate = &mut buffer.candidate;
let mut parse_text = |text: &str| {
let (parsed_text, text_tokens) = format(text, graph);
state.format_tokens.extend_from_slice(&text_tokens);
parsed_text
};
#[allow(clippy::wildcard_enum_match_arm)]
match state.context.block {
Block::Table => {
if Table::probe_end(lexeme) {
log!(VERBOSE, "Probed end of table on {lexeme}");
if buffer.in_header {
log!(VERBOSE, "Adding unterminated header: {lexeme}");
candidate.add_header(&parse_text(&buffer.cell));
buffer.cell.clear();
iterator.next();
iterator.next();
} else if buffer.in_cell {
log!(VERBOSE, "Adding unterminated cell: {lexeme}");
candidate.add_cell(&parse_text(&buffer.cell));
buffer.cell.clear();
iterator.next();
iterator.next();
} else {
log!(VERBOSE, "Adding undelimited cell: {lexeme}");
candidate.add_cell(&parse_text(&buffer.cell));
buffer.cell.clear();
iterator.next();
iterator.next();
}
tokens.push(Token::Table(candidate.clone()));
log!(VERBOSE, "Block Context: Table -> None on {lexeme}");
state.context.block = Block::None;
*buffer = state::TableBuffer::default();
iterator.next();
} else if lexeme.match_char('\n') {
log!(VERBOSE, "Adding row: found newline on {lexeme}");
if !buffer.cell.is_empty() {
if buffer.in_header {
log!(VERBOSE, "Adding unterminated header: {lexeme}");
candidate.add_header(&parse_text(&buffer.cell));
buffer.cell.clear();
iterator.next();
iterator.next();
} else if buffer.in_cell {
log!(VERBOSE, "Adding unterminated cell: {lexeme}");
candidate.add_cell(&parse_text(&buffer.cell));
buffer.cell.clear();
iterator.next();
iterator.next();
} else {
log!(VERBOSE, "Adding undelimited cell: {lexeme}");
candidate.add_cell(&parse_text(&buffer.cell));
buffer.cell.clear();
iterator.next();
iterator.next();
}
}
candidate.add_row(vec![]);
} else if lexeme.match_char_triple(' ', '!', ' ') {
log!(VERBOSE, "Adding header: found spaced ! on {lexeme}");
candidate.add_header(&parse_text(&buffer.cell));
buffer.cell.clear();
iterator.next();
iterator.next();
} else if lexeme.match_char_triple(' ', '|', ' ') {
log!(VERBOSE, "Adding cell: found spaced | on {lexeme}");
candidate.add_cell(&parse_text(&buffer.cell));
buffer.cell.clear();
iterator.next();
iterator.next();
} else {
log!(VERBOSE, "Extending cell text on {lexeme}");
buffer.cell.push_str(lexeme.text().as_str());
}
},
_ => {
panic!("Table context parser called to handle non-table context")
},
}
true
}

View file

@ -3,7 +3,7 @@ use std::collections::HashMap;
use crate::syntax::content::parser::{
Token,
context::Context,
token::{Anchor, Item, List, Quote},
token::{Anchor, Item, List, Quote, Table},
};
#[derive(Clone, Default, Debug)]
@ -28,6 +28,7 @@ pub struct Buffers {
pub anchor: AnchorBuffer,
pub list: ListBuffer,
pub quote: QuoteBuffer,
pub table: TableBuffer,
}
#[derive(Default, Clone, Debug)]
@ -50,6 +51,14 @@ pub struct QuoteBuffer {
pub in_citation: bool,
}
#[derive(Default, Clone, Debug)]
pub struct TableBuffer {
pub candidate: Table,
pub cell: String,
pub in_cell: bool,
pub in_header: bool,
}
impl std::fmt::Display for AnchorBuffer {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let display_text = if self.text.is_empty() {

View file

@ -14,14 +14,15 @@ pub mod paragraph;
pub mod preformat;
pub mod quote;
pub mod strike;
pub mod table;
pub mod underline;
pub mod verse;
pub use {
anchor::Anchor, bold::Bold, checkbox::CheckBox, code::Code, header::Header,
item::Item, linebreak::LineBreak, list::List, literal::Literal,
oblique::Oblique, paragraph::Paragraph, preformat::PreFormat,
strike::Strike, underline::Underline, quote::Quote, verse::Verse,
oblique::Oblique, paragraph::Paragraph, preformat::PreFormat, quote::Quote,
strike::Strike, table::Table, underline::Underline, verse::Verse,
};
#[derive(Debug, Eq, PartialEq, Clone)]
@ -40,6 +41,7 @@ pub enum Token {
Paragraph(Paragraph),
PreFormat(PreFormat),
Quote(Quote),
Table(Table),
Underline(Underline),
Verse(Verse),
}
@ -61,6 +63,7 @@ impl Token {
Token::Paragraph(d) => d.render(),
Token::PreFormat(d) => d.render(),
Token::Quote(d) => d.render(),
Token::Table(d) => d.render(),
Token::Underline(d) => d.render(),
Token::Verse(d) => d.render(),
}
@ -82,6 +85,7 @@ impl Token {
Token::Paragraph(d) => d.flatten(),
Token::PreFormat(d) => d.flatten(),
Token::Quote(d) => d.flatten(),
Token::Table(d) => d.flatten(),
Token::Underline(d) => d.flatten(),
Token::Verse(d) => d.flatten(),
}
@ -105,6 +109,7 @@ impl std::fmt::Display for Token {
Token::Paragraph(d) => format!("{d}"),
Token::PreFormat(d) => format!("{d}"),
Token::Quote(d) => format!("{d}"),
Token::Table(d) => format!("{d}"),
Token::Underline(d) => format!("{d}"),
Token::Verse(d) => format!("{d}"),
};

View file

@ -0,0 +1,72 @@
use crate::syntax::content::{Parseable, parser::Lexeme};
#[derive(Debug, Default, Clone, Eq, PartialEq)]
pub struct Table {
pub headers: Vec<String>,
pub contents: Vec<Vec<String>>,
}
impl Table {
pub fn probe_end(lexeme: &Lexeme) -> bool {
lexeme.match_char_triple('\n', '%', '\n') || lexeme.last()
}
pub fn add_header(&mut self, header: &str) {
self.headers.push(header.trim().to_string());
}
pub fn add_row(&mut self, row: Vec<String>) {
self.contents.push(row);
}
pub fn add_cell(&mut self, content: &str) {
if let Some(last) = self.contents.last_mut() {
last.push(content.trim().to_string());
}
}
}
impl Parseable for Table {
fn probe(lexeme: &Lexeme) -> bool {
lexeme.match_char_triple('\n', '%', '\n')
}
fn lex(_lexeme: &Lexeme) -> Table {
Table::default()
}
fn render(&self) -> String {
let mut xml = String::from("\n<table>\n");
if !self.headers.is_empty() {
xml.push_str("<tr>\n");
for header in &self.headers {
xml.push_str(format!("<th>{header}</th>\n").as_str());
}
xml.push_str("\n</tr>\n");
}
for row in &self.contents {
if !row.is_empty() {
xml.push_str("<tr>\n");
for cell in row {
xml.push_str(format!("<td>{cell}</td>\n").as_str());
}
xml.push_str("\n</tr>\n");
}
}
xml.push_str("\n</table>\n");
xml
}
fn flatten(&self) -> String {
String::default()
}
}
impl std::fmt::Display for Table {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Table")
}
}