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/// Per-node data provenance: the three optional components behind
25/// the `:::source` / `:::instant` / `:::dpid` core-metadata keys and
26/// their composite `:::provenance` (`?src@ts#dpid`, kaiv's
27/// spelling). A component is present only where the source genuinely
28/// records it; wrapper adapters layer them ([`or`](Self::or), inner
29/// wins per component).
30#[derive(Debug, Clone, Default, PartialEq)]
31pub struct Provenance {
32 /// Where the datum came from (a URI, a path, a repo).
33 pub source: Option<String>,
34 /// The datum's own instant — `(secs, nanos, offset_min)`, the
35 /// shape `Value::Instant` carries. Never the invocation clock.
36 pub instant: Option<(i64, u32, Option<i16>)>,
37 /// The source-assigned data-point identifier (kaiv `#dpid`).
38 pub dpid: Option<String>,
39}
40
41impl Provenance {
42 /// Component-wise layering: `self` (inner, more specific) wins;
43 /// missing components fill from `outer`.
44 pub fn or(self, outer: Provenance) -> Provenance {
45 Provenance {
46 source: self.source.or(outer.source),
47 instant: self.instant.or(outer.instant),
48 dpid: self.dpid.or(outer.dpid),
49 }
50 }
51
52 pub fn is_empty(&self) -> bool {
53 self.source.is_none() && self.instant.is_none() && self.dpid.is_none()
54 }
55
56 /// The composite canonical text `?src@ts#dpid` — kaiv's
57 /// optionality grammar (`?src`, `?src@ts`, `?@ts#dpid`, …), the
58 /// instant in Quarb's dashed-extended display form. `None` when
59 /// fully empty.
60 pub fn canonical(&self) -> Option<String> {
61 if self.is_empty() {
62 return None;
63 }
64 let mut out = String::from("?");
65 if let Some(src) = &self.source {
66 out.push_str(src);
67 }
68 if let Some((secs, nanos, offset)) = self.instant {
69 out.push('@');
70 out.push_str(&crate::temporal::format_instant(secs, nanos, offset));
71 }
72 if let Some(dpid) = &self.dpid {
73 out.push('#');
74 out.push_str(dpid);
75 }
76 Some(out)
77 }
78}
79
80/// The interface a data source implements to be queried by Quarb.
81///
82/// An adapter maps its native structure onto the arbor model: a tree
83/// backbone whose edges carry *names*. The engine drives navigation
84/// purely through this trait, so the same query language runs over
85/// any adapter.
86pub trait AstAdapter {
87 /// The root node — the initial navigation context.
88 fn root(&self) -> NodeId;
89
90 /// The tree children of `node`, in document order.
91 ///
92 /// Returns an empty vector for a leaf (or an unreadable node).
93 fn children(&self, node: NodeId) -> Vec<NodeId>;
94
95 /// The name of `node` — the label of its incoming tree edge.
96 ///
97 /// `None` when the adapter leaves a node unnamed (typically the
98 /// root; e.g. the filesystem root `/` carries no name).
99 fn name(&self, node: NodeId) -> Option<String>;
100
101 /// The parent of `node`, or `None` for the root.
102 fn parent(&self, _node: NodeId) -> Option<NodeId> {
103 None
104 }
105
106 /// The traits of `node` — its adapter-defined classifications,
107 /// used by `<trait>` navigation filters (e.g. a filesystem
108 /// adapter's `<dir>`, `<code>`, `<image>`).
109 fn traits(&self, _node: NodeId) -> Vec<String> {
110 Vec::new()
111 }
112
113 /// A named property of `node` — `::prop`. `None` if absent.
114 fn property(&self, _node: NodeId, _name: &str) -> Option<Value> {
115 None
116 }
117
118 /// The children of `node` whose edge name is exactly `name` —
119 /// the engine's fast path for name-matcher child hops. The
120 /// default filters [`children`](Self::children); an adapter
121 /// whose containers cannot be enumerated (permission-scoped or
122 /// unbounded remote trees) overrides this with a direct,
123 /// name-addressed lookup. Must be observationally identical to
124 /// the default wherever enumeration works. The adapter owns the
125 /// name test: it may deliberately *alias* — resolve a name to a
126 /// node whose edge name differs (git revision syntax landing on
127 /// a hash-named commit) — and the engine will not re-filter.
128 fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
129 self.children(node)
130 .into_iter()
131 .filter(|&c| self.name(c).as_deref() == Some(name))
132 .collect()
133 }
134
135 /// The default projection of `node` — bare `::`, adapter-specific
136 /// (a filesystem adapter returns file content).
137 fn default_value(&self, _node: NodeId) -> Option<Value> {
138 None
139 }
140
141 /// Adapter-defined metadata — `::::key` (a filesystem adapter's
142 /// `size`, `modified`, `permissions`, …). `None` if absent.
143 fn metadata(&self, _node: NodeId, _key: &str) -> Option<Value> {
144 None
145 }
146
147 /// Outgoing crosslinks from `node`, as `(label, target)` pairs,
148 /// for `->` navigation (a filesystem adapter's symlinks).
149 fn links(&self, _node: NodeId) -> Vec<(String, NodeId)> {
150 Vec::new()
151 }
152
153 /// Incoming crosslinks to `node`, as `(label, source)` pairs, for
154 /// `<-` navigation. May be expensive (an adapter that does not
155 /// precompute edges must search for referrers).
156 fn backlinks(&self, _node: NodeId) -> Vec<(String, NodeId)> {
157 Vec::new()
158 }
159
160 /// Resolve a cross-reference: `::property~>hint` maps `node`'s
161 /// `property` (a value that references another node) to its target,
162 /// with an optional adapter-specific relation `hint`. A JSON
163 /// adapter resolves a `$ref` JSON Pointer; `None` if unresolvable.
164 fn resolve(&self, _node: NodeId, _property: &str, _hint: Option<&str>) -> Option<NodeId> {
165 None
166 }
167
168 /// A property of the crosslink `source --label--> target` — the
169 /// `$-::prop` read. Adapters whose edges carry data (a property
170 /// graph's relationship properties) override this; `None` if the
171 /// edge is bare or unknown. Where parallel edges share source,
172 /// label, and target, the adapter answers for one of them,
173 /// consistently.
174 fn link_property(
175 &self,
176 _source: NodeId,
177 _label: &str,
178 _target: NodeId,
179 _name: &str,
180 ) -> Option<Value> {
181 None
182 }
183
184 /// The quantifier bound N_max: the depth to which open-ended path
185 /// quantifiers (`+`, `*`, `{m,}`) expand, and the ceiling of any
186 /// explicit `{m,n}` (the effective upper bound is min(n, N_max)).
187 /// An adapter whose natural structures run deep may raise it; the
188 /// CLI overrides it per run (`qua --quantifier-bound`).
189 fn quantifier_bound(&self) -> usize {
190 32
191 }
192
193 /// Whether the `sh(...)` pipeline stage may run external
194 /// commands. False by default — query text stays inert data —
195 /// and enabled per run by the CLI (`qua --allow-shell`) through
196 /// the [`AllowShell`] wrapper.
197 fn allow_shell(&self) -> bool {
198 false
199 }
200
201 /// The invocation instant `now()` denotes (spec: The Temporal
202 /// Fragment, Determinism): one UTC timeline point bound by the
203 /// runner BEFORE evaluation begins — evaluation itself never
204 /// reads a clock. None by default (a library `run` is fully
205 /// deterministic; `now()` reads as null); the CLI binds it at
206 /// startup — pinnable with `qua --now` — through the
207 /// [`WithNow`] wrapper.
208 fn invocation_instant(&self) -> Option<(i64, u32)> {
209 None
210 }
211
212 /// The data provenance of `node` — the `:::source` /
213 /// `:::instant` / `:::dpid` / `:::provenance` core-metadata
214 /// keys. Empty by default: an adapter answers only the
215 /// components its substrate genuinely records (never the
216 /// invocation clock); wrapper adapters fill missing components
217 /// from what they know — the mount its target, a graft its
218 /// outer leaf, a model its derivation — and forward the rest
219 /// inward, so resolution is nearest-ancestor per component.
220 fn provenance(&self, _node: NodeId) -> Provenance {
221 Provenance::default()
222 }
223
224 /// The scale of a unit expression — (factor, canonical SI-base
225 /// expansion) — for the unital reading's criterion text (spec:
226 /// The Quantital Fragment). The default answers from the
227 /// engine's frozen built-in table; a unit-aware adapter (kaiv)
228 /// overrides it to include the mounted document's own custom
229 /// units, so `[::range < '50kellicam']` resolves through the
230 /// document's `.!units` imports.
231 fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
232 crate::quantity::scale_expr(expr)
233 }
234}
235
236/// An adapter view with the quantifier bound overridden (the CLI's
237/// `--quantifier-bound`); every other method forwards to the wrapped
238/// adapter.
239pub struct QuantifierBound<'a, A: AstAdapter> {
240 pub inner: &'a A,
241 pub bound: usize,
242}
243
244impl<A: AstAdapter> AstAdapter for QuantifierBound<'_, A> {
245 fn root(&self) -> NodeId {
246 self.inner.root()
247 }
248 fn children(&self, node: NodeId) -> Vec<NodeId> {
249 self.inner.children(node)
250 }
251 fn name(&self, node: NodeId) -> Option<String> {
252 self.inner.name(node)
253 }
254 fn parent(&self, node: NodeId) -> Option<NodeId> {
255 self.inner.parent(node)
256 }
257 fn traits(&self, node: NodeId) -> Vec<String> {
258 self.inner.traits(node)
259 }
260 fn property(&self, node: NodeId, name: &str) -> Option<Value> {
261 self.inner.property(node, name)
262 }
263 fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
264 self.inner.children_named(node, name)
265 }
266 fn default_value(&self, node: NodeId) -> Option<Value> {
267 self.inner.default_value(node)
268 }
269 fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
270 self.inner.metadata(node, key)
271 }
272 fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
273 self.inner.links(node)
274 }
275 fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
276 self.inner.backlinks(node)
277 }
278 fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
279 self.inner.resolve(node, property, hint)
280 }
281 fn link_property(
282 &self,
283 source: NodeId,
284 label: &str,
285 target: NodeId,
286 name: &str,
287 ) -> Option<Value> {
288 self.inner.link_property(source, label, target, name)
289 }
290 fn quantifier_bound(&self) -> usize {
291 self.bound
292 }
293 fn allow_shell(&self) -> bool {
294 self.inner.allow_shell()
295 }
296 fn invocation_instant(&self) -> Option<(i64, u32)> {
297 self.inner.invocation_instant()
298 }
299 fn provenance(&self, node: NodeId) -> Provenance {
300 self.inner.provenance(node)
301 }
302 fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
303 self.inner.unit_scale(expr)
304 }
305}
306
307/// An adapter view with the shell stage enabled (the CLI's
308/// `--allow-shell`); every other method forwards to the wrapped
309/// adapter.
310pub struct AllowShell<'a, A: AstAdapter> {
311 pub inner: &'a A,
312}
313
314impl<A: AstAdapter> AstAdapter for AllowShell<'_, A> {
315 fn root(&self) -> NodeId {
316 self.inner.root()
317 }
318 fn children(&self, node: NodeId) -> Vec<NodeId> {
319 self.inner.children(node)
320 }
321 fn name(&self, node: NodeId) -> Option<String> {
322 self.inner.name(node)
323 }
324 fn parent(&self, node: NodeId) -> Option<NodeId> {
325 self.inner.parent(node)
326 }
327 fn traits(&self, node: NodeId) -> Vec<String> {
328 self.inner.traits(node)
329 }
330 fn property(&self, node: NodeId, name: &str) -> Option<Value> {
331 self.inner.property(node, name)
332 }
333 fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
334 self.inner.children_named(node, name)
335 }
336 fn default_value(&self, node: NodeId) -> Option<Value> {
337 self.inner.default_value(node)
338 }
339 fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
340 self.inner.metadata(node, key)
341 }
342 fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
343 self.inner.links(node)
344 }
345 fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
346 self.inner.backlinks(node)
347 }
348 fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
349 self.inner.resolve(node, property, hint)
350 }
351 fn link_property(
352 &self,
353 source: NodeId,
354 label: &str,
355 target: NodeId,
356 name: &str,
357 ) -> Option<Value> {
358 self.inner.link_property(source, label, target, name)
359 }
360 fn quantifier_bound(&self) -> usize {
361 self.inner.quantifier_bound()
362 }
363 fn allow_shell(&self) -> bool {
364 true
365 }
366 fn invocation_instant(&self) -> Option<(i64, u32)> {
367 self.inner.invocation_instant()
368 }
369 fn provenance(&self, node: NodeId) -> Provenance {
370 self.inner.provenance(node)
371 }
372 fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
373 self.inner.unit_scale(expr)
374 }
375}
376
377/// An adapter view with the invocation instant bound (the CLI binds
378/// it at startup, `--now` pins it); every other method forwards to
379/// the wrapped adapter.
380pub struct WithNow<'a, A: AstAdapter> {
381 pub inner: &'a A,
382 pub secs: i64,
383 pub nanos: u32,
384}
385
386impl<A: AstAdapter> AstAdapter for WithNow<'_, A> {
387 fn root(&self) -> NodeId {
388 self.inner.root()
389 }
390 fn children(&self, node: NodeId) -> Vec<NodeId> {
391 self.inner.children(node)
392 }
393 fn name(&self, node: NodeId) -> Option<String> {
394 self.inner.name(node)
395 }
396 fn parent(&self, node: NodeId) -> Option<NodeId> {
397 self.inner.parent(node)
398 }
399 fn traits(&self, node: NodeId) -> Vec<String> {
400 self.inner.traits(node)
401 }
402 fn property(&self, node: NodeId, name: &str) -> Option<Value> {
403 self.inner.property(node, name)
404 }
405 fn children_named(&self, node: NodeId, name: &str) -> Vec<NodeId> {
406 self.inner.children_named(node, name)
407 }
408 fn default_value(&self, node: NodeId) -> Option<Value> {
409 self.inner.default_value(node)
410 }
411 fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
412 self.inner.metadata(node, key)
413 }
414 fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
415 self.inner.links(node)
416 }
417 fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
418 self.inner.backlinks(node)
419 }
420 fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
421 self.inner.resolve(node, property, hint)
422 }
423 fn link_property(
424 &self,
425 source: NodeId,
426 label: &str,
427 target: NodeId,
428 name: &str,
429 ) -> Option<Value> {
430 self.inner.link_property(source, label, target, name)
431 }
432 fn quantifier_bound(&self) -> usize {
433 self.inner.quantifier_bound()
434 }
435 fn allow_shell(&self) -> bool {
436 self.inner.allow_shell()
437 }
438 fn invocation_instant(&self) -> Option<(i64, u32)> {
439 Some((self.secs, self.nanos))
440 }
441 // Forwarding, not synthesizing: the pinned invocation instant
442 // never becomes a node's `:::instant` — absence is the true
443 // answer for a source that records no time.
444 fn provenance(&self, node: NodeId) -> Provenance {
445 self.inner.provenance(node)
446 }
447 fn unit_scale(&self, expr: &str) -> Option<(f64, String)> {
448 self.inner.unit_scale(expr)
449 }
450}