1mod entry_book;
2
3#[cfg(test)]
4mod tests;
5
6use std::cell::RefCell;
7use std::collections::{HashMap, HashSet, VecDeque};
8use std::sync::Arc;
9use std::sync::LazyLock;
10use std::sync::RwLock;
11
12use cirru_parser::Cirru;
13
14use crate::calcit::{self, Calcit, CalcitErr, CalcitScope, CalcitThunk, CalcitThunkInfo, CalcitTypeAnnotation, DYNAMIC_TYPE};
15use crate::call_stack::CallStackList;
16use crate::data::{cirru::code_to_calcit, data_to_calcit};
17use crate::runner;
18use crate::snapshot;
19use crate::snapshot::Snapshot;
20use crate::util::string::extract_pkg_from_ns;
21
22pub use entry_book::EntryBook;
23
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub enum RuntimeCell {
26 Cold,
27 Resolving,
28 Lazy { code: Arc<Calcit>, info: Arc<CalcitThunkInfo> },
29 Ready(Calcit),
30 Errored(Arc<str>),
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum RuntimeResolveMode {
35 Strict,
36 Lenient,
37}
38
39#[derive(Debug, Clone)]
40pub enum RuntimeResolveError {
41 RuntimeCell(RuntimeCell),
42 Eval(CalcitErr),
43}
44
45pub type ProgramRuntimeData = Vec<RuntimeCell>;
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
48pub struct DefId(pub u32);
49
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub enum CompiledDefKind {
52 Proc,
53 Fn,
54 Macro,
55 Syntax,
56 LazyValue,
57 Value,
58}
59
60impl CompiledDefKind {
61 fn from_runtime_value(value: &Calcit) -> Self {
62 match value {
63 Calcit::Proc(..) => Self::Proc,
64 Calcit::Fn { .. } => Self::Fn,
65 Calcit::Macro { .. } => Self::Macro,
66 Calcit::Syntax(..) => Self::Syntax,
67 Calcit::Thunk(..) => Self::LazyValue,
68 _ => Self::Value,
69 }
70 }
71
72 fn from_preprocessed_code(code: &Calcit) -> Self {
73 match code {
74 Calcit::Proc(..) => Self::Proc,
75 Calcit::Syntax(..) => Self::Syntax,
76 Calcit::List(xs) => match xs.first() {
77 Some(Calcit::Syntax(calcit::CalcitSyntax::Defn, _)) => Self::Fn,
78 Some(Calcit::Symbol { sym, .. }) if sym.as_ref() == "defn" => Self::Fn,
79 Some(Calcit::Syntax(calcit::CalcitSyntax::Defmacro, _)) => Self::Macro,
80 Some(Calcit::Symbol { sym, .. }) if sym.as_ref() == "defmacro" => Self::Macro,
81 _ => Self::LazyValue,
82 },
83 _ => Self::LazyValue,
84 }
85 }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct CompiledDef {
90 pub def_id: DefId,
91 pub version_id: u32,
92 pub kind: CompiledDefKind,
93 pub preprocessed_code: Calcit,
94 pub codegen_form: Calcit,
95 pub deps: Vec<DefId>,
96 pub type_summary: Option<Arc<str>>,
97 pub source_code: Option<Calcit>,
98 pub schema: Arc<CalcitTypeAnnotation>,
99 pub doc: Arc<str>,
100 pub examples: Vec<Cirru>,
101}
102
103pub struct CompiledDefPayload {
104 pub version_id: u32,
105 pub preprocessed_code: Calcit,
106 pub codegen_form: Calcit,
107 pub deps: Vec<DefId>,
108 pub type_summary: Option<Arc<str>>,
109 pub source_code: Option<Calcit>,
110 pub schema: Arc<CalcitTypeAnnotation>,
111 pub doc: Arc<str>,
112 pub examples: Vec<Cirru>,
113}
114
115pub type ProgramCompiledData = HashMap<Arc<str>, CompiledFileData>;
116
117#[derive(Debug, Clone, PartialEq, Eq)]
118pub struct CompiledFileData {
119 pub defs: HashMap<Arc<str>, CompiledDef>,
120}
121
122impl CompiledFileData {
123 pub fn keys(&self) -> impl Iterator<Item = &Arc<str>> {
124 self.defs.keys()
125 }
126
127 pub fn get(&self, def: &str) -> Option<&CompiledDef> {
128 self.defs.get(def)
129 }
130}
131
132pub type CompiledProgram = HashMap<Arc<str>, CompiledFileData>;
133
134#[derive(Debug, Default)]
135struct ProgramDefIdIndex {
136 next_id: u32,
137 by_ns: HashMap<Arc<str>, HashMap<Arc<str>, DefId>>,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
142pub struct ProgramDefEntry {
143 pub code: Calcit,
144 pub schema: Arc<CalcitTypeAnnotation>,
145 pub doc: Arc<str>,
146 pub examples: Vec<Cirru>,
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
151pub struct ProgramFileData {
152 pub import_map: HashMap<Arc<str>, Arc<ImportRule>>,
153 pub defs: HashMap<Arc<str>, ProgramDefEntry>,
154}
155
156type ImportMapPair = (Arc<str>, Arc<ImportRule>);
157
158#[derive(Debug, Clone, PartialEq, Eq)]
160pub enum ImportRule {
161 NsAs(Arc<str>),
163 NsReferDef(Arc<str>, Arc<str>),
165 NsDefault(Arc<str>),
167}
168
169pub type ProgramCodeData = HashMap<Arc<str>, ProgramFileData>;
170
171static PROGRAM_RUNTIME_DATA_STATE: LazyLock<RwLock<ProgramRuntimeData>> = LazyLock::new(|| RwLock::new(vec![]));
173static PROGRAM_COMPILED_DATA_STATE: LazyLock<RwLock<ProgramCompiledData>> = LazyLock::new(|| RwLock::new(HashMap::new()));
175pub static PROGRAM_CODE_DATA: LazyLock<RwLock<ProgramCodeData>> = LazyLock::new(|| RwLock::new(HashMap::new()));
177static PROGRAM_DEF_ID_INDEX: LazyLock<RwLock<ProgramDefIdIndex>> = LazyLock::new(|| RwLock::new(ProgramDefIdIndex::default()));
178
179fn ensure_runtime_capacity(runtime: &mut ProgramRuntimeData, def_id: DefId) {
180 let idx = def_id.0 as usize;
181 if runtime.len() <= idx {
182 runtime.resize(idx + 1, RuntimeCell::Cold);
183 }
184}
185
186fn register_program_def_id(ns: &str, def: &str) -> DefId {
187 let mut index = PROGRAM_DEF_ID_INDEX.write().expect("write program def id index");
188 if let Some(def_id) = index.by_ns.get(ns).and_then(|defs| defs.get(def)) {
189 *def_id
190 } else {
191 let def_id = DefId(index.next_id);
192 index.next_id += 1;
193 index.by_ns.entry(Arc::from(ns)).or_default().insert(Arc::from(def), def_id);
194 def_id
195 }
196}
197
198pub fn ensure_def_id(ns: &str, def: &str) -> DefId {
199 register_program_def_id(ns, def)
200}
201
202pub fn lookup_def_id(ns: &str, def: &str) -> Option<DefId> {
203 let index = PROGRAM_DEF_ID_INDEX.read().expect("read program def id index");
204 index.by_ns.get(ns).and_then(|defs| defs.get(def)).copied()
205}
206
207fn collect_ns_def_ids(ns: &str) -> Vec<DefId> {
208 let index = PROGRAM_DEF_ID_INDEX.read().expect("read program def id index");
209 index.by_ns.get(ns).map(|defs| defs.values().copied().collect()).unwrap_or_default()
210}
211
212fn write_runtime_cell(def_id: DefId, cell: RuntimeCell) {
213 let mut runtime = PROGRAM_RUNTIME_DATA_STATE.write().expect("write runtime data");
214 ensure_runtime_capacity(&mut runtime, def_id);
215 runtime[def_id.0 as usize] = cell;
216}
217
218fn write_runtime_value(def_id: DefId, value: Calcit) {
219 write_runtime_cell(def_id, RuntimeCell::Ready(value));
220}
221
222fn write_runtime_lazy(def_id: DefId, code: Arc<Calcit>, info: Arc<CalcitThunkInfo>) {
223 write_runtime_cell(def_id, RuntimeCell::Lazy { code, info });
224}
225
226pub fn write_runtime_lazy_value(ns: &str, def: &str, code: Arc<Calcit>, info: Arc<CalcitThunkInfo>) {
227 let def_id = ensure_def_id(ns, def);
228 write_runtime_lazy(def_id, code, info);
229}
230
231fn clear_runtime_value(def_id: DefId) {
232 let mut runtime = PROGRAM_RUNTIME_DATA_STATE.write().expect("write runtime data");
233 if let Some(slot) = runtime.get_mut(def_id.0 as usize) {
234 *slot = RuntimeCell::Cold;
235 }
236}
237
238pub fn mark_runtime_def_cold(ns: &str, def: &str) {
239 let def_id = ensure_def_id(ns, def);
240 clear_runtime_value(def_id);
241}
242
243pub fn seed_runtime_lazy_from_compiled(ns: &str, def: &str) -> bool {
244 let Some((def_id, preprocessed_code)) = with_compiled_def(ns, def, |compiled| {
245 if compiled.kind == CompiledDefKind::LazyValue {
246 Some((compiled.def_id, compiled.preprocessed_code.clone()))
247 } else {
248 None
249 }
250 })
251 .flatten() else {
252 return false;
253 };
254
255 match lookup_runtime_cell_by_id(def_id) {
256 Some(RuntimeCell::Lazy { .. } | RuntimeCell::Ready(_) | RuntimeCell::Resolving | RuntimeCell::Errored(_)) => false,
257 Some(RuntimeCell::Cold) | None => {
258 write_runtime_lazy(
259 def_id,
260 Arc::new(preprocessed_code),
261 Arc::new(CalcitThunkInfo {
262 ns: Arc::from(ns),
263 def: Arc::from(def),
264 }),
265 );
266 true
267 }
268 }
269}
270
271fn clear_runtime_ns(ns: &str) {
272 for def_id in collect_ns_def_ids(ns) {
273 clear_runtime_value(def_id);
274 }
275}
276
277pub fn lookup_runtime_cell_by_id(def_id: DefId) -> Option<RuntimeCell> {
278 let runtime = PROGRAM_RUNTIME_DATA_STATE.read().expect("read runtime data");
279 runtime.get(def_id.0 as usize).cloned()
280}
281
282pub fn lookup_runtime_cell(ns: &str, def: &str) -> Option<RuntimeCell> {
283 lookup_def_id(ns, def).and_then(lookup_runtime_cell_by_id)
284}
285
286pub fn lookup_runtime_ready_by_id(def_id: DefId) -> Option<Calcit> {
287 match lookup_runtime_cell_by_id(def_id)? {
288 RuntimeCell::Ready(value) => Some(value),
289 _ => None,
290 }
291}
292
293pub fn lookup_runtime_ready(ns: &str, def: &str) -> Option<Calcit> {
294 lookup_def_id(ns, def).and_then(lookup_runtime_ready_by_id)
295}
296
297pub fn mark_runtime_def_resolving(ns: &str, def: &str) {
298 let def_id = ensure_def_id(ns, def);
299 write_runtime_cell(def_id, RuntimeCell::Resolving);
300}
301
302pub fn mark_runtime_def_errored(ns: &str, def: &str, message: Arc<str>) {
303 let def_id = ensure_def_id(ns, def);
304 write_runtime_cell(def_id, RuntimeCell::Errored(message));
305}
306
307fn collect_compiled_dep_keys(code: &Calcit, deps: &mut Vec<(Arc<str>, Arc<str>)>) {
308 match code {
309 Calcit::Import(import) => deps.push((import.ns.to_owned(), import.def.to_owned())),
310 Calcit::Thunk(thunk) => collect_compiled_dep_keys(thunk.get_code(), deps),
311 Calcit::Fn { info, .. } => {
312 for item in info.body.iter() {
313 collect_compiled_dep_keys(item, deps);
314 }
315 }
316 Calcit::List(xs) => {
317 for item in xs.iter() {
318 collect_compiled_dep_keys(item, deps);
319 }
320 }
321 _ => {}
322 }
323}
324
325pub fn collect_compiled_deps(code: &Calcit) -> Vec<DefId> {
326 let mut keys: Vec<(Arc<str>, Arc<str>)> = vec![];
327 collect_compiled_dep_keys(code, &mut keys);
328
329 let mut deps: Vec<DefId> = vec![];
330 for (ns, def) in keys {
331 if let Some(def_id) = lookup_def_id(&ns, &def)
332 && !deps.contains(&def_id)
333 {
334 deps.push(def_id);
335 }
336 }
337 deps.sort();
338 deps
339}
340
341fn build_compiled_def(ns: &str, def: &str, payload: CompiledDefPayload) -> CompiledDef {
342 let kind = CompiledDefKind::from_preprocessed_code(&payload.preprocessed_code);
343
344 CompiledDef {
345 def_id: ensure_def_id(ns, def),
346 version_id: payload.version_id,
347 kind,
348 preprocessed_code: payload.preprocessed_code,
349 codegen_form: payload.codegen_form,
350 deps: payload.deps,
351 type_summary: payload.type_summary,
352 source_code: payload.source_code,
353 schema: payload.schema,
354 doc: payload.doc,
355 examples: payload.examples,
356 }
357}
358
359fn build_runtime_only_snapshot_fallback_compiled_def(ns: &str, def: &str, runtime_value: Calcit) -> Option<CompiledDef> {
360 let codegen_form = data_to_calcit(&runtime_value, ns, def).ok()?;
361 let kind = CompiledDefKind::from_runtime_value(&runtime_value);
362 let deps = collect_compiled_deps(&codegen_form);
363
364 Some(CompiledDef {
365 def_id: ensure_def_id(ns, def),
366 version_id: 0,
367 kind,
368 preprocessed_code: codegen_form.to_owned(),
369 codegen_form,
370 deps,
371 type_summary: None,
372 source_code: None,
373 schema: DYNAMIC_TYPE.clone(),
374 doc: Arc::from(""),
375 examples: vec![],
376 })
377}
378
379fn ensure_source_backed_compiled_def_for_snapshot(ns: &str, def: &str) -> Option<CompiledDef> {
380 if let Some(compiled) = lookup_compiled_def(ns, def) {
381 return Some(compiled);
382 }
383
384 if !has_def_code(ns, def) {
385 return None;
386 }
387
388 let warnings: RefCell<Vec<calcit::LocatedWarning>> = RefCell::new(vec![]);
389 if runner::preprocess::compile_source_def_for_snapshot(ns, def, &warnings, &CallStackList::default()).is_err() {
390 return None;
391 }
392
393 lookup_compiled_def(ns, def)
394}
395
396pub fn write_compiled_def(ns: &str, def: &str, compiled: CompiledDef) {
397 let mut program = PROGRAM_COMPILED_DATA_STATE.write().expect("write compiled program data");
398 let file = program
399 .entry(Arc::from(ns))
400 .or_insert_with(|| CompiledFileData { defs: HashMap::new() });
401 file.defs.insert(Arc::from(def), compiled);
402}
403
404pub fn store_compiled_output(ns: &str, def: &str, payload: CompiledDefPayload) {
405 let compiled = build_compiled_def(ns, def, payload);
406 write_compiled_def(ns, def, compiled);
407}
408
409pub fn lookup_compiled_def(ns: &str, def: &str) -> Option<CompiledDef> {
410 let program = PROGRAM_COMPILED_DATA_STATE.read().expect("read compiled program data");
411 let file = program.get(ns)?;
412 file.defs.get(def).cloned()
413}
414
415fn with_compiled_def<T>(ns: &str, def: &str, f: impl FnOnce(&CompiledDef) -> T) -> Option<T> {
416 let program = PROGRAM_COMPILED_DATA_STATE.read().expect("read compiled program data");
417 let file = program.get(ns)?;
418 let compiled = file.defs.get(def)?;
419 Some(f(compiled))
420}
421
422fn is_compiled_executable_kind(kind: &CompiledDefKind) -> bool {
423 matches!(
424 kind,
425 CompiledDefKind::Fn | CompiledDefKind::Macro | CompiledDefKind::Proc | CompiledDefKind::Syntax
426 )
427}
428
429#[cfg(test)]
430fn lookup_compiled_executable_code(ns: &str, def: &str) -> Option<Calcit> {
431 with_compiled_def(ns, def, |compiled| {
432 if is_compiled_executable_kind(&compiled.kind) {
433 Some(compiled.preprocessed_code.to_owned())
434 } else {
435 None
436 }
437 })
438 .flatten()
439}
440
441fn materialize_compiled_executable_payload(
442 ns: &str,
443 def: &str,
444 call_stack: &CallStackList,
445) -> Result<Option<Calcit>, RuntimeResolveError> {
446 let Some(result) = with_compiled_def(ns, def, |compiled| {
447 if !is_compiled_executable_kind(&compiled.kind) {
448 return None;
449 }
450
451 match compiled.kind {
452 CompiledDefKind::Proc | CompiledDefKind::Syntax => Some(Ok(compiled.preprocessed_code.to_owned())),
453 CompiledDefKind::Fn | CompiledDefKind::Macro => Some(runner::evaluate_expr(
454 &compiled.preprocessed_code,
455 &CalcitScope::default(),
456 ns,
457 call_stack,
458 )),
459 CompiledDefKind::LazyValue | CompiledDefKind::Value => None,
460 }
461 })
462 .flatten() else {
463 return Ok(None);
464 };
465
466 result.map(Some).map_err(RuntimeResolveError::Eval)
467}
468
469pub fn resolve_compiled_executable_def(ns: &str, def: &str, call_stack: &CallStackList) -> Result<Option<Calcit>, RuntimeResolveError> {
470 materialize_compiled_executable_payload(ns, def, call_stack)
471}
472
473fn resolve_runtime_ready_value(value: Calcit, call_stack: &CallStackList) -> Result<Calcit, RuntimeResolveError> {
474 match value {
475 Calcit::Thunk(thunk) => thunk.evaluated_default(call_stack).map_err(RuntimeResolveError::Eval),
476 _ => Ok(value),
477 }
478}
479
480fn resolve_runtime_cell_value(
481 cell: RuntimeCell,
482 mode: RuntimeResolveMode,
483 call_stack: &CallStackList,
484) -> Result<Option<Calcit>, RuntimeResolveError> {
485 match cell {
486 RuntimeCell::Lazy { code, info } => CalcitThunk::Code { code, info }
487 .evaluated_default(call_stack)
488 .map(Some)
489 .map_err(RuntimeResolveError::Eval),
490 RuntimeCell::Ready(value) => resolve_runtime_ready_value(value, call_stack).map(Some),
491 RuntimeCell::Resolving | RuntimeCell::Errored(_) => {
492 if matches!(mode, RuntimeResolveMode::Strict) {
493 Err(RuntimeResolveError::RuntimeCell(cell))
494 } else {
495 Ok(None)
496 }
497 }
498 RuntimeCell::Cold => Ok(None),
499 }
500}
501
502fn resolve_runtime_def_value(
503 ns: &str,
504 def: &str,
505 def_id: Option<DefId>,
506 mode: RuntimeResolveMode,
507 call_stack: &CallStackList,
508) -> Result<Option<Calcit>, RuntimeResolveError> {
509 let runtime_def_id = def_id.or_else(|| lookup_def_id(ns, def));
510
511 if let Some(def_id) = runtime_def_id {
512 match lookup_runtime_cell_by_id(def_id) {
513 Some(RuntimeCell::Cold) | None => {
514 let _ = seed_runtime_lazy_from_compiled(ns, def);
515 if let Some(cell) = lookup_runtime_cell_by_id(def_id) {
517 return resolve_runtime_cell_value(cell, mode, call_stack);
518 }
519 }
520 Some(cell) => {
521 return resolve_runtime_cell_value(cell, mode, call_stack);
523 }
524 }
525 }
526
527 Ok(None)
528}
529
530pub fn resolve_runtime_or_compiled_def(
531 ns: &str,
532 def: &str,
533 def_id: Option<DefId>,
534 mode: RuntimeResolveMode,
535 call_stack: &CallStackList,
536) -> Result<Option<Calcit>, RuntimeResolveError> {
537 if let Some(value) = resolve_runtime_def_value(ns, def, def_id, mode, call_stack)? {
538 return Ok(Some(value));
539 }
540
541 materialize_compiled_executable_payload(ns, def, call_stack)
542}
543
544fn annotation_from_value(value: &Calcit) -> Option<Arc<CalcitTypeAnnotation>> {
545 let annotation = Arc::new(calcit::CalcitTypeAnnotation::from_calcit(value));
546 if matches!(annotation.as_ref(), CalcitTypeAnnotation::Dynamic) {
547 None
548 } else {
549 Some(annotation)
550 }
551}
552
553pub fn lookup_codegen_type_hint(ns: &str, def: &str) -> Option<Arc<CalcitTypeAnnotation>> {
554 if let Some(schema) = with_compiled_def(ns, def, |compiled| compiled.schema.clone())
555 && !matches!(schema.as_ref(), CalcitTypeAnnotation::Dynamic)
556 {
557 return Some(schema);
558 }
559
560 let source_schema = lookup_def_schema(ns, def);
561 if !matches!(source_schema.as_ref(), CalcitTypeAnnotation::Dynamic) {
562 return Some(source_schema);
563 }
564
565 lookup_runtime_ready(ns, def).as_ref().and_then(annotation_from_value)
566}
567
568fn remove_compiled_def(ns: &str, def: &str) {
569 let mut program = PROGRAM_COMPILED_DATA_STATE.write().expect("write compiled program data");
570 if let Some(file) = program.get_mut(ns) {
571 file.defs.remove(def);
572 if file.defs.is_empty() {
573 program.remove(ns);
574 }
575 }
576}
577
578fn remove_compiled_ns(ns: &str) {
579 let mut program = PROGRAM_COMPILED_DATA_STATE.write().expect("write compiled program data");
580 program.remove(ns);
581}
582
583fn collect_transitive_dependent_def_ids(compiled: &ProgramCompiledData, seed_ids: &HashSet<DefId>) -> HashSet<DefId> {
584 if seed_ids.is_empty() {
585 return HashSet::new();
586 }
587
588 let mut reverse_deps: HashMap<DefId, Vec<DefId>> = HashMap::new();
589 for file in compiled.values() {
590 for compiled_def in file.defs.values() {
591 for dep in &compiled_def.deps {
592 reverse_deps.entry(*dep).or_default().push(compiled_def.def_id);
593 }
594 }
595 }
596
597 let mut affected = seed_ids.clone();
598 let mut pending: VecDeque<DefId> = seed_ids.iter().copied().collect();
599
600 while let Some(def_id) = pending.pop_front() {
601 if let Some(dependents) = reverse_deps.get(&def_id) {
602 for dependent_id in dependents {
603 if affected.insert(*dependent_id) {
604 pending.push_back(*dependent_id);
605 }
606 }
607 }
608 }
609
610 affected
611}
612
613fn collect_changed_seed_def_ids(changes: &snapshot::ChangesDict, index: &ProgramDefIdIndex) -> HashSet<DefId> {
614 let mut seeds: HashSet<DefId> = HashSet::new();
615
616 for ns in &changes.removed {
617 if let Some(defs) = index.by_ns.get(ns) {
618 seeds.extend(defs.values().copied());
619 }
620 }
621
622 for (ns, info) in &changes.changed {
623 if info.ns.is_some()
624 && let Some(defs) = index.by_ns.get(ns)
625 {
626 seeds.extend(defs.values().copied());
627 }
628
629 for def in info.removed_defs.iter() {
630 if let Some(def_id) = index.by_ns.get(ns).and_then(|defs| defs.get(def.as_str())) {
631 seeds.insert(*def_id);
632 }
633 }
634
635 for def in info.changed_defs.keys() {
636 if let Some(def_id) = index.by_ns.get(ns).and_then(|defs| defs.get(def.as_str())) {
637 seeds.insert(*def_id);
638 }
639 }
640 }
641
642 seeds
643}
644
645fn collect_reload_affected_def_ids(
646 changes: &snapshot::ChangesDict,
647 compiled: &ProgramCompiledData,
648 index: &ProgramDefIdIndex,
649) -> HashSet<DefId> {
650 let seed_ids = collect_changed_seed_def_ids(changes, index);
651 collect_transitive_dependent_def_ids(compiled, &seed_ids)
652}
653
654fn register_program_def_ids(program_data: &ProgramCodeData) {
655 for (ns, file) in program_data {
656 for def in file.defs.keys() {
657 let _ = register_program_def_id(ns, def);
658 }
659 }
660}
661
662fn extract_import_rule(nodes: &Cirru) -> Result<Vec<ImportMapPair>, String> {
663 match nodes {
664 Cirru::Leaf(_) => Err(String::from("Expected import rule in expr")),
665 Cirru::List(rule_nodes) => {
666 let mut xs = rule_nodes.to_owned();
667 match xs.first() {
668 Some(Cirru::Leaf(s)) if &**s == "[]" => xs = xs[1..4].to_vec(),
670 Some(Cirru::Leaf(s)) if &**s == ";" => return Ok(vec![]),
672 _ => (),
673 }
674 match (&xs[0], &xs[1], &xs[2]) {
675 (Cirru::Leaf(ns), x, Cirru::Leaf(alias)) if x.eq_leaf(":as") => Ok(vec![(
676 (*alias.to_owned()).into(),
677 Arc::new(ImportRule::NsAs((*ns.to_owned()).into())),
678 )]),
679 (Cirru::Leaf(ns), x, Cirru::Leaf(alias)) if x.eq_leaf(":default") => Ok(vec![(
680 (*alias.to_owned()).into(),
681 Arc::new(ImportRule::NsDefault((*ns.to_owned()).into())),
682 )]),
683 (Cirru::Leaf(ns), x, Cirru::List(ys)) if x.eq_leaf(":refer") => {
684 let mut rules: Vec<(Arc<str>, Arc<ImportRule>)> = Vec::with_capacity(ys.len());
685 for y in ys {
686 match y {
687 Cirru::Leaf(s) if &**s == "[]" => (), Cirru::Leaf(s) => rules.push((
689 (*s.to_owned()).into(),
690 Arc::new(ImportRule::NsReferDef((*ns.to_owned()).into(), (*s.to_owned()).into())),
691 )),
692 Cirru::List(_defs) => return Err(format!("invalid refer values, {y}")),
693 }
694 }
695 Ok(rules)
696 }
697 (_, x, _) if x.eq_leaf(":as") => Err(format!("invalid import rule: {nodes}")),
698 (_, x, _) if x.eq_leaf(":default") => Err(format!("invalid default rule: {nodes}")),
699 (_, x, _) if x.eq_leaf(":refer") => Err(format!("invalid import rule: {nodes}")),
700 _ if xs.len() != 3 => Err(format!("expected import rule has length 3: {nodes}")),
701 _ => Err(String::from("unknown rule")),
702 }
703 }
704 }
705}
706
707fn extract_import_map(nodes: &Cirru, ns_name: &str) -> Result<HashMap<Arc<str>, Arc<ImportRule>>, String> {
708 match nodes {
709 Cirru::Leaf(_) => unreachable!("Expected expr for ns"),
710 Cirru::List(xs) => match (xs.first(), xs.get(1), xs.get(2)) {
711 (Some(x), Some(Cirru::Leaf(_)), Some(Cirru::List(xs))) if x.eq_leaf("ns") => {
713 if !xs.is_empty() && xs[0].eq_leaf(":require") {
714 let mut ys: HashMap<Arc<str>, Arc<ImportRule>> = HashMap::with_capacity(xs.len());
715 for x in xs.iter().skip(1) {
716 let rules = extract_import_rule(x).map_err(|e| format!("in namespace '{ns_name}': {e}"))?;
717 for (target, rule) in rules {
718 ys.insert(target, rule);
719 }
720 }
721 Ok(ys)
722 } else {
723 Ok(HashMap::new())
724 }
725 }
726 _ if xs.len() < 3 => Ok(HashMap::new()),
727 _ => {
728 let preview = cirru_parser::format(&[nodes.clone()], true.into()).unwrap_or_else(|_| format!("{nodes:?}"));
729 let preview_short = if preview.len() > 200 {
730 format!("{}...", &preview[..200])
731 } else {
732 preview
733 };
734 Err(format!("invalid ns form in '{ns_name}':\n{preview_short}"))
735 }
736 },
737 }
738}
739
740fn extract_file_data(file: &snapshot::FileInSnapShot, ns: Arc<str>) -> Result<ProgramFileData, String> {
741 let import_map = extract_import_map(&file.ns.code, &ns)?;
742 let mut defs: HashMap<Arc<str>, ProgramDefEntry> = HashMap::with_capacity(file.defs.len());
743 for (def, entry) in &file.defs {
744 let at_def = def.to_owned();
745 let code = code_to_calcit(&entry.code, &ns, &at_def, vec![])?;
746 let schema = entry.schema.clone();
747 let doc = Arc::from(entry.doc.as_str());
748 defs.insert(
749 def.to_owned().into(),
750 ProgramDefEntry {
751 code,
752 schema,
753 doc,
754 examples: entry.examples.clone(),
755 },
756 );
757 }
758 Ok(ProgramFileData { import_map, defs })
759}
760
761pub fn extract_program_data(s: &Snapshot) -> Result<ProgramCodeData, String> {
762 calcit::register_program_lookups(lookup_runtime_ready, lookup_def_code, lookup_def_schema);
765 calcit::clear_type_slots();
766
767 let mut xs: ProgramCodeData = HashMap::with_capacity(s.files.len());
768
769 for (ns, file) in &s.files {
770 let file_info = extract_file_data(file, ns.to_owned().into())?;
771 xs.insert(ns.to_owned().into(), file_info);
772 }
773
774 register_program_def_ids(&xs);
775
776 Ok(xs)
777}
778
779pub fn has_def_code(ns: &str, def: &str) -> bool {
781 let program_code = { PROGRAM_CODE_DATA.read().expect("read program code") };
782 match &program_code.get(ns) {
783 Some(v) => v.defs.contains_key(def),
784 None => false,
785 }
786}
787
788pub fn list_source_def_names(ns: &str) -> Vec<Arc<str>> {
790 let program_code = PROGRAM_CODE_DATA.read().expect("read program code");
791 match program_code.get(ns) {
792 Some(file) => file.defs.keys().cloned().collect(),
793 None => vec![],
794 }
795}
796
797pub fn lookup_def_code(ns: &str, def: &str) -> Option<Calcit> {
798 let program_code = { PROGRAM_CODE_DATA.read().expect("read program code") };
799 let file = program_code.get(ns)?;
800 let entry = file.defs.get(def)?;
801 Some(entry.code.to_owned())
802}
803
804pub fn lookup_def_schema(ns: &str, def: &str) -> Arc<CalcitTypeAnnotation> {
805 let program_code = { PROGRAM_CODE_DATA.read().expect("read program code") };
806 let file = match program_code.get(ns) {
807 Some(f) => f,
808 None => return DYNAMIC_TYPE.clone(),
809 };
810 let entry = match file.defs.get(def) {
811 Some(e) => e,
812 None => return DYNAMIC_TYPE.clone(),
813 };
814 entry.schema.clone()
815}
816
817pub fn lookup_def_doc(ns: &str, def: &str) -> Option<String> {
819 let program_code = { PROGRAM_CODE_DATA.read().expect("read program code") };
820 let file = program_code.get(ns)?;
821 let entry = file.defs.get(def)?;
822 if entry.doc.is_empty() { None } else { Some(entry.doc.to_string()) }
823}
824
825pub fn lookup_def_examples(ns: &str, def: &str) -> Option<Vec<Cirru>> {
827 let program_code = { PROGRAM_CODE_DATA.read().expect("read program code") };
828 let file = program_code.get(ns)?;
829 let entry = file.defs.get(def)?;
830 if entry.examples.is_empty() {
831 None
832 } else {
833 Some(entry.examples.clone())
834 }
835}
836
837pub fn lookup_def_target_in_import(ns: &str, def: &str) -> Option<Arc<str>> {
838 let program = { PROGRAM_CODE_DATA.read().expect("read program code") };
839 let file = program.get(ns)?;
840 let import_rule = file.import_map.get(def)?;
841 match &**import_rule {
842 ImportRule::NsReferDef(ns, _def) => Some(ns.to_owned()),
843 ImportRule::NsAs(_ns) => None,
844 ImportRule::NsDefault(_ns) => None,
845 }
846}
847
848pub fn lookup_ns_target_in_import(ns: &str, alias: &str) -> Option<Arc<str>> {
849 let program = { PROGRAM_CODE_DATA.read().expect("read program code") };
850 let file = program.get(ns)?;
851 let import_rule = file.import_map.get(alias)?;
852 match &**import_rule {
853 ImportRule::NsReferDef(_ns, _def) => None,
854 ImportRule::NsAs(ns) => Some(ns.to_owned()),
855 ImportRule::NsDefault(_ns) => None,
856 }
857}
858
859pub fn lookup_default_target_in_import(at_ns: &str, alias: &str) -> Option<Arc<str>> {
861 let program = { PROGRAM_CODE_DATA.read().expect("read program code") };
862 let file = program.get(at_ns)?;
863 let import_rule = file.import_map.get(alias)?;
864 match &**import_rule {
865 ImportRule::NsReferDef(_ns, _def) => None,
866 ImportRule::NsAs(_ns) => None,
867 ImportRule::NsDefault(ns) => Some(ns.to_owned()),
868 }
869}
870
871pub fn write_runtime_ready(ns: &str, def: &str, value: Calcit) -> Result<(), String> {
873 let def_id = ensure_def_id(ns, def);
874
875 match value {
876 Calcit::Thunk(CalcitThunk::Code { code, info }) => write_runtime_lazy(def_id, code, info),
877 other => write_runtime_value(def_id, other),
878 }
879
880 Ok(())
881}
882
883#[derive(Debug, Clone)]
884struct SnapshotFillTask {
885 ns: Arc<str>,
886 def: Arc<str>,
887 source_backed: bool,
888 referenced_by_compiled: bool,
889 runtime_value: Option<Calcit>,
890}
891
892pub fn calcit_contains_bind_type(node: &Calcit) -> bool {
896 use crate::calcit::CalcitProc;
897 match node {
898 Calcit::Proc(CalcitProc::BindType) => true,
899 Calcit::Symbol { sym, .. } if sym.as_ref() == "bind-type" => true,
900 Calcit::Import(imp) if imp.def.as_ref() == "bind-type" => true,
901 Calcit::List(list) => list.iter().any(calcit_contains_bind_type),
902 Calcit::Thunk(crate::calcit::CalcitThunk::Code { code, .. }) => calcit_contains_bind_type(code),
903 _ => false,
904 }
905}
906
907fn collect_snapshot_fill_tasks(compiled: &CompiledProgram) -> Vec<SnapshotFillTask> {
908 let program_code = PROGRAM_CODE_DATA.read().expect("read program code");
909 let program_def_ids = PROGRAM_DEF_ID_INDEX.read().expect("read program def id index");
910 let runtime = PROGRAM_RUNTIME_DATA_STATE.read().expect("read runtime data");
911 let mut referenced_def_ids: HashSet<DefId> = HashSet::new();
912 for file in compiled.values() {
913 for compiled_def in file.defs.values() {
914 referenced_def_ids.extend(compiled_def.deps.iter().copied());
915 }
916 }
917
918 let mut tasks = vec![];
919
920 for (ns, defs) in &program_def_ids.by_ns {
921 let existing_defs = compiled.get(ns).map(|file| &file.defs);
922 let source_file = program_code.get(ns.as_ref());
923
924 for (def, def_id) in defs {
925 if existing_defs.is_some_and(|defs| defs.contains_key(def.as_ref())) {
926 continue;
927 }
928
929 let source_backed = source_file.is_some_and(|data| data.defs.contains_key(def.as_ref()));
930 let referenced_by_compiled = referenced_def_ids.contains(def_id);
931 let runtime_value = runtime.get(def_id.0 as usize).and_then(|cell| match cell {
932 RuntimeCell::Ready(value) => Some(value.to_owned()),
933 _ => None,
934 });
935
936 if !source_backed && (!referenced_by_compiled || runtime_value.is_none()) {
937 continue;
938 }
939
940 tasks.push(SnapshotFillTask {
941 ns: ns.to_owned(),
942 def: def.to_owned(),
943 source_backed,
944 referenced_by_compiled,
945 runtime_value,
946 });
947 }
948 }
949
950 tasks.sort_by(|a, b| {
958 let a_has_bind = if a.source_backed {
959 {
960 program_code
961 .get(a.ns.as_ref())
962 .and_then(|f| f.defs.get(a.def.as_ref()))
963 .is_some_and(|entry| calcit_contains_bind_type(&entry.code))
964 }
965 } else {
966 false
967 };
968 let b_has_bind = if b.source_backed {
969 {
970 program_code
971 .get(b.ns.as_ref())
972 .and_then(|f| f.defs.get(b.def.as_ref()))
973 .is_some_and(|entry| calcit_contains_bind_type(&entry.code))
974 }
975 } else {
976 false
977 };
978 b_has_bind
980 .cmp(&a_has_bind)
981 .then_with(|| a.ns.cmp(&b.ns).then_with(|| a.def.cmp(&b.def)))
982 });
983
984 tasks
985}
986
987fn build_snapshot_fill_compiled_def(task: SnapshotFillTask) -> Option<CompiledDef> {
988 if task.source_backed {
989 return ensure_source_backed_compiled_def_for_snapshot(task.ns.as_ref(), task.def.as_ref());
990 }
991
992 if !task.source_backed
993 && task.referenced_by_compiled
994 && task.runtime_value.is_some()
995 && let Some(runtime_value) = task.runtime_value
996 {
997 return build_runtime_only_snapshot_fallback_compiled_def(task.ns.as_ref(), task.def.as_ref(), runtime_value);
998 }
999
1000 None
1001}
1002
1003pub fn clone_compiled_program_snapshot() -> Result<CompiledProgram, String> {
1004 let mut compiled: CompiledProgram = PROGRAM_COMPILED_DATA_STATE.read().expect("read compiled program data").to_owned();
1005 let tasks = collect_snapshot_fill_tasks(&compiled);
1006
1007 for task in tasks {
1008 let ns = task.ns.clone();
1009 let def = task.def.clone();
1010 if let Some(compiled_def) = build_snapshot_fill_compiled_def(task) {
1011 let compiled_file = compiled.entry(ns).or_insert_with(|| CompiledFileData { defs: HashMap::new() });
1012 compiled_file.defs.insert(def, compiled_def);
1013 }
1014 }
1015
1016 Ok(compiled)
1017}
1018
1019pub fn apply_code_changes(changes: &snapshot::ChangesDict) -> Result<(), String> {
1020 let mut program_code = PROGRAM_CODE_DATA.write().expect("open program code");
1021 let coord0 = vec![];
1022
1023 for (ns, file) in &changes.added {
1024 let file_info = extract_file_data(file, ns.to_owned())?;
1025 for def in file_info.defs.keys() {
1026 let _ = register_program_def_id(ns, def);
1027 }
1028 program_code.insert(ns.to_owned(), file_info);
1029 remove_compiled_ns(ns);
1030 }
1031 for ns in &changes.removed {
1032 clear_runtime_ns(ns);
1033 program_code.remove(ns);
1034 remove_compiled_ns(ns);
1035 }
1036 for (ns, info) in &changes.changed {
1037 let file = program_code.get_mut(ns).ok_or_else(|| format!("can not load ns: {ns}"))?;
1039 if let Some(v) = &info.ns {
1040 file.import_map = extract_import_map(v, ns)?;
1041 }
1042 for (def, code) in &info.added_defs {
1043 let _ = register_program_def_id(ns, def);
1044 clear_runtime_value(ensure_def_id(ns, def));
1045 remove_compiled_def(ns, def);
1046 let calcit_code = code_to_calcit(code, ns, def, coord0.to_owned())?;
1047 let entry = ProgramDefEntry {
1048 code: calcit_code,
1049 schema: DYNAMIC_TYPE.clone(),
1050 doc: Arc::from(""), examples: vec![], };
1053 file.defs.insert(def.to_owned().into(), entry);
1054 }
1055 for def in &info.removed_defs {
1056 if let Some(def_id) = lookup_def_id(ns, def) {
1057 clear_runtime_value(def_id);
1058 }
1059 file.defs.remove(def.as_str());
1060 remove_compiled_def(ns, def);
1061 }
1062 for (def, code) in &info.changed_defs {
1063 let def_id = register_program_def_id(ns, def);
1064 clear_runtime_value(def_id);
1065 remove_compiled_def(ns, def);
1066 let calcit_code = code_to_calcit(code, ns, def, coord0.to_owned())?;
1067 let (schema, doc, examples) = match file.defs.get(def.as_str()) {
1068 Some(existing) => (existing.schema.clone(), existing.doc.clone(), existing.examples.clone()),
1069 None => (DYNAMIC_TYPE.clone(), Arc::from(""), Vec::new()),
1070 };
1071 file.defs.insert(
1072 def.to_owned().into(),
1073 ProgramDefEntry {
1074 code: calcit_code,
1075 schema,
1076 doc,
1077 examples,
1078 },
1079 );
1080 }
1081 }
1082
1083 Ok(())
1084}
1085
1086pub fn clear_runtime_caches_for_reload(init_ns: Arc<str>, reload_ns: Arc<str>, reload_libs: bool) -> Result<(), String> {
1088 if reload_libs {
1089 let mut runtime = PROGRAM_RUNTIME_DATA_STATE.write().expect("open runtime data");
1090 let mut compiled = PROGRAM_COMPILED_DATA_STATE.write().expect("open compiled program data");
1091 runtime.clear();
1092 compiled.clear();
1093 return Ok(());
1094 }
1095
1096 let init_pkg = extract_pkg_from_ns(init_ns.to_owned()).ok_or_else(|| format!("failed to extract pkg from: {init_ns}"))?;
1097 let reload_pkg = extract_pkg_from_ns(reload_ns.to_owned()).ok_or_else(|| format!("failed to extract pkg from: {reload_ns}"))?;
1098 let ns_keys: Vec<Arc<str>> = {
1099 let index = PROGRAM_DEF_ID_INDEX.read().expect("read program def id index");
1100 index.by_ns.keys().cloned().collect()
1101 };
1102
1103 let mut changes = snapshot::ChangesDict::default();
1104 for ns in ns_keys {
1105 if ns == init_pkg || ns == reload_pkg || ns.starts_with(&format!("{init_pkg}.")) || ns.starts_with(&format!("{reload_pkg}.")) {
1106 changes.changed.insert(
1107 ns,
1108 snapshot::FileChangeInfo {
1109 ns: Some(Cirru::Leaf(Arc::from("ns"))),
1110 added_defs: HashMap::new(),
1111 removed_defs: HashSet::new(),
1112 changed_defs: HashMap::new(),
1113 },
1114 );
1115 }
1116 }
1117
1118 clear_runtime_caches_for_changes(&changes, false)
1119}
1120
1121pub fn clear_runtime_caches_for_changes(changes: &snapshot::ChangesDict, reload_libs: bool) -> Result<(), String> {
1122 if reload_libs {
1123 let mut runtime = PROGRAM_RUNTIME_DATA_STATE.write().expect("open runtime data");
1124 let mut compiled = PROGRAM_COMPILED_DATA_STATE.write().expect("open compiled program data");
1125 runtime.clear();
1126 compiled.clear();
1127 return Ok(());
1128 }
1129
1130 let affected_def_ids = {
1131 let compiled = PROGRAM_COMPILED_DATA_STATE.read().expect("read compiled program data");
1132 let index = PROGRAM_DEF_ID_INDEX.read().expect("read program def id index");
1133 collect_reload_affected_def_ids(changes, &compiled, &index)
1134 };
1135
1136 if affected_def_ids.is_empty() {
1137 return Ok(());
1138 }
1139
1140 let mut runtime = PROGRAM_RUNTIME_DATA_STATE.write().expect("open runtime data");
1141 for def_id in &affected_def_ids {
1142 if let Some(slot) = runtime.get_mut(def_id.0 as usize) {
1143 *slot = RuntimeCell::Cold;
1144 }
1145 }
1146
1147 let mut compiled = PROGRAM_COMPILED_DATA_STATE.write().expect("open compiled program data");
1148 for file in compiled.values_mut() {
1149 file.defs.retain(|_, compiled_def| !affected_def_ids.contains(&compiled_def.def_id));
1150 }
1151 compiled.retain(|_, file| !file.defs.is_empty());
1152
1153 Ok(())
1154}