Skip to main content

quarb_compose/
lib.rs

1//! Adapter composition: descend *through* a leaf into its content.
2//!
3//! A filesystem's `.json` file, an archive's `config.xml` entry, a
4//! message's HTML part — trees of files are full of leaves whose
5//! *content* is itself a tree in another notation. Composition
6//! grafts that inner tree onto the outer one: wrap any adapter in
7//! a [`ComposeAdapter`] and a leaf whose name or content says
8//! "parse me" gains the parsed arbor as its children:
9//!
10//! ```text
11//! /data/store.json/books/*[/price:: < 12]/title::
12//!       └ outer (fs) ┘└──── inner (json) ────┘
13//! ```
14//!
15//! The graft happens on first touch, lazily: a leaf is only read
16//! and parsed when navigation actually enters it. Detection is by
17//! extension (`.json`, `.xml`, `.html`/`.htm`/`.xhtml`/`.svg`,
18//! `.csv`/`.tsv`), else by sniffing the content's first
19//! character; a parse failure simply leaves the leaf a leaf. The
20//! inner root is *identified with* the outer leaf node — the leaf
21//! itself answers the inner root's children — so there is no
22//! phantom node between the file and its content.
23//!
24//! Locators show the boundary with a bang, jar-URL style:
25//! `store.json!/books/1`.
26
27use quarb::{AstAdapter, NodeId, Value};
28use std::cell::RefCell;
29use std::collections::HashMap;
30
31/// A parsed inner arbor.
32enum Inner {
33    Json(quarb_json::JsonAdapter),
34    Xml(quarb_xml::XmlAdapter),
35    Html(quarb_html::HtmlAdapter),
36    Csv(quarb_csv::CsvAdapter),
37    Code(quarb_code::CodeAdapter),
38}
39
40impl Inner {
41    fn adapter(&self) -> &dyn AstAdapter {
42        match self {
43            Inner::Json(a) => a,
44            Inner::Xml(a) => a,
45            Inner::Html(a) => a,
46            Inner::Csv(a) => a,
47            Inner::Code(a) => a,
48        }
49    }
50
51    fn locator(&self, node: NodeId) -> String {
52        match self {
53            Inner::Json(a) => a.pointer(node),
54            Inner::Xml(a) => a.locator(node),
55            Inner::Html(a) => a.locator(node),
56            Inner::Csv(a) => a.locator(node),
57            Inner::Code(a) => a.locator(node),
58        }
59    }
60}
61
62/// Parse `content` by `name`'s extension, else by sniffing.
63fn parse_inner(name: &str, content: &str) -> Option<Inner> {
64    let ext = name.rsplit('.').next().unwrap_or("").to_ascii_lowercase();
65    match ext.as_str() {
66        "json" => {
67            return quarb_json::JsonAdapter::parse(content)
68                .ok()
69                .map(Inner::Json);
70        }
71        "xml" | "svg" | "xhtml" => {
72            return quarb_xml::XmlAdapter::parse(content).ok().map(Inner::Xml);
73        }
74        "html" | "htm" => return Some(Inner::Html(quarb_html::HtmlAdapter::parse(content))),
75        "yaml" | "yml" => return quarb_yaml::parse(content).ok().map(Inner::Json),
76        "toml" => return quarb_toml::parse(content).ok().map(Inner::Json),
77        "md" | "markdown" => return Some(Inner::Html(quarb_markdown::parse(content))),
78        "csv" => return quarb_csv::CsvAdapter::parse(content).ok().map(Inner::Csv),
79        "tsv" => {
80            return quarb_csv::CsvAdapter::parse_with_delimiter(content, b'\t')
81                .ok()
82                .map(Inner::Csv);
83        }
84        ext if quarb_code::supported(ext) => {
85            return quarb_code::CodeAdapter::parse(content, ext)
86                .ok()
87                .map(Inner::Code);
88        }
89        _ => {}
90    }
91    // Content sniff for extensionless names.
92    let t = content.trim_start();
93    if t.starts_with('{') || t.starts_with('[') {
94        return quarb_json::JsonAdapter::parse(content)
95            .ok()
96            .map(Inner::Json);
97    }
98    if t.starts_with("<?xml") {
99        return quarb_xml::XmlAdapter::parse(content).ok().map(Inner::Xml);
100    }
101    None
102}
103
104/// One graft: an inner arbor mounted at an outer leaf.
105struct Graft {
106    outer: NodeId,
107    inner: Inner,
108}
109
110/// Grafted node ids carry this tag bit; the rest indexes the intern
111/// table. The bit sits at the top of the 56-bit id window that
112/// `MountAdapter` leaves an inner adapter — it reserves the high byte
113/// (bits 56–63) for the mount index — so a grafted id survives being
114/// packed into a mount and still round-trips. It must stay below bit
115/// 56 for that reason; the top bit (1 << 63) collided with the mount
116/// byte. Outer adapters use small sequential indices, far below this
117/// bit, so a set bit 55 unambiguously marks a graft.
118const GRAFT_BIT: u64 = 1 << 55;
119
120/// Any adapter, with parseable leaf content grafted as subtrees.
121pub struct ComposeAdapter<A: AstAdapter> {
122    outer: A,
123    grafts: RefCell<Vec<Graft>>,
124    /// Outer leaf → its graft index (`None`: probed, not
125    /// parseable).
126    probed: RefCell<HashMap<NodeId, Option<usize>>>,
127    /// (graft, inner id) → interned composite id, and the reverse.
128    interned: RefCell<HashMap<(usize, NodeId), NodeId>>,
129    reverse: RefCell<Vec<(usize, NodeId)>>,
130}
131
132impl<A: AstAdapter> ComposeAdapter<A> {
133    pub fn new(outer: A) -> Self {
134        ComposeAdapter {
135            outer,
136            grafts: RefCell::new(Vec::new()),
137            probed: RefCell::new(HashMap::new()),
138            interned: RefCell::new(HashMap::new()),
139            reverse: RefCell::new(Vec::new()),
140        }
141    }
142
143    /// The wrapped adapter (for outer-specific calls).
144    pub fn outer(&self) -> &A {
145        &self.outer
146    }
147
148    /// A combined locator: `outer_locator(leaf)!inner-path` for
149    /// grafted nodes.
150    pub fn locator(&self, node: NodeId, outer_locator: impl Fn(NodeId) -> String) -> String {
151        match self.split(node) {
152            None => outer_locator(node),
153            Some((g, inner)) => {
154                let grafts = self.grafts.borrow();
155                let graft = &grafts[g];
156                format!(
157                    "{}!{}",
158                    outer_locator(graft.outer),
159                    graft.inner.locator(inner)
160                )
161            }
162        }
163    }
164
165    /// Decode a composite id.
166    fn split(&self, node: NodeId) -> Option<(usize, NodeId)> {
167        if node.0 & GRAFT_BIT == 0 {
168            return None;
169        }
170        self.reverse
171            .borrow()
172            .get((node.0 & !GRAFT_BIT) as usize)
173            .copied()
174    }
175
176    fn intern(&self, graft: usize, inner: NodeId) -> NodeId {
177        if let Some(&id) = self.interned.borrow().get(&(graft, inner)) {
178            return id;
179        }
180        let mut rev = self.reverse.borrow_mut();
181        let id = NodeId(GRAFT_BIT | rev.len() as u64);
182        rev.push((graft, inner));
183        self.interned.borrow_mut().insert((graft, inner), id);
184        id
185    }
186
187    /// The graft at an outer leaf, probing (read + parse) on first
188    /// touch. Only childless outer nodes with text content are
189    /// candidates.
190    fn graft_at(&self, node: NodeId) -> Option<usize> {
191        if let Some(&g) = self.probed.borrow().get(&node) {
192            return g;
193        }
194        let g = (|| {
195            if !self.outer.children(node).is_empty() {
196                return None;
197            }
198            let name = self.outer.name(node)?;
199            let content = match self.outer.default_value(node)? {
200                Value::Str(s) => s,
201                _ => return None,
202            };
203            let inner = parse_inner(&name, &content)?;
204            let mut grafts = self.grafts.borrow_mut();
205            grafts.push(Graft { outer: node, inner });
206            Some(grafts.len() - 1)
207        })();
208        self.probed.borrow_mut().insert(node, g);
209        g
210    }
211
212    /// Map an inner node up: the inner root becomes the outer
213    /// leaf.
214    fn wrap(&self, graft: usize, inner: NodeId) -> NodeId {
215        let grafts = self.grafts.borrow();
216        if inner == grafts[graft].inner.adapter().root() {
217            grafts[graft].outer
218        } else {
219            drop(grafts);
220            self.intern(graft, inner)
221        }
222    }
223}
224
225impl<A: AstAdapter> AstAdapter for ComposeAdapter<A> {
226    fn root(&self) -> NodeId {
227        self.outer.root()
228    }
229
230    fn children(&self, node: NodeId) -> Vec<NodeId> {
231        match self.split(node) {
232            Some((g, inner)) => {
233                let ids: Vec<NodeId> = {
234                    let grafts = self.grafts.borrow();
235                    grafts[g].inner.adapter().children(inner)
236                };
237                ids.into_iter().map(|c| self.wrap(g, c)).collect()
238            }
239            None => {
240                let outer = self.outer.children(node);
241                if !outer.is_empty() {
242                    return outer;
243                }
244                match self.graft_at(node) {
245                    Some(g) => {
246                        let ids: Vec<NodeId> = {
247                            let grafts = self.grafts.borrow();
248                            let a = grafts[g].inner.adapter();
249                            a.children(a.root())
250                        };
251                        ids.into_iter().map(|c| self.wrap(g, c)).collect()
252                    }
253                    None => Vec::new(),
254                }
255            }
256        }
257    }
258
259    fn name(&self, node: NodeId) -> Option<String> {
260        match self.split(node) {
261            Some((g, inner)) => self.grafts.borrow()[g].inner.adapter().name(inner),
262            None => self.outer.name(node),
263        }
264    }
265
266    fn parent(&self, node: NodeId) -> Option<NodeId> {
267        match self.split(node) {
268            Some((g, inner)) => {
269                let p = self.grafts.borrow()[g].inner.adapter().parent(inner)?;
270                Some(self.wrap(g, p))
271            }
272            None => self.outer.parent(node),
273        }
274    }
275
276    fn traits(&self, node: NodeId) -> Vec<String> {
277        match self.split(node) {
278            Some((g, inner)) => self.grafts.borrow()[g].inner.adapter().traits(inner),
279            None => self.outer.traits(node),
280        }
281    }
282
283    fn property(&self, node: NodeId, name: &str) -> Option<Value> {
284        match self.split(node) {
285            Some((g, inner)) => self.grafts.borrow()[g]
286                .inner
287                .adapter()
288                .property(inner, name),
289            None => self.outer.property(node, name),
290        }
291    }
292
293    fn default_value(&self, node: NodeId) -> Option<Value> {
294        match self.split(node) {
295            Some((g, inner)) => self.grafts.borrow()[g].inner.adapter().default_value(inner),
296            None => self.outer.default_value(node),
297        }
298    }
299
300    fn metadata(&self, node: NodeId, key: &str) -> Option<Value> {
301        match self.split(node) {
302            Some((g, inner)) => self.grafts.borrow()[g].inner.adapter().metadata(inner, key),
303            None => self.outer.metadata(node, key),
304        }
305    }
306
307    fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
308        match self.split(node) {
309            Some((g, inner)) => {
310                let t = self.grafts.borrow()[g]
311                    .inner
312                    .adapter()
313                    .resolve(inner, property, hint)?;
314                Some(self.wrap(g, t))
315            }
316            None => self.outer.resolve(node, property, hint),
317        }
318    }
319
320    fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
321        match self.split(node) {
322            Some((g, inner)) => {
323                let ls: Vec<(String, NodeId)> = {
324                    let grafts = self.grafts.borrow();
325                    grafts[g].inner.adapter().links(inner)
326                };
327                ls.into_iter().map(|(l, n)| (l, self.wrap(g, n))).collect()
328            }
329            None => self.outer.links(node),
330        }
331    }
332
333    fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
334        match self.split(node) {
335            Some((g, inner)) => {
336                let ls: Vec<(String, NodeId)> = {
337                    let grafts = self.grafts.borrow();
338                    grafts[g].inner.adapter().backlinks(inner)
339                };
340                ls.into_iter().map(|(l, n)| (l, self.wrap(g, n))).collect()
341            }
342            None => self.outer.backlinks(node),
343        }
344    }
345
346    fn link_property(
347        &self,
348        source: NodeId,
349        label: &str,
350        target: NodeId,
351        name: &str,
352    ) -> Option<Value> {
353        // An edge lives on one side of the graft boundary: both
354        // endpoints are inner (same graft) or both outer.
355        match (self.split(source), self.split(target)) {
356            (Some((g, src)), Some((gt, tgt))) if g == gt => self.grafts.borrow()[g]
357                .inner
358                .adapter()
359                .link_property(src, label, tgt, name),
360            (None, None) => self.outer.link_property(source, label, target, name),
361            _ => None,
362        }
363    }
364}