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
892fn collect_snapshot_fill_tasks(compiled: &CompiledProgram) -> Vec<SnapshotFillTask> {
893 let program_code = PROGRAM_CODE_DATA.read().expect("read program code");
894 let program_def_ids = PROGRAM_DEF_ID_INDEX.read().expect("read program def id index");
895 let runtime = PROGRAM_RUNTIME_DATA_STATE.read().expect("read runtime data");
896 let mut referenced_def_ids: HashSet<DefId> = HashSet::new();
897 for file in compiled.values() {
898 for compiled_def in file.defs.values() {
899 referenced_def_ids.extend(compiled_def.deps.iter().copied());
900 }
901 }
902
903 let mut tasks = vec![];
904
905 for (ns, defs) in &program_def_ids.by_ns {
906 let existing_defs = compiled.get(ns).map(|file| &file.defs);
907 let source_file = program_code.get(ns.as_ref());
908
909 for (def, def_id) in defs {
910 if existing_defs.is_some_and(|defs| defs.contains_key(def.as_ref())) {
911 continue;
912 }
913
914 let source_backed = source_file.is_some_and(|data| data.defs.contains_key(def.as_ref()));
915 let referenced_by_compiled = referenced_def_ids.contains(def_id);
916 let runtime_value = runtime.get(def_id.0 as usize).and_then(|cell| match cell {
917 RuntimeCell::Ready(value) => Some(value.to_owned()),
918 _ => None,
919 });
920
921 if !source_backed && (!referenced_by_compiled || runtime_value.is_none()) {
922 continue;
923 }
924
925 tasks.push(SnapshotFillTask {
926 ns: ns.to_owned(),
927 def: def.to_owned(),
928 source_backed,
929 referenced_by_compiled,
930 runtime_value,
931 });
932 }
933 }
934
935 tasks
936}
937
938fn build_snapshot_fill_compiled_def(task: SnapshotFillTask) -> Option<CompiledDef> {
939 if task.source_backed {
940 return ensure_source_backed_compiled_def_for_snapshot(task.ns.as_ref(), task.def.as_ref());
941 }
942
943 if !task.source_backed
944 && task.referenced_by_compiled
945 && task.runtime_value.is_some()
946 && let Some(runtime_value) = task.runtime_value
947 {
948 return build_runtime_only_snapshot_fallback_compiled_def(task.ns.as_ref(), task.def.as_ref(), runtime_value);
949 }
950
951 None
952}
953
954pub fn clone_compiled_program_snapshot() -> Result<CompiledProgram, String> {
955 let mut compiled: CompiledProgram = PROGRAM_COMPILED_DATA_STATE.read().expect("read compiled program data").to_owned();
956 let tasks = collect_snapshot_fill_tasks(&compiled);
957
958 for task in tasks {
959 let ns = task.ns.clone();
960 let def = task.def.clone();
961 if let Some(compiled_def) = build_snapshot_fill_compiled_def(task) {
962 let compiled_file = compiled.entry(ns).or_insert_with(|| CompiledFileData { defs: HashMap::new() });
963 compiled_file.defs.insert(def, compiled_def);
964 }
965 }
966
967 Ok(compiled)
968}
969
970pub fn apply_code_changes(changes: &snapshot::ChangesDict) -> Result<(), String> {
971 let mut program_code = PROGRAM_CODE_DATA.write().expect("open program code");
972 let coord0 = vec![];
973
974 for (ns, file) in &changes.added {
975 let file_info = extract_file_data(file, ns.to_owned())?;
976 for def in file_info.defs.keys() {
977 let _ = register_program_def_id(ns, def);
978 }
979 program_code.insert(ns.to_owned(), file_info);
980 remove_compiled_ns(ns);
981 }
982 for ns in &changes.removed {
983 clear_runtime_ns(ns);
984 program_code.remove(ns);
985 remove_compiled_ns(ns);
986 }
987 for (ns, info) in &changes.changed {
988 let file = program_code.get_mut(ns).ok_or_else(|| format!("can not load ns: {ns}"))?;
990 if let Some(v) = &info.ns {
991 file.import_map = extract_import_map(v, ns)?;
992 }
993 for (def, code) in &info.added_defs {
994 let _ = register_program_def_id(ns, def);
995 clear_runtime_value(ensure_def_id(ns, def));
996 remove_compiled_def(ns, def);
997 let calcit_code = code_to_calcit(code, ns, def, coord0.to_owned())?;
998 let entry = ProgramDefEntry {
999 code: calcit_code,
1000 schema: DYNAMIC_TYPE.clone(),
1001 doc: Arc::from(""), examples: vec![], };
1004 file.defs.insert(def.to_owned().into(), entry);
1005 }
1006 for def in &info.removed_defs {
1007 if let Some(def_id) = lookup_def_id(ns, def) {
1008 clear_runtime_value(def_id);
1009 }
1010 file.defs.remove(def.as_str());
1011 remove_compiled_def(ns, def);
1012 }
1013 for (def, code) in &info.changed_defs {
1014 let def_id = register_program_def_id(ns, def);
1015 clear_runtime_value(def_id);
1016 remove_compiled_def(ns, def);
1017 let calcit_code = code_to_calcit(code, ns, def, coord0.to_owned())?;
1018 let (schema, doc, examples) = match file.defs.get(def.as_str()) {
1019 Some(existing) => (existing.schema.clone(), existing.doc.clone(), existing.examples.clone()),
1020 None => (DYNAMIC_TYPE.clone(), Arc::from(""), Vec::new()),
1021 };
1022 file.defs.insert(
1023 def.to_owned().into(),
1024 ProgramDefEntry {
1025 code: calcit_code,
1026 schema,
1027 doc,
1028 examples,
1029 },
1030 );
1031 }
1032 }
1033
1034 Ok(())
1035}
1036
1037pub fn clear_runtime_caches_for_reload(init_ns: Arc<str>, reload_ns: Arc<str>, reload_libs: bool) -> Result<(), String> {
1039 if reload_libs {
1040 let mut runtime = PROGRAM_RUNTIME_DATA_STATE.write().expect("open runtime data");
1041 let mut compiled = PROGRAM_COMPILED_DATA_STATE.write().expect("open compiled program data");
1042 runtime.clear();
1043 compiled.clear();
1044 return Ok(());
1045 }
1046
1047 let init_pkg = extract_pkg_from_ns(init_ns.to_owned()).ok_or_else(|| format!("failed to extract pkg from: {init_ns}"))?;
1048 let reload_pkg = extract_pkg_from_ns(reload_ns.to_owned()).ok_or_else(|| format!("failed to extract pkg from: {reload_ns}"))?;
1049 let ns_keys: Vec<Arc<str>> = {
1050 let index = PROGRAM_DEF_ID_INDEX.read().expect("read program def id index");
1051 index.by_ns.keys().cloned().collect()
1052 };
1053
1054 let mut changes = snapshot::ChangesDict::default();
1055 for ns in ns_keys {
1056 if ns == init_pkg || ns == reload_pkg || ns.starts_with(&format!("{init_pkg}.")) || ns.starts_with(&format!("{reload_pkg}.")) {
1057 changes.changed.insert(
1058 ns,
1059 snapshot::FileChangeInfo {
1060 ns: Some(Cirru::Leaf(Arc::from("ns"))),
1061 added_defs: HashMap::new(),
1062 removed_defs: HashSet::new(),
1063 changed_defs: HashMap::new(),
1064 },
1065 );
1066 }
1067 }
1068
1069 clear_runtime_caches_for_changes(&changes, false)
1070}
1071
1072pub fn clear_runtime_caches_for_changes(changes: &snapshot::ChangesDict, reload_libs: bool) -> Result<(), String> {
1073 if reload_libs {
1074 let mut runtime = PROGRAM_RUNTIME_DATA_STATE.write().expect("open runtime data");
1075 let mut compiled = PROGRAM_COMPILED_DATA_STATE.write().expect("open compiled program data");
1076 runtime.clear();
1077 compiled.clear();
1078 return Ok(());
1079 }
1080
1081 let affected_def_ids = {
1082 let compiled = PROGRAM_COMPILED_DATA_STATE.read().expect("read compiled program data");
1083 let index = PROGRAM_DEF_ID_INDEX.read().expect("read program def id index");
1084 collect_reload_affected_def_ids(changes, &compiled, &index)
1085 };
1086
1087 if affected_def_ids.is_empty() {
1088 return Ok(());
1089 }
1090
1091 let mut runtime = PROGRAM_RUNTIME_DATA_STATE.write().expect("open runtime data");
1092 for def_id in &affected_def_ids {
1093 if let Some(slot) = runtime.get_mut(def_id.0 as usize) {
1094 *slot = RuntimeCell::Cold;
1095 }
1096 }
1097
1098 let mut compiled = PROGRAM_COMPILED_DATA_STATE.write().expect("open compiled program data");
1099 for file in compiled.values_mut() {
1100 file.defs.retain(|_, compiled_def| !affected_def_ids.contains(&compiled_def.def_id));
1101 }
1102 compiled.retain(|_, file| !file.defs.is_empty());
1103
1104 Ok(())
1105}