Start writing the parser for headings.

This commit is contained in:
Tom Alexander 2023-03-23 19:35:32 -04:00
parent d3c804942f
commit 5c8a064eca
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
4 changed files with 101 additions and 8 deletions

View File

@ -1,3 +1,4 @@
#![feature(round_char_boundary)]
// use crate::parser::document; // use crate::parser::document;
use tracing::Level; use tracing::Level;
use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::fmt::format::FmtSpan;

View File

@ -1,9 +1,22 @@
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::combinator::eof;
use nom::combinator::recognize;
use nom::multi::many1_count;
use nom::sequence::tuple;
use crate::parser::error::CustomError;
use crate::parser::error::MyError;
use crate::parser::parser_context::ChainBehavior;
use crate::parser::parser_context::ContextElement; use crate::parser::parser_context::ContextElement;
use crate::parser::parser_context::ContextTree; use crate::parser::parser_context::ContextTree;
use crate::parser::parser_context::ExitMatcherNode;
use super::element::Element; use super::element::Element;
use super::error::Res; use super::error::Res;
use super::parser_with_context::parser_with_context;
use super::source::Source; use super::source::Source;
use super::Context;
#[derive(Debug)] #[derive(Debug)]
pub struct Document<'s> { pub struct Document<'s> {
@ -52,3 +65,76 @@ pub fn document(input: &str) -> Res<&str, Document> {
todo!() todo!()
} }
fn section<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Section<'s>> {
// TODO: The zeroth section is specialized so it probably needs its own parser
let parser_context = context
.with_additional_node(ContextElement::ExitMatcherNode(ExitMatcherNode {
exit_matcher: ChainBehavior::AndParent(Some(&section_end)),
}))
.with_additional_node(ContextElement::Context("section"));
todo!()
}
fn section_end<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, &'s str> {
let heading_matcher = parser_with_context!(heading)(context);
alt((recognize(heading_matcher), eof))(input)
}
fn heading<'r, 's>(context: Context<'r, 's>, input: &'s str) -> Res<&'s str, Heading<'s>> {
let document_root = context.get_document_root().unwrap();
let preceding_character = get_one_before(document_root, input)
.map(|slice| slice.chars().next())
.flatten();
match preceding_character {
Some('\n') => {}
Some(_) => {
// Not at start of line, cannot be a heading
return Err(nom::Err::Error(CustomError::MyError(MyError(
"Heading not at start of line",
))));
}
// If None, we are at the start of the file which allows for headings
None => {}
};
tuple((
many1_count(tag("*")),
// standard set of objects
))(input)?;
todo!()
}
fn get_one_before<'s>(document: &'s str, current_position: &'s str) -> Option<&'s str> {
assert!(is_slice_of(document, current_position));
if document.as_ptr() as usize == current_position.as_ptr() as usize {
return None;
}
let offset = current_position.as_ptr() as usize - document.as_ptr() as usize;
let previous_character_offset = document.floor_char_boundary(offset - 1);
Some(&document[previous_character_offset..offset])
}
fn is_slice_of(parent: &str, child: &str) -> bool {
let parent_start = parent.as_ptr() as usize;
let parent_end = parent_start + parent.len();
let child_start = child.as_ptr() as usize;
let child_end = child_start + child.len();
child_start >= parent_start && child_end <= parent_end
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_one_before_unicode() {
let input = "🧡💛💚💙💜";
let (green_heart_index, _) = input.char_indices().skip(2).next().unwrap();
let starting_with_green_heart = &input[green_heart_index..];
let yellow_heart = get_one_before(input, starting_with_green_heart).unwrap();
assert!(is_slice_of(input, yellow_heart));
assert_eq!(yellow_heart, "💛");
}
}

View File

@ -14,6 +14,6 @@ mod parser_with_context;
// mod plain_list; // mod plain_list;
mod source; mod source;
// mod text; // mod text;
mod token; // mod token;
mod util; mod util;
type Context<'r, 's> = &'r parser_context::ContextTree<'r, 's>; type Context<'r, 's> = &'r parser_context::ContextTree<'r, 's>;

View File

@ -7,7 +7,6 @@ use super::error::MyError;
use super::error::Res; use super::error::Res;
use super::list::List; use super::list::List;
use super::list::Node; use super::list::Node;
use super::token::Token;
use super::Context; use super::Context;
type Matcher = dyn for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str>; type Matcher = dyn for<'r, 's> Fn(Context<'r, 's>, &'s str) -> Res<&'s str, &'s str>;
@ -90,13 +89,25 @@ impl<'r, 's> ContextTree<'r, 's> {
// TODO: Make this a specific error instead of just a generic MyError // TODO: Make this a specific error instead of just a generic MyError
return Err(nom::Err::Error(CustomError::MyError(MyError("NoExit")))); return Err(nom::Err::Error(CustomError::MyError(MyError("NoExit"))));
} }
pub fn get_document_root(&self) -> Option<&'s str> {
for current_node in self.iter() {
let context_element = current_node.get_data();
match context_element {
ContextElement::DocumentRoot(body) => {
return Some(body);
}
_ => {}
}
}
None
}
} }
#[derive(Debug)] #[derive(Debug)]
pub enum ContextElement<'r, 's> { pub enum ContextElement<'r, 's> {
DocumentRoot(&'s str), DocumentRoot(&'s str),
ExitMatcherNode(ExitMatcherNode<'r>), ExitMatcherNode(ExitMatcherNode<'r>),
PreviousElementNode(PreviousElementNode<'s>),
Context(&'r str), Context(&'r str),
ListItem(usize), ListItem(usize),
StartOfParagraph, StartOfParagraph,
@ -107,11 +118,6 @@ pub struct ExitMatcherNode<'r> {
pub exit_matcher: ChainBehavior<'r>, pub exit_matcher: ChainBehavior<'r>,
} }
#[derive(Debug)]
pub struct PreviousElementNode<'r> {
pub element: Token<'r>,
}
#[derive(Clone)] #[derive(Clone)]
pub enum ChainBehavior<'r> { pub enum ChainBehavior<'r> {
AndParent(Option<&'r Matcher>), AndParent(Option<&'r Matcher>),