pub mod bird;
pub mod html;
pub mod md;
pub mod tex;
pub use self::bird::BirdParser;
pub use self::html::HtmlParser;
pub use self::md::MdParser;
pub use self::tex::TexParser;
use crate::document::code::{CodeBlock, Line, Segment, Source};
use crate::document::text::TextBlock;
use crate::document::Document;
pub trait ParserConfig {
fn comment_start(&self) -> &str;
fn interpolation_start(&self) -> &str;
fn interpolation_end(&self) -> &str;
fn macro_start(&self) -> &str;
fn macro_end(&self) -> &str;
}
pub trait Parser: ParserConfig {
type Error: std::error::Error;
fn parse<'a>(&self, input: &'a str) -> Result<Document<'a>, Self::Error>;
fn parse_name<'a>(&self, mut input: &'a str) -> Result<(String, Vec<&'a str>), ParseError> {
let original = input;
let mut name = String::new();
let mut vars = vec![];
let start = self.interpolation_start();
let end = self.interpolation_end();
loop {
if let Some(start_index) = input.find(start) {
if let Some(end_index) = input[start_index + start.len()..].find(end) {
name.push_str(&input[..start_index]);
name.push_str(&start);
name.push_str(&end);
vars.push(
&input[start_index + start.len()..start_index + start.len() + end_index],
);
input = &input[start_index + start.len() + end_index + end.len()..];
} else {
return Err(ParseError::UnclosedVariableError(original.to_owned()));
}
} else {
name.push_str(input);
break;
}
}
return Ok((name, vars));
}
fn parse_line<'a>(&self, line_number: usize, input: &'a str) -> Result<Line<'a>, ParseError> {
let original = input;
let indent_len = input
.chars()
.take_while(|ch| ch.is_whitespace())
.collect::<String>()
.len();
let (indent, rest) = input.split_at(indent_len);
let (mut rest, comment) = if let Some(comment_index) = rest.find(self.comment_start()) {
let (rest, comment) = rest.split_at(comment_index);
(rest, Some(&comment[self.comment_start().len()..]))
} else {
(rest, None)
};
if rest.starts_with(self.macro_start()) {
if let Some(end_index) = rest.find(self.macro_end()) {
let (name, scope) = self.parse_name(&rest[self.macro_start().len()..end_index])?;
return Ok(Line {
line_number,
indent,
source: Source::Macro { name, scope },
comment,
});
}
}
let mut source = vec![];
let start = self.interpolation_start();
let end = self.interpolation_end();
loop {
if let Some(start_index) = rest.find(start) {
if let Some(end_index) = rest[start_index + start.len()..].find(end) {
source.push(Segment::Source(&rest[..start_index]));
source.push(Segment::MetaVar(
&rest[start_index + start.len()..start_index + start.len() + end_index],
));
rest = &rest[start_index + start.len() + end_index + end.len()..];
} else {
return Err(ParseError::UnclosedVariableError(original.to_owned()));
}
} else {
if !rest.is_empty() {
source.push(Segment::Source(rest));
}
break;
}
}
Ok(Line {
line_number,
indent,
source: Source::Source(source),
comment,
})
}
}
#[derive(Debug)]
pub enum ParseError {
UnclosedVariableError(String),
}
pub trait Printer: ParserConfig {
fn print_code_block<'a>(&self, block: &CodeBlock<'a>) -> String;
fn print_text_block<'a>(&self, block: &TextBlock<'a>) -> String;
fn print_name(&self, mut name: String, vars: &[&str]) -> String {
let start = self.interpolation_start();
let end = self.interpolation_end();
let var_placeholder = format!("{}{}", start, end);
for var in vars {
let var_name = format!("{}{}{}", start, var, end);
name = name.replacen(&var_placeholder, &var_name, 1);
}
name
}
fn print_line<'a>(&self, line: &Line<'a>, print_comments: bool) -> String {
let mut output = line.indent.to_string();
match &line.source {
Source::Macro { name, scope } => {
output.push_str(self.macro_start());
output.push_str(&self.print_name(name.clone(), &scope));
output.push_str(self.macro_end());
}
Source::Source(segments) => {
for segment in segments {
match segment {
Segment::Source(source) => output.push_str(source),
Segment::MetaVar(name) => {
output.push_str(self.interpolation_start());
output.push_str(&name);
output.push_str(self.interpolation_end());
}
}
}
}
}
if print_comments {
if let Some(comment) = line.comment {
output.push_str(self.comment_start());
output.push_str(comment);
}
}
output
}
}