use crate::cancel::CancelToken;
use crate::executor::Executor;
use crate::job::Job;
use crate::node::NodeId;
use crate::outcome::Outcome;
use crate::pass::Pass;
use crate::schedule::Schedule;
use crate::span::Span;
use crate::tree::ParseTree;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RunReport {
pub rounds_run: usize,
pub nodes_processed: usize,
pub nodes_failed: usize,
pub reached_fixpoint: bool,
pub cancelled: bool,
pub violations: Vec<Violation>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Violation {
pub node: NodeId,
pub round: usize,
pub pass: Option<&'static str>,
pub span: Span,
pub kind: ViolationKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ViolationKind {
OutsideParent,
WrongRevision,
NotSmaller,
}
impl std::fmt::Display for Violation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let pass = self.pass.unwrap_or("pass");
match self.kind {
ViolationKind::OutsideParent => write!(
f,
"{pass} (round {}) produced child region {}, which escapes its parent region — children must be contained in the region they were parsed from",
self.round, self.span
),
ViolationKind::WrongRevision => write!(
f,
"{pass} (round {}) produced child region {} against a stale source revision — children must use their parent's revision",
self.round, self.span
),
ViolationKind::NotSmaller => write!(
f,
"{pass} (round {}) produced child region {} that is not strictly smaller than its parent (EngineConfig::enforce_shrink is enabled)",
self.round, self.span
),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct EngineConfig {
pub enforce_shrink: bool,
pub max_rounds: Option<usize>,
}
pub struct Engine<C> {
schedule: Schedule<C>,
config: EngineConfig,
}
#[doc(hidden)]
pub trait Passes<C> {
fn into_schedule(self) -> Schedule<C>;
}
macro_rules! impl_passes_for_tuple {
($($name:ident),+) => {
impl<C, $($name),+> Passes<C> for ($($name,)+)
where
$($name: Pass<Ctx = C> + Send + Sync + 'static,)+
{
fn into_schedule(self) -> Schedule<C> {
#[allow(non_snake_case)]
let ($($name,)+) = self;
let mut schedule = Schedule::new();
$( schedule.push($name); )+
schedule
}
}
};
}
impl_passes_for_tuple!(P1);
impl_passes_for_tuple!(P1, P2);
impl_passes_for_tuple!(P1, P2, P3);
impl_passes_for_tuple!(P1, P2, P3, P4);
impl_passes_for_tuple!(P1, P2, P3, P4, P5);
impl_passes_for_tuple!(P1, P2, P3, P4, P5, P6);
impl_passes_for_tuple!(P1, P2, P3, P4, P5, P6, P7);
impl_passes_for_tuple!(P1, P2, P3, P4, P5, P6, P7, P8);
impl_passes_for_tuple!(P1, P2, P3, P4, P5, P6, P7, P8, P9);
impl_passes_for_tuple!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10);
impl_passes_for_tuple!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11);
impl_passes_for_tuple!(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12);
impl<C> Passes<C> for Vec<Box<dyn Pass<Ctx = C> + Send + Sync>> {
fn into_schedule(self) -> Schedule<C> {
let mut schedule = Schedule::new();
for pass in self {
schedule.push_boxed(pass);
}
schedule
}
}
impl<C> Passes<C> for Schedule<C> {
fn into_schedule(self) -> Schedule<C> {
self
}
}
impl<C> Engine<C> {
pub fn new(schedule: Schedule<C>) -> Self {
Self {
schedule,
config: EngineConfig::default(),
}
}
pub fn with(passes: impl Passes<C>) -> Self {
Self::new(passes.into_schedule())
}
pub fn with_config(passes: impl Passes<C>, config: EngineConfig) -> Self {
Self {
schedule: passes.into_schedule(),
config,
}
}
pub fn schedule(&self) -> &Schedule<C> {
&self.schedule
}
pub fn config(&self) -> &EngineConfig {
&self.config
}
pub fn max_rounds(&self) -> usize {
self.config.max_rounds.unwrap_or(self.schedule.len())
}
pub fn run<E>(
&self,
source: &str,
tree: &mut ParseTree<C>,
exec: &E,
cancel: &CancelToken,
) -> RunReport
where
C: Clone + PartialEq + Send + 'static,
E: Executor,
{
let mut report = RunReport::default();
let max_rounds = self.max_rounds();
let mut violations = Vec::new();
for round in 0..max_rounds {
if cancel.is_cancelled() {
report.cancelled = true;
report.violations = violations;
return report;
}
let jobs = tree.ready_jobs(round);
if jobs.is_empty() {
report.reached_fixpoint = true;
report.violations = violations;
return report;
}
let batch_size = jobs.len();
let pass_name = self.schedule.pass_name(round);
let executed = exec.execute(jobs, |job| self.execute_job(source, job), cancel);
let merged = executed.len();
for (job, outcome) in executed {
if let crate::tree::Applied::Failed(violation) = tree.apply(
job.node,
outcome,
round,
self.config.enforce_shrink,
pass_name,
) {
report.nodes_failed += 1;
if let Some(violation) = violation {
violations.push(violation);
}
}
}
report.rounds_run += 1;
report.nodes_processed += merged;
if merged < batch_size {
report.cancelled = true;
report.violations = violations;
return report;
}
}
report.reached_fixpoint = tree.pending(max_rounds).is_empty();
report.violations = violations;
report
}
fn execute_job(&self, source: &str, job: &Job<C>) -> Outcome<C>
where
C: Clone + Send + 'static,
{
match self.schedule.pass_at(job.pass_index) {
Some(pass) => pass.parse(source, job.span, &job.ctx),
None => Outcome::Failed,
}
}
}