use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::fmt;
pub trait EffectDisplay {
fn display_for_ide(&self) -> String;
fn display_for_error(&self) -> String;
fn short_description(&self) -> String;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EffectRowDisplay {
pub effects: Vec<EffectInfo>,
pub is_open: bool,
pub tail_var: Option<String>,
}
impl EffectRowDisplay {
#[inline]
pub fn empty() -> Self {
EffectRowDisplay {
effects: Vec::new(),
is_open: false,
tail_var: None,
}
}
#[inline]
pub fn closed(effects: Vec<EffectInfo>) -> Self {
EffectRowDisplay {
effects,
is_open: false,
tail_var: None,
}
}
#[inline]
pub fn open(effects: Vec<EffectInfo>, tail_var: impl Into<String>) -> Self {
EffectRowDisplay {
effects,
is_open: true,
tail_var: Some(tail_var.into()),
}
}
#[inline]
pub fn with_effect(mut self, effect: EffectInfo) -> Self {
self.effects.push(effect);
self
}
}
impl fmt::Display for EffectRowDisplay {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.effects.is_empty() && !self.is_open {
return write!(f, "Pure");
}
let mut effect_strs: Vec<String> = Vec::with_capacity(self.effects.len());
effect_strs.extend(self.effects.iter().map(ToString::to_string));
let effects_part = effect_strs.join(" | ");
if self.is_open {
if let Some(ref tail) = self.tail_var {
if self.effects.is_empty() {
write!(f, "{tail}")
} else {
write!(f, "{effects_part} | {tail}")
}
} else {
write!(f, "{effects_part} | ...")
}
} else {
write!(f, "{effects_part}")
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EffectInfo {
pub name: String,
pub type_params: Vec<String>,
pub description: Option<String>,
pub is_core_effect: bool,
}
impl EffectInfo {
pub fn new(name: impl Into<String>) -> Self {
EffectInfo {
name: name.into(),
type_params: Vec::new(),
description: None,
is_core_effect: false,
}
}
pub fn with_param(mut self, param: impl Into<String>) -> Self {
self.type_params.push(param.into());
self
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = Some(desc.into());
self
}
pub fn core(mut self) -> Self {
self.is_core_effect = true;
self
}
pub fn io() -> Self {
Self::new("IO")
.with_description("Performs input/output operations")
.core()
}
pub fn state(state_type: impl Into<String>) -> Self {
Self::new("State")
.with_param(state_type)
.with_description("Manages mutable state")
.core()
}
pub fn error(error_type: impl Into<String>) -> Self {
Self::new("Error")
.with_param(error_type)
.with_description("May fail with an error")
.core()
}
pub fn reader(env_type: impl Into<String>) -> Self {
Self::new("Reader")
.with_param(env_type)
.with_description("Reads from an environment")
.core()
}
pub fn writer(log_type: impl Into<String>) -> Self {
Self::new("Writer")
.with_param(log_type)
.with_description("Writes to a log")
.core()
}
pub fn async_effect() -> Self {
Self::new("Async")
.with_description("Performs asynchronous operations")
.core()
}
}
impl fmt::Display for EffectInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.type_params.is_empty() {
write!(f, "{}", self.name)
} else {
write!(f, "{}<{}>", self.name, self.type_params.join(", "))
}
}
}
#[derive(Debug, Clone)]
pub struct ComputationTypeDisplay {
pub effects: EffectRowDisplay,
pub return_type: String,
}
impl ComputationTypeDisplay {
pub fn new(effects: EffectRowDisplay, return_type: impl Into<String>) -> Self {
ComputationTypeDisplay {
effects,
return_type: return_type.into(),
}
}
pub fn pure(return_type: impl Into<String>) -> Self {
ComputationTypeDisplay {
effects: EffectRowDisplay::empty(),
return_type: return_type.into(),
}
}
}
impl fmt::Display for ComputationTypeDisplay {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Computation<{{ {} }}, {}>",
self.effects, self.return_type
)
}
}
pub fn format_type_name(type_name: &str) -> String {
let cleaned = type_name
.replace("ordofp_core::", "")
.replace("ordofp::", "")
.replace("effects::", "")
.replace("::Effectus", "");
let translations = [
("Computatio", "Computation"),
("StatusEffectus", "State"),
("ErrorEffectus", "Error"),
("ReaderEffectus", "Reader"),
("ScriptorEffectus", "Writer"),
("IoEffectus", "IO"),
("AsyncEffectus", "Async"),
("Effectus", "Effect"), ("RowVacuus", "∅"),
("RowExtensio", "+"),
];
let mut result = cleaned;
for (latin, english) in translations {
result = result.replace(latin, english);
}
result
}
pub fn format_effect_row(row_type: &str) -> String {
let cleaned = format_type_name(row_type);
if cleaned.contains('+') {
cleaned
.replace('+', "|")
.replace("∅", "")
.trim_end_matches(" |")
.to_string()
} else {
cleaned
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_effect_info_display() {
let io = EffectInfo::io();
assert_eq!(io.to_string(), "IO");
let state = EffectInfo::state("Config");
assert_eq!(state.to_string(), "State<Config>");
let error = EffectInfo::error("AppError");
assert_eq!(error.to_string(), "Error<AppError>");
}
#[test]
fn test_effect_row_display_empty() {
let row = EffectRowDisplay::empty();
assert_eq!(row.to_string(), "Pure");
}
#[test]
fn test_effect_row_display_single() {
let row = EffectRowDisplay::closed(alloc::vec![EffectInfo::io()]);
assert_eq!(row.to_string(), "IO");
}
#[test]
fn test_effect_row_display_multiple() {
let row = EffectRowDisplay::closed(alloc::vec![
EffectInfo::io(),
EffectInfo::state("Config"),
EffectInfo::error("AppError"),
]);
assert_eq!(row.to_string(), "IO | State<Config> | Error<AppError>");
}
#[test]
fn test_effect_row_display_open() {
let row = EffectRowDisplay::open(alloc::vec![EffectInfo::io()], "R");
assert_eq!(row.to_string(), "IO | R");
}
#[test]
fn test_computation_type_display() {
let comp = ComputationTypeDisplay::new(
EffectRowDisplay::closed(alloc::vec![EffectInfo::io()]),
"Response",
);
assert_eq!(comp.to_string(), "Computation<{ IO }, Response>");
}
#[test]
fn test_format_type_name() {
assert_eq!(
format_type_name("ordofp_core::effects::Computatio"),
"Computation"
);
assert_eq!(format_type_name("StatusEffectus"), "State");
assert_eq!(format_type_name("RowVacuus"), "∅");
}
}