use std::fmt::Display;
use std::io::{self, BufRead, Write};
use std::str::FromStr;
use corophage::prelude::*;
#[effect(T)]
struct Pause<T> {
label: String,
value: T,
}
fn pause<T>(label: &str, value: T) -> Pause<T> {
Pause {
label: label.into(),
value,
}
}
#[effectful(Pause<i64>)]
fn example_program() -> i64 {
let x = yield_!(pause("x", 1 + 1));
let inner = yield_!(pause("what's this?", 99 + 1));
let y = yield_!(pause("y", x + x + inner));
x + y
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
struct Decision<T> {
label: String,
original: T,
resumed: T,
}
#[derive(Clone, Copy)]
enum Mode {
Step,
Go,
Silent,
}
struct DebuggerState<T> {
replay: Vec<Decision<T>>,
decisions: Vec<Decision<T>>,
mode: Mode,
went_back: bool,
}
fn debugger_handler<T>(state: &mut DebuggerState<T>, effect: Pause<T>) -> Control<T>
where
T: Clone + Display + FromStr + PartialEq + Send + Sync,
<T as FromStr>::Err: Display,
{
let index = state.decisions.len();
if index < state.replay.len() {
let decision = state.replay[index].clone();
if decision.resumed != decision.original {
println!(
" \u{23E9} {}: {} -> {}",
decision.label, decision.original, decision.resumed
);
} else {
println!(" \u{23E9} {}: {}", decision.label, decision.resumed);
}
state.decisions.push(decision.clone());
return Control::resume(decision.resumed);
}
if matches!(state.mode, Mode::Silent) {
state.decisions.push(Decision {
label: effect.label,
original: effect.value.clone(),
resumed: effect.value.clone(),
});
return Control::resume(effect.value);
}
println!(" \u{1F440} {}", effect.label);
println!(" {}", effect.value);
if matches!(state.mode, Mode::Go) {
println!();
state.decisions.push(Decision {
label: effect.label,
original: effect.value.clone(),
resumed: effect.value.clone(),
});
return Control::resume(effect.value);
}
println!(" \u{23F8} Debugger paused \u{1F41B}");
let can_back = !state.decisions.is_empty();
let prompt = if can_back {
" resume (Enter), (b)ack, (g)o, (s)ilent, (r)eplace: "
} else {
" resume (Enter), (g)o, (s)ilent, (r)eplace: "
};
loop {
print!("{prompt}");
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin().lock().read_line(&mut input).unwrap();
let cmd = input.trim();
match cmd {
"" => {
println!();
state.decisions.push(Decision {
label: effect.label,
original: effect.value.clone(),
resumed: effect.value.clone(),
});
return Control::resume(effect.value);
}
"b" if can_back => {
state.decisions.pop();
state.went_back = true;
return Control::cancel();
}
"g" => {
println!();
state.mode = Mode::Go;
state.decisions.push(Decision {
label: effect.label,
original: effect.value.clone(),
resumed: effect.value.clone(),
});
return Control::resume(effect.value);
}
"s" => {
state.mode = Mode::Silent;
state.decisions.push(Decision {
label: effect.label,
original: effect.value.clone(),
resumed: effect.value.clone(),
});
return Control::resume(effect.value);
}
"r" => {
print!(" > ");
io::stdout().flush().unwrap();
let mut val_input = String::new();
io::stdin().lock().read_line(&mut val_input).unwrap();
match val_input.trim().parse::<T>() {
Ok(new_val) => {
println!();
state.decisions.push(Decision {
label: effect.label,
original: effect.value,
resumed: new_val.clone(),
});
return Control::resume(new_val);
}
Err(e) => {
println!(" Invalid value: {e}, try again.");
}
}
}
_ => {
println!(" Unknown command, try again.");
}
}
}
}
fn main() {
println!("=== Stepwise Debugger ===");
println!();
let mut replay: Vec<Decision<i64>> = Vec::new();
loop {
let mut state = DebuggerState {
replay: replay.clone(),
decisions: Vec::new(),
mode: Mode::Step,
went_back: false,
};
let result = example_program()
.handle(debugger_handler)
.run_sync_stateful(&mut state);
match result {
Ok(value) => {
println!(" Result: {value}");
break;
}
Err(_) if state.went_back => {
replay = state.decisions;
println!(" << Rewinding...");
println!();
}
Err(e) => {
panic!("Unexpected cancellation: {e}");
}
}
}
}