use super::{cpp::CppResult, files::FileProcessor};
use crate::{
error::CppError, CompileError, CompileResult, InternedStr, Locatable, Location, Token,
};
use std::collections::{HashMap, HashSet, VecDeque};
pub type Definitions = HashMap<InternedStr, Definition>;
pub trait Peekable: Iterator {
fn peek(&mut self) -> Option<&Self::Item>;
}
impl Peekable for FileProcessor {
fn peek(&mut self) -> Option<&Self::Item> {
self.peek()
}
}
impl<T: Iterator> Peekable for std::iter::Peekable<T> {
fn peek(&mut self) -> Option<&Self::Item> {
self.peek()
}
}
impl<T> Peekable for std::iter::Empty<T> {
fn peek(&mut self) -> Option<&Self::Item> {
None
}
}
impl<I: Peekable + ?Sized> Peekable for &mut I {
fn peek(&mut self) -> Option<&Self::Item> {
(**self).peek()
}
}
#[derive(Debug, PartialEq)]
pub enum Definition {
Object(Vec<Token>),
Function {
params: Vec<InternedStr>,
body: Vec<Token>,
},
}
#[must_use = "does not change internal state"]
pub fn replace(
definitions: &Definitions,
token: Token,
mut inner: impl Iterator<Item = CppResult<Token>> + Peekable,
location: Location,
) -> Vec<CompileResult<Locatable<Token>>> {
let mut ids_seen = HashSet::new();
let mut replacements = Vec::new();
let mut pending = VecDeque::new();
pending.push_back(Ok(location.with(token)));
while let Some(token) = pending.pop_front() {
if let Ok(Locatable {
data: Token::Id(id),
..
}) = token
{
if !ids_seen.contains(&id) {
match definitions.get(&id) {
Some(Definition::Object(replacement_list)) => {
ids_seen.insert(id);
let mut new_pending = VecDeque::new();
new_pending.extend(
replacement_list
.iter()
.cloned()
.map(|t| Ok(location.with(t))),
);
new_pending.append(&mut pending);
pending = new_pending;
continue;
}
Some(Definition::Function { .. }) => {
ids_seen.insert(id);
let func_replacements =
replace_function(definitions, id, location, &mut pending, &mut inner);
let mut func_replacements: VecDeque<_> =
func_replacements.into_iter().collect();
func_replacements.append(&mut pending);
pending = func_replacements;
continue;
}
None => {}
}
}
}
replacements.push(token);
}
replacements
}
#[must_use = "does not change internal state"]
fn replace_function(
definitions: &Definitions,
id: InternedStr,
location: Location,
incoming: &mut VecDeque<CompileResult<Locatable<Token>>>,
mut inner: impl Iterator<Item = CppResult<Token>> + Peekable,
) -> Vec<Result<Locatable<Token>, CompileError>> {
use std::mem;
let mut errors = Vec::new();
loop {
match incoming.front().or_else(|| inner.peek()) {
Some(Err(_)) => {
let next = incoming.pop_front().or_else(|| inner.next());
errors.push(Err(next.unwrap().unwrap_err()));
}
Some(Ok(Locatable {
data: Token::LeftParen,
..
})) => {
if incoming.pop_front().is_none() {
inner.next();
}
break;
}
Some(Ok(Locatable {
data: Token::Whitespace(_),
..
})) => {
let spaces = incoming.pop_front().or_else(|| inner.next()).unwrap();
let left_paren = incoming.front().or_else(|| inner.peek());
if let Some(Ok(Locatable {
data: Token::LeftParen,
..
})) = left_paren
{
if incoming.pop_front().is_none() {
inner.next();
}
break;
}
let id_token = Ok(location.with(Token::Id(id)));
errors.push(id_token);
errors.push(spaces);
return errors;
}
Some(Ok(_)) => {
let token = incoming.pop_front().or_else(|| inner.next()).unwrap();
let id_token = Ok(location.with(Token::Id(id)));
errors.push(id_token);
errors.push(token);
return errors;
}
None => {
let id_token = Ok(location.with(Token::Id(id)));
errors.push(id_token);
return errors;
}
}
}
let mut args = Vec::new();
let mut current_arg = Vec::new();
let mut nested_parens = 1;
loop {
let next = match incoming.pop_front().or_else(|| inner.next()) {
None => return errors,
Some(Err(err)) => {
errors.push(Err(err));
continue;
}
Some(Ok(token)) => token,
};
match next.data {
Token::Comma if nested_parens == 1 => {
args.push(mem::take(&mut current_arg));
continue;
}
Token::RightParen => {
nested_parens -= 1;
if nested_parens == 0 {
args.push(mem::take(&mut current_arg));
break;
}
}
Token::LeftParen => {
nested_parens += 1;
}
_ => {}
}
current_arg.push(next.data);
}
let (params, body) = match definitions.get(&id) {
Some(Definition::Function { params, body }) => (params, body),
_ => unreachable!("checked above"),
};
let mut replacements = Vec::new();
if args.len() != params.len() {
if !(args.len() == 1 && params.is_empty() && args[0].is_empty()) {
return vec![Err(
location.with(CppError::TooFewArguments(params.len(), args.len()).into())
)];
}
}
for token in body {
if let Token::Id(id) = *token {
if let Some(index) = params.iter().position(|¶m| param == id) {
let replacement = args[index].clone();
replacements.extend(replacement);
} else {
let token = Token::Id(id);
replacements.push(token);
}
} else {
replacements.push(token.clone());
}
}
errors
.into_iter()
.chain(replacements.into_iter().map(|t| Ok(location.with(t))))
.collect()
}