Skip to main content

calcit/
program.rs

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/// definition entry with code and documentation
141#[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/// information extracted from snapshot
150#[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/// defRule: ns def
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub enum ImportRule {
161  /// ns imported via `:as`
162  NsAs(Arc<str>),
163  /// (ns, def) imported via `:refer`
164  NsReferDef(Arc<str>, Arc<str>),
165  /// ns imported via `:default`, js only
166  NsDefault(Arc<str>),
167}
168
169pub type ProgramCodeData = HashMap<Arc<str>, ProgramFileData>;
170
171/// runtime values keyed by stable DefId, used by normal runtime lookup paths
172static PROGRAM_RUNTIME_DATA_STATE: LazyLock<RwLock<ProgramRuntimeData>> = LazyLock::new(|| RwLock::new(vec![]));
173/// preprocessed / compiled definitions for codegen and future runtime boundary split
174static PROGRAM_COMPILED_DATA_STATE: LazyLock<RwLock<ProgramCompiledData>> = LazyLock::new(|| RwLock::new(HashMap::new()));
175/// raw code information before program running
176pub 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        // Re-read only after seeding changed the cell
516        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        // Fast path: reuse the already-read cell (avoids redundant RwLock read)
522        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        // strip leading `[]` symbols
669        Some(Cirru::Leaf(s)) if &**s == "[]" => xs = xs[1..4].to_vec(),
670        // allow using comment
671        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 == "[]" => (), // `[]` symbol are ignored
688              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      // Too many clones
712      (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  // Register the program lookup functions in type_annotation so it can resolve
763  // imported type definitions without a circular module dependency.
764  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
779// lookup without cloning
780pub 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
788/// List all def names in a source-code namespace.
789pub 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
817/// lookup documentation for a definition from program data
818pub 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
825/// lookup examples for a definition from program data
826pub 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
859// imported via :default
860pub 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
871// Dirty mutating global states
872pub 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    // println!("handling ns: {:?} {}", ns, program_code.contains_key(ns));
989    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(""), // No doc info in changes, use empty string
1002        examples: vec![],   // No examples info in changes, use empty vector
1003      };
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
1037/// clear runtime and compiled caches after reloading
1038pub 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}