typenum-consts 0.1.2

Procedural macros that take a literal integer (or the result of an evaluation of simple mathematical expressions or an environment variable whose value is a literal integer) and convert it to a `typenum::Unsigned` / `typenum::Integer` type-level positive/negative/unsigned integer.
Documentation
use crate::ast::{LitInteger, MathExprs};
use crate::rsc::{parse, tokenize, Interpreter, Variant};
use proc_macro2::Span;
use quote::ToTokens;
use syn::{spanned::Spanned, Error, LitInt, Result, Stmt};

const ANS: &str = "ANS";

pub(crate) fn eval_exprs(math_exprs: MathExprs) -> Result<LitInteger> {
    match math_exprs {
        MathExprs::Negative(stmts) => eval_exprs_neg(stmts),
        MathExprs::Positive(stmts) => eval_exprs_pos(stmts),
        MathExprs::Unsigned(stmts) => eval_exprs_usig(stmts),
    }
}

fn interpreter_eval(math_stmts: &[Stmt]) -> Result<isize> {
    let mut interpreter = Interpreter::new();
    interpreter.set_var(ANS.into(), Variant::Num(0));
    if math_stmts.is_empty() {
        let last_stmt = get_last_stmt(math_stmts)?;
        return Err(Error::new(
            last_stmt.span(),
            "the mathematical expressions provided into the macro cannot be empty",
        ));
    }
    let mut final_ans: Option<isize> = None;
    for math_stmt in math_stmts {
        let mut ts_string = math_stmt.to_token_stream().to_string();
        if ts_string.ends_with(';') {
            ts_string = ts_string[..ts_string.len() - 1].to_string();
        }
        let tokens = tokenize::<isize>(ts_string.as_str())
            .map_err(|err| Error::new(math_stmt.span(), format!("{err:?}")))?;
        let expr = parse::<isize>(&tokens)
            .map_err(|err| Error::new(math_stmt.span(), format!("{err:?}")))?;
        let ans = interpreter
            .eval(&expr)
            .map_err(|err| Error::new(math_stmt.span(), format!("{err:?}")))?;
        interpreter.set_var(ANS.into(), Variant::Num(ans));
        final_ans = Some(ans);
    }
    if let Some(final_ans) = final_ans {
        Ok(final_ans)
    } else {
        Err(Error::new(
            Span::call_site(),
            "the mathematical expressions passed into the procedural macro should not be empty",
        ))
    }
}

fn eval_exprs_neg(math_stmts: Vec<Stmt>) -> Result<LitInteger> {
    let final_ans = interpreter_eval(&math_stmts)?;
    let last_stmt = get_last_stmt(&math_stmts)?;
    if final_ans > 0 {
        return Err(Error::new(
            last_stmt.span(),
            format!(
                "expressions should be evaluated to a negative integer, got `{final_ans}` instead"
            ),
        ));
    };
    Ok(LitInteger::Negative {
        lit_integer: LitInt::new(format! {"{}", final_ans}.as_str(), last_stmt.span()),
    })
}

fn get_last_stmt(math_stmts: &[Stmt]) -> Result<&Stmt> {
    math_stmts.last().ok_or_else(|| {
        Error::new(
            Span::call_site(),
            "cannot get the last statement from `math_stmts`",
        )
    })
}

fn eval_exprs_pos(math_stmts: Vec<Stmt>) -> Result<LitInteger> {
    let final_ans = interpreter_eval(&math_stmts)?;
    let last_stmt = get_last_stmt(&math_stmts)?;
    if final_ans <= 0 {
        return Err(Error::new(
            last_stmt.span(),
            format!(
                "expressions should be evaluated to a positive integer, got `{final_ans}` instead"
            ),
        ));
    };
    Ok(LitInteger::Positive {
        lit_integer: LitInt::new(format! {"{}", final_ans}.as_str(), last_stmt.span()),
    })
}

fn eval_exprs_usig(math_stmts: Vec<Stmt>) -> Result<LitInteger> {
    let final_ans = interpreter_eval(&math_stmts)?;
    let last_stmt = get_last_stmt(&math_stmts)?;

    if final_ans < 0 {
        return Err(Error::new(
            last_stmt.span(),
            format!("expressions should be evaluated to a non-negative or positive integer, got `{final_ans}` instead")
        ));
    };
    Ok(LitInteger::Unsigned {
        lit_integer: LitInt::new(format! {"{}", final_ans}.as_str(), last_stmt.span()),
    })
}