on master help sign in
j/src/lookup.rs
78 lines 3.0 kB history raw
1//! Indexed snapshot lookups used by the reference configuration.
3use crate::value::{value_eq, BlobVal, Crash, ListVal, Value};
4use std::cell::RefCell;
5use std::collections::{HashMap, VecDeque};
6use std::rc::Weak;
8struct SnapshotIndex {
9 identity: (*const Vec<Value>, usize),
10 storage: Weak<Vec<Value>>,
11 // None means a non-path was encountered: use structural equality, as
12 // the language's entryAt does, including errors in later entries.
13 paths: Option<HashMap<Vec<String>, usize>>,
16thread_local! {
17 // `diffs` alternates the before/after snapshots. Weak keys and integer
18 // positions keep neither snapshot nor its blobs alive after the caller.
19 static SNAPSHOTS: RefCell<VecDeque<SnapshotIndex>> = const { RefCell::new(VecDeque::new()) };
22fn path_key(path: &Value) -> Option<Vec<String>> {
23 path.as_list().ok()?.iter()
24 .map(|v| v.as_text().ok().map(str::to_owned))
25 .collect()
28fn indexed_entry(path: &[String], entries: &ListVal) -> Option<Option<usize>> {
29 SNAPSHOTS.with(|cache| {
30 let mut cache = cache.borrow_mut();
31 let identity = entries.identity();
32 if let Some(pos) = cache.iter().position(|c| {
33 c.identity == identity && c.storage.strong_count() > 0
34 }) {
35 let entry = cache.remove(pos).unwrap();
36 cache.push_front(entry);
37 } else {
38 let paths = (|| {
39 let mut paths = HashMap::new();
40 for (i, entry) in entries.iter().enumerate() {
41 let path = path_key(&entry.field("path").ok()?)?;
42 paths.entry(path).or_insert(i);
43 }
44 Some(paths)
45 })();
46 cache.push_front(SnapshotIndex {
47 identity,
48 storage: entries.weak_storage(),
49 paths,
50 });
51 cache.truncate(2);
52 }
53 cache.front().unwrap().paths.as_ref()
54 .map(|paths| paths.get(path).copied())
55 })
58/// `(head (filter (\e -> e.path == path) files)).content or blob ""`.
59/// Index ordinary paths; preserve the full filter's errors and first-match
60/// behavior for arbitrary values the shallow Snapshot contract also admits.
61pub(crate) fn content_at(path: &Value, files: &Value) -> Result<Value, Crash> {
62 let lookup = || -> Result<Value, Crash> {
63 if let (Some(key), Value::List(entries)) = (path_key(path), files) {
64 if let Some(found) = indexed_entry(&key, entries) {
65 return found.ok_or_else(|| Crash::new("head: empty list"))
66 .and_then(|i| entries[i].field("content"));
67 }
68 }
69 let mut first = None;
70 for entry in files.as_list()? {
71 if value_eq(&entry.field("path")?, path)? && first.is_none() {
72 first = Some(entry);
73 }
74 }
75 first.ok_or_else(|| Crash::new("head: empty list"))?.field("content")
76 };
77 Ok(lookup().unwrap_or_else(|_| BlobVal::text_blob("")))