#![allow(clippy::cognitive_complexity)]
use std::{mem, path::PathBuf};
use syn::visit::Visit;
use yarte_helpers::config::Config;
use yarte_parser::{Helper, Node, Partial, PartialBlock, SNode};
use super::{is_super, Context, LoweringContext};
use crate::{
error::{GError, GResult},
Struct,
};
pub(super) fn find_loop_var(g: &LoweringContext, nodes: &[SNode]) -> GResult<bool> {
FindEach::from(g).find(nodes)
}
#[derive(Clone)]
pub struct FindEach<'a> {
loop_var: bool,
s: &'a Struct<'a>,
c: &'a Config<'a>,
ctx: Context<'a>,
on_path: PathBuf,
block: Vec<(&'a [SNode<'a>], FindEach<'a>)>,
on_: usize,
recursion: usize,
on_error: Option<GError>,
}
impl<'a> From<&LoweringContext<'a>> for FindEach<'a> {
fn from(g: &LoweringContext<'a>) -> FindEach<'a> {
FindEach {
loop_var: false,
c: g.c,
s: g.s,
ctx: g.ctx,
on_path: g.on_path.clone(),
block: g.block.iter().map(|(_, x, g)| (*x, g.into())).collect(),
on_: 0,
recursion: g.recursion,
on_error: None,
}
}
}
macro_rules! breaks {
($_self:ident) => {
if $_self.loop_var || $_self.on_error.is_some() {
break;
}
};
}
impl<'a> FindEach<'a> {
pub fn find(&mut self, nodes: &'a [SNode]) -> GResult<bool> {
macro_rules! partial {
($path:ident, $expr:ident) => {{
self.recursion += 1;
if self.s.recursion_limit <= self.recursion {
self.on_error.replace(GError::RecursionLimit);
break;
}
let p = self.c.resolve_partial(&self.on_path, $path.t());
let nodes = self.ctx.get(&p).unwrap();
let expr = $expr.t();
if !expr.is_empty() {
let at = if let syn::Expr::Assign(_) = *expr[0] {
0
} else {
1
};
for e in &expr[at..] {
self.visit_expr(e);
breaks!(self);
}
if at == 1 {
break;
}
}
(mem::replace(&mut self.on_path, p), nodes)
}};
}
for n in nodes {
match n.t() {
Node::Local(expr) => self.visit_local(expr.t()),
Node::Expr(_, expr) | Node::Safe(_, expr) => self.visit_expr(expr.t()),
#[cfg(feature = "wasm-app")]
Node::RExpr(_, expr) => self.visit_expr(expr.t()),
Node::Helper(h) => {
let h: &Helper = &*h;
match h {
Helper::If((_, first, block), else_if, els) => {
self.visit_expr(first.t());
breaks!(self);
self.find(block)?;
for (_, e, b) in else_if {
breaks!(self);
self.visit_expr(e.t());
breaks!(self);
self.find(b)?;
}
breaks!(self);
if let Some((_, els)) = els {
self.find(els)?;
}
}
Helper::With(_, e, b) => {
self.visit_expr(e.t());
breaks!(self);
self.on_ += 1;
self.find(b)?;
self.on_ -= 1;
}
Helper::Unless(_, expr, block) => {
self.visit_expr(expr.t());
breaks!(self);
self.find(block)?;
}
Helper::Each(_, expr, block) => {
self.visit_expr(expr.t());
breaks!(self);
self.on_ += 1;
self.find(block)?;
self.on_ -= 1;
}
Helper::Defined(..) => {
self.on_error.replace(GError::Unimplemented);
}
}
}
Node::Partial(Partial(_, path, expr)) => {
let (parent, nodes) = partial!(path, expr);
self.find(nodes)?;
self.on_path = parent;
self.recursion -= 1;
}
Node::PartialBlock(PartialBlock(_, path, expr, block)) => {
let (parent, nodes) = partial!(path, expr);
self.block.push((block, self.clone()));
self.find(nodes)?;
self.on_path = parent;
self.block.pop();
self.recursion -= 1;
}
Node::Block(_) => {
if let Some((block, mut old)) = self.block.pop() {
old.find(block)?;
self.loop_var |= old.loop_var;
self.block.push((block, old));
} else {
self.on_error.replace(GError::PartialBlockNoParent);
}
}
Node::Raw(..) | Node::Lit(..) | Node::Comment(_) => (),
#[allow(unreachable_patterns)]
_ => (),
}
breaks!(self);
}
if let Some(err) = self.on_error.take() {
Err(err)
} else {
Ok(self.loop_var)
}
}
}
impl<'a> Visit<'a> for FindEach<'a> {
fn visit_expr_path(&mut self, i: &'a syn::ExprPath) {
macro_rules! search {
($ident:expr) => {
match $ident.as_ref() {
"index" | "index0" | "first" => self.loop_var = true,
_ => (),
}
};
}
if !self.loop_var {
if i.path.segments.len() == 1 {
search!(i.path.segments[0].ident.to_string());
} else if 0 < self.on_ {
if let Some((j, ident)) = is_super(&i.path.segments) {
if j == self.on_ {
search!(ident);
}
}
}
}
}
}