Skip to main content

quarb_session/
local.rs

1//! The in-process executor: materialize and query a [`Doc`] here.
2//!
3//! Serves both native (the full adapter fleet) and wasm (the
4//! text-format subset) — the two differ only in which `Doc` variants
5//! compile. A native executor may also carry its source spec, so a
6//! `&N!` live reading can re-open the source and see current data.
7
8use crate::doc::Doc;
9use crate::{Cell, Executor};
10use quarb::QueryResult;
11
12pub struct LocalExecutor {
13    doc: Doc,
14    /// The reference targets: external-reference identifier
15    /// (fragment-stripped URL, for html) → mounted root, fed to
16    /// the engine before each run so `::href-->` can land on a
17    /// sibling source. See [`Doc::url_roots`].
18    url_roots: std::collections::HashMap<String, quarb::NodeId>,
19    /// The unresolved external references of the last run —
20    /// documents queried but not mounted. The host may acquire
21    /// them, remount, and re-run; the executor only reports.
22    refs: std::cell::RefCell<Vec<String>>,
23    /// The invocation instant `now()` denotes, bound once per session.
24    now: (i64, u32),
25    allow_shell: bool,
26    /// A `--model` file enriching the source with derived structure
27    /// (wasm-safe — the playground uses it too).
28    model: Option<quarb_model::Model>,
29    /// The source spec, kept so a `&N!` reading can re-materialize.
30    /// `None` when the source can't be re-opened (wasm pasted text,
31    /// which never drifts anyway).
32    #[cfg(feature = "native")]
33    respec: Option<(Vec<crate::MountSpec>, crate::Options)>,
34}
35
36impl LocalExecutor {
37    /// An executor over a fixed materialized `Doc` (no live re-read).
38    pub fn new(doc: Doc, now: (i64, u32), allow_shell: bool) -> Self {
39        Self {
40            doc,
41            url_roots: Default::default(),
42            refs: Default::default(),
43            now,
44            allow_shell,
45            #[cfg(feature = "native")]
46            respec: None,
47            model: None,
48        }
49    }
50
51    /// Register the reference targets (identifier → mounted root)
52    /// the arrow may land on. See [`Doc::url_roots`].
53    pub fn with_url_roots(
54        mut self,
55        map: std::collections::HashMap<String, quarb::NodeId>,
56    ) -> Self {
57        self.url_roots = map;
58        self
59    }
60
61    /// Attach a `--model` file: the session runs every query against
62    /// the enriched view.
63    pub fn with_model(mut self, model: Option<quarb_model::Model>) -> Self {
64        self.model = model;
65        self
66    }
67
68    /// A native executor that can re-materialize its source for a
69    /// `&N!` live reading.
70    #[cfg(feature = "native")]
71    pub fn with_respec(
72        doc: Doc,
73        now: (i64, u32),
74        allow_shell: bool,
75        specs: Vec<crate::MountSpec>,
76        opts: crate::Options,
77    ) -> Self {
78        Self {
79            doc,
80            url_roots: Default::default(),
81            refs: Default::default(),
82            now,
83            allow_shell,
84            respec: Some((specs, opts)),
85            model: None,
86        }
87    }
88}
89
90/// Run one query against a `Doc`, rendering its result to [`Cell`]s.
91fn run_doc(doc: &Doc, query: &str, now: (i64, u32), allow_shell: bool) -> anyhow::Result<Vec<Cell>> {
92    let result = doc
93        .run(query, now, allow_shell)
94        .map_err(|e| anyhow::anyhow!("{e}"))?;
95    Ok(match result {
96        QueryResult::Nodes(nodes) => nodes.into_iter().map(|n| Cell::Node(doc.render(n))).collect(),
97        QueryResult::Values(values) => values.into_iter().map(Cell::Value).collect(),
98    })
99}
100
101/// [`run_doc`], against a model-enriched view.
102fn run_doc_modeled(
103    doc: &Doc,
104    query: &str,
105    now: (i64, u32),
106    allow_shell: bool,
107    model: &quarb_model::Model,
108) -> anyhow::Result<Vec<Cell>> {
109    let result = doc
110        .run_modeled(query, now, allow_shell, model)
111        .map_err(|e| anyhow::anyhow!("{e}"))?;
112    Ok(match result {
113        QueryResult::Nodes(nodes) => nodes
114            .into_iter()
115            .map(|n| Cell::Node(doc.render_modeled(n, model)))
116            .collect(),
117        QueryResult::Values(values) => values.into_iter().map(Cell::Value).collect(),
118    })
119}
120
121impl Executor for LocalExecutor {
122    fn run(&self, query: &str) -> anyhow::Result<Vec<Cell>> {
123        quarb::set_ref_targets(self.url_roots.clone());
124        let out = if let Some(model) = &self.model {
125            run_doc_modeled(&self.doc, query, self.now, self.allow_shell, model)
126        } else {
127            run_doc(&self.doc, query, self.now, self.allow_shell)
128        };
129        *self.refs.borrow_mut() = quarb::take_refs();
130        out
131    }
132
133    fn refs(&self) -> Vec<String> {
134        self.refs.borrow().clone()
135    }
136
137    fn run_fresh(&self, query: &str) -> anyhow::Result<Vec<Cell>> {
138        #[cfg(feature = "native")]
139        if let Some((specs, opts)) = &self.respec {
140            let fresh = match specs.as_slice() {
141                [one] if one.name.is_none() => Doc::open(&one.path, opts)?,
142                many => Doc::mount_specs(many, opts)?,
143            };
144            return run_doc(&fresh, query, self.now, self.allow_shell);
145        }
146        self.run(query)
147    }
148
149    fn complete(&self, text: &str, cursor: usize) -> Vec<quarb::complete::Candidate> {
150        quarb::complete::complete_with(text, cursor, self.doc.base_dyn())
151    }
152
153    fn export(&self, query: &str, kind: &str) -> anyhow::Result<String> {
154        if self.model.is_some() {
155            anyhow::bail!("rendered export over a --model session is not supported yet");
156        }
157        self.doc.export(query, self.now, self.allow_shell, kind)
158    }
159}