use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StackValue {
Type(String),
Value(String),
TypeVar(String),
Rest(String),
}
impl StackValue {
pub fn ty(s: impl Into<String>) -> Self {
Self::Type(s.into())
}
pub fn val(s: impl Into<String>) -> Self {
Self::Value(s.into())
}
pub fn var(s: impl Into<String>) -> Self {
Self::TypeVar(s.into())
}
pub fn rest(s: impl Into<String>) -> Self {
Self::Rest(s.into())
}
fn display(&self) -> String {
match self {
Self::Type(s) | Self::Value(s) | Self::TypeVar(s) => s.clone(),
Self::Rest(s) => format!("..{}", s),
}
}
#[allow(dead_code)]
fn is_rest(&self) -> bool {
matches!(self, Self::Rest(_))
}
}
impl fmt::Display for StackValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.display())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Stack {
values: Vec<StackValue>,
}
impl Stack {
pub fn new(values: Vec<StackValue>) -> Self {
Self { values }
}
#[allow(dead_code)]
pub fn empty() -> Self {
Self { values: vec![] }
}
pub fn with_rest(name: impl Into<String>) -> Self {
Self {
values: vec![StackValue::rest(name)],
}
}
pub fn push(mut self, value: StackValue) -> Self {
self.values.push(value);
self
}
#[allow(dead_code)]
pub fn values(&self) -> &[StackValue] {
&self.values
}
#[allow(dead_code)]
pub fn has_concrete_values(&self) -> bool {
self.values.iter().any(|v| !v.is_rest())
}
fn display_width(&self) -> usize {
self.values
.iter()
.map(|v| v.display().len())
.max()
.unwrap_or(0)
.max(3) }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StackEffect {
pub name: String,
pub inputs: Stack,
pub outputs: Stack,
}
impl StackEffect {
pub fn new(name: impl Into<String>, inputs: Stack, outputs: Stack) -> Self {
Self {
name: name.into(),
inputs,
outputs,
}
}
pub fn literal(value: impl Into<String>) -> Self {
let val = value.into();
Self {
name: val.clone(),
inputs: Stack::with_rest("a"),
outputs: Stack::with_rest("a").push(StackValue::val(val)),
}
}
pub fn render_signature(&self) -> String {
let inputs: Vec<_> = self.inputs.values.iter().map(|v| v.display()).collect();
let outputs: Vec<_> = self.outputs.values.iter().map(|v| v.display()).collect();
format!(
"{} ( {} -- {} )",
self.name,
inputs.join(" "),
outputs.join(" ")
)
}
}
pub fn render_stack(stack: &Stack) -> Vec<String> {
if stack.values.is_empty() {
return vec!["(empty)".to_string()];
}
let width = stack.display_width();
let inner_width = width + 2;
let top_border = format!("┌{}┐", "─".repeat(inner_width));
let mid_border = format!("├{}┤", "─".repeat(inner_width));
let bot_border = format!("└{}┘", "─".repeat(inner_width));
let mut lines = Vec::new();
let values: Vec<_> = stack.values.iter().rev().collect();
for (i, value) in values.iter().enumerate() {
if i == 0 {
lines.push(top_border.clone());
} else {
lines.push(mid_border.clone());
}
let display = value.display();
let padding = inner_width - display.len();
let left_pad = padding / 2;
let right_pad = padding - left_pad;
lines.push(format!(
"│{}{}{}│",
" ".repeat(left_pad),
display,
" ".repeat(right_pad)
));
}
lines.push(bot_border);
lines
}
pub fn render_transition(effect: &StackEffect, before: &Stack, after: &Stack) -> Vec<String> {
let sig = effect.render_signature();
let before_lines = render_stack(before);
let after_lines = render_stack(after);
let before_width = before_lines.iter().map(|l| l.len()).max().unwrap_or(0);
let arrow_col = " → ";
let max_height = before_lines.len().max(after_lines.len());
let mut result = vec![format!(" {}", sig)];
for i in 0..max_height {
let before_line = before_lines
.get(i)
.map(|s| s.as_str())
.unwrap_or("")
.to_string();
let after_line = after_lines
.get(i)
.map(|s| s.as_str())
.unwrap_or("")
.to_string();
let padded_before = format!("{:width$}", before_line, width = before_width);
let arrow = if i == max_height / 2 {
arrow_col
} else {
" "
};
result.push(format!(" {}{}{}", padded_before, arrow, after_line));
}
result
}
#[allow(dead_code)]
pub fn render_sequence(steps: &[(StackEffect, Stack, Stack)]) -> Vec<String> {
if steps.is_empty() {
return vec![];
}
let mut result = Vec::new();
for (i, (effect, before, after)) in steps.iter().enumerate() {
if i > 0 {
result.push(String::new()); }
result.extend(render_transition(effect, before, after));
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_stack_value_display() {
assert_eq!(StackValue::ty("Int").display(), "Int");
assert_eq!(StackValue::val("42").display(), "42");
assert_eq!(StackValue::var("x").display(), "x");
assert_eq!(StackValue::rest("a").display(), "..a");
}
#[test]
fn test_empty_stack_render() {
let stack = Stack::empty();
let lines = render_stack(&stack);
assert_eq!(lines, vec!["(empty)"]);
}
#[test]
fn test_single_value_stack() {
let stack = Stack::new(vec![StackValue::val("42")]);
let lines = render_stack(&stack);
assert_eq!(lines.len(), 3); assert!(lines[0].contains("┌"));
assert!(lines[1].contains("42"));
assert!(lines[2].contains("└"));
}
#[test]
fn test_multi_value_stack() {
let stack = Stack::new(vec![
StackValue::rest("a"),
StackValue::ty("Int"),
StackValue::ty("Float"),
]);
let lines = render_stack(&stack);
assert_eq!(lines.len(), 7);
assert!(lines[1].contains("Float"));
assert!(lines[3].contains("Int"));
assert!(lines[5].contains("..a"));
}
#[test]
fn test_stack_effect_signature() {
let effect = StackEffect::new(
"swap",
Stack::new(vec![
StackValue::rest("a"),
StackValue::var("x"),
StackValue::var("y"),
]),
Stack::new(vec![
StackValue::rest("a"),
StackValue::var("y"),
StackValue::var("x"),
]),
);
assert_eq!(effect.render_signature(), "swap ( ..a x y -- ..a y x )");
}
#[test]
fn test_swap_transition() {
let effect = StackEffect::new(
"swap",
Stack::new(vec![
StackValue::rest("a"),
StackValue::var("x"),
StackValue::var("y"),
]),
Stack::new(vec![
StackValue::rest("a"),
StackValue::var("y"),
StackValue::var("x"),
]),
);
let before = Stack::new(vec![
StackValue::rest("a"),
StackValue::ty("Int"),
StackValue::ty("Float"),
]);
let after = Stack::new(vec![
StackValue::rest("a"),
StackValue::ty("Float"),
StackValue::ty("Int"),
]);
let lines = render_transition(&effect, &before, &after);
assert!(lines[0].contains("swap ( ..a x y -- ..a y x )"));
let arrow_line = lines.iter().find(|l| l.contains("→"));
assert!(arrow_line.is_some());
}
#[test]
fn test_dup_multiply_example() {
let dup_effect = StackEffect::new(
"dup",
Stack::new(vec![StackValue::rest("a"), StackValue::var("x")]),
Stack::new(vec![
StackValue::rest("a"),
StackValue::var("x"),
StackValue::var("x"),
]),
);
let before_dup = Stack::new(vec![StackValue::val("5")]);
let after_dup = Stack::new(vec![StackValue::val("5"), StackValue::val("5")]);
let mult_effect = StackEffect::new(
"i.multiply",
Stack::new(vec![
StackValue::rest("a"),
StackValue::ty("Int"),
StackValue::ty("Int"),
]),
Stack::new(vec![StackValue::rest("a"), StackValue::ty("Int")]),
);
let after_mult = Stack::new(vec![StackValue::val("25")]);
let sequence = vec![
(dup_effect, before_dup, after_dup.clone()),
(mult_effect, after_dup, after_mult),
];
let lines = render_sequence(&sequence);
let text = lines.join("\n");
assert!(text.contains("dup"));
assert!(text.contains("i.multiply"));
assert!(text.contains("5"));
assert!(text.contains("25"));
}
#[test]
fn test_width_calculation() {
let stack = Stack::new(vec![
StackValue::ty("VeryLongTypeName"),
StackValue::ty("Int"),
]);
let lines = render_stack(&stack);
let widths: Vec<_> = lines.iter().map(|l| l.chars().count()).collect();
assert!(widths.windows(2).all(|w| w[0] == w[1]));
}
}