use replique::RepliqueFile;
use replique::dialogue::Value;
use replique::vm::{DialogueEvent, DialogueVm, ResumeEvent};
use std::collections::BTreeMap;
use std::fmt::Write;
const MAX_EVENTS: usize = 200;
fn trace(src: &str, entry: &str) -> String {
let file = RepliqueFile::from_source(src);
let dialogue = file
.dialogue
.expect("a valid corpus file always compiles to a dialogue");
let mut out = String::new();
let mut vm = DialogueVm::default();
let mut known: BTreeMap<String, Value> = BTreeMap::new();
let mut event = vm.start(dialogue, entry);
for _ in 0..MAX_EVENTS {
render_writes(&mut out, &mut known, &vm);
event = match event {
Ok(DialogueEvent::Say {
speaker,
text,
tags,
}) => {
let speaker = speaker.unwrap_or_else(|| "-".to_owned());
let tags: String = tags.iter().map(|tag| format!(" {tag}")).collect();
let _ = writeln!(out, "say {speaker} {text:?}{tags}");
vm.resume(ResumeEvent::Advance)
}
Ok(DialogueEvent::Command { name, args, .. }) => {
let args: Vec<_> = args.iter().map(render_value).collect();
let _ = writeln!(out, "command {name}({})", args.join(", "));
vm.resume(ResumeEvent::Advance)
}
Ok(DialogueEvent::Choices { choices }) => {
let texts: Vec<&str> = choices.iter().map(|c| c.text.as_str()).collect();
let _ = writeln!(out, "choices {texts:?}");
for (index, choice) in choices.iter().enumerate() {
if !choice.tags.is_empty() {
let tags: String = choice.tags.iter().map(|t| format!(" {t}")).collect();
let _ = writeln!(out, " tags {index}{tags}");
}
}
let _ = writeln!(out, " pick 0 {:?}", choices[0].text);
vm.resume(ResumeEvent::Select(0))
}
Ok(DialogueEvent::Finished) => {
out.push_str("end\n");
return out;
}
Err(err) => {
let _ = writeln!(out, "error {err}");
return out;
}
};
}
out.push_str("(too many events)\n");
out
}
fn render_writes(out: &mut String, known: &mut BTreeMap<String, Value>, vm: &DialogueVm) {
let now: BTreeMap<_, _> = vm
.vars()
.vars()
.iter()
.map(|(name, value)| (name.clone(), value.clone()))
.collect();
for (name, value) in &now {
if known.get(name) != Some(value) {
let _ = writeln!(out, " ${name} = {}", render_value(value));
}
}
*known = now;
}
fn render_value(value: &Value) -> String {
match value {
Value::Bool(val) => val.to_string(),
Value::Int(val) => val.to_string(),
Value::Float(val) => val.to_string(),
Value::String(val) => format!("{val:?}"),
Value::Dict(map) => {
let mut attrs = map
.iter()
.map(|(key, val)| format!("{key}: {}", render_value(val)))
.collect::<Vec<_>>();
attrs.sort();
format!("{{{}}}", attrs.join(", "))
}
}
}
macro_rules! run {
($(($name:ident, $entry:literal)),* $(,)?) => {
$(
#[test]
fn $name() {
let src = include_str!(concat!("corpus/", stringify!($name), ".rep"));
insta::assert_snapshot!(trace(src, $entry));
}
)*
};
}
run!(
(minimal, "start"),
(example, "start"),
(nested_choices, "start"),
(empty_body, "start"),
(two_groups, "start"),
(unicode, "depart"),
(forward_jump, "start"),
(jump_to_end, "start"),
(jump_back, "start"),
(commands, "start"),
(lets, "start"),
(conditions, "start"),
(else_branch, "start"),
(dicts, "start"),
(inline, "start"),
(loops, "start"),
(has, "start"),
(tags, "start"),
);