use std::collections::BTreeMap;
use super::Refusal;
use crate::{
check::Host,
grammar::{RuleCommon, Schema, States},
markdown::{BlockKind, Dialect},
};
pub(crate) struct Deriver<'a> {
pub(super) schema: &'a Schema,
pub(super) host: &'a dyn Host,
pub(super) dialect: Dialect,
pub(super) states: &'a States,
pub(super) keys: BTreeMap<String, Vec<String>>,
pub(super) kinds: Vec<(BlockKind, bool, usize, usize)>,
pub(super) values: BTreeMap<String, String>,
pub(super) section_rule: Option<String>,
pub(super) refusals: Vec<Refusal>,
pub(super) omitted: Vec<Refusal>,
pub(super) in_optional: bool,
pub(super) scope: Option<BTreeMap<String, usize>>,
}
struct Trial {
pub(super) lines: usize,
pub(super) refusals: usize,
pub(super) keys: BTreeMap<String, Vec<String>>,
pub(super) kinds: usize,
}
impl Deriver<'_> {
pub(super) fn attempt(
&mut self,
optional: bool,
out: &mut Vec<String>,
write: impl FnOnce(&mut Self, &mut Vec<String>),
) {
if !optional {
return write(self, out);
}
let at = self.save(out);
write(self, out);
if self.refusals.len() > at.refusals {
self.undo(at, out);
}
}
fn save(&self, out: &[String]) -> Trial {
Trial {
lines: out.len(),
refusals: self.refusals.len(),
keys: self.keys.clone(),
kinds: self.kinds.len(),
}
}
fn undo(&mut self, at: Trial, out: &mut Vec<String>) {
out.truncate(at.lines);
let stopped: Vec<Refusal> = self.refusals.split_off(at.refusals);
self.keys = at.keys;
self.kinds.truncate(at.kinds);
push_unique(&mut self.omitted, stopped);
}
pub(super) fn optional_region(
&mut self,
out: &mut Vec<String>,
write: impl FnOnce(&mut Self, &mut Vec<String>),
) {
let start = out.len();
let held = std::mem::replace(&mut self.in_optional, true);
write(self, out);
self.in_optional = held;
wrap(&self.dialect, start, out);
}
pub(super) fn gate_text(&self, common: &RuleCommon) -> Option<String> {
common
.only
.is_gated()
.then(|| format!("{} {}", self.dialect.spell("only"), common.only.render()))
}
pub(super) fn refuse(&mut self, line: usize, what: impl Into<String>) {
self.refuse_on(line, "TLMD_116", what);
}
pub(super) fn refuse_on(
&mut self,
line: usize,
criterion: &'static str,
what: impl Into<String>,
) {
let what = what.into();
if !self
.refusals
.iter()
.any(|r| r.line == line && r.what == what)
{
self.refusals.push(Refusal {
line,
criterion,
what,
});
}
}
}
pub(super) fn push_unique<T: PartialEq>(into: &mut Vec<T>, from: impl IntoIterator<Item = T>) {
for one in from {
if !into.contains(&one) {
into.push(one);
}
}
}
fn wrap(dialect: &Dialect, start: usize, out: &mut Vec<String>) {
while out.len() > start && out.last().is_some_and(String::is_empty) {
out.pop();
}
if out.len() == start {
return;
}
out.insert(start, String::new());
out.insert(start, format!("<!-- {} -->", dialect.spell("optional")));
out.push(String::new());
out.push(format!("<!-- /{} -->", dialect.spell("optional")));
out.push(String::new());
}