euph 0.1.1

A functional stack-based programming language
use std::{
    hash,
    iter::{
        self,
        Peekable,
    },
    mem,
    rc::Rc,
};

use anyhow::{
    Context,
    anyhow,
};
use derive_more::Display;
use ecow::EcoVec;
use hipstr::LocalHipStr;
use itertools::Itertools;
use winnow::Parser;

use crate::{
    fns::CORE,
    parser::euphrates,
    types::{
        EuIter,
        EuRes,
        EuSyn,
        EuType,
    },
};

#[derive(Display)]
#[display("stack: {stack:?}\nscope: {scope:?}")]
pub struct EuEnv<'eu> {
    pub queue: Peekable<EuIter<'eu>>,
    pub stack: EcoVec<EuType<'eu>>,
    pub scope: EuScope<'eu>,
}

pub type EuScope<'eu> =
    imbl::GenericHashMap<LocalHipStr<'eu>, EuType<'eu>, hash::RandomState, imbl::shared_ptr::RcK>;

impl<'eu> EuEnv<'eu> {
    pub fn new<T>(ts: T, args: &[EuType<'eu>], scope: EuScope<'eu>) -> Self
    where
        T: IntoIterator<Item = EuSyn<'eu>>,
        T::IntoIter: 'eu,
    {
        let it: EuIter<'eu> = Box::new(ts.into_iter());
        Self {
            queue: it.peekable(),
            stack: args.into(),
            scope,
        }
    }

    #[inline]
    pub fn apply<T>(ts: T, args: &[EuType<'eu>], scope: EuScope<'eu>) -> EuRes<EuEnv<'eu>>
    where
        T: IntoIterator<Item = EuSyn<'eu>>,
        T::IntoIter: 'eu,
    {
        let mut env = Self::new(ts, args, scope);
        env.eval()?;
        Ok(env)
    }

    #[inline]
    pub fn apply_n_1<T>(ts: T, args: &[EuType<'eu>], scope: EuScope<'eu>) -> EuRes<EuType<'eu>>
    where
        T: IntoIterator<Item = EuSyn<'eu>>,
        T::IntoIter: 'eu,
    {
        Self::apply(ts, args, scope).and_then(|mut env| env.pop())
    }

    #[inline]
    pub fn apply_n_2<T>(
        ts: T,
        args: &[EuType<'eu>],
        scope: EuScope<'eu>,
    ) -> EuRes<(EuType<'eu>, EuType<'eu>)>
    where
        T: IntoIterator<Item = EuSyn<'eu>>,
        T::IntoIter: 'eu,
    {
        Self::apply(ts, args, scope).and_then(|mut env| {
            env.check_nargs(2)?;
            #[expect(clippy::missing_panics_doc, reason = "infallible")]
            let a1 = env.stack.pop().unwrap();
            #[expect(clippy::missing_panics_doc, reason = "infallible")]
            let a0 = env.stack.pop().unwrap();
            Ok((a0, a1))
        })
    }

    pub fn run_str(input: &str) -> EuRes<Self> {
        let mut env = Self::str(input)?;
        env.eval()?;
        Ok(env)
    }

    pub fn str(input: &str) -> EuRes<Self> {
        Ok(Self::new(
            euphrates.parse(input).map_err(|e| anyhow!(e.to_string()))?,
            &[],
            imbl::GenericHashMap::new(),
        ))
    }

    pub fn eval(&mut self) -> EuRes<()> {
        while let Some(t) = self.queue.next() {
            println!("{t:?}\n>>>");
            self.eval_syn(t)?;
            println!("{self}\n<<<\n");
        }
        Ok(())
    }

    fn eval_syn(&mut self, t: EuSyn<'eu>) -> EuRes<()> {
        match t {
            EuSyn::Raw(t) => self.eval_type(t),
            EuSyn::Var(s) => self.eval_var(&s),
            EuSyn::Move(s) => self.eval_move(&s),
            EuSyn::Vec(ts) => {
                self.push(EuType::vec(
                    EuEnv::apply(ts, &[], self.scope.clone())?.stack,
                ));
                Ok(())
            }
            EuSyn::Map(ts) => {
                self.push(EuType::Map(Rc::new(
                    EuEnv::apply(ts, &[], self.scope.clone())?
                        .stack
                        .into_iter()
                        .map(EuType::to_pair)
                        .try_collect()?,
                )));
                Ok(())
            }
        }
    }

    fn eval_type(&mut self, t: EuType<'eu>) -> EuRes<()> {
        match t {
            EuType::Word(w) => self.eval_word(&w),
            EuType::Res(Err(e)) => Err(anyhow!(e.to_string()).into()),
            _ => {
                self.push(t);
                Ok(())
            }
        }
    }

    fn eval_word(&mut self, w: &str) -> EuRes<()> {
        if let Some(v) = self.scope.get(w) {
            if let EuType::Expr(ts) = v {
                self.eval_iter(ts.clone())
            } else {
                self.push(v.clone());
                Ok(())
            }
        } else if let Some(f) = CORE.get(w) {
            f(self)
                .with_context(|| format!("`{w}` failed"))
                .map_err(Into::into)
        } else {
            Err(anyhow!("unknown word `{w}`").into())
        }
    }

    fn eval_var(&mut self, w: &str) -> EuRes<()> {
        if let Some(v) = self.scope.get(w) {
            self.push(v.clone());
            Ok(())
        } else {
            Err(anyhow!("unknown var `{w}`").into())
        }
    }

    fn eval_move(&mut self, w: &str) -> EuRes<()> {
        if let Some(v) = self.scope.remove(w) {
            self.push(v);
            Ok(())
        } else {
            Err(anyhow!("unknown var `{w}`").into())
        }
    }

    pub fn eval_iter<T>(&mut self, ts: T) -> EuRes<()>
    where
        T: IntoIterator<Item = EuSyn<'eu>>,
        T::IntoIter: 'eu,
    {
        if self.queue.peek().is_none() {
            self.load_iter(ts);
        } else {
            let mut env = self.frame(ts);
            env.eval()?;
            self.stack = env.stack;
        }
        Ok(())
    }

    pub fn load_iter<T>(&mut self, ts: T)
    where
        T: IntoIterator<Item = EuSyn<'eu>>,
        T::IntoIter: 'eu,
    {
        let empty: EuIter<'eu> = Box::new(iter::empty());
        let it: EuIter<'eu> = Box::new(
            ts.into_iter()
                .chain(mem::replace(&mut self.queue, empty.peekable())),
        );
        self.queue = it.peekable();
    }

    #[must_use]
    pub fn frame<T>(&self, ts: T) -> Self
    where
        T: IntoIterator<Item = EuSyn<'eu>>,
        T::IntoIter: 'eu,
    {
        let it: EuIter<'eu> = Box::new(ts.into_iter());
        Self {
            queue: it.peekable(),
            stack: self.stack.clone(),
            scope: self.scope.clone(),
        }
    }

    pub fn bind_args(&mut self, t: EuSyn<'eu>) -> EuRes<()> {
        match t {
            EuSyn::Raw(EuType::Word(w)) => {
                let v = self.stack.pop().context("insufficient args passed")?;
                self.scope.insert(w, v);
            }
            EuSyn::Raw(EuType::Expr(ts)) => {
                for t in ts.into_iter().rev() {
                    self.bind_args(t)?;
                }
            }
            _ => todo!(),
        }
        Ok(())
    }

    #[inline]
    pub fn push(&mut self, t: EuType<'eu>) {
        self.stack.push(t);
    }

    #[inline]
    pub fn pop(&mut self) -> EuRes<EuType<'eu>> {
        self.check_nargs(1).map(|()| {
            #[expect(clippy::missing_panics_doc, reason = "infallible")]
            self.stack.pop().unwrap()
        })
    }

    #[inline]
    pub fn last(&self) -> EuRes<&EuType<'eu>> {
        self.check_nargs(1).map(|()| {
            #[expect(clippy::missing_panics_doc, reason = "infallible")]
            self.stack.last().unwrap()
        })
    }

    pub fn iflip(&self, i: isize) -> EuRes<usize> {
        let len = self.stack.len().cast_signed();
        let j = if i < 0 { !i } else { len - i - 1 };
        (0 <= j && j < len)
            .then_some(j.cast_unsigned())
            .ok_or_else(|| anyhow!("{i} out of bounds [{}, {}]", -len, len - 1).into())
    }

    pub fn check_nargs(&self, n: usize) -> EuRes<()> {
        if self.stack.len() < n {
            Err(anyhow!("actual stack len {} < {} expected", self.stack.len(), n).into())
        } else {
            Ok(())
        }
    }

    pub fn clear_queue(&mut self) {
        let queue: EuIter<'_> = Box::new(iter::empty());
        self.queue = queue.peekable();
    }
}