use serde_derive::Deserialize;
use std::iter::FromIterator;
use super::{ParseError, Parser, ParserConfig, Printer};
use crate::document::ast::Node;
use crate::document::code::CodeBlock;
use crate::document::text::TextBlock;
use crate::document::Document;
use crate::util::try_collect::TryCollectExt;
#[derive(Deserialize, Debug)]
pub struct BirdParser {
pub code_marker: String,
pub code_name_marker: String,
pub comment_start: String,
pub interpolation_start: String,
pub interpolation_end: String,
pub macro_start: String,
pub macro_end: String,
}
impl Default for BirdParser {
fn default() -> Self {
Self {
code_marker: String::from("> "),
code_name_marker: String::from(">>> "),
comment_start: String::from("//"),
interpolation_start: String::from("@{"),
interpolation_end: String::from("}"),
macro_start: String::from("==> "),
macro_end: String::from("."),
}
}
}
impl ParserConfig for BirdParser {
fn comment_start(&self) -> &str {
&self.comment_start
}
fn interpolation_start(&self) -> &str {
&self.interpolation_start
}
fn interpolation_end(&self) -> &str {
&self.interpolation_end
}
fn macro_start(&self) -> &str {
&self.macro_start
}
fn macro_end(&self) -> &str {
&self.macro_end
}
}
impl Parser for BirdParser {
type Error = BirdError;
fn parse<'a>(&self, input: &'a str) -> Result<Document<'a>, Self::Error> {
struct State<'a> {
node: Node<'a>,
blank_line: bool,
}
enum Parse<'a> {
Incomplete,
Complete(Node<'a>),
Error(BirdError),
}
let mut state = State {
node: Node::Text(TextBlock::new()),
blank_line: true,
};
let mut document = input
.lines()
.enumerate()
.scan(&mut state, |state, (line_number, line)| match state {
State { .. } if line.is_empty() => {
state.blank_line = true;
match &mut state.node {
Node::Code(..) => {
let mut new_block = TextBlock::new();
new_block.add_line(line);
let node = std::mem::replace(&mut state.node, Node::Text(new_block));
Some(Parse::Complete(node))
}
Node::Text(text_block) => {
text_block.add_line(line);
Some(Parse::Incomplete)
}
}
}
State {
blank_line: true, ..
} if line.starts_with(&self.code_name_marker) => {
let (name, vars) = match self.parse_name(&line[self.code_name_marker.len()..]) {
Ok((name, vars)) => (name, vars),
Err(error) => {
return Some(Parse::Error(BirdError::Single {
line_number,
kind: error.into(),
}))
}
};
let code_block = CodeBlock::new().named(name, vars);
state.blank_line = false;
let node = std::mem::replace(&mut state.node, Node::Code(code_block));
Some(Parse::Complete(node))
}
State {
blank_line: true, ..
} if line.starts_with(&self.code_marker) => {
let line = match self.parse_line(line_number, &line[self.code_marker.len()..]) {
Ok(line) => line,
Err(error) => {
return Some(Parse::Error(BirdError::Single {
line_number,
kind: error.into(),
}))
}
};
let mut code_block = CodeBlock::new();
code_block.add_line(line);
let node = std::mem::replace(&mut state.node, Node::Code(code_block));
state.blank_line = false;
Some(Parse::Complete(node))
}
State {
node: Node::Code(code_block),
..
} if line.starts_with(&self.code_marker) => {
let line = match self.parse_line(line_number, &line[self.code_marker.len()..]) {
Ok(line) => line,
Err(error) => {
return Some(Parse::Error(BirdError::Single {
line_number,
kind: error.into(),
}))
}
};
code_block.add_line(line);
Some(Parse::Incomplete)
}
State {
node: Node::Code(..),
..
} => Some(Parse::Error(BirdError::Single {
line_number,
kind: BirdErrorKind::UnterminatedCodeBlock,
})),
State {
node: Node::Text(text_block),
..
} => {
state.blank_line = false;
text_block.add_line(&line);
Some(Parse::Incomplete)
}
})
.filter_map(|parse| match parse {
Parse::Incomplete => None,
Parse::Error(error) => Some(Err(error)),
Parse::Complete(node) => Some(Ok(node)),
})
.try_collect::<_, _, Vec<_>, BirdError>()?;
document.push(state.node);
Ok(Document::from_iter(document))
}
}
impl Printer for BirdParser {
fn print_text_block<'a>(&self, block: &TextBlock<'a>) -> String {
format!("{}\n", block.to_string())
}
fn print_code_block<'a>(&self, block: &CodeBlock<'a>) -> String {
let mut output = String::new();
if let Some(name) = &block.name {
output.push_str(&self.code_name_marker);
output.push_str(&self.print_name(name.clone(), &block.vars));
output.push('\n');
}
for line in &block.source {
output.push_str(&self.code_marker);
output.push_str(&self.print_line(&line, true));
output.push('\n');
}
output
}
}
#[derive(Debug)]
pub enum BirdErrorKind {
UnterminatedCodeBlock,
Parse(ParseError),
}
#[derive(Debug)]
pub enum BirdError {
#[doc(hidden)]
Single {
line_number: usize,
kind: BirdErrorKind,
},
#[doc(hidden)]
Multi(Vec<BirdError>),
}
impl std::fmt::Display for BirdError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BirdError::Multi(errors) => {
for error in errors {
writeln!(f, "{}", error)?;
}
Ok(())
}
BirdError::Single { line_number, kind } => {
writeln!(f, "{:?} (line {})", kind, line_number)
}
}
}
}
impl std::error::Error for BirdError {}
impl FromIterator<BirdError> for BirdError {
fn from_iter<I: IntoIterator<Item = BirdError>>(iter: I) -> Self {
BirdError::Multi(iter.into_iter().collect())
}
}
impl From<Vec<BirdError>> for BirdError {
fn from(multi: Vec<BirdError>) -> Self {
BirdError::Multi(multi)
}
}
impl From<ParseError> for BirdErrorKind {
fn from(error: ParseError) -> Self {
BirdErrorKind::Parse(error)
}
}