use super::arguments::Arguments;
use super::BlockRule;
use crate::parsing::collect::{collect_text, collect_text_keep};
use crate::parsing::condition::ParseCondition;
use crate::parsing::consume::consume;
use crate::parsing::{
gather_paragraphs, parse_string, ExtractedToken, ParseResult, ParseWarning,
ParseWarningKind, Parser, Token,
};
use crate::tree::Element;
use regex::Regex;
lazy_static! {
static ref ARGUMENT_KEY: Regex = Regex::new(r"[A-Za-z0-9_\-]+").unwrap();
}
impl<'r, 't> Parser<'r, 't>
where
'r: 't,
{
fn get_token(
&mut self,
token: Token,
kind: ParseWarningKind,
) -> Result<&'t str, ParseWarning> {
trace!(
&self.log(),
"Looking for token {:?} (warning {:?})",
token,
kind;
"token" => token,
"warning-kind" => kind,
);
let current = self.current();
if current.token == token {
let text = current.slice;
self.step()?;
Ok(text)
} else {
Err(self.make_warn(kind))
}
}
fn get_optional_token(&mut self, token: Token) -> Result<(), ParseWarning> {
trace!(
&self.log(),
"Looking for optional token {:?}",
token;
"token" => token,
);
if self.current().token == token {
self.step()?;
}
Ok(())
}
pub fn get_optional_line_break(&mut self) -> Result<(), ParseWarning> {
debug!(&self.log(), "Looking for optional line break");
self.get_optional_token(Token::LineBreak)
}
#[inline]
pub fn get_optional_space(&mut self) -> Result<(), ParseWarning> {
debug!(&self.log(), "Looking for optional space");
self.get_optional_token(Token::Whitespace)
}
pub fn get_block_name(
&mut self,
special: bool,
) -> Result<(&'t str, bool), ParseWarning> {
debug!(&self.log(), "Looking for identifier");
if special {
self.get_optional_token(Token::LeftBlockSpecial)?;
} else {
self.get_optional_token(Token::LeftBlock)?;
}
self.get_optional_space()?;
self.get_block_name_internal(ParseWarningKind::BlockMissingName)
}
fn get_block_name_internal(
&mut self,
kind: ParseWarningKind,
) -> Result<(&'t str, bool), ParseWarning> {
collect_text_keep(
&self.log(),
self,
self.rule(),
&[
ParseCondition::current(Token::Whitespace),
ParseCondition::current(Token::RightBlock),
],
&[
ParseCondition::current(Token::ParagraphBreak),
ParseCondition::current(Token::LineBreak),
],
Some(kind),
)
.map(|(name, last)| {
let name = name.trim();
let in_head = match last.token {
Token::Whitespace => true,
Token::RightBlock => false,
_ => unreachable!(),
};
(name, in_head)
})
}
pub fn get_end_block(&mut self) -> Result<&'t str, ParseWarning> {
debug!(&self.log(), "Looking for end block");
self.get_token(Token::LeftBlockEnd, ParseWarningKind::BlockExpectedEnd)?;
self.get_optional_space()?;
let (name, in_head) = self.get_block_name(false)?;
if in_head {
self.get_optional_space()?;
self.get_token(Token::RightBlock, ParseWarningKind::BlockExpectedEnd)?;
}
Ok(name)
}
fn verify_end_block(
&mut self,
first_iteration: bool,
block_rule: &BlockRule,
) -> Option<&'r ExtractedToken<'t>> {
self.save_evaluate_fn(|parser| {
if block_rule.accepts_newlines {
if !first_iteration {
parser.get_optional_line_break()?;
}
}
let name = parser.get_end_block()?;
for end_block_name in block_rule.accepts_names {
if name.eq_ignore_ascii_case(end_block_name) {
return Ok(true);
}
}
Ok(false)
})
}
fn get_body_generic<F>(
&mut self,
block_rule: &BlockRule,
mut process: F,
) -> Result<(&'r ExtractedToken<'t>, &'r ExtractedToken<'t>), ParseWarning>
where
F: FnMut(&mut Parser<'r, 't>) -> Result<(), ParseWarning>,
{
trace!(&self.log(), "Running generic in block body parser");
debug_assert_eq!(
block_rule.accepts_names.is_empty(),
false,
"List of valid end block names is empty, no success is possible",
);
let mut first = true;
let start = self.current();
loop {
let at_end_block = self.verify_end_block(first, block_rule);
if let Some(end) = at_end_block {
return Ok((start, end));
}
process(self)?;
self.step()?;
first = false;
}
}
pub fn get_body_text(
&mut self,
block_rule: &BlockRule,
) -> Result<&'t str, ParseWarning> {
debug!(
&self.log(),
"Getting block body as text";
"block-rule" => format!("{:#?}", block_rule),
);
let (start, end) = self.get_body_generic(block_rule, |_| Ok(()))?;
let slice = self.full_text().slice_partial(&self.log(), start, end);
Ok(slice)
}
#[inline]
pub fn get_body_elements(
&mut self,
block_rule: &BlockRule,
as_paragraphs: bool,
) -> ParseResult<'r, 't, Vec<Element<'t>>> {
debug!(
&self.log(),
"Getting block body as elements";
"block-rule" => format!("{:#?}", block_rule),
"as_paragraphs" => as_paragraphs,
);
if as_paragraphs {
self.get_body_elements_paragraphs(block_rule)
} else {
self.get_body_elements_no_paragraphs(block_rule)
}
}
fn get_body_elements_paragraphs(
&mut self,
block_rule: &BlockRule,
) -> ParseResult<'r, 't, Vec<Element<'t>>> {
let mut first = true;
gather_paragraphs(
&self.log(),
self,
self.rule(),
Some(move |parser: &mut Parser<'r, 't>| {
let result = parser.verify_end_block(first, block_rule);
first = false;
Ok(result.is_some())
}),
)
}
fn get_body_elements_no_paragraphs(
&mut self,
block_rule: &BlockRule,
) -> ParseResult<'r, 't, Vec<Element<'t>>> {
let mut all_elements = Vec::new();
let mut all_exceptions = Vec::new();
let mut first = true;
loop {
let result = self.verify_end_block(first, block_rule);
if result.is_some() {
return ok!(all_elements, all_exceptions);
}
first = false;
let old_remaining = self.remaining();
let elements = consume(&self.log(), self)?.chain(&mut all_exceptions);
all_elements.extend(elements);
if self.same_pointer(old_remaining) {
self.step()?;
}
}
}
pub fn get_head_map(
&mut self,
block_rule: &BlockRule,
in_head: bool,
) -> Result<Arguments<'t>, ParseWarning> {
debug!(&self.log(), "Looking for key value arguments, then ']]'");
let mut map = Arguments::new();
if in_head {
loop {
self.get_optional_space()?;
let key = {
let start = self.current();
let mut args_finished = false;
loop {
let current = self.current();
match current.token {
Token::RightBlock => {
args_finished = true;
break;
}
Token::Whitespace | Token::Equals => break,
_ if ARGUMENT_KEY.is_match(current.slice) => {
self.step()?;
}
_ => {
return Err(self.make_warn(
ParseWarningKind::BlockMalformedArguments,
))
}
}
}
if args_finished {
break;
}
let end = self.current();
self.full_text().slice_partial(&self.log(), start, end)
};
self.get_optional_space()?;
self.get_token(Token::Equals, ParseWarningKind::BlockMalformedArguments)?;
self.get_optional_space()?;
let value_raw = self.get_token(
Token::String,
ParseWarningKind::BlockMalformedArguments,
)?;
let value = parse_string(value_raw);
map.insert(key, value);
}
}
self.get_head_block(block_rule, in_head)?;
Ok(map)
}
pub fn get_head_name_map(
&mut self,
block_rule: &BlockRule,
in_head: bool,
) -> Result<(&'t str, Arguments<'t>), ParseWarning> {
debug!(
&self.log(),
"Looking for a name, then key value arguments, then ']]'"
);
if !in_head {
debug!(
&self.log(),
"Block is already over, there is no name or arguments",
);
return Err(self.make_warn(ParseWarningKind::BlockMissingName));
}
let (subname, in_head) =
self.get_block_name_internal(ParseWarningKind::ModuleMissingName)?;
let arguments = self.get_head_map(block_rule, in_head)?;
Ok((subname, arguments))
}
pub fn get_head_value<F, T>(
&mut self,
block_rule: &BlockRule,
in_head: bool,
convert: F,
) -> Result<T, ParseWarning>
where
F: FnOnce(&Self, Option<&'t str>) -> Result<T, ParseWarning>,
{
debug!(
&self.log(),
"Looking for a value argument, then ']]'";
"in-head" => in_head,
);
let argument = if in_head {
let slice = collect_text(
&self.log(),
self,
self.rule(),
&[ParseCondition::current(Token::RightBlock)],
&[
ParseCondition::current(Token::ParagraphBreak),
ParseCondition::current(Token::LineBreak),
],
Some(ParseWarningKind::BlockMalformedArguments),
)?;
Some(slice)
} else {
None
};
let value = convert(self, argument)?;
self.get_head_block(block_rule, false)?;
Ok(value)
}
pub fn get_head_none(
&mut self,
block_rule: &BlockRule,
in_head: bool,
) -> Result<(), ParseWarning> {
debug!(&self.log(), "No arguments, looking for end of head block");
self.get_optional_space()?;
self.get_head_block(block_rule, in_head)?;
Ok(())
}
fn get_head_block(
&mut self,
block_rule: &BlockRule,
in_head: bool,
) -> Result<(), ParseWarning> {
trace!(&self.log(), "Getting end of the head block");
if in_head {
self.get_token(
Token::RightBlock,
ParseWarningKind::BlockMissingCloseBrackets,
)?;
}
if self.current().token != Token::InputEnd && block_rule.accepts_newlines {
self.get_optional_line_break()?;
}
Ok(())
}
#[inline]
pub fn set_block(&mut self, block_rule: &BlockRule) {
info!(
&self.log(),
"Running block rule {} for these tokens",
block_rule.name;
);
self.set_rule(block_rule.rule());
}
}