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(mut self, map: std::collections::HashMap<String, quarb::NodeId>) -> Self {
54        self.url_roots = map;
55        self
56    }
57
58    /// Attach a `--model` file: the session runs every query against
59    /// the enriched view.
60    pub fn with_model(mut self, model: Option<quarb_model::Model>) -> Self {
61        self.model = model;
62        self
63    }
64
65    /// A native executor that can re-materialize its source for a
66    /// `&N!` live reading.
67    #[cfg(feature = "native")]
68    pub fn with_respec(
69        doc: Doc,
70        now: (i64, u32),
71        allow_shell: bool,
72        specs: Vec<crate::MountSpec>,
73        opts: crate::Options,
74    ) -> Self {
75        Self {
76            doc,
77            url_roots: Default::default(),
78            refs: Default::default(),
79            now,
80            allow_shell,
81            respec: Some((specs, opts)),
82            model: None,
83        }
84    }
85}
86
87/// Run one query against a `Doc`, rendering its result to [`Cell`]s.
88fn run_doc(
89    doc: &Doc,
90    query: &str,
91    now: (i64, u32),
92    allow_shell: bool,
93) -> anyhow::Result<Vec<Cell>> {
94    let result = doc
95        .run(query, now, allow_shell)
96        .map_err(|e| anyhow::anyhow!("{e}"))?;
97    Ok(match result {
98        QueryResult::Nodes(nodes) => nodes
99            .into_iter()
100            .map(|n| Cell::Node(doc.render(n)))
101            .collect(),
102        QueryResult::Values(values) => values.into_iter().map(Cell::Value).collect(),
103    })
104}
105
106/// [`run_doc`], against a model-enriched view.
107fn run_doc_modeled(
108    doc: &Doc,
109    query: &str,
110    now: (i64, u32),
111    allow_shell: bool,
112    model: &quarb_model::Model,
113) -> anyhow::Result<Vec<Cell>> {
114    let result = doc
115        .run_modeled(query, now, allow_shell, model)
116        .map_err(|e| anyhow::anyhow!("{e}"))?;
117    Ok(match result {
118        QueryResult::Nodes(nodes) => nodes
119            .into_iter()
120            .map(|n| Cell::Node(doc.render_modeled(n, model)))
121            .collect(),
122        QueryResult::Values(values) => values.into_iter().map(Cell::Value).collect(),
123    })
124}
125
126impl Executor for LocalExecutor {
127    fn run(&self, query: &str) -> anyhow::Result<Vec<Cell>> {
128        quarb::set_ref_targets(self.url_roots.clone());
129        let out = if let Some(model) = &self.model {
130            run_doc_modeled(&self.doc, query, self.now, self.allow_shell, model)
131        } else {
132            run_doc(&self.doc, query, self.now, self.allow_shell)
133        };
134        *self.refs.borrow_mut() = quarb::take_refs();
135        out
136    }
137
138    fn refs(&self) -> Vec<String> {
139        self.refs.borrow().clone()
140    }
141
142    fn run_fresh(&self, query: &str) -> anyhow::Result<Vec<Cell>> {
143        #[cfg(feature = "native")]
144        if let Some((specs, opts)) = &self.respec {
145            let fresh = match specs.as_slice() {
146                [one] if one.name.is_none() => Doc::open(&one.path, opts)?,
147                many => Doc::mount_specs(many, opts)?,
148            };
149            return run_doc(&fresh, query, self.now, self.allow_shell);
150        }
151        self.run(query)
152    }
153
154    fn complete(&self, text: &str, cursor: usize) -> Vec<quarb::complete::Candidate> {
155        quarb::complete::complete_with(text, cursor, self.doc.base_dyn())
156    }
157
158    fn export(&self, query: &str, kind: &str) -> anyhow::Result<String> {
159        if self.model.is_some() {
160            anyhow::bail!("rendered export over a --model session is not supported yet");
161        }
162        self.doc.export(query, self.now, self.allow_shell, kind)
163    }
164}