QoL changes

This commit is contained in:
ef3d0c3e 2024-08-07 09:50:52 +02:00
parent ce9effd465
commit 62e0aeecef
20 changed files with 181 additions and 103 deletions

View file

@ -55,7 +55,7 @@ pub mod tests {
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
assert_eq!(validate_refname(&*doc, " abc ", true), Ok("abc")); assert_eq!(validate_refname(&*doc, " abc ", true), Ok("abc"));
assert_eq!( assert_eq!(

View file

@ -63,7 +63,9 @@ impl Variable for BaseVariable {
self.to_string(), self.to_string(),
)); ));
state.with_state(|new_state| new_state.parser.parse_into(new_state, source, document)) state.with_state(|new_state| {
let _ = new_state.parser.parse_into(new_state, source, document);
});
} }
} }

View file

@ -680,7 +680,7 @@ fn fact(n: usize) -> usize
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
let borrow = doc.content().borrow(); let borrow = doc.content().borrow();
let found = borrow let found = borrow
@ -726,7 +726,7 @@ fn fact(n: usize) -> usize
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
let borrow = doc.content().borrow(); let borrow = doc.content().borrow();
let found = borrow let found = borrow

View file

@ -123,7 +123,7 @@ COMMENT ::Test
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Paragraph { Paragraph {

View file

@ -399,6 +399,8 @@ impl Rule for CustomStyleRule {
.custom_styles .custom_styles
.borrow_mut() .borrow_mut()
.insert(Rc::new(style)); .insert(Rc::new(style));
ctx.state.reset_match("Custom Style").unwrap();
}); });
}); });
@ -454,6 +456,8 @@ impl Rule for CustomStyleRule {
return; return;
} }
ctx.state.shared.custom_styles.borrow_mut().insert(Rc::new(style)); ctx.state.shared.custom_styles.borrow_mut().insert(Rc::new(style));
ctx.state.reset_match("Custom Style").unwrap();
}); });
}); });
@ -505,7 +509,7 @@ pre |styled| post °Hello°.
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Paragraph { Paragraph {
@ -549,7 +553,7 @@ pre [styled] post (Hello).
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Paragraph { Paragraph {

View file

@ -161,7 +161,7 @@ impl RegexRule for ImportRule {
}; };
state.with_state(|new_state| { state.with_state(|new_state| {
let import_doc = new_state.parser.parse(new_state, import, Some(document)); let (import_doc, _) = new_state.parser.parse(new_state, import, Some(document));
document.merge(import_doc.content(), import_doc.scope(), Some(&import_as)); document.merge(import_doc.content(), import_doc.scope(), Some(&import_as));
}); });

View file

@ -895,7 +895,7 @@ mod tests {
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Layout { token == LayoutToken::Begin, id == 0 }; Layout { token == LayoutToken::Begin, id == 0 };
@ -947,7 +947,7 @@ mod tests {
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Layout { token == LayoutToken::Begin, id == 0 }; Layout { token == LayoutToken::Begin, id == 0 };

View file

@ -284,7 +284,7 @@ Some [link](url).
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Paragraph { Paragraph {
@ -314,7 +314,7 @@ nml.link.push("**BOLD link**", "another url")
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Paragraph { Paragraph {

View file

@ -463,7 +463,7 @@ mod tests {
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let state = ParserState::new(&parser, None); let state = ParserState::new(&parser, None);
let doc = parser.parse(state, source, None); let (doc, _) = parser.parse(state, source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
ListMarker { numbered == false, kind == MarkerKind::Open }; ListMarker { numbered == false, kind == MarkerKind::Open };

View file

@ -548,7 +548,7 @@ mod tests {
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
let borrow = doc.content().borrow(); let borrow = doc.content().borrow();
let group = borrow.first().as_ref().unwrap().as_container().unwrap(); let group = borrow.first().as_ref().unwrap().as_container().unwrap();

View file

@ -165,7 +165,7 @@ Last paragraph
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Paragraph { Paragraph {

View file

@ -283,7 +283,7 @@ Break{?[kind=block] Raw?}NewParagraph{?<b>?}
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Paragraph; Paragraph;
@ -306,7 +306,7 @@ Break%<nml.raw.push("block", "Raw")>%NewParagraph%<nml.raw.push("inline", "<b>")
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Paragraph; Paragraph;

View file

@ -320,7 +320,7 @@ Evaluation: %<! make_ref("hello", "id")>%
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Paragraph; Paragraph;

View file

@ -438,7 +438,7 @@ mod tests {
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Section { depth == 1, title == "1" }; Section { depth == 1, title == "1" };
@ -468,7 +468,7 @@ nml.section.push("6", 6, "", "refname")
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Section { depth == 1, title == "1" }; Section { depth == 1, title == "1" };
@ -495,18 +495,16 @@ nml.section.push("6", 6, "", "refname")
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let state = ParserState::new(&parser, None); let state = ParserState::new(&parser, None);
let _ = parser.parse(state, source, None); let (_, state) = parser.parse(state, source, None);
// TODO2
/*
let style = state.shared let style = state.shared
.styles .styles
.current_style(section_style::STYLE_KEY) .borrow()
.current(section_style::STYLE_KEY)
.downcast_rc::<SectionStyle>() .downcast_rc::<SectionStyle>()
.unwrap(); .unwrap();
assert_eq!(style.link_pos, SectionLinkPos::None); assert_eq!(style.link_pos, SectionLinkPos::None);
assert_eq!(style.link, ["a".to_string(), "b".to_string(), "c".to_string()]); assert_eq!(style.link, ["a".to_string(), "b".to_string(), "c".to_string()]);
*/
} }
} }

View file

@ -230,7 +230,7 @@ __`UNDERLINE+EM`__
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Paragraph { Paragraph {

View file

@ -450,7 +450,7 @@ $[kind=block,env=another] e^{i\pi}=-1$
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Tex { mathmode == true, tex == "1+1=2", env == "main", caption == Some("Some, text\\".to_string()) }; Tex { mathmode == true, tex == "1+1=2", env == "main", caption == Some("Some, text\\".to_string()) };
@ -472,7 +472,7 @@ $[env=another] e^{i\pi}=-1$
None, None,
)); ));
let parser = LangParser::default(); let parser = LangParser::default();
let doc = parser.parse(ParserState::new(&parser, None), source, None); let (doc, _) = parser.parse(ParserState::new(&parser, None), source, None);
validate_document!(doc.content().borrow(), 0, validate_document!(doc.content().borrow(), 0,
Paragraph { Paragraph {

View file

@ -56,7 +56,7 @@ fn parse(
// Parse // Parse
let source = SourceFile::new(input.to_string(), None).unwrap(); let source = SourceFile::new(input.to_string(), None).unwrap();
let doc = parser.parse(ParserState::new(parser, None), Rc::new(source), None); let (doc, _) = parser.parse(ParserState::new(parser, None), Rc::new(source), None);
if debug_opts.contains(&"ast".to_string()) { if debug_opts.contains(&"ast".to_string()) {
println!("-- BEGIN AST DEBUGGING --"); println!("-- BEGIN AST DEBUGGING --");

View file

@ -50,12 +50,12 @@ impl Parser for LangParser {
fn has_error(&self) -> bool { *self.err_flag.borrow() } fn has_error(&self) -> bool { *self.err_flag.borrow() }
fn parse<'a>( fn parse<'p, 'a, 'doc>(
&self, &'p self,
state: ParserState, state: ParserState<'p, 'a>,
source: Rc<dyn Source>, source: Rc<dyn Source>,
parent: Option<&'a dyn Document<'a>>, parent: Option<&'doc dyn Document<'doc>>,
) -> Box<dyn Document<'a> + 'a> { ) -> (Box<dyn Document<'doc> + 'doc>, ParserState<'p, 'a>) {
let doc = LangDocument::new(source.clone(), parent); let doc = LangDocument::new(source.clone(), parent);
let content = source.content(); let content = source.content();
@ -105,7 +105,6 @@ impl Parser for LangParser {
} }
// Rule States // Rule States
self.handle_reports(state.shared.rule_state.borrow_mut().on_scope_end( self.handle_reports(state.shared.rule_state.borrow_mut().on_scope_end(
&state, &state,
&doc, &doc,
@ -120,15 +119,15 @@ impl Parser for LangParser {
))), ))),
); );
return Box::new(doc); return (Box::new(doc), state);
} }
fn parse_into<'a>( fn parse_into<'p, 'a, 'doc>(
&self, &'p self,
state: ParserState, state: ParserState<'p, 'a>,
source: Rc<dyn Source>, source: Rc<dyn Source>,
document: &'a dyn Document<'a>, document: &'doc dyn Document<'doc>,
) { ) -> ParserState<'p, 'a> {
let content = source.content(); let content = source.content();
let mut cursor = Cursor::new(0usize, source.clone()); let mut cursor = Cursor::new(0usize, source.clone());
@ -164,6 +163,7 @@ impl Parser for LangParser {
} }
} }
return state;
// State // State
//self.handle_reports(source.clone(), //self.handle_reports(source.clone(),
// self.state_mut().on_scope_end(&self, &document, super::state::Scope::DOCUMENT)); // self.state_mut().on_scope_end(&self, &document, super::state::Scope::DOCUMENT));

View file

@ -1,10 +1,10 @@
use ariadne::Label;
use ariadne::Report;
use std::any::Any; use std::any::Any;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashSet; use std::collections::HashSet;
use std::ops::Range; use std::ops::Range;
use std::rc::Rc; use std::rc::Rc;
use ariadne::Label;
use ariadne::Report;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use super::customstyle::CustomStyleHolder; use super::customstyle::CustomStyleHolder;
@ -55,7 +55,7 @@ impl ReportColors {
} }
} }
/// The state that is shared with the state's children /// The state that is shared with the state's childre
pub struct SharedState { pub struct SharedState {
pub rule_state: RefCell<RuleStateHolder>, pub rule_state: RefCell<RuleStateHolder>,
@ -84,9 +84,11 @@ impl SharedState {
}; };
// Register default kernel // Register default kernel
s.kernels.borrow_mut() s.kernels
.borrow_mut()
.insert("main".to_string(), Kernel::new(parser)); .insert("main".to_string(), Kernel::new(parser));
// Default styles & layouts
parser.rules().iter().for_each(|rule| { parser.rules().iter().for_each(|rule| {
rule.register_styles(&mut *s.styles.borrow_mut()); rule.register_styles(&mut *s.styles.borrow_mut());
rule.register_layouts(&mut *s.layouts.borrow_mut()); rule.register_layouts(&mut *s.layouts.borrow_mut());
@ -118,9 +120,9 @@ pub struct ParserState<'a, 'b> {
impl<'a, 'b> ParserState<'a, 'b> { impl<'a, 'b> ParserState<'a, 'b> {
/// Constructs a new state for a given parser with an optional parent /// Constructs a new state for a given parser with an optional parent
/// ///
/// Parent should be None when parsing a brand new document. /// Parent should be None when parsing a brand new document. If you have to
/// If you have to set the parent to Some(..) (e.g for imports or sub-document), /// set the parent to Some(..) (e.g for imports or sub-document), be sure
/// be sure to use the [`ParserState::with_state`] method instead, this create a /// to use the [`ParserState::with_state`] method instead, this create a
/// RAII lived state for use within bounded lifetime. /// RAII lived state for use within bounded lifetime.
pub fn new(parser: &'a dyn Parser, parent: Option<&'a ParserState<'a, 'b>>) -> Self { pub fn new(parser: &'a dyn Parser, parent: Option<&'a ParserState<'a, 'b>>) -> Self {
let matches = parser.rules().iter().map(|_| (0, None)).collect::<Vec<_>>(); let matches = parser.rules().iter().map(|_| (0, None)).collect::<Vec<_>>();
@ -153,24 +155,37 @@ impl<'a, 'b> ParserState<'a, 'b> {
/// Updates matches from a given start position e.g [`Cursor`] /// Updates matches from a given start position e.g [`Cursor`]
/// ///
/// # Return /// # Return
///
/// 1. The cursor position after updating the matches /// 1. The cursor position after updating the matches
/// 2. (Optional) The winning match with it's match data /// 2. (Optional) The winning match with it's match data
/// If the winning match is None, it means that the document has no more
/// rule to match. I.e The rest of the content should be added as a
/// [`Text`] element.
/// The match data should be passed to the [`Rule::on_match`] method.
/// ///
/// If the winning match is None, it means that the document has no more rule to match /// # Strategy
/// I.E The rest of the content should be added as a [`Text`] element. ///
/// The match data should be passed to the [`Rule::on_match`] method /// This function call [`Rule::next_match`] on the rules defined for the
pub fn update_matches( /// parser. It then takes the rule that has the closest `next_match` and
&self, /// returns it. If next_match starts on an escaped character i.e `\\`,
cursor: &Cursor, /// then it starts over to find another match for that rule.
) -> (Cursor, Option<(usize, Box<dyn Any>)>) { /// In case multiple rules have the same `next_match`, the rules that are
/// defined first in the parser are prioritized. See [Parser::add_rule] for
/// information on how to prioritize rules.
///
/// Notes that the result of every call to [`Rule::next_match`] gets stored
/// in a table: [`ParserState::matches`]. Until the cursor steps over a
/// position in the table, `next_match` won't be called.
pub fn update_matches(&self, cursor: &Cursor) -> (Cursor, Option<(usize, Box<dyn Any>)>) {
let mut matches_borrow = self.matches.borrow_mut(); let mut matches_borrow = self.matches.borrow_mut();
self.parser.rules() self.parser
.rules()
.iter() .iter()
.zip(matches_borrow.iter_mut()) .zip(matches_borrow.iter_mut())
.for_each(|(rule, (matched_at, match_data))| { .for_each(|(rule, (matched_at, match_data))| {
// Don't upate if not stepped over yet // Don't upate if not stepped over yet
if *matched_at > cursor.pos && rule.downcast_ref::<CustomStyleRule>().is_none() { if *matched_at > cursor.pos {
// TODO: maybe we should expose matches() so it becomes possible to dynamically register a new rule // TODO: maybe we should expose matches() so it becomes possible to dynamically register a new rule
return; return;
} }
@ -215,15 +230,18 @@ impl<'a, 'b> ParserState<'a, 'b> {
.map(|(winner, (pos, _))| (winner, *pos)) .map(|(winner, (pos, _))| (winner, *pos))
.unwrap(); .unwrap();
if next_pos == usize::MAX // No rule has matched if next_pos == usize::MAX
// No rule has matched
{ {
let content = cursor.source.content(); let content = cursor.source.content();
// No winners, i.e no matches left // No winners, i.e no matches left
return (cursor.at(content.len()), None); return (cursor.at(content.len()), None);
} }
return (cursor.at(next_pos), return (
Some((winner, matches_borrow[winner].1.take().unwrap()))) cursor.at(next_pos),
Some((winner, matches_borrow[winner].1.take().unwrap())),
);
} }
/// Add an [`Element`] to the [`Document`] /// Add an [`Element`] to the [`Document`]
@ -244,15 +262,53 @@ impl<'a, 'b> ParserState<'a, 'b> {
} else { } else {
// Process paragraph events // Process paragraph events
if doc.last_element::<Paragraph>().is_some_and(|_| true) { if doc.last_element::<Paragraph>().is_some_and(|_| true) {
self.parser.handle_reports( self.parser
self.shared.rule_state.borrow_mut() .handle_reports(self.shared.rule_state.borrow_mut().on_scope_end(
.on_scope_end(&self, doc, super::state::Scope::PARAGRAPH), &self,
); doc,
super::state::Scope::PARAGRAPH,
));
} }
doc.push(elem); doc.push(elem);
} }
} }
/// Resets the position and the match_data for a given rule. This is used
/// in order to have 'dynamic' rules that may not match at first, but their
/// matching rule is modified through the parsing process.
///
/// This function also recursively calls itself on it's `parent`, in order
/// to fully reset the match.
///
/// See [`CustomStyleRule`] for an example of how this is used.
///
/// # Error
///
/// Returns an error if `rule_name` was not found in the parser's ruleset.
pub fn reset_match(&self, rule_name: &str) -> Result<(), String>
{
if self.parser.rules().iter()
.zip(self.matches.borrow_mut().iter_mut())
.try_for_each(|(rule, (match_pos, match_data))| {
if rule.name() != rule_name { return Ok(()) }
*match_pos = 0;
match_data.take();
Err(())
}).is_ok()
{
return Err(format!("Could not find rule: {rule_name}"));
}
// Resurcively reset
if let Some(parent) = self.parent
{
return parent.reset_match(rule_name);
}
Ok(())
}
} }
pub trait Parser { pub trait Parser {
@ -268,37 +324,52 @@ pub trait Parser {
/// Whether the parser emitted an error during it's parsing process /// Whether the parser emitted an error during it's parsing process
fn has_error(&self) -> bool; fn has_error(&self) -> bool;
/// Parse [`Source`] into a new [`Document`] /// Parse [`Source`] into a new [`Document`]
/// ///
/// # Errors /// # Errors
/// ///
/// This method will not fail because we try to optimistically recover from parsing errors. /// This method will not fail because we try to optimistically recover from
/// However the resulting document should not get compiled if an error has happened /// parsing errors. However the resulting document should not get compiled
/// see [`Parser::has_error()`] for reference /// if an error has happenedn, see [`Parser::has_error()`] for reference
fn parse<'a>( ///
&self, /// # Returns
state: ParserState, ///
/// This method returns the resulting [`Document`] after psrsing `source`,
/// note that the [`ParserState`] is only meant to perform testing and not
/// meant to be reused.
fn parse<'p, 'a, 'doc>(
&'p self,
state: ParserState<'p, 'a>,
source: Rc<dyn Source>, source: Rc<dyn Source>,
parent: Option<&'a dyn Document<'a>>, parent: Option<&'doc dyn Document<'doc>>,
) -> Box<dyn Document<'a> + 'a>; ) -> (Box<dyn Document<'doc> + 'doc>, ParserState<'p, 'a>);
/// Parse [`Source`] into an already existing [`Document`] /// Parse [`Source`] into an already existing [`Document`]
/// ///
/// # Errors /// # Errors
/// ///
/// This method will not fail because we try to optimistically recover from parsing errors. /// This method will not fail because we try to optimistically recover from
/// However the resulting document should not get compiled if an error has happened /// parsing errors. However the resulting document should not get compiled
/// see [`Parser::has_error()`] for reference /// if an error has happened see [`Parser::has_error()`] for reference
fn parse_into<'a>(&self, ///
state: ParserState, /// # Returns
source: Rc<dyn Source>, document: &'a dyn Document<'a>); ///
/// The returned [`ParserState`] is not meant to be reused, it's meant for
/// testing.
fn parse_into<'p, 'a, 'doc>(
&'p self,
state: ParserState<'p, 'a>,
source: Rc<dyn Source>,
document: &'doc dyn Document<'doc>,
) -> ParserState<'p, 'a>;
fn add_rule( /// Adds a rule to the parser.
&mut self, ///
rule: Box<dyn Rule>, /// # Warning
after: Option<&'static str>, ///
) -> Result<(), String> { /// This method must not be called if a [`ParserState`] for this parser exists.
fn add_rule(&mut self, rule: Box<dyn Rule>, after: Option<&'static str>) -> Result<(), String> {
if let Some(_) = self if let Some(_) = self
.rules() .rules()
.iter() .iter()
@ -312,12 +383,12 @@ pub trait Parser {
// Try to insert after // Try to insert after
if let Some(after) = after { if let Some(after) = after {
let index = let index = self
self.rules() .rules()
.iter() .iter()
.enumerate() .enumerate()
.find(|(_, rule)| rule.name() == after) .find(|(_, rule)| rule.name() == after)
.map(|(idx, _)| idx); .map(|(idx, _)| idx);
if let Some(index) = index { if let Some(index) = index {
self.rules_mut().insert(index, rule); self.rules_mut().insert(index, rule);
@ -331,10 +402,9 @@ pub trait Parser {
Ok(()) Ok(())
} }
fn handle_reports( /// Handles the reports produced by parsing. The default is to output them
&self, /// to stderr, but you are free to modify it.
reports: Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>, fn handle_reports(&self, reports: Vec<Report<'_, (Rc<dyn Source>, Range<usize>)>>) {
) {
for mut report in reports { for mut report in reports {
let mut sources: HashSet<Rc<dyn Source>> = HashSet::new(); let mut sources: HashSet<Rc<dyn Source>> = HashSet::new();
fn recurse_source(sources: &mut HashSet<Rc<dyn Source>>, source: Rc<dyn Source>) { fn recurse_source(sources: &mut HashSet<Rc<dyn Source>>, source: Rc<dyn Source>) {
@ -364,8 +434,8 @@ pub trait Parser {
if let Some(_s) = source.downcast_ref::<SourceFile>() { if let Some(_s) = source.downcast_ref::<SourceFile>() {
report.labels.push( report.labels.push(
Label::new((location.source(), location.start() + 1..location.end())) Label::new((location.source(), location.start() + 1..location.end()))
.with_message("In file included from here") .with_message("In file included from here")
.with_order(-1), .with_order(-1),
); );
}; };
@ -373,12 +443,12 @@ pub trait Parser {
let start = location.start() let start = location.start()
+ (location.source().content().as_bytes()[location.start()] + (location.source().content().as_bytes()[location.start()]
== '\n' as u8) == '\n' as u8)
.then_some(1) .then_some(1)
.unwrap_or(0); .unwrap_or(0);
report.labels.push( report.labels.push(
Label::new((location.source(), start..location.end())) Label::new((location.source(), start..location.end()))
.with_message("In evaluation of") .with_message("In evaluation of")
.with_order(-1), .with_order(-1),
); );
}; };
} }

View file

@ -144,6 +144,7 @@ pub fn parse_paragraph<'a>(
new_state new_state
.parser .parser
.parse(new_state, source.clone(), Some(document)) .parse(new_state, source.clone(), Some(document))
.0
}); });
if parsed.content().borrow().len() > 1 { if parsed.content().borrow().len() > 1 {
return Err("Parsed document contains more than a single paragraph"); return Err("Parsed document contains more than a single paragraph");
@ -422,20 +423,23 @@ mod tests {
(&doc as &dyn Document) (&doc as &dyn Document)
.last_element_mut::<Paragraph>() .last_element_mut::<Paragraph>()
.unwrap() .unwrap()
.push(Box::new(Comment::new(tok.clone(), "COMMENT".to_string()))); .push(Box::new(Comment::new(tok.clone(), "COMMENT".to_string())))
.unwrap();
assert_eq!(process_text(&doc, "\na"), "a"); assert_eq!(process_text(&doc, "\na"), "a");
// A space is appended as previous element is inline // A space is appended as previous element is inline
(&doc as &dyn Document) (&doc as &dyn Document)
.last_element_mut::<Paragraph>() .last_element_mut::<Paragraph>()
.unwrap() .unwrap()
.push(Box::new(Text::new(tok.clone(), "TEXT".to_string()))); .push(Box::new(Text::new(tok.clone(), "TEXT".to_string())))
.unwrap();
assert_eq!(process_text(&doc, "\na"), " a"); assert_eq!(process_text(&doc, "\na"), " a");
(&doc as &dyn Document) (&doc as &dyn Document)
.last_element_mut::<Paragraph>() .last_element_mut::<Paragraph>()
.unwrap() .unwrap()
.push(Box::new(Style::new(tok.clone(), 0, false))); .push(Box::new(Style::new(tok.clone(), 0, false)))
.unwrap();
assert_eq!(process_text(&doc, "\na"), " a"); assert_eq!(process_text(&doc, "\na"), " a");
} }