use super::*;
#[derive(Clone, Default)]
pub struct RunOptions {
pub now: Option<(i64, u32)>,
pub allow_shell: bool,
pub quantifier_bound: Option<usize>,
pub reproducible: bool,
pub ref_targets: std::collections::HashMap<String, NodeId>,
pub collation: Option<String>,
pub bonds: Option<std::rc::Rc<dyn crate::SentenceBonds>>,
pub budget: Option<crate::plan::Budget>,
pub plan: bool,
}
#[derive(Default)]
pub(super) struct RunState {
pub(super) options: RunOptions,
pub(super) refusal: Option<String>,
pub(super) enclosing_topic: Vec<Value>,
pub(super) refs: std::collections::BTreeSet<String>,
pub(super) reads: Vec<Origins>,
pub(super) stage: u32,
pub(super) scopes: HashMap<(usize, String), std::rc::Rc<ScopeMemo>>,
pub(super) last_out: LastLinks,
pub(super) last_in: LastLinks,
pub(super) children: HashMap<NodeId, std::rc::Rc<Vec<NodeId>>>,
}
thread_local! {
static RUNS: std::cell::RefCell<Vec<RunState>> = const { std::cell::RefCell::new(Vec::new()) };
static FINISHED: std::cell::RefCell<RunState> = std::cell::RefCell::new(RunState::default());
}
pub(super) fn with_run<R>(f: impl FnOnce(&mut RunState) -> R) -> R {
RUNS.with(|runs| {
let mut runs = runs.borrow_mut();
match runs.last_mut() {
Some(top) => f(top),
None => FINISHED.with(|fin| f(&mut fin.borrow_mut())),
}
})
}
pub(crate) struct RunGuard;
pub(crate) fn begin_run(options: Option<RunOptions>) -> RunGuard {
RUNS.with(|runs| {
let mut runs = runs.borrow_mut();
let options = options
.or_else(|| runs.last().map(|r| r.options.clone()))
.unwrap_or_default();
runs.push(RunState {
options,
..RunState::default()
});
});
RunGuard
}
pub(crate) fn in_run() -> bool {
RUNS.with(|runs| !runs.borrow().is_empty())
}
pub(crate) fn run_option<R>(f: impl FnOnce(&RunOptions) -> R) -> R {
with_run(|s| f(&s.options))
}
impl Drop for RunGuard {
fn drop(&mut self) {
if let Some(done) = RUNS.with(|runs| runs.borrow_mut().pop()) {
FINISHED.with(|fin| *fin.borrow_mut() = done);
}
}
}
pub(super) fn with_enclosing_topic<R>(scope: &Scope<'_>, f: impl FnOnce() -> R) -> R {
let carried = scope.topic.is_some();
if let Some(t) = scope.topic {
with_run(|s| s.enclosing_topic.push(t.clone()));
}
let out = f();
if carried {
with_run(|s| {
s.enclosing_topic.pop();
});
}
out
}
pub(super) fn capture<T>(f: impl FnOnce() -> T) -> (T, Origins) {
with_run(|s| s.reads.push(Origins::default()));
let out = f();
let origins = with_run(|s| s.reads.pop()).unwrap_or_default();
(out, origins)
}
pub(super) fn discard<T>(f: impl FnOnce() -> T) -> T {
with_run(|s| s.reads.push(Origins::default()));
let out = f();
with_run(|s| s.reads.pop());
out
}
pub(super) fn note(node: NodeId) {
with_run(|s| {
let stage = s.stage;
if let Some(top) = s.reads.last_mut() {
top.insert(Origin { node, stage });
}
});
}
pub(super) fn note_all(o: &Origins) {
with_run(|s| {
if let Some(top) = s.reads.last_mut() {
top.union(o);
}
});
}
pub(super) fn stage_now() -> u32 {
with_run(|s| s.stage)
}
pub(super) fn record_ref(id: String) {
with_run(|s| {
s.refs.insert(id);
});
}
pub(super) fn ref_target(id: &str) -> Option<NodeId> {
run_option(|o| o.ref_targets.get(id).copied())
}
pub(crate) fn take_refs() -> Vec<String> {
FINISHED.with(|f| {
std::mem::take(&mut f.borrow_mut().refs)
.into_iter()
.collect()
})
}
pub fn record_refusal(msg: String) {
with_run(|s| {
if s.refusal.is_none() {
s.refusal = Some(msg);
}
});
}
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 fn take_refusal() -> Option<String> {
FINISHED.with(|f| f.borrow_mut().refusal.take())
}
pub(crate) fn quantifier_bound(adapter: &impl AstAdapter) -> usize {
run_option(|o| o.quantifier_bound).unwrap_or_else(|| adapter.quantifier_bound())
}
pub(crate) fn gate_shell(query: &Query, allow_shell: bool) -> crate::Result<()> {
if uses_shell_query(query) && !allow_shell {
return Err(crate::QuarbError::unsupported(
"the sh(...) stage runs external commands; pass --allow-shell",
));
}
Ok(())
}
pub(super) fn uses_shell_query(q: &Query) -> bool {
struct Shell(bool);
impl visit::Visitor for Shell {
fn stage(&mut self, s: &Stage) {
if matches!(s, Stage::Func(c) | Stage::Agg(c) if c.name == "sh") {
self.0 = true;
}
visit::walk_stage(self, s);
}
}
let mut v = Shell(false);
v.query(q);
v.0
}