Skip to main content

quarb/
adapter.rs

1//! The adapter surface: how a data source plugs into the engine.
2//!
3//! The live methods\,---\,those the engine currently drives\,---\,are
4//! the navigation set ([`root`](AstAdapter::root),
5//! [`children`](AstAdapter::children), [`name`](AstAdapter::name),
6//! [`parent`](AstAdapter::parent)) plus the projection set
7//! ([`traits`](AstAdapter::traits), [`property`](AstAdapter::property),
8//! [`default_value`](AstAdapter::default_value),
9//! [`metadata`](AstAdapter::metadata)). The projection methods have
10//! defaults, so an adapter can implement only what its domain
11//! supports. Crosslink resolution (`~>`) and pattern search (`=>`)
12//! are still planned. See `doc/impl.tex`.
13
14use crate::value::Value;
15
16/// An opaque handle to a node in an arbor.
17///
18/// The engine treats a `NodeId` as an opaque token: it is minted and
19/// interpreted solely by the adapter that produced it. The `u64`
20/// payload is an adapter-private index or key.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub struct NodeId(pub u64);
23
24/// The interface a data source implements to be queried by Quarb.
25///
26/// An adapter maps its native structure onto the arbor model: a tree
27/// backbone whose edges carry *names*. The engine drives navigation
28/// purely through this trait, so the same query language runs over
29/// any adapter.
30pub trait AstAdapter {
31    /// The root node — the initial navigation context.
32    fn root(&self) -> NodeId;
33
34    /// The tree children of `node`, in document order.
35    ///
36    /// Returns an empty vector for a leaf (or an unreadable node).
37    fn children(&self, node: NodeId) -> Vec<NodeId>;
38
39    /// The name of `node` — the label of its incoming tree edge.
40    ///
41    /// `None` when the adapter leaves a node unnamed (typically the
42    /// root; e.g. the filesystem root `/` carries no name).
43    fn name(&self, node: NodeId) -> Option<String>;
44
45    /// The parent of `node`, or `None` for the root.
46    fn parent(&self, _node: NodeId) -> Option<NodeId> {
47        None
48    }
49
50    /// The traits of `node` — its adapter-defined classifications,
51    /// used by `<trait>` navigation filters (e.g. a filesystem
52    /// adapter's `<dir>`, `<code>`, `<image>`).
53    fn traits(&self, _node: NodeId) -> Vec<String> {
54        Vec::new()
55    }
56
57    /// A named property of `node` — `::prop`. `None` if absent.
58    fn property(&self, _node: NodeId, _name: &str) -> Option<Value> {
59        None
60    }
61
62    /// The children of `node` whose edge name is exactly `name` —
63    /// the engine's fast path for name-matcher child hops. The
64    /// default filters [`children`](Self::children); an adapter
65    /// whose containers cannot be enumerated (permission-scoped or
66    /// unbounded remote trees) overrides this with a direct,
67    /// name-addressed lookup. Must be observationally identical to
68    /// the default wherever enumeration works. The adapter owns the
69    /// name test: it may deliberately *alias* — resolve a name to a
70    /// node whose edge name differs (git revision syntax landing on
71    /// a hash-named commit) — and the engine will not re-filter.
72    fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
73        self.children(node)
74            .into_iter()
75            .filter(|&c| self.name(c).as_deref() == Some(name))
76            .collect()
77    }
78
79    /// The default projection of `node` — bare `::`, adapter-specific
80    /// (a filesystem adapter returns file content).
81    fn default_value(&self, _node: NodeId) -> Option<Value> {
82        None
83    }
84
85    /// Adapter-defined metadata — `::;key` (a filesystem adapter's
86    /// `size`, `modified`, `permissions`, …). `None` if absent.
87    fn metadata(&self, _node: NodeId, _key: &str) -> Option<Value> {
88        None
89    }
90
91    /// Outgoing crosslinks from `node`, as `(label, target)` pairs,
92    /// for `->` navigation (a filesystem adapter's symlinks).
93    fn links(&self, _node: NodeId) -> Vec<(String, NodeId)> {
94        Vec::new()
95    }
96
97    /// Incoming crosslinks to `node`, as `(label, source)` pairs, for
98    /// `<-` navigation. May be expensive (an adapter that does not
99    /// precompute edges must search for referrers).
100    fn backlinks(&self, _node: NodeId) -> Vec<(String, NodeId)> {
101        Vec::new()
102    }
103
104    /// Resolve a cross-reference: `::property~>hint` maps `node`'s
105    /// `property` (a value that references another node) to its target,
106    /// with an optional adapter-specific relation `hint`. A JSON
107    /// adapter resolves a `$ref` JSON Pointer; `None` if unresolvable.
108    fn resolve(&self, _node: NodeId, _property: &str, _hint: Option<&str>) -> Option<NodeId> {
109        None
110    }
111
112    /// A property of the crosslink `source --label--> target` — the
113    /// `$-::prop` read. Adapters whose edges carry data (a property
114    /// graph's relationship properties) override this; `None` if the
115    /// edge is bare or unknown. Where parallel edges share source,
116    /// label, and target, the adapter answers for one of them,
117    /// consistently.
118    fn link_property(
119        &self,
120        _source: NodeId,
121        _label: &str,
122        _target: NodeId,
123        _name: &str,
124    ) -> Option<Value> {
125        None
126    }
127
128    /// The quantifier bound N_max: the depth to which open-ended path
129    /// quantifiers (`+`, `*`, `{m,}`) expand, and the ceiling of any
130    /// explicit `{m,n}` (the effective upper bound is min(n, N_max)).
131    /// An adapter whose natural structures run deep may raise it; the
132    /// CLI overrides it per run (`qua --quantifier-bound`).
133    fn quantifier_bound(&self) -> usize {
134        32
135    }
136
137    /// Whether the `sh(...)` pipeline stage may run external
138    /// commands. False by default — query text stays inert data —
139    /// and enabled per run by the CLI (`qua --allow-shell`) through
140    /// the [`AllowShell`] wrapper.
141    fn allow_shell(&self) -> bool {
142        false
143    }
144
145    /// The invocation instant `now()` denotes (spec: The Temporal
146    /// Fragment, Determinism): one UTC timeline point bound by the
147    /// runner BEFORE evaluation begins — evaluation itself never
148    /// reads a clock. None by default (a library `run` is fully
149    /// deterministic; `now()` reads as null); the CLI binds it at
150    /// startup — pinnable with `qua --now` — through the
151    /// [`WithNow`] wrapper.
152    fn invocation_instant(&self) -> Option<(i64, u32)> {
153        None
154    }
155
156    /// The scale of a unit expression — (factor, canonical SI-base
157    /// expansion) — for the unital reading's criterion text (spec:
158    /// The Quantital Fragment). The default answers from the
159    /// engine's frozen built-in table; a unit-aware adapter (kaiv)
160    /// overrides it to include the mounted document's own custom
161    /// units, so `[::range < '50kellicam']` resolves through the
162    /// document's `.!units` imports.
163    fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
164        crate::quantity::scale_expr(expr)
165    }
166}
167
168/// An adapter view with the quantifier bound overridden (the CLI's
169/// `--quantifier-bound`); every other method forwards to the wrapped
170/// adapter.
171pub struct QuantifierBound<'a, A: AstAdapter> {
172    pub inner: &'a A,
173    pub bound: usize,
174}
175
176impl<A: AstAdapter> AstAdapter for QuantifierBound<'_, A> {
177    fn root(&self) -> NodeId {
178        self.inner.root()
179    }
180    fn children(&self, node: NodeId) -> Vec<NodeId> {
181        self.inner.children(node)
182    }
183    fn name(&self, node: NodeId) -> Option<String> {
184        self.inner.name(node)
185    }
186    fn parent(&self, node: NodeId) -> Option<NodeId> {
187        self.inner.parent(node)
188    }
189    fn traits(&self, node: NodeId) -> Vec<String> {
190        self.inner.traits(node)
191    }
192    fn property(&self, node: NodeId, name: &str) -> Option<Value> {
193        self.inner.property(node, name)
194    }
195    fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
196        self.inner.children_named(node, name)
197    }
198    fn default_value(&self, node: NodeId) -> Option<Value> {
199        self.inner.default_value(node)
200    }
201    fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
202        self.inner.metadata(node, key)
203    }
204    fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
205        self.inner.links(node)
206    }
207    fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
208        self.inner.backlinks(node)
209    }
210    fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
211        self.inner.resolve(node, property, hint)
212    }
213    fn link_property(
214        &self,
215        source: NodeId,
216        label: &str,
217        target: NodeId,
218        name: &str,
219    ) -> Option<Value> {
220        self.inner.link_property(source, label, target, name)
221    }
222    fn quantifier_bound(&self) -> usize {
223        self.bound
224    }
225    fn allow_shell(&self) -> bool {
226        self.inner.allow_shell()
227    }
228    fn invocation_instant(&self) -> Option<(i64, u32)> {
229        self.inner.invocation_instant()
230    }
231    fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
232        self.inner.unit_scale(expr)
233    }
234}
235
236/// An adapter view with the shell stage enabled (the CLI's
237/// `--allow-shell`); every other method forwards to the wrapped
238/// adapter.
239pub struct AllowShell<'a, A: AstAdapter> {
240    pub inner: &'a A,
241}
242
243impl<A: AstAdapter> AstAdapter for AllowShell<'_, A> {
244    fn root(&self) -> NodeId {
245        self.inner.root()
246    }
247    fn children(&self, node: NodeId) -> Vec<NodeId> {
248        self.inner.children(node)
249    }
250    fn name(&self, node: NodeId) -> Option<String> {
251        self.inner.name(node)
252    }
253    fn parent(&self, node: NodeId) -> Option<NodeId> {
254        self.inner.parent(node)
255    }
256    fn traits(&self, node: NodeId) -> Vec<String> {
257        self.inner.traits(node)
258    }
259    fn property(&self, node: NodeId, name: &str) -> Option<Value> {
260        self.inner.property(node, name)
261    }
262    fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
263        self.inner.children_named(node, name)
264    }
265    fn default_value(&self, node: NodeId) -> Option<Value> {
266        self.inner.default_value(node)
267    }
268    fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
269        self.inner.metadata(node, key)
270    }
271    fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
272        self.inner.links(node)
273    }
274    fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
275        self.inner.backlinks(node)
276    }
277    fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
278        self.inner.resolve(node, property, hint)
279    }
280    fn link_property(
281        &self,
282        source: NodeId,
283        label: &str,
284        target: NodeId,
285        name: &str,
286    ) -> Option<Value> {
287        self.inner.link_property(source, label, target, name)
288    }
289    fn quantifier_bound(&self) -> usize {
290        self.inner.quantifier_bound()
291    }
292    fn allow_shell(&self) -> bool {
293        true
294    }
295    fn invocation_instant(&self) -> Option<(i64, u32)> {
296        self.inner.invocation_instant()
297    }
298    fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
299        self.inner.unit_scale(expr)
300    }
301}
302
303/// An adapter view with the invocation instant bound (the CLI binds
304/// it at startup, `--now` pins it); every other method forwards to
305/// the wrapped adapter.
306pub struct WithNow<'a, A: AstAdapter> {
307    pub inner: &'a A,
308    pub secs: i64,
309    pub nanos: u32,
310}
311
312impl<A: AstAdapter> AstAdapter for WithNow<'_, A> {
313    fn root(&self) -> NodeId {
314        self.inner.root()
315    }
316    fn children(&self, node: NodeId) -> Vec<NodeId> {
317        self.inner.children(node)
318    }
319    fn name(&self, node: NodeId) -> Option<String> {
320        self.inner.name(node)
321    }
322    fn parent(&self, node: NodeId) -> Option<NodeId> {
323        self.inner.parent(node)
324    }
325    fn traits(&self, node: NodeId) -> Vec<String> {
326        self.inner.traits(node)
327    }
328    fn property(&self, node: NodeId, name: &str) -> Option<Value> {
329        self.inner.property(node, name)
330    }
331    fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
332        self.inner.children_named(node, name)
333    }
334    fn default_value(&self, node: NodeId) -> Option<Value> {
335        self.inner.default_value(node)
336    }
337    fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
338        self.inner.metadata(node, key)
339    }
340    fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
341        self.inner.links(node)
342    }
343    fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
344        self.inner.backlinks(node)
345    }
346    fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
347        self.inner.resolve(node, property, hint)
348    }
349    fn link_property(
350        &self,
351        source: NodeId,
352        label: &str,
353        target: NodeId,
354        name: &str,
355    ) -> Option<Value> {
356        self.inner.link_property(source, label, target, name)
357    }
358    fn quantifier_bound(&self) -> usize {
359        self.inner.quantifier_bound()
360    }
361    fn allow_shell(&self) -> bool {
362        self.inner.allow_shell()
363    }
364    fn invocation_instant(&self) -> Option<(i64, u32)> {
365        Some((self.secs, self.nanos))
366    }
367    fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
368        self.inner.unit_scale(expr)
369    }
370}