1use crate::ast::{Decl, DeclBody, Expr, TPart};
11use crate::checker::check_module;
12use crate::engine::{fmt_f, Engine, RootSrc};
13use crate::fmt::format;
14use crate::infer::{infer, make_ctx, std_names, type_text, Ctx, Ty};
15use crate::module::{load_modules, Module};
16use crate::package::{open_package_universe, verify_lock};
17use crate::parse::parse_source;
18use crate::semantics::{
19 json_str, parse_path, path_str, read_json, rec_members, seg_text, sort_diags, Diag, Env, Fail,
20 MKind, RTk, Scope, Seg, SegPath, SlotState, Value, RT,
21};
22use regex::Regex;
23use std::cell::{Cell, RefCell};
24use std::collections::{HashMap, HashSet};
25use std::path::{Path, PathBuf};
26use std::rc::Rc;
27use std::sync::LazyLock;
28use std::time::Instant;
29
30static SQUEEZE_WS: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"\s*\n\s*").unwrap());
32
33#[derive(Clone, Debug)]
35pub enum BindSource {
36 File { file: String, text: String },
37 Inline { text: String },
38 Expr { text: String },
39}
40
41#[derive(Clone, Copy, Debug, PartialEq)]
42pub enum EditKind {
43 Create,
44 Update,
45 Remove,
46}
47impl EditKind {
48 pub fn word(self) -> &'static str {
49 match self {
50 EditKind::Create => "create",
51 EditKind::Update => "update",
52 EditKind::Remove => "remove",
53 }
54 }
55}
56
57#[derive(Clone)]
58pub enum Op {
59 Bind {
60 name: String,
61 src: BindSource,
62 },
63 Unbind {
64 name: String,
65 },
66 Edit {
67 kind: EditKind,
68 path: String,
69 expr: Option<String>,
70 },
71 Declare {
72 name: String,
73 text: String,
74 },
75 Output {
76 name: String,
77 ty: Option<String>,
78 expr: String,
79 },
80 Drop {
81 name: String,
82 },
83 Reload {
84 snapshot: HashMap<PathBuf, String>,
85 },
86 Reset,
87}
88
89#[derive(Clone, Copy, Debug, PartialEq)]
90pub enum Origin {
91 File,
92 Inline,
93 Expr,
94 Fallback,
95 Detached,
96}
97impl Origin {
98 pub fn word(self) -> &'static str {
99 match self {
100 Origin::File => "file",
101 Origin::Inline => "inline",
102 Origin::Expr => "expr",
103 Origin::Fallback => "fallback",
104 Origin::Detached => "detached",
105 }
106 }
107}
108
109#[derive(Clone)]
111pub struct Document {
112 pub origin: Origin,
113 pub file: Option<String>,
114 pub doc: Value, pub base: Value, pub edited: bool,
117}
118
119#[derive(Default)]
120struct State {
121 snapshot: HashMap<PathBuf, String>,
122 decls: Vec<(String, String)>, outputs: Vec<(String, Option<String>, String)>, documents: Vec<(String, Document)>,
125}
126impl State {
127 fn decl(&self, n: &str) -> Option<&String> {
128 self.decls.iter().find(|(k, _)| k == n).map(|(_, t)| t)
129 }
130 fn output(&self, n: &str) -> Option<(&Option<String>, &String)> {
131 self.outputs
132 .iter()
133 .find(|(k, _, _)| k == n)
134 .map(|(_, t, e)| (t, e))
135 }
136 fn document(&self, n: &str) -> Option<&Document> {
137 self.documents.iter().find(|(k, _)| k == n).map(|(_, d)| d)
138 }
139 fn document_mut(&mut self, n: &str) -> Option<&mut Document> {
140 self.documents
141 .iter_mut()
142 .find(|(k, _)| k == n)
143 .map(|(_, d)| d)
144 }
145 fn set_document(&mut self, n: &str, d: Document) {
146 if let Some(e) = self.documents.iter_mut().find(|(k, _)| k == n) {
147 e.1 = d;
148 } else {
149 self.documents.push((n.to_string(), d));
150 }
151 }
152 fn remove_decl(&mut self, n: &str) -> bool {
153 let before = self.decls.len();
154 self.decls.retain(|(k, _)| k != n);
155 before != self.decls.len()
156 }
157 fn remove_output(&mut self, n: &str) -> bool {
158 let before = self.outputs.len();
159 self.outputs.retain(|(k, _, _)| k != n);
160 before != self.outputs.len()
161 }
162}
163
164#[derive(Debug, Clone)]
165pub struct SessionError(pub String);
166impl SessionError {
167 fn new(msg: impl Into<String>) -> SessionError {
168 SessionError(msg.into())
169 }
170}
171pub type SResult<T> = Result<T, SessionError>;
172
173#[derive(Clone, Copy, Debug)]
174pub struct Timing {
175 pub load: f64,
176 pub check: f64,
177 pub bind: f64,
178 pub evaluate: f64,
179 pub total: f64,
180 pub recomputed: Option<usize>,
182 pub slots: Option<usize>,
183}
184
185pub fn full_recompute() -> bool {
187 std::env::var("DECL_FULL_RECOMPUTE")
188 .map(|v| !v.is_empty())
189 .unwrap_or(false)
190}
191
192#[derive(Clone, Copy, PartialEq)]
193pub enum Mode {
194 Check,
195 Lazy,
196 Full,
197}
198
199#[derive(Clone)]
200pub struct Run {
201 pub modules: Vec<Rc<Module>>,
202 pub entry: Option<Rc<Module>>,
203 pub load_diags: Vec<Diag>,
204 pub checks: Vec<(String, Diag)>,
205 pub session_checks: Vec<Diag>, pub session_roots: Vec<(String, Rc<Expr>, RT)>, pub eng: Option<Rc<Engine>>,
208 pub diags: Vec<Diag>,
209 pub timing: Timing,
210}
211
212pub struct RootInfo {
213 pub kind: &'static str, pub name: String,
215 pub module: String, pub exported: bool,
217 pub session: bool,
218 pub binding: String, pub detail: String, pub edited: bool,
221}
222
223pub struct ExprResult {
224 pub value: Option<String>,
225 pub diags: Vec<Diag>,
226 pub error: Option<(Option<String>, String)>,
228}
229
230fn ms(i: Instant) -> f64 {
231 i.elapsed().as_secs_f64() * 1000.0
232}
233pub fn is_root_diag(d: &Diag, root: &str) -> bool {
234 d.path == root
235 || d.path.starts_with(&format!("{root}."))
236 || d.path.starts_with(&format!("{root}["))
237}
238
239pub fn parse_expr(text: &str) -> SResult<Rc<Expr>> {
241 let r = parse_source(&format!("const __e = {text}\n"));
242 if r.errors.is_empty() && r.decls.len() == 1 {
243 if let DeclBody::Const { expr, .. } = &r.decls[0].body {
244 return Ok(expr.clone());
245 }
246 }
247 Err(SessionError::new(format!(
248 "cannot parse expression: {}",
249 text.trim()
250 )))
251}
252
253pub fn parse_decl(text: &str) -> SResult<(Decl, String)> {
255 let r = parse_source(&format!("{}\n", text.trim()));
256 if !r.errors.is_empty() || r.decls.len() != 1 {
257 return Err(SessionError::new(format!(
258 "cannot parse declaration: {}",
259 text.trim().lines().next().unwrap_or("")
260 )));
261 }
262 let d = r.decls.into_iter().next().unwrap();
263 let name = match (&d.body, d.name()) {
264 (_, Some(n)) => n.to_string(),
265 (DeclBody::Import { from, .. }, None) => format!("import {from}"),
266 (DeclBody::ReExport { from, .. }, None) => format!("re_export {from}"),
267 _ => String::new(),
268 };
269 Ok((d, name))
270}
271
272fn parse_doc(text: &str, what: &str) -> SResult<Value> {
273 read_json(text).map_err(|_| SessionError::new(format!("{what} is not well-formed JSON")))
274}
275
276pub fn doc_json(v: &Value) -> String {
278 match v {
279 Value::Null => "null".into(),
280 Value::Bool(b) => b.to_string(),
281 Value::Int(i) => i.to_string(),
282 Value::Float(f) => fmt_f(*f),
283 Value::Str(s) => json_str(s),
284 Value::JArr(items) => format!(
285 "[{}]",
286 items.iter().map(doc_json).collect::<Vec<_>>().join(",")
287 ),
288 Value::JObj(es) => format!(
289 "{{{}}}",
290 es.iter()
291 .map(|(k, x)| format!("{}:{}", json_str(k), doc_json(x)))
292 .collect::<Vec<_>>()
293 .join(",")
294 ),
295 _ => "null".into(),
296 }
297}
298
299fn doc_step(v: &Value, seg: &Seg) -> Option<Value> {
300 match (v, seg) {
301 (Value::JObj(es), Seg::Name(k)) | (Value::JObj(es), Seg::Key(k)) => {
302 es.iter().find(|(kk, _)| kk == k).map(|(_, x)| x.clone())
303 }
304 (Value::JArr(items), Seg::Idx(i)) => items.get(*i).cloned(),
305 _ => None,
306 }
307}
308
309pub fn pretty_json(compact: &str) -> String {
312 let cs: Vec<char> = compact.chars().collect();
313 let mut out = String::new();
314 let mut depth = 0usize;
315 let mut i = 0;
316 let pad = |d: usize| " ".repeat(d);
317 while i < cs.len() {
318 let c = cs[i];
319 if c == '"' {
320 let mut j = i + 1;
321 while j < cs.len() && cs[j] != '"' {
322 if cs[j] == '\\' {
323 j += 1;
324 }
325 j += 1;
326 }
327 out.extend(&cs[i..=j.min(cs.len() - 1)]);
328 i = j + 1;
329 continue;
330 }
331 if c == '{' || c == '[' {
332 let close = if c == '{' { '}' } else { ']' };
333 if i + 1 < cs.len() && cs[i + 1] == close {
334 out.push(c);
335 out.push(close);
336 i += 2;
337 continue;
338 }
339 depth += 1;
340 out.push(c);
341 out.push('\n');
342 out.push_str(&pad(depth));
343 i += 1;
344 continue;
345 }
346 if c == '}' || c == ']' {
347 depth = depth.saturating_sub(1);
348 out.push('\n');
349 out.push_str(&pad(depth));
350 out.push(c);
351 i += 1;
352 continue;
353 }
354 if c == ',' {
355 out.push_str(",\n");
356 out.push_str(&pad(depth));
357 i += 1;
358 continue;
359 }
360 if c == ':' {
361 out.push_str(": ");
362 i += 1;
363 continue;
364 }
365 out.push(c);
366 i += 1;
367 }
368 out
369}
370
371fn identifiers(text: &str) -> HashSet<String> {
372 Regex::new(r"[A-Za-z_][A-Za-z0-9_]*")
373 .unwrap()
374 .find_iter(text)
375 .map(|m| m.as_str().to_string())
376 .collect()
377}
378
379fn relative_path(from_dir: &Path, p: &Path) -> String {
380 let a: Vec<_> = from_dir.components().collect();
381 let b: Vec<_> = p.components().collect();
382 let mut i = 0;
383 while i < a.len() && i < b.len() && a[i] == b[i] {
384 i += 1;
385 }
386 let mut parts: Vec<String> = vec!["..".to_string(); a.len() - i];
387 parts.extend(
388 b[i..]
389 .iter()
390 .map(|c| c.as_os_str().to_string_lossy().to_string()),
391 );
392 parts.join("/")
393}
394
395pub struct Session {
397 pub entry_path: Option<PathBuf>, pub log: Vec<Op>,
399 pub cursor: usize,
400 pub last_timing: Cell<Option<Timing>>,
401 snapshot0: HashMap<PathBuf, String>,
402 state: State,
403 last: RefCell<Option<Last>>,
407 pub overlay: HashMap<PathBuf, String>,
409}
410
411pub const SCRATCH: &str = "<session>";
412
413#[derive(Clone)]
414struct Last {
415 key: String,
416 docs: Vec<(String, String)>,
417 run: Run,
418}
419
420fn under(path: &str, root: &str) -> bool {
421 path == root || path.starts_with(&format!("{root}.")) || path.starts_with(&format!("{root}["))
422}
423
424impl Session {
425 pub fn new(entry: Option<&str>) -> Session {
426 Session::with_overlay(entry, None)
427 }
428 pub fn with_overlay(
430 entry: Option<&str>,
431 overlay: Option<&HashMap<PathBuf, String>>,
432 ) -> Session {
433 let entry_path = entry.map(|e| std::path::absolute(e).unwrap_or_else(|_| PathBuf::from(e)));
434 let mut s = Session {
435 entry_path,
436 log: vec![],
437 cursor: 0,
438 last_timing: Cell::new(None),
439 snapshot0: HashMap::new(),
440 state: State::default(),
441 last: RefCell::new(None),
442 overlay: overlay.cloned().unwrap_or_default(),
443 };
444 s.snapshot0 = s.snapshot_from_disk();
445 s.state = s.initial_state();
446 s
447 }
448
449 pub fn entry_abs(&self) -> PathBuf {
450 self.entry_path.clone().unwrap_or_else(|| {
451 std::path::absolute(SCRATCH).unwrap_or_else(|_| PathBuf::from(SCRATCH))
452 })
453 }
454 pub fn entry_name(&self) -> String {
455 self.entry_path
456 .as_ref()
457 .and_then(|p| p.file_name())
458 .map(|n| n.to_string_lossy().to_string())
459 .unwrap_or_else(|| SCRATCH.to_string())
460 }
461
462 fn snapshot_from_disk(&self) -> HashMap<PathBuf, String> {
466 let mut snap = HashMap::new();
467 let Some(entry) = &self.entry_path else {
468 return snap;
469 };
470 let pkg = open_package_universe(entry);
471 let r = load_modules(
472 entry,
473 pkg.as_ref().map(|u| &u.resolver),
474 Some(&self.overlay),
475 );
476 let mut paths: Vec<PathBuf> = vec![entry.clone()];
477 paths.extend(r.modules.iter().map(|m| m.path.clone()));
478 for p in paths {
479 if let Some(t) = self.overlay.get(&p) {
480 snap.insert(p, t.clone());
481 continue;
482 }
483 if let Ok(t) = std::fs::read_to_string(&p) {
484 snap.insert(p, t);
485 }
486 }
487 snap
488 }
489 fn initial_state(&self) -> State {
490 State {
491 snapshot: self.snapshot0.clone(),
492 decls: vec![],
493 outputs: vec![],
494 documents: vec![],
495 }
496 }
497
498 pub fn apply(&mut self, op: Op) -> SResult<()> {
500 self.log.truncate(self.cursor); let mut st = std::mem::take(&mut self.state);
502 let r = self.apply_to(&mut st, &op); self.state = st;
504 r?;
505 self.log.push(op);
506 self.cursor += 1;
507 Ok(())
508 }
509 pub fn undo(&mut self, n: usize) -> usize {
510 let to = self.cursor.saturating_sub(n);
511 let stepped = self.cursor - to;
512 self.cursor = to;
513 self.replay();
514 stepped
515 }
516 pub fn redo(&mut self, n: usize) -> usize {
517 let to = (self.cursor + n).min(self.log.len());
518 let stepped = to - self.cursor;
519 self.cursor = to;
520 self.replay();
521 stepped
522 }
523 fn replay(&mut self) {
524 let mut st = self.initial_state();
525 let ops: Vec<Op> = self.log[..self.cursor].to_vec();
526 for op in &ops {
527 let _ = self.apply_to(&mut st, op);
528 }
529 self.state = st;
530 }
531 pub fn reload_op(&self) -> Op {
532 Op::Reload {
533 snapshot: self.snapshot_from_disk(),
534 }
535 }
536
537 fn apply_to(&self, st: &mut State, op: &Op) -> SResult<()> {
538 match op {
539 Op::Bind { name, src } => {
540 let (modules, _, _) = self.build(st);
541 if !modules
542 .iter()
543 .any(|m| m.env.inputs.borrow().contains_key(name))
544 {
545 return Err(SessionError::new(format!("no input named {name}")));
546 }
547 let (doc, origin, file) = match src {
548 BindSource::Expr { text } => (self.eval_to_doc(st, text)?, Origin::Expr, None),
549 BindSource::File { file, text } => {
550 (parse_doc(text, file)?, Origin::File, Some(file.clone()))
551 }
552 BindSource::Inline { text } => {
553 (parse_doc(text, "the document")?, Origin::Inline, None)
554 }
555 };
556 st.set_document(
557 name,
558 Document {
559 origin,
560 file,
561 base: doc.clone(),
562 doc,
563 edited: false,
564 },
565 );
566 Ok(())
567 }
568 Op::Unbind { name } => {
569 if st.document(name).is_none() {
570 return Err(SessionError::new(format!("{name} is not bound")));
571 }
572 st.documents.retain(|(k, _)| k != name);
573 Ok(())
574 }
575 Op::Edit { kind, path, expr } => self.edit(st, *kind, path, expr.as_deref()),
576 Op::Declare { name, text } => {
577 st.remove_decl(name);
578 st.remove_output(name);
579 st.decls.push((name.clone(), text.clone()));
580 Ok(())
581 }
582 Op::Output { name, ty, expr } => {
583 st.remove_decl(name);
584 st.remove_output(name);
585 st.outputs.push((name.clone(), ty.clone(), expr.clone()));
586 Ok(())
587 }
588 Op::Drop { name } => {
589 let a = st.remove_decl(name);
590 let b = st.remove_output(name);
591 if !a && !b {
592 return Err(SessionError::new(format!(
593 "no session declaration named {name}"
594 )));
595 }
596 Ok(())
597 }
598 Op::Reload { snapshot } => {
599 st.snapshot = snapshot.clone();
600 Ok(())
601 }
602 Op::Reset => {
603 st.decls.clear();
604 st.outputs.clear();
605 st.documents.clear();
606 Ok(())
607 }
608 }
609 }
610
611 fn eval_to_doc(&self, st: &State, expr_text: &str) -> SResult<Value> {
613 let expr = parse_expr(expr_text)?;
614 let r = self.engine_for(st);
615 if r.eng.is_none() || r.entry.is_none() {
616 return Err(SessionError::new(self.load_failure(&r)));
617 }
618 let sc = Scope::new("", Some(r.entry.as_ref().unwrap().env.clone()));
619 self.scratch(&r, |eng, _| {
620 let result = (|| -> Result<Value, Fail> {
621 let v = eng.ev(&expr, &sc)?;
622 let v = eng.materialize(v, &[Seg::Name("_".into())])?;
623 eng.force_all(&v);
624 Ok(v)
625 })();
626 match result {
627 Ok(v) => {
628 let text = eng.serialize(&v, "", false);
629 if text.is_empty() {
630 return Err(SessionError::new("the value is not data"));
631 }
632 read_json(&text).map_err(|_| SessionError::new("the value is not data"))
633 }
634 Err(Fail::Eval(e)) => Err(SessionError::new(e.msg)),
635 Err(_) => Err(SessionError::new("the value is invalid")),
636 }
637 })
638 }
639
640 fn edit(&self, st: &mut State, kind: EditKind, path: &str, expr: Option<&str>) -> SResult<()> {
641 let segs: SegPath =
642 parse_path(path, "").map_err(|_| SessionError::new(format!("bad path {path}")))?;
643 let root = match segs.first() {
644 Some(Seg::Name(n)) if !n.is_empty() => n.clone(),
645 _ => return Err(SessionError::new(format!("bad path {path}"))),
646 };
647 if segs.len() < 2 {
648 return Err(SessionError::new(format!(
649 "a path below a root is required, got {path}"
650 )));
651 }
652 let value = match kind {
653 EditKind::Remove => None,
654 _ => Some(self.eval_to_doc(st, expr.unwrap_or(""))?),
655 };
656 self.document_of(st, &root)?;
657 let doc = st.document(&root).cloned().unwrap();
658 let new_doc = edit_value(&doc.doc, &segs, 1, kind, value.as_ref(), path)?;
659 let d = st.document_mut(&root).unwrap();
660 d.doc = new_doc;
661 d.edited = true;
662 Ok(())
663 }
664
665 fn document_of(&self, st: &mut State, root: &str) -> SResult<()> {
668 if st.document(root).is_some() {
669 return Ok(());
670 }
671 let (modules, _, _) = self.build(st);
672 let input_mod = modules
673 .iter()
674 .any(|m| m.env.inputs.borrow().contains_key(root));
675 let output_mod = modules
676 .iter()
677 .any(|m| m.env.outputs.borrow().iter().any(|(o, _, _)| o == root));
678 if !input_mod && !output_mod {
679 return Err(SessionError::new(if st.output(root).is_some() {
680 format!("{root} is a session output; edit the roots it reads")
681 } else {
682 format!("no root named {root}")
683 }));
684 }
685 let r = self.run_state(st, Mode::Full);
686 let (Some(eng), Some(entry)) = (&r.eng, &r.entry) else {
687 return Err(SessionError::new(self.load_failure(&r)));
688 };
689 let v = entry.env.root(root);
690 let bad = v.is_none()
691 || r.diags
692 .iter()
693 .any(|d| d.severity == "error" && is_root_diag(d, root));
694 if bad {
695 return Err(SessionError::new(format!(
696 "{root} is invalid; fix it before editing"
697 )));
698 }
699 let text = eng.serialize(&v.unwrap(), root, true);
700 let doc = read_json(&text)
701 .map_err(|_| SessionError::new(format!("{root} is invalid; fix it before editing")))?;
702 st.set_document(
703 root,
704 Document {
705 origin: if input_mod {
706 Origin::Fallback
707 } else {
708 Origin::Detached
709 },
710 file: None,
711 base: doc.clone(),
712 doc,
713 edited: false,
714 },
715 );
716 Ok(())
717 }
718
719 fn build(&self, st: &State) -> (Vec<Rc<Module>>, Option<Rc<Module>>, Vec<Diag>) {
721 let entry_abs = self.entry_abs();
722 let mut overlay = st.snapshot.clone();
723 let text = st.snapshot.get(&entry_abs).cloned().or_else(|| {
724 if self.entry_path.is_none() {
725 Some(String::new())
726 } else {
727 None
728 }
729 });
730 if let Some(mut text) = text {
731 let detached: Vec<String> = st
732 .documents
733 .iter()
734 .filter(|(_, d)| d.origin == Origin::Detached)
735 .map(|(n, _)| n.clone())
736 .collect();
737 text = detach_outputs(&text, &detached);
738 let extra: Vec<&str> = st.decls.iter().map(|(_, t)| t.as_str()).collect();
739 if !extra.is_empty() {
740 if text.ends_with('\n') {
741 text.pop();
742 }
743 text.push('\n');
744 text.push_str(&extra.join("\n"));
745 text.push('\n');
746 }
747 overlay.insert(entry_abs.clone(), text);
748 }
749 let pkg = if self.entry_path.is_some() {
750 open_package_universe(&entry_abs)
751 } else {
752 None
753 };
754 let mut diags: Vec<Diag> = vec![];
755 if let Some(u) = &pkg {
756 diags.extend(u.diags.clone());
757 diags.extend(verify_lock(u));
758 }
759 let r = load_modules(
760 &entry_abs,
761 pkg.as_ref().map(|u| &u.resolver),
762 Some(&overlay),
763 );
764 diags.extend(r.diags);
765 (r.modules, r.entry, diags)
766 }
767
768 fn load_failure(&self, r: &Run) -> String {
769 match r.load_diags.first() {
770 Some(d) => format!(
771 "{}{}",
772 d.code
773 .as_ref()
774 .map(|c| format!("[{c}] "))
775 .unwrap_or_default(),
776 d.message
777 ),
778 None => "the universe did not load".into(),
779 }
780 }
781
782 fn session_ctx(
785 &self,
786 st: &State,
787 env: &Rc<Env>,
788 report: Rc<dyn Fn(&str, String)>,
789 up_to: Option<&str>,
790 ) -> Ctx {
791 let mut cx = make_ctx(env.clone(), report);
792 for (name, ty_text, expr_text) in &st.outputs {
793 if Some(name.as_str()) == up_to {
794 break;
795 }
796 let Ok(expr) = parse_expr(expr_text) else {
797 continue;
798 }; let mut quiet = make_ctx(env.clone(), Rc::new(|_, _| {}));
800 quiet.vars = cx.vars.clone();
801 let mut rt = infer(&quiet, &expr).rt;
802 if let Some(t) = ty_text {
803 if let Ok((d, _)) = parse_decl(&format!("output {name}: {t} = 0")) {
804 if let DeclBody::Output { ty, .. } = &d.body {
805 rt = env.resolve(ty, None).ok();
806 }
807 }
808 }
809 cx.vars.insert(name.clone(), Ty { rt, abs: false });
810 }
811 cx
812 }
813
814 pub fn run(&self, mode: Mode) -> Run {
816 self.run_state(&self.state, mode)
817 }
818 fn run_state(&self, st: &State, mode: Mode) -> Run {
819 if mode == Mode::Full && !full_recompute() {
820 if let Some(r) = self.step_from(st) {
821 return r;
822 }
823 }
824 let r = self.run_fresh(st, mode);
825 if mode == Mode::Full {
826 *self.last.borrow_mut() = if r.eng.is_some() {
827 Some(Last {
828 key: self.universe_key(st),
829 docs: self.doc_keys(st),
830 run: r.clone(),
831 })
832 } else {
833 None
834 };
835 }
836 r
837 }
838 fn universe_key(&self, st: &State) -> String {
839 let mut snap: Vec<(String, &String)> = st
840 .snapshot
841 .iter()
842 .map(|(p, t)| (p.display().to_string(), t))
843 .collect();
844 snap.sort();
845 let mut detached: Vec<&String> = st
846 .documents
847 .iter()
848 .filter(|(_, d)| d.origin == Origin::Detached)
849 .map(|(n, _)| n)
850 .collect();
851 detached.sort();
852 format!(
853 "{:?}|{:?}|{:?}|{:?}|{:?}",
854 self.entry_abs(),
855 snap,
856 st.decls,
857 st.outputs,
858 detached
859 )
860 }
861 fn doc_keys(&self, st: &State) -> Vec<(String, String)> {
862 st.documents
863 .iter()
864 .map(|(n, d)| (n.clone(), doc_json(&d.doc)))
865 .collect()
866 }
867 fn step_from(&self, st: &State) -> Option<Run> {
869 let last = self.last.borrow().clone()?;
870 let (Some(eng), Some(entry)) = (last.run.eng.clone(), last.run.entry.clone()) else {
871 return None;
872 };
873 if last.key != self.universe_key(st) {
874 return None;
875 }
876 let docs = self.doc_keys(st);
877 let mut changed: Vec<String> = vec![];
878 for (n, k) in &docs {
879 if last.docs.iter().find(|(m, _)| m == n).map(|(_, v)| v) != Some(k) {
880 changed.push(n.clone());
881 }
882 }
883 for (n, _) in &last.docs {
884 if !docs.iter().any(|(m, _)| m == n) {
885 changed.push(n.clone());
886 }
887 }
888 if changed.is_empty() {
889 self.last_timing.set(Some(last.run.timing));
890 return Some(last.run.clone());
891 }
892 let t0 = Instant::now();
893 let r = &last.run;
894 let env = entry.env.clone();
895 let mut seeds: Vec<String> = vec![];
898 let read_keys: Vec<String> = eng.reads.borrow().keys().cloned().collect();
899 let registry = env.registry_snapshot();
900 for root in &changed {
901 seeds.push(format!("root:{root}"));
902 for k in &read_keys {
903 if !k.starts_with("root:") && under(k.strip_prefix("assert:").unwrap_or(k), root) {
904 seeds.push(k.clone());
905 }
906 }
907 for inst in ®istry {
908 let b = inst.borrow();
909 if under(&path_str(&b.path, None), root) {
910 if let Some(tn) = &b.type_name {
911 seeds.push(format!("referrers:{tn}"));
912 }
913 }
914 }
915 }
916 let mut readers: HashMap<String, HashSet<String>> = HashMap::new();
918 for (reader, set) in eng.reads.borrow().iter() {
919 for k in set {
920 readers.entry(k.clone()).or_default().insert(reader.clone());
921 }
922 }
923 let mut invalid: HashSet<String> = HashSet::new();
924 let mut queue: Vec<String> = seeds;
925 while let Some(k) = queue.pop() {
926 if !invalid.insert(k.clone()) {
927 continue;
928 }
929 if let Some(rs) = readers.get(&k) {
930 for rd in rs {
931 if !invalid.contains(rd) {
932 queue.push(rd.clone());
933 }
934 }
935 }
936 }
937 let mut rebind: HashSet<String> = invalid
939 .iter()
940 .filter_map(|k| k.strip_prefix("root:").map(|s| s.to_string()))
941 .collect();
942 loop {
943 let mut grew = false;
944 for root in rebind.clone() {
945 for inst in ®istry {
946 let (p, tn) = {
947 let b = inst.borrow();
948 (path_str(&b.path, None), b.type_name.clone())
949 };
950 let Some(tn) = tn else { continue };
951 if !under(&p, &root) {
952 continue;
953 }
954 let rk = format!("referrers:{tn}");
955 if invalid.insert(rk.clone()) {
956 if let Some(rs) = readers.get(&rk) {
957 for rd in rs {
958 if !invalid.contains(rd) {
959 invalid.insert(rd.clone());
960 queue.push(rd.clone());
961 }
962 }
963 }
964 }
965 }
966 }
967 while let Some(k) = queue.pop() {
968 if let Some(rs) = readers.get(&k) {
969 for rd in rs {
970 if !invalid.contains(rd) {
971 invalid.insert(rd.clone());
972 queue.push(rd.clone());
973 }
974 }
975 }
976 }
977 for k in &invalid {
978 if let Some(r) = k.strip_prefix("root:") {
979 if rebind.insert(r.to_string()) {
980 grew = true;
981 }
982 }
983 }
984 if !grew {
985 break;
986 }
987 }
988 let gone = |d: &Diag| {
990 d.by.as_ref().map(|b| invalid.contains(b)).unwrap_or(false)
991 || rebind.iter().any(|root| under(&d.path, root))
992 };
993 env.diag_set(
994 env.diagnostics_vec()
995 .into_iter()
996 .filter(|d| !gone(d))
997 .collect(),
998 );
999 let mut recomputed = 0usize;
1000 for k in &invalid {
1001 if k.starts_with("root:") || k.starts_with("assert:") || k.starts_with("referrers:") {
1002 continue;
1003 }
1004 if rebind.iter().any(|root| under(k, root)) {
1005 eng.slots_by_key.borrow_mut().remove(k);
1006 eng.reads.borrow_mut().remove(k);
1007 continue;
1008 }
1009 if eng.reset_slot(k) {
1010 recomputed += 1;
1011 }
1012 eng.reads.borrow_mut().remove(k);
1013 }
1014 let mut dropped: Vec<crate::engine::Inst> = vec![];
1015 env.registry_retain(|inst| {
1016 let g = rebind
1017 .iter()
1018 .any(|root| under(&path_str(&inst.borrow().path, None), root));
1019 if g {
1020 dropped.push(inst.clone());
1021 }
1022 !g
1023 });
1024 for root in &rebind {
1025 env.remove_root(root);
1026 eng.failed_inputs.borrow_mut().remove(root);
1027 eng.reads.borrow_mut().remove(&format!("root:{root}"));
1028 }
1029 eng.deferred_slots
1030 .borrow_mut()
1031 .retain(|(i, _)| !dropped.iter().any(|d| Rc::ptr_eq(d, i)));
1032 let assert_keys: Vec<String> = eng
1033 .reads
1034 .borrow()
1035 .keys()
1036 .filter(|k| k.starts_with("assert:"))
1037 .cloned()
1038 .collect();
1039 for k in assert_keys {
1040 if rebind.iter().any(|root| under(&k[7..], root)) {
1041 eng.reads.borrow_mut().remove(&k);
1042 }
1043 }
1044 eng.set_phase(1);
1049 for (name, d) in &st.documents {
1050 if !rebind.contains(name) {
1051 continue;
1052 }
1053 let m = r
1054 .modules
1055 .iter()
1056 .find(|x| x.env.inputs.borrow().contains_key(name))
1057 .cloned()
1058 .unwrap_or_else(|| entry.clone());
1059 let decl = m.env.inputs.borrow().get(name).cloned();
1060 let Some((ty_ast, _)) = decl else { continue };
1061 let sc = Scope::new(name, Some(m.env.clone()));
1062 match m.env.resolve(&ty_ast, None) {
1063 Ok(rt) => eng.bind_root(name, RootSrc::Doc(d.doc.clone()), &rt, &sc),
1064 Err(e) => env.report(Diag::error(e, name.clone(), None)),
1065 }
1066 }
1067 for m in &r.modules {
1068 let outs = m.env.outputs.borrow().clone();
1069 for (name, ty_ast, expr) in outs {
1070 if !rebind.contains(&name) {
1071 continue;
1072 }
1073 let sc = Scope::new(&name, Some(m.env.clone()));
1074 match m.env.resolve(&ty_ast, None) {
1075 Ok(rt) => eng.bind_root(&name, RootSrc::Expr(&expr), &rt, &sc),
1076 Err(e) => env.report(Diag::error(e, name.clone(), None)),
1077 }
1078 }
1079 }
1080 for (name, expr, rt) in &r.session_roots {
1081 if !rebind.contains(name) {
1082 continue;
1083 }
1084 let sc = Scope::new(name, Some(entry.env.clone()));
1085 eng.bind_root(name, RootSrc::Expr(expr), rt, &sc);
1086 }
1087 eng.force_roots(&env);
1088 eng.set_phase(2);
1089 let mut i = 0;
1090 loop {
1091 let item = {
1092 let d = eng.deferred_slots.borrow();
1093 if i >= d.len() {
1094 break;
1095 }
1096 d[i].clone()
1097 };
1098 eng.force_slot_safe(&item.0, &item.1);
1099 i += 1;
1100 }
1101 eng.bind_deferred_roots();
1102 eng.force_roots(&env);
1103 for inst in env.registry_snapshot() {
1105 let key = format!("assert:{}", path_str(&inst.borrow().path, None));
1106 let fresh = !eng.reads.borrow().contains_key(&key);
1107 if fresh || invalid.contains(&key) {
1108 eng.validate_inst(&inst, "");
1109 }
1110 }
1111 let sorted = sort_diags(env.diagnostics_vec());
1112 env.diag_set(sorted.clone());
1113 let elapsed = ms(t0);
1114 let timing = Timing {
1115 load: 0.0,
1116 check: 0.0,
1117 bind: 0.0,
1118 evaluate: elapsed,
1119 total: elapsed,
1120 recomputed: Some(recomputed),
1121 slots: Some(eng.slots_by_key.borrow().len()),
1122 };
1123 let run = Run {
1124 diags: sorted,
1125 timing,
1126 ..r.clone()
1127 };
1128 *self.last.borrow_mut() = Some(Last {
1129 key: last.key.clone(),
1130 docs,
1131 run: run.clone(),
1132 });
1133 self.last_timing.set(Some(timing));
1134 Some(run)
1135 }
1136 fn run_fresh(&self, st: &State, mode: Mode) -> Run {
1137 let t0 = Instant::now();
1138 let (modules, entry, load_diags) = self.build(st);
1139 let load = ms(t0);
1140 let mut out = Run {
1141 modules,
1142 entry,
1143 load_diags,
1144 checks: vec![],
1145 session_checks: vec![],
1146 session_roots: vec![],
1147 eng: None,
1148 diags: vec![],
1149 timing: Timing {
1150 load,
1151 check: 0.0,
1152 bind: 0.0,
1153 evaluate: 0.0,
1154 total: 0.0,
1155 recomputed: None,
1156 slots: None,
1157 },
1158 };
1159 let finish = |mut out: Run| -> Run {
1160 out.timing.total = ms(t0);
1161 self.last_timing.set(Some(out.timing));
1162 out
1163 };
1164 if !out.load_diags.is_empty() || out.entry.is_none() {
1165 return finish(out);
1166 }
1167 let entry = out.entry.clone().unwrap();
1168 let t1 = Instant::now();
1169 for m in &out.modules {
1170 for d in check_module(&m.decls, Some(m.env.clone()), None) {
1171 out.checks.push((m.path.display().to_string(), d));
1172 }
1173 }
1174 let mut session_roots: Vec<(String, Rc<Expr>, RT)> = vec![];
1177 for (name, ty_text, expr_text) in &st.outputs {
1178 let taken = out.modules.iter().any(|m| {
1179 m.env.inputs.borrow().contains_key(name)
1180 || m.env.outputs.borrow().iter().any(|(o, _, _)| o == name)
1181 });
1182 if taken {
1183 out.session_checks.push(Diag {
1184 severity: "error".into(),
1185 id: None,
1186 code: Some("E3018".into()),
1187 message: format!("root {name} is already declared by the universe"),
1188 path: name.clone(),
1189 loc: None,
1190 by: None,
1191 });
1192 continue;
1193 }
1194 let expr = match parse_expr(expr_text) {
1195 Ok(e) => e,
1196 Err(e) => {
1197 out.session_checks.push(Diag {
1198 severity: "error".into(),
1199 id: None,
1200 code: None,
1201 message: e.0,
1202 path: name.clone(),
1203 loc: None,
1204 by: None,
1205 });
1206 continue;
1207 }
1208 };
1209 let sink: Rc<RefCell<Vec<Diag>>> = Rc::new(RefCell::new(vec![]));
1210 let sink2 = sink.clone();
1211 let n2 = name.clone();
1212 let cx = self.session_ctx(
1213 st,
1214 &entry.env,
1215 Rc::new(move |code, msg| {
1216 sink2.borrow_mut().push(Diag {
1217 severity: "error".into(),
1218 id: None,
1219 code: Some(code.to_string()),
1220 message: msg,
1221 path: n2.clone(),
1222 loc: None,
1223 by: None,
1224 })
1225 }),
1226 Some(name),
1227 );
1228 let ty = infer(&cx, &expr);
1229 let found: Vec<Diag> = sink.borrow().clone();
1230 if !found.is_empty() {
1231 out.session_checks.extend(found);
1232 continue;
1233 }
1234 let rt: RT = match ty_text {
1235 Some(t) => {
1236 let resolved =
1237 parse_decl(&format!("output {name}: {t} = 0")).and_then(|(d, _)| match &d
1238 .body
1239 {
1240 DeclBody::Output { ty, .. } => {
1241 entry.env.resolve(ty, None).map_err(SessionError::new)
1242 }
1243 _ => Err(SessionError::new("not an output")),
1244 });
1245 match resolved {
1246 Ok(rt) => rt,
1247 Err(e) => {
1248 out.session_checks.push(Diag {
1249 severity: "error".into(),
1250 id: None,
1251 code: None,
1252 message: e.0,
1253 path: name.clone(),
1254 loc: None,
1255 by: None,
1256 });
1257 continue;
1258 }
1259 }
1260 }
1261 None => ty.rt.unwrap_or_else(|| crate::semantics::ty(RTk::Any)),
1262 };
1263 session_roots.push((name.clone(), expr, rt));
1264 }
1265 out.timing.check = ms(t1);
1266 if mode == Mode::Check
1270 || (mode == Mode::Full && out.checks.iter().any(|(_, d)| d.severity == "error"))
1271 {
1272 return finish(out);
1273 }
1274
1275 let t2 = Instant::now();
1276 let eng = Engine::new(entry.env.clone());
1277 for m in &out.modules {
1278 eng.install_hooks(&m.env, true);
1279 }
1280 for (name, d) in &st.documents {
1283 let m = out
1284 .modules
1285 .iter()
1286 .find(|x| x.env.inputs.borrow().contains_key(name))
1287 .cloned()
1288 .unwrap_or_else(|| entry.clone());
1289 let decl = m.env.inputs.borrow().get(name).cloned();
1290 let Some((ty_ast, _)) = decl else { continue };
1291 let sc = Scope::new(name, Some(m.env.clone()));
1292 match m.env.resolve(&ty_ast, None) {
1293 Ok(rt) => eng.bind_root(name, RootSrc::Doc(d.doc.clone()), &rt, &sc),
1294 Err(e) => entry.env.report(Diag::error(e, name.clone(), None)),
1295 }
1296 }
1297 for m in &out.modules {
1298 let outs = m.env.outputs.borrow().clone();
1299 for (name, ty_ast, expr) in outs {
1300 let sc = Scope::new(&name, Some(m.env.clone()));
1301 match m.env.resolve(&ty_ast, None) {
1302 Ok(rt) => eng.bind_root(&name, RootSrc::Expr(&expr), &rt, &sc),
1303 Err(e) => entry.env.report(Diag::error(e, name.clone(), None)),
1304 }
1305 }
1306 }
1307 for (name, expr, rt) in &session_roots {
1308 let sc = Scope::new(name, Some(entry.env.clone()));
1309 eng.bind_root(name, RootSrc::Expr(expr), rt, &sc);
1310 }
1311 out.session_roots = session_roots;
1312 out.eng = Some(eng.clone());
1313 out.timing.bind = ms(t2);
1314 if mode == Mode::Lazy {
1315 eng.set_phase(2);
1316 out.diags = entry.env.diagnostics_vec();
1317 return finish(out);
1318 }
1319 let t3 = Instant::now();
1320 eng.drive(&entry.env);
1321 out.diags = entry.env.diagnostics_vec(); let sorted = sort_diags(out.diags.clone());
1323 entry.env.diag_set(sorted.clone());
1324 out.diags = sorted;
1325 out.timing.evaluate = ms(t3);
1326 finish(out)
1327 }
1328
1329 fn engine_for(&self, st: &State) -> Run {
1334 let full = self.run_state(st, Mode::Full);
1335 if full.eng.is_some() {
1336 full
1337 } else {
1338 self.run_state(st, Mode::Lazy)
1339 }
1340 }
1341 fn scratch<T>(&self, r: &Run, f: impl FnOnce(&Rc<Engine>, &Rc<Env>) -> T) -> T {
1345 let (eng, env) = (r.eng.clone().unwrap(), r.entry.clone().unwrap().env.clone());
1346 let n = env.diag_len();
1347 let reg = env.registry_snapshot().len();
1348 let roots: HashSet<String> = env.root_names().into_iter().collect();
1349 let out = f(&eng, &env);
1350 let demanded: Vec<String> = env
1352 .root_names()
1353 .into_iter()
1354 .filter(|k| !roots.contains(k))
1355 .collect();
1356 let under_demanded = |p: &str| demanded.iter().any(|k| under(p, k));
1357 for k in &demanded {
1358 env.remove_root(k);
1359 eng.failed_inputs.borrow_mut().remove(k);
1360 eng.reads.borrow_mut().remove(&format!("root:{k}"));
1361 }
1362 let keys: Vec<String> = eng.reads.borrow().keys().cloned().collect();
1363 for k in keys {
1364 if under_demanded(k.strip_prefix("assert:").unwrap_or(&k)) {
1365 eng.reads.borrow_mut().remove(&k);
1366 }
1367 }
1368 let skeys: Vec<String> = eng.slots_by_key.borrow().keys().cloned().collect();
1369 for k in skeys {
1370 if under_demanded(&k) {
1371 eng.slots_by_key.borrow_mut().remove(&k);
1372 }
1373 }
1374 env.diag_truncate(n);
1375 let mut i = 0usize;
1376 env.registry_retain(|inst| {
1377 let idx = i;
1378 i += 1;
1379 let p = path_str(&inst.borrow().path, None);
1380 let scratch_root = matches!(inst.borrow().path.first(), Some(Seg::Name(n)) if n == "_");
1381 if idx < reg {
1382 !under_demanded(&p)
1383 } else {
1384 !scratch_root && !under_demanded(&p)
1385 }
1386 });
1387 eng.computing.borrow_mut().clear();
1388 out
1389 }
1390
1391 pub fn evaluate_expr(&self, text: &str) -> SResult<ExprResult> {
1394 let expr = parse_expr(text)?;
1395 let r = self.engine_for(&self.state);
1396 if r.eng.is_none() || r.entry.is_none() {
1397 return Ok(ExprResult {
1398 value: None,
1399 diags: r.load_diags.clone(),
1400 error: Some((None, String::new())),
1401 });
1402 }
1403 let entry_env = r.entry.as_ref().unwrap().env.clone();
1404 let sc = Scope::new("", Some(entry_env.clone()));
1405 let named = identifiers(text);
1406 Ok(self.scratch(&r, |eng, env| {
1407 let from = env.diag_len();
1411 let arising = |env: &Env| -> Vec<Diag> {
1412 let all = env.diagnostics_vec();
1413 let mut out: Vec<Diag> = all[..from.min(all.len())]
1414 .iter()
1415 .filter(|d| named.contains(&d.path))
1416 .cloned()
1417 .collect();
1418 out.extend(all[from.min(all.len())..].iter().cloned());
1419 sort_diags(out)
1420 };
1421 let result = (|| -> Result<Value, Fail> {
1422 let v = eng.ev(&expr, &sc)?;
1423 let v = eng.materialize(v, &[Seg::Name("_".into())])?;
1424 eng.force_all(&v);
1425 Ok(v)
1426 })();
1427 match result {
1428 Ok(v) => ExprResult {
1429 value: Some(self.value_text(eng, &v)),
1430 diags: arising(env),
1431 error: None,
1432 },
1433 Err(Fail::Eval(e)) => ExprResult {
1434 value: None,
1435 diags: arising(env),
1436 error: Some((e.code, e.msg)),
1437 },
1438 Err(_) => ExprResult {
1439 value: None,
1440 diags: arising(env),
1441 error: Some((None, String::new())),
1442 },
1443 }
1444 }))
1445 }
1446 fn value_text(&self, eng: &Engine, v: &Value) -> String {
1447 match v {
1448 Value::Absent | Value::Undef => "absent".into(),
1449 Value::Clo(_) | Value::Nat(_) | Value::Std(_) => "<function>".into(),
1450 Value::NsRef(_) => "<namespace>".into(),
1451 Value::Pat(re) => format!("/{re}/"),
1452 _ => eng.serialize(v, "", false),
1453 }
1454 }
1455
1456 pub fn roots(&self) -> Vec<RootInfo> {
1458 let (modules, _, _) = self.build(&self.state);
1459 let entry_abs = self.entry_abs();
1460 let entry_dir = entry_abs
1461 .parent()
1462 .map(|p| p.to_path_buf())
1463 .unwrap_or_default();
1464 let rel = |p: &Path| {
1465 if p == entry_abs {
1466 self.entry_name()
1467 } else {
1468 relative_path(&entry_dir, p)
1469 }
1470 };
1471 let mut out = vec![];
1472 for m in &modules {
1473 let parsed;
1476 let decls: &Vec<Decl> = if m.path == entry_abs {
1477 parsed = parse_source(
1478 self.state
1479 .snapshot
1480 .get(&m.path)
1481 .map(|s| s.as_str())
1482 .unwrap_or(""),
1483 )
1484 .decls;
1485 &parsed
1486 } else {
1487 &m.decls
1488 };
1489 for decl in decls {
1490 match &decl.body {
1491 DeclBody::Output { name, .. } => {
1492 let d = self.state.document(name);
1493 out.push(RootInfo {
1494 kind: "output",
1495 name: name.clone(),
1496 module: rel(&m.path),
1497 exported: decl.exported,
1498 session: false,
1499 binding: if d.map(|d| d.origin) == Some(Origin::Detached) {
1500 "detached".into()
1501 } else {
1502 String::new()
1503 },
1504 detail: String::new(),
1505 edited: d.map(|d| d.edited).unwrap_or(false),
1506 });
1507 }
1508 DeclBody::Input { name, fallback, .. } => {
1509 let d = self.state.document(name);
1510 let binding = match d {
1511 Some(d) if d.origin == Origin::Fallback => "fallback",
1512 Some(_) => "bound",
1513 None => {
1514 if fallback.is_some() {
1515 "fallback"
1516 } else {
1517 "unbound"
1518 }
1519 }
1520 };
1521 let detail = match d {
1522 Some(d) => match d.origin {
1523 Origin::File => d.file.clone().unwrap_or_default(),
1524 Origin::Inline => "(inline)".into(),
1525 Origin::Expr => "(expression)".into(),
1526 _ => String::new(),
1527 },
1528 None => String::new(),
1529 };
1530 out.push(RootInfo {
1531 kind: "input",
1532 name: name.clone(),
1533 module: rel(&m.path),
1534 exported: false,
1535 session: false,
1536 binding: binding.into(),
1537 detail,
1538 edited: d.map(|d| d.edited).unwrap_or(false),
1539 });
1540 }
1541 _ => {}
1542 }
1543 }
1544 }
1545 for (name, _, _) in &self.state.outputs {
1546 out.push(RootInfo {
1547 kind: "output",
1548 name: name.clone(),
1549 module: String::new(),
1550 exported: false,
1551 session: true,
1552 binding: String::new(),
1553 detail: String::new(),
1554 edited: false,
1555 });
1556 }
1557 out
1558 }
1559 pub fn all_root_names(&self) -> Vec<String> {
1560 self.roots().into_iter().map(|r| r.name).collect()
1561 }
1562 pub fn has_root(&self, name: &str) -> bool {
1563 self.all_root_names().iter().any(|n| n == name)
1564 }
1565
1566 pub fn check(&self) -> Vec<(String, Diag)> {
1568 let r = self.run(Mode::Check);
1569 let entry = self.entry_abs().display().to_string();
1570 let mut out: Vec<(String, Diag)> = r
1571 .load_diags
1572 .iter()
1573 .map(|d| (entry.clone(), d.clone()))
1574 .collect();
1575 out.extend(r.checks);
1576 out.extend(r.session_checks.into_iter().map(|d| (entry.clone(), d)));
1577 out
1578 }
1579
1580 pub fn evaluate(
1582 &self,
1583 names: &[String],
1584 ) -> SResult<(Run, Vec<(String, Option<String>)>, bool)> {
1585 let r = self.run(Mode::Full);
1586 let exported = names.is_empty();
1587 let Some(entry) = r.entry.clone() else {
1588 return Ok((r, vec![], exported));
1589 };
1590 let want: Vec<String> = if names.is_empty() {
1591 entry
1592 .decls
1593 .iter()
1594 .filter(|d| d.exported)
1595 .filter_map(|d| match &d.body {
1596 DeclBody::Output { name, .. } => Some(name.clone()),
1597 _ => None,
1598 })
1599 .collect()
1600 } else {
1601 names.to_vec()
1602 };
1603 for n in names {
1604 if !self.has_root(n) {
1605 return Err(SessionError::new(format!("no root named {n}")));
1606 }
1607 }
1608 let Some(eng) = r.eng.clone() else {
1609 return Ok((r, want.into_iter().map(|n| (n, None)).collect(), exported));
1610 };
1611 let mut docs = vec![];
1612 for name in want {
1613 let v = entry.env.root(&name);
1614 let bad = v.is_none()
1615 || r.diags
1616 .iter()
1617 .any(|d| d.severity == "error" && is_root_diag(d, &name));
1618 let json = if bad {
1619 None
1620 } else {
1621 Some(eng.serialize(&v.unwrap(), &name, false))
1622 };
1623 docs.push((name, json));
1624 }
1625 Ok((r, docs, exported))
1626 }
1627
1628 pub fn validate(
1630 &self,
1631 names: &[String],
1632 ) -> SResult<(Run, Vec<(String, usize, usize)>, Vec<Diag>)> {
1633 for n in names {
1634 if !self.has_root(n) {
1635 return Err(SessionError::new(format!("no root named {n}")));
1636 }
1637 }
1638 let r = self.run(Mode::Full);
1639 let want: Vec<String> = if !names.is_empty() {
1640 names.to_vec()
1641 } else if let Some(entry) = &r.entry {
1642 entry
1643 .env
1644 .roots
1645 .borrow()
1646 .borrow()
1647 .iter()
1648 .map(|(n, _)| n.clone())
1649 .collect()
1650 } else {
1651 vec![]
1652 };
1653 let diags: Vec<Diag> = r
1654 .diags
1655 .iter()
1656 .filter(|d| {
1657 want.iter().any(|n| is_root_diag(d, n)) || (d.path.is_empty() && names.is_empty())
1658 })
1659 .cloned()
1660 .collect();
1661 let verdicts = want
1662 .iter()
1663 .map(|name| {
1664 let errors = r
1665 .diags
1666 .iter()
1667 .filter(|d| d.severity == "error" && is_root_diag(d, name))
1668 .count()
1669 + if r
1670 .entry
1671 .as_ref()
1672 .map(|e| e.env.root(name).is_some())
1673 .unwrap_or(false)
1674 {
1675 0
1676 } else if r.eng.is_some() {
1677 1
1678 } else {
1679 0
1680 };
1681 let warnings = r
1682 .diags
1683 .iter()
1684 .filter(|d| d.severity == "warning" && is_root_diag(d, name))
1685 .count();
1686 (name.clone(), errors, warnings)
1687 })
1688 .collect();
1689 Ok((r, verdicts, diags))
1690 }
1691
1692 pub fn type_of(&self, text: &str) -> SResult<(String, bool, Vec<Diag>)> {
1694 let expr = parse_expr(text)?;
1695 let (_, entry, diags) = self.build(&self.state);
1696 let Some(entry) = entry else {
1697 return Err(SessionError::new(
1698 diags
1699 .first()
1700 .map(|d| d.message.clone())
1701 .unwrap_or_else(|| "the universe did not load".into()),
1702 ));
1703 };
1704 let sink: Rc<RefCell<Vec<Diag>>> = Rc::new(RefCell::new(vec![]));
1705 let sink2 = sink.clone();
1706 let cx = self.session_ctx(
1707 &self.state,
1708 &entry.env,
1709 Rc::new(move |code, message| {
1710 sink2.borrow_mut().push(Diag {
1711 severity: "error".into(),
1712 id: None,
1713 code: Some(code.to_string()),
1714 message,
1715 path: String::new(),
1716 loc: None,
1717 by: None,
1718 })
1719 }),
1720 None,
1721 );
1722 let ty = infer(&cx, &expr);
1723 let found = sink.borrow().clone();
1724 Ok((type_text(ty.rt.as_ref()), ty.abs, found))
1725 }
1726
1727 pub fn path_of(&self, text: &str) -> SResult<String> {
1729 let expr = parse_expr(text)?;
1730 let r = self.engine_for(&self.state);
1731 if r.eng.is_none() || r.entry.is_none() {
1732 return Err(SessionError::new(self.load_failure(&r)));
1733 }
1734 let entry = r.entry.clone().unwrap();
1735 let sc = Scope::new("", Some(entry.env.clone()));
1736 self.scratch(&r, |eng, _| {
1737 let result = (|| -> Result<Option<SegPath>, Fail> {
1738 let mut segs = eng.eval_place(&expr, &sc)?;
1739 if segs.is_none() {
1741 match &*expr {
1742 Expr::Member { x, name, .. } => {
1743 if let Some(mut base) = eng.eval_place(x, &sc)? {
1744 base.push(Seg::Name(name.clone()));
1745 segs = Some(base);
1746 }
1747 }
1748 Expr::Index { x, i } => {
1749 if let Some(mut base) = eng.eval_place(x, &sc)? {
1750 let iv = eng.ev(i, &sc)?;
1751 base.push(match iv {
1752 Value::Int(n) => Seg::Idx(n.to_string().parse().unwrap_or(0)),
1753 Value::Str(s) => Seg::Key(s),
1754 other => Seg::Key(crate::infer::js_str(&other)),
1755 });
1756 segs = Some(base);
1757 }
1758 }
1759 Expr::Name(n) if entry.env.root(n).is_some() => {
1760 segs = Some(vec![Seg::Name(n.clone())])
1761 }
1762 _ => {}
1763 }
1764 }
1765 Ok(segs)
1766 })();
1767 match result {
1768 Ok(Some(segs)) => Ok(path_str(&segs, None)),
1769 Ok(None) => Err(SessionError::new("the expression does not name a place")),
1770 Err(Fail::Eval(e)) => Err(SessionError::new(e.msg)),
1771 Err(_) => Err(SessionError::new("the place is invalid")),
1772 }
1773 })
1774 }
1775
1776 pub fn doc_of(&self, name: &str) -> SResult<Vec<String>> {
1778 let mut parts = name.splitn(2, '.');
1779 let head = parts.next().unwrap_or("");
1780 let member = parts.next();
1781 if member.is_none() {
1783 if let Some(t) = self.state.decl(head) {
1784 return Ok(t.split('\n').map(|s| s.to_string()).collect());
1785 }
1786 if let Some((ty, expr)) = self.state.output(head) {
1787 return Ok(vec![format!(
1788 "{head}{} = {expr}",
1789 ty.as_ref().map(|t| format!(": {t}")).unwrap_or_default()
1790 )]);
1791 }
1792 }
1793 let (modules, entry, diags) = self.build(&self.state);
1794 let Some(entry) = entry else {
1795 return Err(SessionError::new(
1796 diags
1797 .first()
1798 .map(|d| d.message.clone())
1799 .unwrap_or_else(|| "the universe did not load".into()),
1800 ));
1801 };
1802 let mut module: Option<Rc<Module>> = Some(entry.clone());
1803 let mut target = head.to_string();
1804 if !entry.decls.iter().any(|d| d.name() == Some(head)) {
1805 let im = entry.env.imports.borrow().get(head).cloned();
1806 match im {
1807 Some(im) => {
1808 module = modules
1809 .iter()
1810 .find(|m| Rc::ptr_eq(&m.env, &im.env))
1811 .cloned();
1812 target = im.name.clone();
1813 }
1814 None => module = None,
1815 }
1816 }
1817 let decl = module.as_ref().and_then(|m| {
1818 m.decls
1819 .iter()
1820 .find(|d| d.name() == Some(target.as_str()) && d.loc.is_some())
1821 .cloned()
1822 });
1823 let (Some(module), Some(decl)) = (module, decl) else {
1824 return Err(SessionError::new(format!("no declaration named {head}")));
1825 };
1826 let text = self
1827 .state
1828 .snapshot
1829 .get(&module.path)
1830 .cloned()
1831 .unwrap_or_default();
1832 let lines: Vec<&str> = text.split('\n').collect();
1833 let loc = decl.loc.unwrap();
1834 let mut from = loc.sl;
1835 let mut doc_lines: Vec<String> = vec![];
1836 let is_doc = |l: &str| l.trim_start().starts_with("///");
1837 while from > 0 && is_doc(lines[from - 1]) {
1838 from -= 1;
1839 doc_lines.insert(0, lines[from].to_string());
1840 }
1841 let body: Vec<&str> = lines[loc.sl..=loc.el.min(lines.len() - 1)].to_vec();
1842 if let Some(member) = member {
1843 let re = Regex::new(&format!(r"^\s*{}\$?\??\s*[:=]", regex::escape(member))).unwrap();
1844 let mut picked: Vec<String> = vec![];
1845 for (i, l) in body.iter().enumerate() {
1846 if re.is_match(l) {
1847 let mut j = i;
1848 let mut ds: Vec<String> = vec![];
1849 while j > 0 && is_doc(body[j - 1]) {
1850 j -= 1;
1851 ds.insert(0, body[j].trim().to_string());
1852 }
1853 picked.extend(ds);
1854 picked.push(l.trim().to_string());
1855 }
1856 }
1857 if picked.is_empty() {
1858 return Err(SessionError::new(format!("{head} has no member {member}")));
1859 }
1860 return Ok(picked);
1861 }
1862 doc_lines.extend(body.iter().map(|s| s.to_string()));
1863 Ok(doc_lines)
1864 }
1865
1866 pub fn trace(&self, path_text: &str) -> SResult<Vec<String>> {
1868 let segs: SegPath = parse_path(path_text, "")
1869 .map_err(|_| SessionError::new(format!("bad path {path_text}")))?;
1870 let root = seg_text(&segs[0]);
1871 if !self.has_root(&root) {
1872 return Err(SessionError::new(format!("no root named {root}")));
1873 }
1874 let r = self.run(Mode::Full);
1875 let (Some(eng), Some(entry)) = (r.eng.clone(), r.entry.clone()) else {
1876 return Err(SessionError::new(self.load_failure(&r)));
1877 };
1878 let mut lines: Vec<String> = vec![];
1879 let mut seen: HashSet<String> = HashSet::new();
1880 let has_doc = self.state.document(&root).is_some();
1881 self.walk(&mut lines, &mut seen, &r, &eng, &entry, &segs, 0, has_doc);
1882 Ok(lines)
1883 }
1884 #[allow(clippy::too_many_arguments)]
1885 fn walk(
1886 &self,
1887 lines: &mut Vec<String>,
1888 seen: &mut HashSet<String>,
1889 r: &Run,
1890 eng: &Rc<Engine>,
1891 entry: &Rc<Module>,
1892 segs: &[Seg],
1893 depth: usize,
1894 has_doc: bool,
1895 ) {
1896 let short = |v: &Value| {
1897 let t = self.value_text(eng, v);
1898 if t.chars().count() > 60 {
1899 format!("{}...", t.chars().take(57).collect::<String>())
1900 } else {
1901 t
1902 }
1903 };
1904 let path = path_str(segs, None);
1905 let ind = " ".repeat(depth);
1906 if seen.contains(&path) {
1907 lines.push(format!("{ind}{path} (above)"));
1908 return;
1909 }
1910 seen.insert(path.clone());
1911 let own: Vec<&Diag> = r.diags.iter().filter(|d| d.path == path).collect();
1912 let parent = if segs.len() > 1 {
1913 self.value_at(eng, entry, &segs[..segs.len() - 1])
1914 } else {
1915 None
1916 };
1917 let last = segs.last().unwrap();
1918 let slot_info = match (&parent, last) {
1919 (Some(Value::Rec(inst)), Seg::Name(n)) => {
1920 let b = inst.borrow();
1921 b.slot(n).map(|s| {
1922 (
1923 s.kind,
1924 s.state,
1925 s.value.clone(),
1926 b.entry_order.contains(n),
1927 rec_members(&b.rt).into_iter().find(|m| &m.name == n),
1928 )
1929 })
1930 }
1931 _ => None,
1932 };
1933 if let Some((kind, state, value, in_entry, m)) = slot_info {
1934 let inst = match &parent {
1935 Some(Value::Rec(i)) => i.clone(),
1936 _ => unreachable!(),
1937 };
1938 let kind_word = match kind {
1939 MKind::Der => "derived",
1940 MKind::Dflt => "defaulted",
1941 MKind::Opt => "optional",
1942 MKind::Req => "required",
1943 };
1944 let supplied =
1945 matches!(kind, MKind::Req | MKind::Opt) || (kind == MKind::Dflt && in_entry);
1946 let m_expr = m.as_ref().and_then(|m| m.expr.clone());
1947 if state == SlotState::Invalid {
1948 lines.push(format!("{ind}{path} (invalid)"));
1949 for d in &own {
1950 lines.push(format!("{ind} {}", fmt_diag(d, None)));
1951 }
1952 if own.is_empty() {
1953 if let Some(e) = &m_expr {
1954 for rd in reads_of(e) {
1955 if let Some(s) = self.read_segs(eng, &inst, &rd, entry) {
1956 self.walk(lines, seen, r, eng, entry, &s, depth + 1, has_doc);
1957 }
1958 }
1959 }
1960 }
1961 return;
1962 }
1963 if state == SlotState::Absent {
1964 lines.push(format!("{ind}{path} absent"));
1965 return;
1966 }
1967 let how = if supplied {
1968 "supplied".to_string()
1969 } else {
1970 kind_word.to_string()
1971 };
1972 let ex = match (&m_expr, supplied) {
1973 (Some(e), false) => format!(": {}", expr_text(e)),
1974 _ => String::new(),
1975 };
1976 lines.push(format!("{ind}{path} = {} ({how}{ex})", short(&value)));
1977 if !supplied && depth < 6 {
1978 if let Some(e) = &m_expr {
1979 for rd in reads_of(e) {
1980 match self.read_segs(eng, &inst, &rd, entry) {
1981 Some(s) => {
1982 self.walk(lines, seen, r, eng, entry, &s, depth + 1, has_doc)
1983 }
1984 None => lines.push(format!("{ind} {} (not a place)", expr_text(&rd))),
1985 }
1986 }
1987 }
1988 }
1989 return;
1990 }
1991 match self.value_at(eng, entry, segs) {
1992 None => {
1993 if r.diags
1994 .iter()
1995 .any(|d| d.severity == "error" && is_root_diag(d, &path))
1996 {
1997 lines.push(format!("{ind}{path} (invalid)"));
1998 for d in r.diags.iter().filter(|d| is_root_diag(d, &path)) {
1999 lines.push(format!("{ind} {}", fmt_diag(d, None)));
2000 }
2001 } else {
2002 lines.push(format!("{ind}{path} nothing there"));
2003 }
2004 }
2005 Some(v) => {
2006 let how = if segs.len() == 1 {
2007 if has_doc {
2008 "document"
2009 } else {
2010 "root literal"
2011 }
2012 } else {
2013 "supplied"
2014 };
2015 lines.push(format!("{ind}{path} = {} ({how})", short(&v)));
2016 for d in &own {
2017 lines.push(format!("{ind} {}", fmt_diag(d, None)));
2018 }
2019 }
2020 }
2021 }
2022 fn value_at(&self, eng: &Engine, entry: &Module, segs: &[Seg]) -> Option<Value> {
2023 let mut v = entry.env.root(&seg_text(&segs[0]))?;
2024 for s in &segs[1..] {
2025 v = eng.deref(v).ok()?;
2026 v = match (&v, s) {
2027 (Value::Rec(inst), Seg::Name(n)) => {
2028 let st = eng.force_state(inst, n);
2029 if st == SlotState::Ok {
2030 inst.borrow().slot(n).map(|s| s.value.clone())?
2031 } else {
2032 return None;
2033 }
2034 }
2035 (Value::Rec(_), _) => return None,
2036 (Value::Arr(a), Seg::Idx(i)) => a.borrow().items.get(*i).cloned()?,
2037 (Value::Arr(_), _) => return None,
2038 (Value::Map(m), _) => m.borrow().get(&seg_text(s)).cloned()?,
2039 _ => return None,
2040 };
2041 if v.is_undef() || v.is_absent() {
2042 return None;
2043 }
2044 }
2045 Some(v)
2046 }
2047 fn read_segs(
2048 &self,
2049 eng: &Engine,
2050 inst: &Rc<RefCell<crate::semantics::RecInst>>,
2051 rd: &Rc<Expr>,
2052 entry: &Module,
2053 ) -> Option<SegPath> {
2054 if let Expr::Name(n) = &**rd {
2057 let mut cur = Some(inst.clone());
2058 while let Some(c) = cur {
2059 if c.borrow().has_slot(n) {
2060 let mut p = c.borrow().path.clone();
2061 p.push(Seg::Name(n.clone()));
2062 return Some(p);
2063 }
2064 cur = c.borrow().parent.clone();
2065 }
2066 return if entry.env.root(n).is_some() {
2067 Some(vec![Seg::Name(n.clone())])
2068 } else {
2069 None
2070 };
2071 }
2072 let root_name = inst.borrow().path.first().map(seg_text).unwrap_or_default();
2073 let sc = Scope {
2074 inst: Some(inst.clone()),
2075 locals: Rc::new(HashMap::new()),
2076 root_name,
2077 menv: Some(entry.env.clone()),
2078 };
2079 eng.eval_place(rd, &sc).ok().flatten()
2080 }
2081
2082 pub fn complete(&self, text: &str, commands: &[&str]) -> Vec<String> {
2084 let uniq = |xs: Vec<String>| -> Vec<String> {
2085 let mut v: Vec<String> = xs.into_iter().collect::<HashSet<_>>().into_iter().collect();
2086 v.sort();
2087 v
2088 };
2089 if text.starts_with(':') {
2090 let Some(sp) = text.find(' ') else {
2091 return uniq(
2092 commands
2093 .iter()
2094 .filter(|c| c.starts_with(text))
2095 .map(|c| c.to_string())
2096 .collect(),
2097 );
2098 };
2099 let cmd = &text[..sp];
2100 let rest = &text[sp + 1..];
2101 let last = Regex::new(r"[\s,=]+")
2102 .unwrap()
2103 .split(rest)
2104 .last()
2105 .unwrap_or("")
2106 .to_string();
2107 let by =
2108 |xs: Vec<String>| uniq(xs.into_iter().filter(|x| x.starts_with(&last)).collect());
2109 return match cmd {
2110 ":evaluate" | ":validate" | ":unbind" | ":diff" | ":save" | ":bind" => {
2111 by(self.all_root_names())
2112 }
2113 ":drop" => by(self
2114 .state
2115 .decls
2116 .iter()
2117 .map(|(n, _)| n.clone())
2118 .chain(self.state.outputs.iter().map(|(n, _, _)| n.clone()))
2119 .collect()),
2120 ":set" => by(vec!["pretty".into(), "compact".into()]),
2121 ":help" => by(commands.iter().map(|c| c.to_string()).collect()),
2122 ":trace" | ":path" | ":create" | ":update" | ":remove" => self.complete_path(&last),
2123 _ => vec![],
2124 };
2125 }
2126 let member_re = Regex::new(
2127 r"([A-Za-z_$][A-Za-z0-9_$]*(?:\.[A-Za-z_][A-Za-z0-9_$]*|\[[^\]]*\])*)\.([A-Za-z_]*)$",
2128 )
2129 .unwrap();
2130 if let Some(m) = member_re.captures(text) {
2131 let base = m.get(1).unwrap().as_str();
2132 let prefix = m.get(2).unwrap().as_str();
2133 if base == "std" || base.starts_with("std.") {
2134 let ns = if base == "std" {
2135 String::new()
2136 } else {
2137 format!("{}.", &base[4..])
2138 };
2139 return uniq(
2140 std_names()
2141 .filter(|k| k.starts_with(&ns))
2142 .map(|k| k[ns.len()..].split('.').next().unwrap_or("").to_string())
2143 .filter(|k| k.starts_with(prefix))
2144 .collect(),
2145 );
2146 }
2147 let (_, entry, _) = self.build(&self.state);
2148 let Some(entry) = entry else { return vec![] };
2149 let Ok(expr) = parse_expr(base) else {
2150 return vec![];
2151 };
2152 let cx = self.session_ctx(&self.state, &entry.env, Rc::new(|_, _| {}), None);
2153 let rt = infer(&cx, &expr).rt;
2154 fn members(t: Option<&RT>) -> Option<Vec<crate::semantics::Member>> {
2155 let t = t?;
2156 match &t.k {
2157 RTk::Rec(_) => Some(rec_members(t)),
2158 RTk::Union(arms) => {
2159 let sets: Vec<Option<Vec<crate::semantics::Member>>> =
2160 arms.iter().map(|a| members(Some(a))).collect();
2161 if sets.iter().any(|s| s.is_none()) {
2162 return None;
2163 }
2164 let sets: Vec<Vec<crate::semantics::Member>> =
2165 sets.into_iter().map(|s| s.unwrap()).collect();
2166 let first = sets.first().cloned().unwrap_or_default();
2167 Some(
2168 first
2169 .into_iter()
2170 .filter(|m| sets.iter().all(|s| s.iter().any(|x| x.name == m.name)))
2171 .collect(),
2172 )
2173 }
2174 RTk::Pred { base, .. } => members(Some(base)),
2175 _ => None,
2176 }
2177 }
2178 let ms = members(rt.as_ref()).unwrap_or_default();
2179 return uniq(
2180 ms.iter()
2181 .filter(|x| x.name.starts_with(prefix))
2182 .map(|x| {
2183 let kind = match x.kind {
2184 MKind::Der => "derived",
2185 MKind::Dflt => "defaulted",
2186 MKind::Opt => "optional",
2187 MKind::Req => "required",
2188 };
2189 format!(
2190 "{}{} {}{}",
2191 x.name,
2192 if x.hidden { "$" } else { "" },
2193 kind,
2194 x.ty.as_ref()
2195 .map(|t| format!(": {}", type_text(Some(t))))
2196 .unwrap_or_default()
2197 )
2198 })
2199 .collect(),
2200 );
2201 }
2202 let word_re = Regex::new(r"([A-Za-z_$][A-Za-z0-9_$]*)$").unwrap();
2203 let prefix = word_re
2204 .captures(text)
2205 .map(|m| m.get(1).unwrap().as_str().to_string())
2206 .unwrap_or_default();
2207 if prefix.starts_with('$') {
2208 return uniq(
2209 ["$this", "$parent", "$root", "$key", "$path", "$referrers"]
2210 .iter()
2211 .filter(|x| x.starts_with(&prefix))
2212 .map(|x| x.to_string())
2213 .collect(),
2214 );
2215 }
2216 let mut names: Vec<String> = vec!["std".into()];
2217 let (_, entry, _) = self.build(&self.state);
2218 if let Some(entry) = entry {
2219 let e = &entry.env;
2220 names.extend(e.type_asts.borrow().keys().cloned());
2221 names.extend(e.consts.borrow().keys().cloned());
2222 names.extend(e.funcs.borrow().keys().cloned());
2223 names.extend(e.inputs.borrow().keys().cloned());
2224 names.extend(e.outputs.borrow().iter().map(|(o, _, _)| o.clone()));
2225 names.extend(e.imports.borrow().keys().cloned());
2226 names.extend(e.namespaces.borrow().keys().cloned());
2227 names.extend(e.diags.borrow().keys().cloned());
2228 }
2229 names.extend(self.state.outputs.iter().map(|(n, _, _)| n.clone()));
2230 let kw = [
2231 "if", "then", "else", "for", "in", "match", "with", "matches", "true", "false", "null",
2232 ];
2233 names.extend(kw.iter().map(|k| k.to_string()));
2234 uniq(
2235 names
2236 .into_iter()
2237 .filter(|n| n.starts_with(&prefix))
2238 .collect(),
2239 )
2240 }
2241 fn complete_path(&self, partial: &str) -> Vec<String> {
2242 if !partial.contains('.') && !partial.contains('[') {
2243 let mut v: Vec<String> = self
2244 .all_root_names()
2245 .into_iter()
2246 .filter(|n| n.starts_with(partial))
2247 .collect();
2248 v.sort();
2249 return v;
2250 }
2251 let tail_dot = Regex::new(r"^\.([A-Za-z_][A-Za-z0-9_]*)?$").unwrap();
2253 let tail_bracket = Regex::new(r#"^\["?[^\]]*$"#).unwrap();
2254 let mut base = partial.to_string();
2255 for (i, c) in partial.char_indices() {
2256 if c == '.' || c == '[' {
2257 let rest = &partial[i..];
2258 if tail_dot.is_match(rest) || tail_bracket.is_match(rest) {
2259 base = partial[..i].to_string();
2260 break;
2261 }
2262 }
2263 }
2264 let r = self.run(Mode::Full);
2265 let (Some(eng), Some(entry)) = (&r.eng, &r.entry) else {
2266 return vec![];
2267 };
2268 let Ok(segs) = parse_path(&base, "") else {
2269 return vec![];
2270 };
2271 let Some(v) = self.value_at(eng, entry, &segs) else {
2272 return vec![];
2273 };
2274 let Ok(v) = eng.deref(v) else { return vec![] };
2275 let mut out: Vec<String> = vec![];
2276 match &v {
2277 Value::Rec(inst) => {
2278 for (n, s) in &inst.borrow().slots {
2279 if s.hidden {
2280 continue;
2281 }
2282 out.push(format!("{base}.{n}"));
2283 }
2284 }
2285 Value::Map(m) => {
2286 for (k, _) in &m.borrow().entries {
2287 out.push(format!("{base}[{}]", json_str(k)));
2288 }
2289 }
2290 Value::Arr(a) => {
2291 for i in 0..a.borrow().items.len() {
2292 out.push(format!("{base}[{i}]"));
2293 }
2294 }
2295 _ => {}
2296 }
2297 let mut out: Vec<String> = out.into_iter().filter(|x| x.starts_with(partial)).collect();
2298 out.sort();
2299 out
2300 }
2301
2302 pub fn scratch_text(&self) -> String {
2304 let mut parts: Vec<String> = self
2305 .state
2306 .decls
2307 .iter()
2308 .map(|(_, t)| t.trim().to_string())
2309 .collect();
2310 for (n, ty, expr) in &self.state.outputs {
2311 parts.push(format!(
2312 "output {n}: {} = {expr}",
2313 ty.clone().unwrap_or_else(|| self.inferred_type_text(expr))
2314 ));
2315 }
2316 if parts.is_empty() {
2317 String::new()
2318 } else {
2319 format!("{}\n", parts.join("\n"))
2320 }
2321 }
2322 fn inferred_type_text(&self, expr: &str) -> String {
2323 self.type_of(expr)
2324 .map(|(t, _, _)| t)
2325 .unwrap_or_else(|_| "any".into())
2326 }
2327 pub fn module_text(&self) -> String {
2329 let body = self.scratch_text();
2330 let (_, entry, _) = self.build(&self.state);
2331 let used = identifiers(&body);
2332 let mut names: Vec<String> = entry
2333 .map(|e| {
2334 e.exports
2335 .borrow()
2336 .keys()
2337 .filter(|n| used.contains(*n))
2338 .cloned()
2339 .collect()
2340 })
2341 .unwrap_or_default();
2342 names.sort();
2343 let header = match (&self.entry_path, names.is_empty()) {
2344 (Some(p), false) => format!(
2345 "import {{ {} }} from \"./{}\"\n\n",
2346 names.join(", "),
2347 p.file_name()
2348 .map(|n| n.to_string_lossy().to_string())
2349 .unwrap_or_default()
2350 ),
2351 _ => String::new(),
2352 };
2353 header + &body
2354 }
2355 pub fn fmt(&self) -> SResult<String> {
2356 let t = self.scratch_text();
2357 if t.is_empty() {
2358 return Ok(String::new());
2359 }
2360 format(&t).map_err(SessionError::new)
2361 }
2362 pub fn write(&self, file: &str) -> SResult<()> {
2363 std::fs::write(file, self.module_text())
2364 .map_err(|_| SessionError::new(format!("cannot write {file}")))
2365 }
2366
2367 pub fn document_text(&self, name: &str) -> SResult<String> {
2369 if let Some(d) = self.state.document(name) {
2370 return Ok(doc_json(&d.doc));
2371 }
2372 if !self.has_root(name) {
2373 return Err(SessionError::new(format!("no root named {name}")));
2374 }
2375 let (_, docs, _) = self.evaluate(&[name.to_string()])?;
2376 docs.into_iter()
2377 .next()
2378 .and_then(|(_, j)| j)
2379 .ok_or_else(|| SessionError::new(format!("{name} is invalid")))
2380 }
2381 pub fn save(&self, name: &str, file: &str) -> SResult<()> {
2382 let text = self.document_text(name)?;
2383 std::fs::write(file, format!("{text}\n"))
2384 .map_err(|_| SessionError::new(format!("cannot write {file}")))
2385 }
2386 pub fn diff(&self, name: &str) -> SResult<Vec<String>> {
2387 let Some(d) = self.state.document(name) else {
2388 return Err(SessionError::new(if self.has_root(name) {
2389 format!("{name} holds no document")
2390 } else {
2391 format!("no root named {name}")
2392 }));
2393 };
2394 let (before, after) = (doc_json(&d.base), doc_json(&d.doc));
2395 if before == after {
2396 return Ok(vec!["(no changes)".to_string()]);
2397 }
2398 let a: Vec<String> = pretty_json(&before)
2399 .split('\n')
2400 .map(|s| s.to_string())
2401 .collect();
2402 let b: Vec<String> = pretty_json(&after)
2403 .split('\n')
2404 .map(|s| s.to_string())
2405 .collect();
2406 Ok(line_diff(&a, &b))
2407 }
2408
2409 pub fn session_lines(&self) -> Vec<String> {
2411 let mut out = vec![];
2412 for (n, t) in &self.state.decls {
2413 out.push(format!(
2414 "declaration {:<16} {}",
2415 n,
2416 t.trim().lines().next().unwrap_or("")
2417 ));
2418 }
2419 for (n, ty, expr) in &self.state.outputs {
2420 out.push(format!(
2421 "output {:<16} {n}{} = {expr}",
2422 n,
2423 ty.as_ref().map(|t| format!(": {t}")).unwrap_or_default()
2424 ));
2425 }
2426 for (n, d) in &self.state.documents {
2427 out.push(format!(
2428 "document {:<16} {}{}{}",
2429 n,
2430 d.origin.word(),
2431 d.file.as_ref().map(|f| format!(" {f}")).unwrap_or_default(),
2432 if d.edited { " (edited)" } else { "" }
2433 ));
2434 }
2435 out
2436 }
2437 pub fn history_lines(&self) -> Vec<String> {
2438 let mut out = vec![format!(
2439 "{} 0 (start)",
2440 if self.cursor == 0 { "*" } else { " " }
2441 )];
2442 for (i, op) in self.log.iter().enumerate() {
2443 out.push(format!(
2444 "{} {} {}",
2445 if self.cursor == i + 1 { "*" } else { " " },
2446 i + 1,
2447 op_text(op)
2448 ));
2449 }
2450 out
2451 }
2452 pub fn script_lines(&self) -> Vec<String> {
2453 self.log[..self.cursor].iter().map(op_text).collect()
2454 }
2455}
2456
2457pub fn fmt_diag(d: &Diag, in_file: Option<&str>) -> String {
2459 format!(
2460 "{}{}{}{}: {}{}",
2461 d.severity,
2462 d.code
2463 .as_ref()
2464 .map(|c| format!(" [{c}]"))
2465 .unwrap_or_default(),
2466 d.id.as_ref().map(|i| format!(" {i}")).unwrap_or_default(),
2467 if d.path.is_empty() {
2468 String::new()
2469 } else {
2470 format!(" at {}", d.path)
2471 },
2472 d.message,
2473 in_file.map(|f| format!(" (in {f})")).unwrap_or_default()
2474 )
2475}
2476
2477pub fn op_text(op: &Op) -> String {
2478 match op {
2479 Op::Bind { name, src } => match src {
2480 BindSource::File { file, .. } => format!(":bind {name}={file}"),
2481 BindSource::Inline { text } => format!(
2482 ":bind {name} {}",
2483 read_json(text).map(|v| doc_json(&v)).unwrap_or_default()
2484 ),
2485 BindSource::Expr { text } => format!(":bind {name} = {}", text.trim()),
2486 },
2487 Op::Unbind { name } => format!(":unbind {name}"),
2488 Op::Edit { kind, path, expr } => format!(
2489 ":{} {path}{}",
2490 kind.word(),
2491 expr.as_ref()
2492 .map(|e| format!(" = {}", e.trim()))
2493 .unwrap_or_default()
2494 ),
2495 Op::Declare { text, .. } => text.trim().to_string(),
2496 Op::Output { name, ty, expr } => format!(
2497 "{name}{} = {}",
2498 ty.as_ref().map(|t| format!(": {t}")).unwrap_or_default(),
2499 expr.trim()
2500 ),
2501 Op::Drop { name } => format!(":drop {name}"),
2502 Op::Reload { .. } => ":reload".into(),
2503 Op::Reset => ":reset".into(),
2504 }
2505}
2506
2507fn edit_value(
2509 node: &Value,
2510 segs: &[Seg],
2511 i: usize,
2512 kind: EditKind,
2513 value: Option<&Value>,
2514 path: &str,
2515) -> SResult<Value> {
2516 if i < segs.len() - 1 {
2517 let s = &segs[i];
2518 let Some(child) = doc_step(node, s) else {
2519 return Err(SessionError::new(format!(
2520 "nothing at {}",
2521 path_str(&segs[..=i], None)
2522 )));
2523 };
2524 let new_child = edit_value(&child, segs, i + 1, kind, value, path)?;
2525 return Ok(replace_child(node, s, new_child));
2526 }
2527 let last = &segs[segs.len() - 1];
2528 let k = seg_text(last);
2529 match (node, last) {
2530 (Value::JObj(es), _) => {
2531 let idx = es.iter().position(|(kk, _)| *kk == k);
2532 let mut es: Vec<(String, Value)> = (**es).clone();
2533 match kind {
2534 EditKind::Create => {
2535 if idx.is_some() {
2536 return Err(SessionError::new(format!("{path} already holds a value")));
2537 }
2538 es.push((k, value.cloned().unwrap_or(Value::Null)));
2539 }
2540 EditKind::Update => match idx {
2541 Some(i) => es[i].1 = value.cloned().unwrap_or(Value::Null),
2542 None => return Err(SessionError::new(format!("nothing at {path}"))),
2543 },
2544 EditKind::Remove => match idx {
2545 Some(i) => {
2546 es.remove(i);
2547 }
2548 None => return Err(SessionError::new(format!("nothing at {path}"))),
2549 },
2550 }
2551 Ok(Value::JObj(Rc::new(es)))
2552 }
2553 (Value::JArr(items), Seg::Idx(k)) => {
2554 let mut items: Vec<Value> = (**items).clone();
2555 match kind {
2556 EditKind::Create => {
2557 if *k < items.len() {
2558 return Err(SessionError::new(format!("{path} already holds a value")));
2559 }
2560 if *k > items.len() {
2561 return Err(SessionError::new(format!(
2562 "{path} is past the end of the array"
2563 )));
2564 }
2565 items.push(value.cloned().unwrap_or(Value::Null));
2566 }
2567 EditKind::Update => {
2568 if *k >= items.len() {
2569 return Err(SessionError::new(format!("nothing at {path}")));
2570 }
2571 items[*k] = value.cloned().unwrap_or(Value::Null);
2572 }
2573 EditKind::Remove => {
2574 if *k >= items.len() {
2575 return Err(SessionError::new(format!("nothing at {path}")));
2576 }
2577 items.remove(*k);
2578 }
2579 }
2580 Ok(Value::JArr(Rc::new(items)))
2581 }
2582 _ => Err(SessionError::new(format!(
2583 "{} is not a record, map, or array",
2584 path_str(&segs[..segs.len() - 1], None)
2585 ))),
2586 }
2587}
2588fn replace_child(node: &Value, s: &Seg, child: Value) -> Value {
2589 match (node, s) {
2590 (Value::JObj(es), _) => {
2591 let k = seg_text(s);
2592 let es: Vec<(String, Value)> = es
2593 .iter()
2594 .map(|(kk, v)| {
2595 if *kk == k {
2596 (kk.clone(), child.clone())
2597 } else {
2598 (kk.clone(), v.clone())
2599 }
2600 })
2601 .collect();
2602 Value::JObj(Rc::new(es))
2603 }
2604 (Value::JArr(items), Seg::Idx(i)) => {
2605 let items: Vec<Value> = items
2606 .iter()
2607 .enumerate()
2608 .map(|(j, v)| if j == *i { child.clone() } else { v.clone() })
2609 .collect();
2610 Value::JArr(Rc::new(items))
2611 }
2612 _ => node.clone(),
2613 }
2614}
2615
2616fn detach_outputs(text: &str, names: &[String]) -> String {
2621 if names.is_empty() {
2622 return text.to_string();
2623 }
2624 let decls = parse_source(text).decls;
2625 let mut lines: Vec<String> = text.split('\n').map(|s| s.to_string()).collect();
2626 for d in &decls {
2627 let (DeclBody::Output { name, .. }, Some(loc)) = (&d.body, d.loc) else {
2628 continue;
2629 };
2630 if !names.contains(name) {
2631 continue;
2632 }
2633 let src = lines[loc.sl..=loc.el.min(lines.len() - 1)].join("\n");
2634 let name_at = src.find(name.as_str()).unwrap_or(0);
2635 let colon = src[name_at..]
2636 .find(':')
2637 .map(|i| i + name_at)
2638 .unwrap_or(src.len());
2639 let bytes = src.as_bytes();
2641 let mut depth = 0i32;
2642 let mut eq: Option<usize> = None;
2643 let mut i = colon + 1;
2644 while i < bytes.len() {
2645 let c = bytes[i] as char;
2646 if "{[(<".contains(c) {
2647 depth += 1;
2648 } else if "}])>".contains(c) {
2649 depth -= 1;
2650 } else if c == '='
2651 && depth == 0
2652 && bytes.get(i + 1) != Some(&b'=')
2653 && i > 0
2654 && !matches!(bytes[i - 1], b'!' | b'<' | b'>')
2655 {
2656 eq = Some(i);
2657 break;
2658 }
2659 i += 1;
2660 }
2661 let type_text = match eq {
2662 Some(e) => src[colon + 1..e].trim().to_string(),
2663 None => src[(colon + 1).min(src.len())..].trim().to_string(),
2664 };
2665 let squeezed = SQUEEZE_WS.replace_all(&type_text, " ").to_string();
2666 lines[loc.sl] = format!("input {name}: {squeezed}");
2667 for l in lines.iter_mut().take(loc.el + 1).skip(loc.sl + 1) {
2668 l.clear();
2669 }
2670 }
2671 lines.join("\n")
2672}
2673
2674fn reads_of(e: &Rc<Expr>) -> Vec<Rc<Expr>> {
2677 fn is_chain(x: &Expr) -> bool {
2678 match x {
2679 Expr::Name(_) | Expr::Ctx(_) => true,
2680 Expr::Member { x, .. } | Expr::Index { x, .. } => is_chain(x),
2681 _ => false,
2682 }
2683 }
2684 fn go(x: &Rc<Expr>, out: &mut Vec<Rc<Expr>>) {
2685 match &**x {
2686 Expr::Member { .. } | Expr::Index { .. } if is_chain(x) => {
2687 out.push(x.clone());
2688 if let Expr::Index { i, .. } = &**x {
2689 go(i, out);
2690 }
2691 }
2692 Expr::Name(_) => out.push(x.clone()),
2693 Expr::Lit(_)
2694 | Expr::UnitLit { .. }
2695 | Expr::Ctx(_)
2696 | Expr::Pattern(_)
2697 | Expr::Referrers { .. } => {}
2698 Expr::Template(parts) => {
2699 for p in parts {
2700 if let TPart::Expr(e) = p {
2701 go(e, out);
2702 }
2703 }
2704 }
2705 Expr::Obj(es) => es.iter().for_each(|(_, v)| go(v, out)),
2706 Expr::Arr(items) => items.iter().for_each(|(_, v)| go(v, out)),
2707 Expr::Comp { head, clauses } => {
2708 go(head, out);
2709 for c in clauses {
2710 go(&c.iter, out);
2711 c.filters.iter().for_each(|f| go(f, out));
2712 }
2713 }
2714 Expr::MapComp { key, val, clauses } => {
2715 go(key, out);
2716 go(val, out);
2717 for c in clauses {
2718 go(&c.iter, out);
2719 c.filters.iter().for_each(|f| go(f, out));
2720 }
2721 }
2722 Expr::Bin { l, r, .. } => {
2723 go(l, out);
2724 go(r, out);
2725 }
2726 Expr::Un { x, .. } | Expr::Paren(x) => go(x, out),
2727 Expr::If { c, t, f } => {
2728 go(c, out);
2729 go(t, out);
2730 go(f, out);
2731 }
2732 Expr::Lambda { body, .. } => go(body, out),
2733 Expr::Call { fun, args } => {
2734 go(fun, out);
2735 args.iter().for_each(|a| go(a, out));
2736 }
2737 Expr::Member { x, .. } => go(x, out),
2738 Expr::Index { x, i } => {
2739 go(x, out);
2740 go(i, out);
2741 }
2742 Expr::With { base, patch } => {
2743 go(base, out);
2744 go(patch, out);
2745 }
2746 Expr::Match { subject, arms } => {
2747 go(subject, out);
2748 arms.iter().for_each(|a| go(&a.body, out));
2749 }
2750 }
2751 }
2752 let mut out = vec![];
2753 go(e, &mut out);
2754 out.into_iter()
2755 .filter(|x| !matches!(&**x, Expr::Name(n) if n == "true" || n == "false" || n == "null"))
2756 .collect()
2757}
2758
2759pub fn expr_text(e: &Expr) -> String {
2761 match e {
2762 Expr::Lit(v) => match v {
2763 Value::Str(s) => json_str(s),
2764 other => crate::infer::js_str(other),
2765 },
2766 Expr::UnitLit { num, unit } => format!("{}{unit}", crate::semantics::js_num_str(*num)),
2767 Expr::Name(n) | Expr::Ctx(n) => n.clone(),
2768 Expr::Member { x, name, safe } => {
2769 format!("{}{}{name}", expr_text(x), if *safe { "?." } else { "." })
2770 }
2771 Expr::Index { x, i } => format!("{}[{}]", expr_text(x), expr_text(i)),
2772 Expr::Paren(x) => format!("({})", expr_text(x)),
2773 Expr::Bin { op, l, r } => format!("{} {op} {}", expr_text(l), expr_text(r)),
2774 Expr::Un { op, x } => format!("{op}{}", expr_text(x)),
2775 Expr::Call { fun, args } => format!(
2776 "{}({})",
2777 expr_text(fun),
2778 args.iter()
2779 .map(|a| expr_text(a))
2780 .collect::<Vec<_>>()
2781 .join(", ")
2782 ),
2783 Expr::If { c, t, f } => format!(
2784 "if {} then {} else {}",
2785 expr_text(c),
2786 expr_text(t),
2787 expr_text(f)
2788 ),
2789 Expr::Referrers { ty, member } => format!("$referrers({ty}, {})", json_str(member)),
2790 Expr::Template(parts) => format!(
2791 "`{}`",
2792 parts
2793 .iter()
2794 .map(|p| match p {
2795 TPart::Text(s) => s.clone(),
2796 TPart::Expr(e) => format!("${{{}}}", expr_text(e)),
2797 })
2798 .collect::<String>()
2799 ),
2800 Expr::Obj(es) => format!(
2801 "{{ {} }}",
2802 es.iter()
2803 .map(|(k, v)| format!("{k}: {}", expr_text(v)))
2804 .collect::<Vec<_>>()
2805 .join(", ")
2806 ),
2807 Expr::Arr(items) => format!(
2808 "[{}]",
2809 items
2810 .iter()
2811 .map(|(spread, v)| format!("{}{}", if *spread { "..." } else { "" }, expr_text(v)))
2812 .collect::<Vec<_>>()
2813 .join(", ")
2814 ),
2815 Expr::Comp { clauses, .. } => format!(
2816 "[for {} … ]",
2817 clauses
2818 .iter()
2819 .map(|c| format!("{} in {}", c.v, expr_text(&c.iter)))
2820 .collect::<Vec<_>>()
2821 .join(", ")
2822 ),
2823 Expr::Lambda { params, .. } => format!("({}) => …", params.join(", ")),
2824 Expr::With { base, .. } => format!("{} with …", expr_text(base)),
2825 Expr::Match { subject, .. } => format!("match {} {{ … }}", expr_text(subject)),
2826 _ => "…".into(),
2827 }
2828}
2829
2830fn line_diff(a: &[String], b: &[String]) -> Vec<String> {
2832 let (n, m) = (a.len(), b.len());
2833 let mut dp = vec![vec![0usize; m + 1]; n + 1];
2834 for i in (0..n).rev() {
2835 for j in (0..m).rev() {
2836 dp[i][j] = if a[i] == b[j] {
2837 dp[i + 1][j + 1] + 1
2838 } else {
2839 dp[i + 1][j].max(dp[i][j + 1])
2840 };
2841 }
2842 }
2843 let mut out = vec![];
2844 let (mut i, mut j) = (0, 0);
2845 while i < n && j < m {
2846 if a[i] == b[j] {
2847 out.push(format!(" {}", a[i]));
2848 i += 1;
2849 j += 1;
2850 } else if dp[i + 1][j] >= dp[i][j + 1] {
2851 out.push(format!("- {}", a[i]));
2852 i += 1;
2853 } else {
2854 out.push(format!("+ {}", b[j]));
2855 j += 1;
2856 }
2857 }
2858 while i < n {
2859 out.push(format!("- {}", a[i]));
2860 i += 1;
2861 }
2862 while j < m {
2863 out.push(format!("+ {}", b[j]));
2864 j += 1;
2865 }
2866 out
2867}