use crate::diagnostic::Diagnostic;
use std::cell::{Cell, RefCell};
use std::rc::Rc;
pub trait SentenceBonds {
fn unicode_sentences<'a>(&self, text: &'a str) -> Vec<&'a str>;
fn as_any(&self) -> &dyn std::any::Any;
}
#[cfg(feature = "desm")]
impl SentenceBonds for syndesmos::Syndesmos {
fn unicode_sentences<'a>(&self, text: &'a str) -> Vec<&'a str> {
syndesmos::Syndesmos::unicode_sentences(self, text)
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
thread_local! {
static NOW: Cell<Option<(i64, u32)>> = const { Cell::new(None) };
static SENTENCE_BONDS: RefCell<Option<Rc<dyn SentenceBonds>>> = const { RefCell::new(None) };
static AMBIENTS: RefCell<Vec<Ambient>> = const { RefCell::new(Vec::new()) };
static FINISHED: RefCell<Ambient> = RefCell::new(Ambient::default());
}
pub fn set_invocation_instant(secs: i64, nanos: u32) {
NOW.with(|c| c.set(Some((secs, nanos))));
}
pub fn invocation_instant() -> Option<(i64, u32)> {
NOW.with(|c| c.get())
}
pub fn now_secs() -> i64 {
match invocation_instant() {
Some((secs, _)) => secs,
None => std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0),
}
}
type UnitScale = dyn Fn(&str) -> Option<(f64, String)>;
thread_local! {
static UNIT_SCALE: RefCell<Option<*const UnitScale>> = const { RefCell::new(None) };
}
pub(crate) fn with_unit_scale<R>(
scale: &(dyn Fn(&str) -> Option<(f64, String)> + '_),
f: impl FnOnce() -> R,
) -> R {
struct Restore(Option<*const UnitScale>);
impl Drop for Restore {
fn drop(&mut self) {
UNIT_SCALE.with(|s| *s.borrow_mut() = self.0);
}
}
let erased: *const UnitScale = unsafe {
std::mem::transmute::<*const (dyn Fn(&str) -> Option<(f64, String)> + '_), *const UnitScale>(
scale,
)
};
let previous = UNIT_SCALE.with(|s| s.replace(Some(erased)));
let _restore = Restore(previous);
f()
}
pub fn unit_scale(expr: &str) -> Option<Option<(f64, String)>> {
let ptr = UNIT_SCALE.with(|s| *s.borrow());
ptr.map(|p| unsafe { (*p)(expr) })
}
pub fn set_sentence_bonds(bonds: Option<Rc<dyn SentenceBonds>>) {
SENTENCE_BONDS.with(|b| *b.borrow_mut() = bonds);
}
pub fn sentence_bonds() -> Option<Rc<dyn SentenceBonds>> {
SENTENCE_BONDS.with(|b| b.borrow().clone())
}
#[derive(Default)]
pub(crate) struct Ambient {
pub(crate) collation: Option<String>,
pub(crate) bonds: Option<Rc<dyn SentenceBonds>>,
pub(crate) refusal: Option<Diagnostic>,
pub(crate) omitted: bool,
pub(crate) keying: u32,
pub(crate) diagnostics: Vec<Diagnostic>,
pub(crate) cells: bool,
pub(crate) cells_next: bool,
pub(crate) untaken: Vec<Untaken>,
pub(crate) surfaced: std::collections::HashMap<u64, Vec<(String, bool)>>,
pub(crate) presence: u32,
pub(crate) tested: Vec<Tested>,
}
#[derive(Default)]
pub(crate) struct Tested {
name: String,
found: bool,
asked: Vec<u64>,
lacking: Vec<Diagnostic>,
}
impl Ambient {
fn tested(&mut self, name: &str) -> &mut Tested {
match self.tested.iter().position(|t| t.name == name) {
Some(i) => &mut self.tested[i],
None => {
self.tested.push(Tested {
name: name.to_string(),
..Tested::default()
});
self.tested.last_mut().expect("just pushed")
}
}
}
}
pub(crate) struct Untaken {
command: String,
takes: &'static str,
count: usize,
first: String,
}
impl Untaken {
fn warning(&self) -> Diagnostic {
let Untaken {
command,
takes,
count,
first,
} = self;
Diagnostic::warning(
crate::diagnostic::NOT_NUMERIC,
"pipeline",
if *count == 1 {
format!(
"{command} takes {takes}: 1 value is of another kind ({first}) and answers \
null"
)
} else {
format!(
"{command} takes {takes}: {count} values are of another kind (the first: \
{first}) and answer null"
)
},
)
}
}
pub(crate) fn enter(a: Ambient) -> AmbientGuard {
AMBIENTS.with(|s| s.borrow_mut().push(a));
AmbientGuard
}
pub(crate) struct AmbientGuard;
impl Drop for AmbientGuard {
fn drop(&mut self) {
if let Some(mut done) = AMBIENTS.with(|s| s.borrow_mut().pop()) {
if done.omitted {
omitted_warning(&mut done);
}
let untaken: Vec<Diagnostic> = done.untaken.iter().map(Untaken::warning).collect();
done.diagnostics.extend(untaken);
for t in std::mem::take(&mut done.tested) {
if t.found {
continue;
}
for d in t.lacking {
if !done
.diagnostics
.iter()
.any(|k| k.code == d.code && k.message == d.message)
{
done.diagnostics.push(d);
}
}
}
FINISHED.with(|f| *f.borrow_mut() = done);
}
}
}
fn with_ambient<R>(f: impl FnOnce(&mut Ambient) -> R) -> R {
AMBIENTS.with(|s| {
let mut s = s.borrow_mut();
match s.last_mut() {
Some(top) => f(top),
None => FINISHED.with(|fin| f(&mut fin.borrow_mut())),
}
})
}
pub(crate) fn ambient<R>(f: impl FnOnce(&Ambient) -> R) -> R {
with_ambient(|a| f(a))
}
pub fn record_refusal(msg: String) {
record_refusal_coded(crate::diagnostic::REFUSED, "query", msg);
}
pub fn record_refusal_coded(code: &str, site: impl Into<String>, msg: String) {
with_ambient(|a| {
if a.refusal.is_none() {
a.refusal = Some(Diagnostic::error(code, site, msg));
}
});
}
fn omitted_warning(a: &mut Ambient) {
let refused = a
.refusal
.as_ref()
.is_some_and(|r| r.code == crate::diagnostic::OMITTED);
let warned = a.diagnostics.iter().any(|d| {
d.code == crate::diagnostic::OMITTED && d.severity == crate::diagnostic::Severity::Warning
});
if !refused && !warned {
a.diagnostics.push(Diagnostic::warning(
crate::diagnostic::OMITTED,
"source",
crate::diagnostic::OMITTED_TEXT,
));
}
}
pub fn record_omitted() {
with_ambient(|a| a.omitted = true);
if with_ambient(|a| a.keying) > 0 {
refuse_omitted();
}
}
pub fn record_omitted_nodes() {
with_ambient(|a| {
a.omitted = true;
omitted_warning(a);
});
}
pub fn with_keying<R>(f: impl FnOnce() -> R) -> R {
with_ambient(|a| a.keying += 1);
let out = f();
with_ambient(|a| a.keying = a.keying.saturating_sub(1));
out
}
pub(crate) fn with_presence<R>(f: impl FnOnce() -> R) -> R {
with_ambient(|a| a.presence += 1);
let out = f();
with_ambient(|a| a.presence = a.presence.saturating_sub(1));
out
}
pub(crate) fn in_presence() -> bool {
with_ambient(|a| a.presence > 0)
}
pub(crate) fn presence_asked(container: u64, name: &str) -> bool {
with_ambient(|a| {
a.tested
.iter()
.any(|t| t.name == name && t.asked.contains(&container))
})
}
pub(crate) fn presence_ask(container: u64, name: &str, lacking: Option<Diagnostic>) {
with_ambient(|a| {
let t = a.tested(name);
t.asked.push(container);
match lacking {
Some(d) => t.lacking.push(d),
None => t.found = true,
}
});
}
pub(crate) fn presence_found(name: &str) {
with_ambient(|a| {
match a.tested.iter_mut().find(|t| t.name == name) {
Some(t) => t.found = true,
None => a.tested(name).found = true,
}
});
}
pub fn refuse_omitted() {
record_refusal_coded(
crate::diagnostic::OMITTED,
"source",
crate::diagnostic::OMITTED_TEXT.to_string(),
);
}
pub fn record_diagnostic(d: Diagnostic) {
with_ambient(|a| a.diagnostics.push(d));
}
pub(crate) fn mark_cells(cells: bool) {
with_ambient(|a| a.cells_next = cells);
}
pub(crate) struct CellsGround(bool);
pub(crate) fn cells_ground() -> CellsGround {
CellsGround(with_ambient(|a| {
let here = std::mem::take(&mut a.cells_next);
std::mem::replace(&mut a.cells, here)
}))
}
impl Drop for CellsGround {
fn drop(&mut self) {
let before = self.0;
with_ambient(|a| a.cells = before);
}
}
pub(crate) fn in_cells() -> bool {
with_ambient(|a| a.cells)
}
pub(crate) fn record_untaken(command: &str, takes: &'static str, first: impl FnOnce() -> String) {
with_ambient(
|a| match a.untaken.iter_mut().find(|u| u.command == command) {
Some(u) => u.count += 1,
None => a.untaken.push(Untaken {
command: command.to_string(),
takes,
count: 1,
first: first(),
}),
},
);
}
pub(crate) fn surface_asked(container: u64, name: &str, label: bool) -> bool {
with_ambient(|a| {
a.surfaced
.get(&container)
.is_some_and(|seen| seen.iter().any(|(n, l)| n == name && *l == label))
})
}
pub(crate) fn surface_ask(container: u64, name: &str, label: bool) {
with_ambient(|a| {
a.surfaced
.entry(container)
.or_default()
.push((name.to_string(), label));
});
}
pub(crate) fn record_warning_once(code: &str, site: &str, message: String) {
with_ambient(|a| {
if !a
.diagnostics
.iter()
.any(|d| d.code == code && d.message == message)
{
a.diagnostics.push(Diagnostic::warning(code, site, message));
}
});
}
pub(crate) fn record_info_once(code: &str, site: &str, message: String) {
with_ambient(|a| {
if !a
.diagnostics
.iter()
.any(|d| d.code == code && d.message == message)
{
a.diagnostics.push(Diagnostic::info(code, site, message));
}
});
}
pub fn retract_diagnostics(drop: impl Fn(&Diagnostic) -> bool) {
with_ambient(|a| a.diagnostics.retain(|d| !drop(d)));
}
#[cfg(feature = "debug-diagnostics")]
pub fn record_debug(code: &str, site: impl Into<String>, message: impl FnOnce() -> String) {
if crate::pipeline::run_option(|o| o.debug) {
record_diagnostic(Diagnostic::new(
crate::diagnostic::Severity::Debug,
code,
site,
message(),
));
}
}
#[cfg(not(feature = "debug-diagnostics"))]
pub fn record_debug(_code: &str, _site: impl Into<String>, _message: impl FnOnce() -> String) {}
pub fn take_diagnostics() -> Vec<Diagnostic> {
FINISHED.with(|f| std::mem::take(&mut f.borrow_mut().diagnostics))
}
pub fn take_refusal_diagnostic() -> Option<Diagnostic> {
FINISHED.with(|f| f.borrow_mut().refusal.take())
}
pub fn fetched<T: Default, E: std::fmt::Display>(
result: Result<T, E>,
what: impl FnOnce() -> String,
) -> T {
match result {
Ok(v) => v,
Err(e) => {
record_refusal(format!("{}: {e}", what()));
T::default()
}
}
}
pub fn fetched_opt<T, E: std::fmt::Display>(
result: Result<T, E>,
what: impl FnOnce() -> String,
) -> Option<T> {
match result {
Ok(v) => Some(v),
Err(e) => {
record_refusal(format!("{}: {e}", what()));
None
}
}
}
pub(crate) fn refusal_pending() -> bool {
with_ambient(|a| a.refusal.is_some())
}
pub fn take_refusal() -> Option<String> {
take_refusal_diagnostic().map(|d| d.message)
}