1use std::collections::HashMap;
37use std::io::Write;
38use std::path::PathBuf;
39use std::rc::Rc;
40
41use quarb::NodeId;
42use tree_sitter::Language;
43
44use crate::Node;
45
46const MAGIC: &[u8; 8] = b"QUARBAST";
47const FORMAT_VERSION: u16 = 1;
53const HEADER_LEN: usize = 124;
54
55#[derive(Clone)]
57pub struct Cache {
58 root: PathBuf,
59}
60
61impl Cache {
62 pub fn new(root: PathBuf) -> Self {
63 Cache { root }
64 }
65
66 pub fn default_dir() -> PathBuf {
68 if let Some(d) = std::env::var_os("QUARB_CACHE_DIR") {
69 return PathBuf::from(d);
70 }
71 let home = std::env::var_os("HOME")
72 .map(PathBuf::from)
73 .unwrap_or_else(|| PathBuf::from("."));
74 home.join(".quarb").join("cache")
75 }
76}
77
78struct Meta {
83 abi: u32,
84 kind_count: u32,
85 field_count: u32,
86 state_count: u32,
87 digest: [u8; 32],
88}
89
90thread_local! {
91 static META: std::cell::RefCell<HashMap<u16, Rc<Meta>>> =
92 std::cell::RefCell::new(HashMap::new());
93}
94
95fn meta(tag: u16, lang: &Language) -> Rc<Meta> {
100 if let Some(m) = META.with(|m| m.borrow().get(&tag).cloned()) {
101 return m;
102 }
103 let m = Rc::new(Meta {
104 abi: lang.version() as u32,
105 kind_count: lang.node_kind_count() as u32,
106 field_count: lang.field_count() as u32,
107 state_count: lang.parse_state_count() as u32,
108 digest: grammar_digest(env!("QUARB_GRAMMAR_STAMP"), env!("CARGO_PKG_VERSION"), lang),
109 });
110 META.with(|mm| mm.borrow_mut().insert(tag, m.clone()));
111 m
112}
113
114fn grammar_digest(stamp: &str, crate_ver: &str, lang: &Language) -> [u8; 32] {
120 let mut buf = Vec::new();
121 for s in [stamp, crate_ver] {
122 buf.extend((s.len() as u32).to_le_bytes());
123 buf.extend_from_slice(s.as_bytes());
124 }
125 let kc = lang.node_kind_count();
126 buf.extend((kc as u32).to_le_bytes());
127 for id in 0..kc as u16 {
128 if let Some(s) = lang.node_kind_for_id(id) {
129 buf.extend(id.to_le_bytes());
130 buf.push(lang.node_kind_is_named(id) as u8);
131 buf.extend((s.len() as u32).to_le_bytes());
132 buf.extend_from_slice(s.as_bytes());
133 }
134 }
135 let fc = lang.field_count();
136 buf.extend((fc as u32).to_le_bytes());
137 for id in 1..=fc as u16 {
139 if let Some(s) = lang.field_name_for_id(id) {
140 buf.extend(id.to_le_bytes());
141 buf.extend((s.len() as u32).to_le_bytes());
142 buf.extend_from_slice(s.as_bytes());
143 }
144 }
145 quarb::sha256(&buf)
146}
147
148pub(crate) fn lang_tag(ext: &str) -> u16 {
151 match ext {
152 "rs" => 1,
153 "py" => 2,
154 "js" | "mjs" | "cjs" | "jsx" => 3,
155 "c" | "h" => 4,
156 _ => 0,
157 }
158}
159
160fn lang_name(tag: u16) -> &'static str {
161 match tag {
162 1 => "rust",
163 2 => "python",
164 3 => "javascript",
165 4 => "c",
166 _ => "unknown",
167 }
168}
169
170fn write_uleb(out: &mut Vec<u8>, mut v: u64) {
175 loop {
176 let b = (v & 0x7f) as u8;
177 v >>= 7;
178 if v == 0 {
179 out.push(b);
180 return;
181 }
182 out.push(b | 0x80);
183 }
184}
185
186fn read_uleb(buf: &[u8], pos: &mut usize) -> Option<u64> {
189 let mut v = 0u64;
190 let mut shift = 0u32;
191 loop {
192 let b = *buf.get(*pos)?;
193 *pos += 1;
194 if shift >= 64 {
195 return None; }
197 v |= ((b & 0x7f) as u64) << shift;
198 if b & 0x80 == 0 {
199 return Some(v);
200 }
201 shift += 7;
202 }
203}
204
205fn fnv1a(data: &[u8]) -> u64 {
206 let mut h = 0xcbf2_9ce4_8422_2325u64;
207 for &b in data {
208 h ^= b as u64;
209 h = h.wrapping_mul(0x0000_0100_0000_01b3);
210 }
211 h
212}
213
214fn hex(bytes: &[u8]) -> String {
215 let mut s = String::with_capacity(bytes.len() * 2);
216 for b in bytes {
217 s.push_str(&format!("{b:02x}"));
218 }
219 s
220}
221
222fn path_for(cache: &Cache, tag: u16, m: &Meta, content_hex: &str) -> PathBuf {
226 let dig8: String = hex(&m.digest).chars().take(8).collect();
227 cache
228 .root
229 .join("ast")
230 .join(format!("v{FORMAT_VERSION}"))
231 .join(format!("{}-{}", lang_name(tag), dig8))
232 .join(&content_hex[..2])
233 .join(format!("{content_hex}.qast"))
234}
235
236fn encode(
245 nodes: &[Node],
246 tag: u16,
247 lang: &Language,
248 m: &Meta,
249 content_hash: &[u8; 32],
250 content_len: u64,
251) -> Option<Vec<u8>> {
252 let mut kind_ids: Vec<u16> = Vec::new();
253 let mut kind_map: HashMap<u16, u32> = HashMap::new();
254 let mut field_ids: Vec<u16> = Vec::new();
255 let mut field_map: HashMap<u16, u32> = HashMap::new();
256 let mut recs: Vec<(u32, u32)> = Vec::with_capacity(nodes.len());
257
258 for n in nodes {
259 let gid = lang.id_for_node_kind(n.kind, true);
262 if lang.node_kind_for_id(gid) != Some(n.kind) {
263 return None;
264 }
265 let kidx = *kind_map.entry(gid).or_insert_with(|| {
266 let i = kind_ids.len() as u32;
267 kind_ids.push(gid);
268 i
269 });
270 let fidx1 = match n.field {
271 None => 0u32,
272 Some(f) => {
273 let fid = lang.field_id_for_name(f)?.get();
274 if lang.field_name_for_id(fid) != Some(f) {
275 return None;
276 }
277 let i = *field_map.entry(fid).or_insert_with(|| {
278 let i = field_ids.len() as u32;
279 field_ids.push(fid);
280 i
281 });
282 i + 1
283 }
284 };
285 recs.push((kidx, fidx1));
286 }
287
288 let mut out = Vec::with_capacity(HEADER_LEN + nodes.len() * 8 + 8);
289 out.extend_from_slice(MAGIC);
290 out.extend(FORMAT_VERSION.to_le_bytes());
291 out.extend(0u16.to_le_bytes()); out.extend(tag.to_le_bytes());
293 out.extend(0u16.to_le_bytes()); out.extend(m.abi.to_le_bytes());
295 out.extend(m.kind_count.to_le_bytes());
296 out.extend(m.field_count.to_le_bytes());
297 out.extend(m.state_count.to_le_bytes());
298 out.extend(0u32.to_le_bytes()); out.extend(0u32.to_le_bytes()); out.extend_from_slice(&m.digest);
301 out.extend_from_slice(content_hash);
302 out.extend(content_len.to_le_bytes());
303 out.extend((nodes.len() as u64).to_le_bytes());
304 out.extend((kind_ids.len() as u16).to_le_bytes());
305 out.extend((field_ids.len() as u16).to_le_bytes());
306 debug_assert_eq!(out.len(), HEADER_LEN);
307
308 for &id in &kind_ids {
309 write_uleb(&mut out, id as u64);
310 }
311 for &id in &field_ids {
312 write_uleb(&mut out, id as u64);
313 }
314
315 let mut prev_start = 0u64;
316 let mut prev_sline = 0u64;
317 for (i, n) in nodes.iter().enumerate() {
318 let (kidx, fidx1) = recs[i];
319 let parent_delta = match n.parent {
320 None => 0u64,
321 Some(p) => (i as u64).checked_sub(p.0)?, };
323 let start = n.start as u64;
324 let start_delta = start.checked_sub(prev_start)?;
325 prev_start = start;
326 let len = (n.end as u64).checked_sub(start)?;
327 let sline = n.start_line as u64;
328 let sline_delta = sline.checked_sub(prev_sline)?;
329 prev_sline = sline;
330 let espan = (n.end_line as u64).checked_sub(sline)?;
331
332 write_uleb(&mut out, parent_delta);
333 write_uleb(&mut out, kidx as u64);
334 write_uleb(&mut out, fidx1 as u64);
335 write_uleb(&mut out, start_delta);
336 write_uleb(&mut out, len);
337 write_uleb(&mut out, sline_delta);
338 write_uleb(&mut out, espan);
339 }
340
341 let f = fnv1a(&out);
342 out.extend(f.to_le_bytes());
343 Some(out)
344}
345
346fn decode(bytes: &[u8], tag: u16, lang: &Language, source: &str) -> Option<Vec<Node>> {
349 if bytes.len() < HEADER_LEN + 8 {
350 return None;
351 }
352 let body_len = bytes.len() - 8;
353 let stored_footer = u64::from_le_bytes(bytes[body_len..].try_into().ok()?);
354 if fnv1a(&bytes[..body_len]) != stored_footer {
355 return None;
356 }
357 if &bytes[0..8] != MAGIC {
358 return None;
359 }
360 let rd_u16 = |o: usize| u16::from_le_bytes([bytes[o], bytes[o + 1]]);
361 let rd_u32 = |o: usize| u32::from_le_bytes(bytes[o..o + 4].try_into().unwrap());
362 let rd_u64 = |o: usize| u64::from_le_bytes(bytes[o..o + 8].try_into().unwrap());
363 if rd_u16(8) != FORMAT_VERSION || rd_u16(12) != tag {
364 return None;
365 }
366 let m = meta(tag, lang);
367 if rd_u32(16) != m.abi
368 || rd_u32(20) != m.kind_count
369 || rd_u32(24) != m.field_count
370 || rd_u32(28) != m.state_count
371 || bytes[40..72] != m.digest
372 {
373 return None;
374 }
375 if bytes[72..104] != quarb::sha256(source.as_bytes()) {
379 return None;
380 }
381 if rd_u64(104) != source.len() as u64 {
382 return None;
383 }
384 let node_count = rd_u64(112) as usize;
385 let kdlen = rd_u16(120) as usize;
386 let fdlen = rd_u16(122) as usize;
387
388 let mut pos = HEADER_LEN;
389 let mut kind_dict: Vec<&'static str> = Vec::with_capacity(kdlen);
390 for _ in 0..kdlen {
391 let gid = read_uleb(bytes, &mut pos)?;
392 if gid > u16::MAX as u64 {
393 return None;
394 }
395 kind_dict.push(lang.node_kind_for_id(gid as u16)?);
396 }
397 let mut field_dict: Vec<&'static str> = Vec::with_capacity(fdlen);
398 for _ in 0..fdlen {
399 let gid = read_uleb(bytes, &mut pos)?;
400 if gid > u16::MAX as u64 {
401 return None;
402 }
403 field_dict.push(lang.field_name_for_id(gid as u16)?);
404 }
405
406 let src_len = source.len() as u64;
407 let reserve = node_count.min(body_len / 7).min(1 << 20);
416 let mut nodes: Vec<Node> = Vec::with_capacity(reserve);
417 let mut start = 0u64;
418 let mut sline = 0u64;
419 for i in 0..node_count {
420 let parent_delta = read_uleb(bytes, &mut pos)?;
421 let kidx = read_uleb(bytes, &mut pos)? as usize;
422 let fidx1 = read_uleb(bytes, &mut pos)?;
423 let start_delta = read_uleb(bytes, &mut pos)?;
424 let len = read_uleb(bytes, &mut pos)?;
425 let sline_delta = read_uleb(bytes, &mut pos)?;
426 let espan = read_uleb(bytes, &mut pos)?;
427
428 let parent = if i == 0 {
429 if parent_delta != 0 {
430 return None;
431 }
432 None
433 } else {
434 if parent_delta == 0 || parent_delta > i as u64 {
435 return None;
436 }
437 Some(NodeId(i as u64 - parent_delta))
438 };
439 if kidx >= kdlen {
440 return None;
441 }
442 let field = if fidx1 == 0 {
443 None
444 } else {
445 let fi = (fidx1 - 1) as usize;
446 if fi >= fdlen {
447 return None;
448 }
449 Some(field_dict[fi])
450 };
451 start = start.checked_add(start_delta)?;
452 let end = start.checked_add(len)?;
453 if end > src_len {
454 return None;
455 }
456 sline = sline.checked_add(sline_delta)?;
457 let end_line = sline.checked_add(espan)?;
458
459 nodes.push(Node {
460 kind: kind_dict[kidx],
461 field,
462 parent,
463 children: Vec::new(),
464 start: start as usize,
465 end: end as usize,
466 start_line: sline as usize,
467 end_line: end_line as usize,
468 fields: Vec::new(),
469 });
470 if let Some(p) = parent {
472 let pi = p.0 as usize;
473 let clen = nodes[pi].children.len();
474 if let Some(f) = field {
475 nodes[pi].fields.push((f, clen));
476 }
477 nodes[pi].children.push(NodeId(i as u64));
478 }
479 }
480 if pos != body_len {
482 return None;
483 }
484 Some(nodes)
485}
486
487pub(crate) fn load(
494 cache: &Cache,
495 tag: u16,
496 lang: &Language,
497 content_hash: &[u8; 32],
498 source: &str,
499) -> Option<Vec<Node>> {
500 let m = meta(tag, lang);
501 let path = path_for(cache, tag, &m, &hex(content_hash));
502 let bytes = std::fs::read(&path).ok()?;
503 decode(&bytes, tag, lang, source)
504}
505
506pub(crate) fn store(
509 cache: &Cache,
510 nodes: &[Node],
511 tag: u16,
512 lang: &Language,
513 content_hash: &[u8; 32],
514 content_len: u64,
515) {
516 let m = meta(tag, lang);
517 let bytes = match encode(nodes, tag, lang, &m, content_hash, content_len) {
518 Some(b) => b,
519 None => return,
520 };
521 let path = path_for(cache, tag, &m, &hex(content_hash));
522 let Some(dir) = path.parent() else { return };
523 if std::fs::create_dir_all(dir).is_err() {
524 return;
525 }
526 if let Ok(mut tmp) = tempfile::NamedTempFile::new_in(dir)
531 && tmp.write_all(&bytes).is_ok()
532 {
533 let _ = tmp.persist(&path);
534 }
535}
536
537#[cfg(test)]
538mod tests;