use std::collections::HashSet;
use std::sync::atomic::{AtomicUsize, Ordering};
use comemo::{Track, Tracked, TrackedMut, Validate};
use ecow::EcoVec;
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
use crate::diag::{bail, HintedStrResult, SourceDiagnostic, SourceResult, StrResult};
use crate::foundations::{Styles, Value};
use crate::introspection::Introspector;
use crate::syntax::{FileId, Span};
use crate::World;
pub struct Engine<'a> {
pub world: Tracked<'a, dyn World + 'a>,
pub introspector: Tracked<'a, Introspector>,
pub traced: Tracked<'a, Traced>,
pub sink: TrackedMut<'a, Sink>,
pub route: Route<'a>,
}
impl Engine<'_> {
pub fn delay<T: Default>(&mut self, result: SourceResult<T>) -> T {
match result {
Ok(value) => value,
Err(errors) => {
self.sink.delay(errors);
T::default()
}
}
}
pub fn parallelize<P, I, T, U, F>(&mut self, iter: P, f: F) -> impl Iterator<Item = U>
where
P: IntoIterator<IntoIter = I>,
I: Iterator<Item = T>,
T: Send,
U: Send,
F: Fn(&mut Engine, T) -> U + Send + Sync,
{
let Engine { world, introspector, traced, ref route, .. } = *self;
let work: Vec<T> = iter.into_iter().collect();
let mut pairs: Vec<(U, Sink)> = Vec::with_capacity(work.len());
work.into_par_iter()
.map(|value| {
let mut sink = Sink::new();
let mut engine = Engine {
world,
introspector,
traced,
sink: sink.track_mut(),
route: route.clone(),
};
(f(&mut engine, value), sink)
})
.collect_into_vec(&mut pairs);
for (_, sink) in &mut pairs {
let sink = std::mem::take(sink);
self.sink.extend(sink.delayed, sink.warnings, sink.values);
}
pairs.into_iter().map(|(output, _)| output)
}
}
#[derive(Default)]
pub struct Traced(Option<Span>);
impl Traced {
pub fn new(traced: Span) -> Self {
Self(Some(traced))
}
}
#[comemo::track]
impl Traced {
pub fn get(&self, id: FileId) -> Option<Span> {
if self.0.and_then(Span::id) == Some(id) {
self.0
} else {
None
}
}
}
#[derive(Default, Clone)]
pub struct Sink {
delayed: EcoVec<SourceDiagnostic>,
warnings: EcoVec<SourceDiagnostic>,
warnings_set: HashSet<u128>,
values: EcoVec<(Value, Option<Styles>)>,
}
impl Sink {
pub const MAX_VALUES: usize = 10;
pub fn new() -> Self {
Self::default()
}
pub fn delayed(&mut self) -> EcoVec<SourceDiagnostic> {
std::mem::take(&mut self.delayed)
}
pub fn warnings(self) -> EcoVec<SourceDiagnostic> {
self.warnings
}
pub fn values(self) -> EcoVec<(Value, Option<Styles>)> {
self.values
}
pub fn extend_from_sink(&mut self, other: Sink) {
self.extend(other.delayed, other.warnings, other.values);
}
}
#[comemo::track]
impl Sink {
pub fn delay(&mut self, errors: EcoVec<SourceDiagnostic>) {
self.delayed.extend(errors);
}
pub fn warn(&mut self, warning: SourceDiagnostic) {
let hash = crate::utils::hash128(&(&warning.span, &warning.message));
if self.warnings_set.insert(hash) {
self.warnings.push(warning);
}
}
pub fn value(&mut self, value: Value, styles: Option<Styles>) {
if self.values.len() < Self::MAX_VALUES {
self.values.push((value, styles));
}
}
fn extend(
&mut self,
delayed: EcoVec<SourceDiagnostic>,
warnings: EcoVec<SourceDiagnostic>,
values: EcoVec<(Value, Option<Styles>)>,
) {
self.delayed.extend(delayed);
for warning in warnings {
self.warn(warning);
}
if let Some(remaining) = Self::MAX_VALUES.checked_sub(self.values.len()) {
self.values.extend(values.into_iter().take(remaining));
}
}
}
pub struct Route<'a> {
outer: Option<Tracked<'a, Self, <Route<'static> as Validate>::Constraint>>,
id: Option<FileId>,
len: usize,
upper: AtomicUsize,
}
impl<'a> Route<'a> {
pub fn root() -> Self {
Self {
id: None,
outer: None,
len: 0,
upper: AtomicUsize::new(0),
}
}
pub fn extend(outer: Tracked<'a, Self>) -> Self {
Route {
outer: Some(outer),
id: None,
len: 1,
upper: AtomicUsize::new(usize::MAX),
}
}
pub fn with_id(self, id: FileId) -> Self {
Self { id: Some(id), ..self }
}
pub fn unnested(self) -> Self {
Self { len: 0, ..self }
}
pub fn track(&self) -> Tracked<'_, Self> {
match self.outer {
Some(outer) if self.id.is_none() && self.len == 0 => outer,
_ => Track::track(self),
}
}
pub fn increase(&mut self) {
self.len += 1;
}
pub fn decrease(&mut self) {
self.len -= 1;
}
}
impl Route<'_> {
const MAX_SHOW_RULE_DEPTH: usize = 64;
const MAX_LAYOUT_DEPTH: usize = 72;
const MAX_CALL_DEPTH: usize = 80;
pub fn check_show_depth(&self) -> HintedStrResult<()> {
if !self.within(Route::MAX_SHOW_RULE_DEPTH) {
bail!(
"maximum show rule depth exceeded";
hint: "check whether the show rule matches its own output"
);
}
Ok(())
}
pub fn check_layout_depth(&self) -> HintedStrResult<()> {
if !self.within(Route::MAX_LAYOUT_DEPTH) {
bail!(
"maximum layout depth exceeded";
hint: "try to reduce the amount of nesting in your layout",
);
}
Ok(())
}
pub fn check_call_depth(&self) -> StrResult<()> {
if !self.within(Route::MAX_CALL_DEPTH) {
bail!("maximum function call depth exceeded");
}
Ok(())
}
}
#[comemo::track]
impl<'a> Route<'a> {
pub fn contains(&self, id: FileId) -> bool {
self.id == Some(id) || self.outer.is_some_and(|outer| outer.contains(id))
}
pub fn within(&self, depth: usize) -> bool {
use Ordering::Relaxed;
let upper = self.upper.load(Relaxed);
if upper.saturating_add(self.len) <= depth {
return true;
}
match self.outer {
Some(_) if depth < self.len => false,
Some(outer) => {
let within = outer.within(depth - self.len);
if within && depth < upper {
self.upper.compare_exchange(upper, depth, Relaxed, Relaxed).ok();
}
within
}
None => true,
}
}
}
impl Default for Route<'_> {
fn default() -> Self {
Self::root()
}
}
impl Clone for Route<'_> {
fn clone(&self) -> Self {
Self {
outer: self.outer,
id: self.id,
len: self.len,
upper: AtomicUsize::new(self.upper.load(Ordering::Relaxed)),
}
}
}