use super::{CompileError, CompileErrorKind};
use crate::util::try_collect::TryCollectExt;
use std::collections::HashMap;
#[derive(Clone, Debug)]
pub enum Segment<'a> {
Source(&'a str),
MetaVar(&'a str),
}
#[derive(Clone, Debug)]
pub enum Source<'a> {
Macro {
name: String,
scope: Vec<&'a str>,
},
Source(Vec<Segment<'a>>),
}
#[derive(Clone, Debug)]
pub struct Line<'a> {
pub line_number: usize,
pub indent: &'a str,
pub source: Source<'a>,
pub comment: Option<&'a str>,
}
impl<'a> Line<'a> {
fn compile_with(
&self,
code_blocks: &HashMap<Option<&str>, CodeBlock<'a>>,
scope: &HashMap<&str, &str>,
) -> Result<String, CompileError> {
match &self.source {
Source::Source(segments) => {
let code = segments
.iter()
.map(|segment| match segment {
Segment::Source(source) => Ok(*source),
Segment::MetaVar(name) => {
scope.get(name).map(|var| *var).ok_or(CompileError::Single {
line_number: self.line_number,
kind: CompileErrorKind::UnknownMetaVariable(name.to_string()),
})
}
})
.try_collect()
.map(|vec: Vec<_>| vec.join(""))?;
Ok(format!("{}{}", self.indent, code))
}
Source::Macro { name, scope } => code_blocks
.get(&Some(name))
.ok_or(CompileError::Single {
line_number: self.line_number,
kind: CompileErrorKind::UnknownMacro(name.to_string()),
})
.and_then(|block| {
block
.compile_with(code_blocks, block.assign_vars(scope))
.map(|code| {
code.split("\n")
.map(|line| format!("{}{}", self.indent, line))
.collect::<Vec<_>>()
.join("\n")
})
}),
}
}
}
#[derive(Clone, Default, Debug)]
pub struct CodeBlock<'a> {
pub indent: &'a str,
pub name: Option<String>,
pub vars: Vec<&'a str>,
pub language: Option<String>,
pub source: Vec<Line<'a>>,
}
impl<'a> CodeBlock<'a> {
pub fn new() -> Self {
Self::default()
}
pub fn indented(self, indent: &'a str) -> Self {
Self { indent, ..self }
}
pub fn named(self, name: String, vars: Vec<&'a str>) -> Self {
Self {
name: Some(name),
vars,
..self
}
}
pub fn in_language(self, language: String) -> Self {
Self {
language: Some(language),
..self
}
}
pub fn add_line(&mut self, line: Line<'a>) {
self.source.push(line);
}
pub fn append(&mut self, other: &CodeBlock<'a>) {
self.source.extend_from_slice(&other.source)
}
pub fn compile(
&self,
code_blocks: &HashMap<Option<&str>, CodeBlock<'a>>,
) -> Result<String, CompileError> {
self.compile_with(code_blocks, HashMap::default())
}
pub fn line_number(&self) -> Option<usize> {
self.source.first().map(|line| line.line_number)
}
fn compile_with(
&self,
code_blocks: &HashMap<Option<&str>, CodeBlock<'a>>,
scope: HashMap<&str, &str>,
) -> Result<String, CompileError> {
self.source
.iter()
.map(|line| line.compile_with(code_blocks, &scope))
.try_collect()
.map(|vec: Vec<_>| vec.join("\n"))
}
fn assign_vars(&self, scope: &[&'a str]) -> HashMap<&str, &'a str> {
self.vars
.iter()
.zip(scope)
.map(|(name, value)| (*name, *value))
.collect()
}
}