use std::fmt::Debug;
use std::panic::{AssertUnwindSafe, catch_unwind};
use zhc_utils::{
iter::{CollectInSmallVec, MultiZip},
small::SmallVec,
svec,
};
use crate::{AnnIR, Annotation, Dialect, DialectInstructionSet, DialectTypeSystem, IR};
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum InterpState<V: Interpretation> {
Pending,
Interpreted(V),
Poisoned,
Panicked(String),
}
impl<V: Interpretation> std::fmt::Debug for InterpState<V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InterpState::Pending => write!(f, "Pending"),
InterpState::Interpreted(v) => v.fmt(f),
InterpState::Poisoned => write!(f, "Poisoned"),
InterpState::Panicked(msg) => f.debug_tuple("Panicked").field(msg).finish(),
}
}
}
impl<V: Interpretation> InterpState<V> {
pub fn is_interpreted(&self) -> bool {
matches!(self, InterpState::Interpreted(_))
}
pub fn is_failed(&self) -> bool {
matches!(self, InterpState::Poisoned | InterpState::Panicked(_))
}
pub fn unwrap_interpreted(self) -> V {
match self {
InterpState::Interpreted(v) => v,
InterpState::Pending => panic!("Called unwrap on Pending"),
InterpState::Poisoned => panic!("Called unwrap on Poisoned"),
InterpState::Panicked(msg) => panic!("Called unwrap on Panicked: {msg}"),
}
}
pub fn as_interpreted(&self) -> Option<&V> {
match self {
InterpState::Interpreted(v) => Some(v),
_ => None,
}
}
pub fn ensure_not_pending(&self, msg: &str) -> &Self {
if matches!(self, InterpState::Pending) {
panic!("{msg}")
}
self
}
}
fn extract_panic_message(payload: Box<dyn std::any::Any + Send>) -> String {
match payload.downcast::<String>() {
Ok(s) => *s,
Err(payload) => match payload.downcast::<&str>() {
Ok(s) => s.to_string(),
Err(_) => "unknown panic".to_string(),
},
}
}
pub trait Interpretation: Annotation {}
pub trait InterpretsTo<I: Interpretation>: DialectTypeSystem {
fn type_of(interp: &I) -> Self;
fn is_inhabited_by(&self, interp: &I) -> bool {
Self::type_of(interp) == *self
}
}
pub trait Interpretable<I: Interpretation>: DialectInstructionSet
where
<Self as DialectInstructionSet>::TypeSystem: InterpretsTo<I>,
{
type Context: Debug;
fn interpret(&self, context: &mut Self::Context, arguments: SmallVec<I>) -> SmallVec<I>;
}
pub fn interpret_ir<'ir, D: Dialect, V: Interpretation>(
ir: &'ir IR<D>,
context: &mut <D::InstructionSet as Interpretable<V>>::Context,
) -> Result<AnnIR<'ir, D, (), V>, AnnIR<'ir, D, (), InterpState<V>>>
where
D::InstructionSet: Interpretable<V>,
D::TypeSystem: InterpretsTo<V>,
{
let mut had_failure = false;
let annotated = ir.forward_dataflow_analysis::<(), InterpState<V>>(|ann_opref| {
let sig = ann_opref.get_instruction().get_signature();
let n_returns = sig.get_returns().len();
let arguments: SmallVec<InterpState<V>> = ann_opref
.get_args_iter()
.map(|arg| {
arg.get_annotation()
.clone()
.unwrap_analyzed()
.ensure_not_pending("Pending value encountered during interpretation")
.to_owned()
})
.cosvec();
if arguments.iter().any(|arg: &InterpState<V>| arg.is_failed()) {
return ((), svec![InterpState::Poisoned; n_returns]);
}
let interpreted_args = arguments
.into_iter()
.map(InterpState::unwrap_interpreted)
.cosvec();
for (i, (arg, expected_type)) in (interpreted_args.iter(), sig.get_args().iter())
.mzip()
.enumerate()
{
if !expected_type.is_inhabited_by(arg) {
panic!(
"Unexpected argument type encountered while interpreting {}. \
At position {i}, expected type {expected_type}, but encountered {}.",
ann_opref.format(),
D::TypeSystem::type_of(arg)
)
}
}
let interpret_result = catch_unwind(AssertUnwindSafe(|| {
ann_opref
.get_instruction()
.interpret(context, interpreted_args)
}));
match interpret_result {
Ok(returns) => {
for (i, (ret, expected_type)) in (returns.iter(), sig.get_returns().iter())
.mzip()
.enumerate()
{
if !expected_type.is_inhabited_by(ret) {
panic!(
"Unexpected return type encountered while interpreting {}. \
At position {i}, expected type {expected_type}, but encountered {}.",
ann_opref.format(),
D::TypeSystem::type_of(ret)
)
}
}
let returns = returns.into_iter().map(InterpState::Interpreted).cosvec();
((), returns)
}
Err(payload) => {
had_failure = true;
let msg = extract_panic_message(payload);
((), svec![InterpState::Panicked(msg); n_returns])
}
}
});
if had_failure {
Err(annotated)
} else {
Ok(annotated.map_valann(|valref| valref.get_annotation().clone().unwrap_interpreted()))
}
}