use formulaa::editor::Editor;
use formulaa::render::{RenderCtx, render_root};
use formulaa::{ast, parse};
#[derive(Default)]
pub struct RoundtripGuard {
checked: Option<ast::Row>,
}
impl RoundtripGuard {
pub fn check(&mut self, ed: &mut Editor) {
if self.checked.as_ref() == Some(&ed.root) {
return;
}
self.checked = Some(ed.root.clone());
let row = ast::normalize(&ed.root);
if row.is_empty() {
return;
}
let ctx = RenderCtx::canonical();
let aa = render_root(&row, None, &ctx).to_text();
let row = ast::normalize(&formulaa::render::absorb_spacers(&row));
let kind = match parse::parse(&aa) {
Err(e) => format!("parse error: {}", e),
Ok(p) if p != row => "AST mismatch".into(),
Ok(p) => {
let aa2 = render_root(&p, None, &ctx).to_text();
if aa2 == render_root(&row, None, &ctx).to_text() {
return; }
"re-render mismatch".into()
}
};
ed.undo();
self.checked = Some(ed.root.clone());
ed.error(format!(
"⚠ edit refused — it would break the AA roundtrip ({})",
kind
));
}
}
#[cfg(test)]
mod tests {
use super::*;
use formulaa::input::Key;
#[test]
fn a_breaking_edit_is_refused() {
let mut ed = Editor::new();
ed.input(Key::Char('a'), false, false);
ed.input(Key::Char('b'), false, false);
ed.root = vec![formulaa::ast::Node::Roman('(')];
let mut guard = RoundtripGuard::default();
guard.check(&mut ed);
assert_ne!(
ed.root,
vec![formulaa::ast::Node::Roman('(')],
"the broken edit stood"
);
assert!(ed.message.contains("refused"), "{:?}", ed.message);
}
}