1use std::collections::{BTreeMap, HashMap, HashSet};
14use std::fmt;
15use std::fs;
16use std::io::{Read, Write};
17use std::panic::{catch_unwind, AssertUnwindSafe};
18use std::path::{Path, PathBuf};
19use std::process::{Command, Stdio};
20use std::thread::{self, sleep, JoinHandle};
21use std::time::{Duration, Instant};
22
23pub mod lean_export;
24pub use lean_export::{export_lean, lean_ident, LeanExportResult};
25
26#[derive(Debug, Clone, PartialEq)]
34pub struct Span {
35 pub file: Option<String>,
36 pub line: usize,
37 pub col: usize,
38 pub length: usize,
39}
40
41impl Span {
42 pub fn new(file: Option<String>, line: usize, col: usize, length: usize) -> Self {
43 Self {
44 file,
45 line,
46 col,
47 length,
48 }
49 }
50
51 pub fn unknown() -> Self {
52 Self {
53 file: None,
54 line: 1,
55 col: 1,
56 length: 0,
57 }
58 }
59}
60
61#[derive(Debug, Clone, PartialEq)]
63pub struct Diagnostic {
64 pub code: String,
65 pub message: String,
66 pub span: Span,
67}
68
69impl Diagnostic {
70 pub fn new(code: &str, message: impl Into<String>, span: Span) -> Self {
71 Self {
72 code: code.to_string(),
73 message: message.into(),
74 span,
75 }
76 }
77}
78
79#[derive(Debug, Clone, Default)]
89pub struct EvaluateResult {
90 pub results: Vec<RunResult>,
91 pub diagnostics: Vec<Diagnostic>,
92 pub trace: Vec<TraceEvent>,
93 pub proofs: Vec<Option<Node>>,
94 pub provenance: Vec<Option<String>>,
101}
102
103#[derive(Debug, Clone, Default)]
107pub struct EvaluateOptions {
108 pub env: Option<EnvOptions>,
109 pub trace: bool,
110 pub with_proofs: bool,
115}
116
117#[derive(Debug, Clone, PartialEq)]
126pub struct TraceEvent {
127 pub kind: String,
128 pub detail: String,
129 pub span: Span,
130}
131
132impl TraceEvent {
133 pub fn new(kind: &str, detail: impl Into<String>, span: Span) -> Self {
134 Self {
135 kind: kind.to_string(),
136 detail: detail.into(),
137 span,
138 }
139 }
140}
141
142pub fn format_trace_event(event: &TraceEvent) -> String {
144 let file = event.span.file.as_deref().unwrap_or("<input>");
145 format!(
146 "[span {}:{}:{}] {} {}",
147 file, event.span.line, event.span.col, event.kind, event.detail
148 )
149}
150
151pub fn format_trace_value(v: f64) -> String {
155 if !v.is_finite() {
156 return v.to_string();
157 }
158 let rounded = format!("{:.6}", v);
159 let trimmed = rounded.trim_end_matches('0').trim_end_matches('.');
161 if trimmed.is_empty() || trimmed == "-" {
162 "0".to_string()
163 } else {
164 trimmed.to_string()
165 }
166}
167
168pub fn format_diagnostic(diag: &Diagnostic, source: Option<&str>) -> String {
173 let file = diag.span.file.as_deref().unwrap_or("<input>");
174 let mut out = format!(
175 "{}:{}:{}: {}: {}",
176 file, diag.span.line, diag.span.col, diag.code, diag.message
177 );
178 if let Some(src) = source {
179 let lines: Vec<&str> = src.split('\n').collect();
180 if diag.span.line >= 1 && diag.span.line <= lines.len() {
181 let line_text = lines[diag.span.line - 1];
182 out.push('\n');
183 out.push_str(line_text);
184 out.push('\n');
185 let pad = diag.span.col.saturating_sub(1);
186 let caret_count = diag.span.length.max(1);
187 out.push_str(&" ".repeat(pad));
188 out.push_str(&"^".repeat(caret_count));
189 }
190 }
191 out
192}
193
194pub fn compute_form_spans(text: &str, file: Option<&str>) -> Vec<Span> {
203 let mut spans = Vec::new();
204 let mut depth: i32 = 0;
205 let mut line: usize = 1;
206 let mut col: usize = 1;
207 let mut pending_start: Option<(usize, usize)> = None;
208 let mut in_line_comment = false;
209 let mut line_start_idx: usize = 0;
210 let mut last_closing_depth_zero_col: i32 = -1;
211 let mut saw_ws_after_close = false;
212 let bytes = text.as_bytes();
213 for (off, &b) in bytes.iter().enumerate() {
214 let ch = b as char;
215 if ch == '\n' {
216 in_line_comment = false;
217 line += 1;
218 col = 1;
219 line_start_idx = off + 1;
220 last_closing_depth_zero_col = -1;
221 saw_ws_after_close = false;
222 continue;
223 }
224 if in_line_comment {
225 col += 1;
226 continue;
227 }
228 if ch == '#' && depth == 0 {
229 let line_so_far = &text[line_start_idx..off];
231 if line_so_far.chars().all(|c| c == ' ' || c == '\t') {
232 in_line_comment = true;
233 col += 1;
234 continue;
235 }
236 if last_closing_depth_zero_col >= 0 && saw_ws_after_close {
238 in_line_comment = true;
239 col += 1;
240 continue;
241 }
242 }
243 if ch == '(' {
244 if depth == 0 {
245 pending_start = Some((line, col));
246 }
247 depth += 1;
248 saw_ws_after_close = false;
249 } else if ch == ')' {
250 depth -= 1;
251 if depth == 0 {
252 if let Some((sl, sc)) = pending_start.take() {
253 spans.push(Span::new(file.map(|s| s.to_string()), sl, sc, 1));
254 }
255 last_closing_depth_zero_col = col as i32;
256 saw_ws_after_close = false;
257 }
258 } else if ch == ' ' || ch == '\t' {
259 if last_closing_depth_zero_col >= 0 {
260 saw_ws_after_close = true;
261 }
262 } else {
263 last_closing_depth_zero_col = -1;
265 saw_ws_after_close = false;
266 }
267 col += 1;
268 }
269 spans
270}
271
272fn inline_comment_index(line: &str) -> Option<usize> {
279 let bytes = line.as_bytes();
280 let mut last_close: Option<usize> = None;
281 for (i, b) in bytes.iter().enumerate() {
282 match *b {
283 b')' => last_close = Some(i),
284 b'#' => {
285 if let Some(close_idx) = last_close {
286 let between = &line[close_idx + 1..i];
287 if !between.is_empty() && between.chars().all(|c| c == ' ' || c == '\t') {
288 return Some(i);
289 }
290 }
291 }
292 _ => {}
293 }
294 }
295 None
296}
297
298pub fn parse_lino(text: &str) -> Vec<String> {
300 parse_lino_with_errors(text).0
301}
302
303fn parse_lino_with_errors(text: &str) -> (Vec<String>, Vec<String>) {
308 let stripped: String = text
312 .lines()
313 .map(|line| {
314 let trimmed = line.trim_start();
315 if trimmed.starts_with('#') {
316 String::new()
317 } else if let Some(idx) = inline_comment_index(line) {
318 line[..idx].trim_end().to_string()
319 } else {
320 line.to_string()
321 }
322 })
323 .collect::<Vec<String>>()
324 .join("\n");
325
326 let mut all_links = Vec::new();
329 let mut errors = Vec::new();
330 for segment in stripped.split("\n\n") {
331 let trimmed = segment.trim();
332 if trimmed.is_empty() {
333 continue;
334 }
335 match links_notation::parse_lino_to_links(trimmed) {
336 Ok(links) => {
337 for link in links {
338 all_links.push(link.to_string());
339 }
340 }
341 Err(e) => {
342 errors.push(format!("{}", e));
343 }
344 }
345 }
346 (all_links, errors)
347}
348
349fn is_literate_lino_path(file: Option<&str>) -> bool {
350 file.map(|path| path.to_ascii_lowercase().ends_with(".lino.md"))
351 .unwrap_or(false)
352}
353
354fn parse_markdown_fence(line: &str) -> Option<(char, usize, &str)> {
355 let trimmed = line.trim_start_matches(|c| c == ' ' || c == '\t');
356 let marker = trimmed.chars().next()?;
357 if marker != '`' && marker != '~' {
358 return None;
359 }
360 let count = trimmed.chars().take_while(|c| *c == marker).count();
361 if count < 3 {
362 return None;
363 }
364 Some((marker, count, &trimmed[count..]))
365}
366
367fn is_closing_markdown_fence(line: &str, marker: char, min_len: usize) -> bool {
368 let Some((found_marker, found_len, rest)) = parse_markdown_fence(line) else {
369 return false;
370 };
371 found_marker == marker
372 && found_len >= min_len
373 && rest.chars().all(|c| c == ' ' || c == '\t')
374}
375
376fn is_lino_fence_info(info: &str) -> bool {
377 info.trim()
378 .split_whitespace()
379 .next()
380 .map(|tag| tag.eq_ignore_ascii_case("lino"))
381 .unwrap_or(false)
382}
383
384pub fn extract_literate_lino(text: &str) -> String {
389 let mut out = Vec::new();
390 let mut active_fence: Option<(char, usize, bool)> = None;
391 for line in text.split('\n') {
392 if let Some((marker, min_len, include)) = active_fence {
393 if is_closing_markdown_fence(line, marker, min_len) {
394 active_fence = None;
395 out.push(String::new());
396 } else if include {
397 out.push(line.to_string());
398 } else {
399 out.push(String::new());
400 }
401 continue;
402 }
403
404 if let Some((marker, len, info)) = parse_markdown_fence(line) {
405 active_fence = Some((marker, len, is_lino_fence_info(info)));
406 out.push(String::new());
407 } else {
408 out.push(String::new());
409 }
410 }
411 out.join("\n")
412}
413
414#[derive(Debug, Clone, PartialEq)]
418pub enum Node {
419 Leaf(String),
420 List(Vec<Node>),
421}
422
423impl fmt::Display for Node {
424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 match self {
426 Node::Leaf(s) => write!(f, "{}", s),
427 Node::List(children) => {
428 write!(f, "(")?;
429 for (i, child) in children.iter().enumerate() {
430 if i > 0 {
431 write!(f, " ")?;
432 }
433 write!(f, "{}", child)?;
434 }
435 write!(f, ")")
436 }
437 }
438 }
439}
440
441pub fn tokenize_one(s: &str) -> Vec<String> {
445 let mut s = s.to_string();
446
447 if let Some(comment_idx) = s.find('#') {
449 s = s[..comment_idx].to_string();
450 let mut depth: i32 = 0;
452 for c in s.chars() {
453 if c == '(' {
454 depth += 1;
455 } else if c == ')' {
456 depth -= 1;
457 }
458 }
459 while depth > 0 {
460 s.push(')');
461 depth -= 1;
462 }
463 }
464
465 let mut out = Vec::new();
466 let chars: Vec<char> = s.chars().collect();
467 let mut i = 0;
468
469 while i < chars.len() {
470 let c = chars[i];
471 if c.is_whitespace() {
472 i += 1;
473 continue;
474 }
475 if c == '(' || c == ')' {
476 out.push(c.to_string());
477 i += 1;
478 continue;
479 }
480 let j_start = i;
481 while i < chars.len() && !chars[i].is_whitespace() && chars[i] != '(' && chars[i] != ')' {
482 i += 1;
483 }
484 out.push(chars[j_start..i].iter().collect());
485 }
486 out
487}
488
489pub fn parse_one(tokens: &[String]) -> Result<Node, String> {
491 let mut i = 0;
492
493 fn read(tokens: &[String], i: &mut usize) -> Result<Node, String> {
494 if *i >= tokens.len() || tokens[*i] != "(" {
495 return Err("expected \"(\"".to_string());
496 }
497 *i += 1;
498 let mut arr = Vec::new();
499 while *i < tokens.len() && tokens[*i] != ")" {
500 if tokens[*i] == "(" {
501 arr.push(read(tokens, i)?);
502 } else {
503 arr.push(Node::Leaf(tokens[*i].clone()));
504 *i += 1;
505 }
506 }
507 if *i >= tokens.len() || tokens[*i] != ")" {
508 return Err("expected \")\"".to_string());
509 }
510 *i += 1;
511 Ok(Node::List(arr))
512 }
513
514 let ast = read(tokens, &mut i)?;
515 if i != tokens.len() {
516 return Err("extra tokens after link".to_string());
517 }
518 Ok(ast)
519}
520
521pub fn desugar_hoas(node: Node) -> Node {
529 match node {
530 Node::Leaf(_) => node,
531 Node::List(children) => {
532 let mapped: Vec<Node> = children.into_iter().map(desugar_hoas).collect();
533 if mapped.len() == 3 {
537 if let Node::Leaf(ref head) = mapped[0] {
538 if head == "forall" {
539 if let Node::List(_) = mapped[1] {
540 let mut rewritten = Vec::with_capacity(3);
541 rewritten.push(Node::Leaf("Pi".to_string()));
542 let mut iter = mapped.into_iter();
543 iter.next();
544 rewritten.extend(iter);
545 return Node::List(rewritten);
546 }
547 }
548 }
549 }
550 Node::List(mapped)
551 }
552 }
553}
554
555pub fn is_num(s: &str) -> bool {
557 let s = s.trim();
558 if s.is_empty() {
559 return false;
560 }
561 let s = if let Some(stripped) = s.strip_prefix('-') {
562 stripped
563 } else {
564 s
565 };
566 if s.is_empty() {
567 return false;
568 }
569 if let Some(rest) = s.strip_prefix('.') {
570 !rest.is_empty() && rest.chars().all(|c| c.is_ascii_digit())
572 } else {
573 let parts: Vec<&str> = s.splitn(2, '.').collect();
575 if parts.is_empty() || !parts[0].chars().all(|c| c.is_ascii_digit()) || parts[0].is_empty()
576 {
577 return false;
578 }
579 if parts.len() == 2 {
580 parts[1].chars().all(|c| c.is_ascii_digit())
581 } else {
582 true
583 }
584 }
585}
586
587pub fn key_of(node: &Node) -> String {
589 match node {
590 Node::Leaf(s) => s.clone(),
591 Node::List(children) => {
592 let inner: Vec<String> = children.iter().map(key_of).collect();
593 format!("({})", inner.join(" "))
594 }
595 }
596}
597
598fn parse_universe_level_token(token: &str) -> Option<u64> {
599 if token.is_empty() || !token.chars().all(|c| c.is_ascii_digit()) {
600 return None;
601 }
602 if token.len() > 1 && token.starts_with('0') {
603 return None;
604 }
605 token.parse::<u64>().ok()
606}
607
608fn universe_type_key(node: &Node) -> Option<String> {
609 let Node::List(children) = node else {
610 return None;
611 };
612 if children.len() != 2 {
613 return None;
614 }
615 let (Node::Leaf(head), Node::Leaf(level_s)) = (&children[0], &children[1]) else {
616 return None;
617 };
618 if head != "Type" {
619 return None;
620 }
621 let level = parse_universe_level_token(level_s)?;
622 Some(format!("(Type {})", level.checked_add(1)?))
623}
624
625fn infer_type_key(node: &Node, env: &mut Env) -> Option<String> {
626 let key = match node {
627 Node::Leaf(s) => s.clone(),
628 other => key_of(other),
629 };
630 if let Some(recorded) = env.get_type(&key) {
631 return Some(recorded.clone());
632 }
633 if let Some(type_key) = universe_type_key(node) {
634 env.set_type(&key, &type_key);
635 return Some(type_key);
636 }
637 None
638}
639
640pub fn is_structurally_same(a: &Node, b: &Node) -> bool {
642 match (a, b) {
643 (Node::Leaf(sa), Node::Leaf(sb)) => sa == sb,
644 (Node::List(la), Node::List(lb)) => {
645 la.len() == lb.len()
646 && la
647 .iter()
648 .zip(lb.iter())
649 .all(|(x, y)| is_structurally_same(x, y))
650 }
651 _ => false,
652 }
653}
654
655const DECIMAL_PRECISION: i32 = 12;
659
660pub fn dec_round(x: f64) -> f64 {
661 if !x.is_finite() {
662 return x;
663 }
664 let factor = 10f64.powi(DECIMAL_PRECISION);
665 (x * factor).round() / factor
666}
667
668pub fn quantize(x: f64, valence: u32, lo: f64, hi: f64) -> f64 {
676 if valence < 2 {
677 return x; }
679 let step = (hi - lo) / (valence as f64 - 1.0);
680 let level = ((x - lo) / step).round();
681 let level = level.max(0.0).min(valence as f64 - 1.0);
682 lo + level * step
683}
684
685#[derive(Debug, Clone, Copy, PartialEq)]
689pub enum Aggregator {
690 Avg,
691 Min,
692 Max,
693 Prod,
694 Ps, }
696
697impl Aggregator {
698 pub fn apply(&self, xs: &[f64], lo: f64) -> f64 {
699 if xs.is_empty() {
700 return lo;
701 }
702 match self {
703 Aggregator::Avg => xs.iter().sum::<f64>() / xs.len() as f64,
704 Aggregator::Min => xs.iter().copied().fold(f64::INFINITY, f64::min),
705 Aggregator::Max => xs.iter().copied().fold(f64::NEG_INFINITY, f64::max),
706 Aggregator::Prod => xs.iter().copied().fold(1.0, |a, b| a * b),
707 Aggregator::Ps => 1.0 - xs.iter().copied().fold(1.0, |a, b| a * (1.0 - b)),
708 }
709 }
710
711 pub fn from_name(name: &str) -> Option<Self> {
712 match name {
713 "avg" => Some(Aggregator::Avg),
714 "min" => Some(Aggregator::Min),
715 "max" => Some(Aggregator::Max),
716 "product" | "prod" => Some(Aggregator::Prod),
717 "probabilistic_sum" | "ps" => Some(Aggregator::Ps),
718 _ => None,
719 }
720 }
721}
722
723fn resolve_truth_table_value(env: &Env, tok: &str) -> Option<f64> {
729 if let Ok(num) = tok.parse::<f64>() {
730 if num.is_finite() {
731 return Some(num);
732 }
733 }
734 env.symbol_prob.get(tok).copied()
735}
736
737fn truth_table_key(values: &[f64]) -> String {
738 values
739 .iter()
740 .map(|v| format!("{:.15}", v))
741 .collect::<Vec<_>>()
742 .join("\u{1}")
743}
744
745fn resolved_carrier_values(env: &Env, foundation: &FoundationDescriptor) -> Option<Vec<f64>> {
746 if !foundation.strict_carrier || foundation.carrier.is_empty() {
747 return None;
748 }
749 let mut values = Vec::new();
750 let mut seen = HashSet::new();
751 for tok in &foundation.carrier {
752 let value = resolve_truth_table_value(env, tok)?;
753 let key = truth_table_key(&[value]);
754 if seen.insert(key) {
755 values.push(value);
756 }
757 }
758 if values.is_empty() {
759 None
760 } else {
761 Some(values)
762 }
763}
764
765fn truth_table_rows_complete_for_carrier(
766 env: &Env,
767 rows: &[TruthTableRow],
768 foundation: &FoundationDescriptor,
769) -> bool {
770 let carrier = match resolved_carrier_values(env, foundation) {
771 Some(values) => values,
772 None => return false,
773 };
774 let mut arity: Option<usize> = None;
775 let mut seen_rows: HashSet<String> = HashSet::new();
776 for row in rows {
777 if arity.is_none() {
778 arity = Some(row.inputs.len());
779 }
780 if Some(row.inputs.len()) != arity {
781 return false;
782 }
783 let mut inputs = Vec::with_capacity(row.inputs.len());
784 for tok in &row.inputs {
785 match resolve_truth_table_value(env, tok) {
786 Some(v) => inputs.push(v),
787 None => return false,
788 }
789 }
790 if resolve_truth_table_value(env, &row.output).is_none() {
791 return false;
792 }
793 seen_rows.insert(truth_table_key(&inputs));
794 }
795 let arity = match arity {
796 Some(a) => a,
797 None => return false,
798 };
799 let required = carrier.len().pow(arity as u32);
800 if seen_rows.len() < required {
801 return false;
802 }
803 fn visit(
804 carrier: &[f64],
805 seen_rows: &HashSet<String>,
806 arity: usize,
807 prefix: &mut Vec<f64>,
808 ) -> bool {
809 if prefix.len() == arity {
810 return seen_rows.contains(&truth_table_key(prefix));
811 }
812 for value in carrier {
813 prefix.push(*value);
814 if !visit(carrier, seen_rows, arity, prefix) {
815 prefix.pop();
816 return false;
817 }
818 prefix.pop();
819 }
820 true
821 }
822 visit(&carrier, &seen_rows, arity, &mut Vec::new())
823}
824
825fn truth_table_fallback_dependencies(
826 env: &Env,
827 op_name: &str,
828 previous_impl: Option<&ActiveImplementationDescriptor>,
829) -> Vec<String> {
830 let mut deps = Vec::new();
831 if let Some(implementation) = previous_impl {
832 deps.extend(implementation.depends_on.iter().cloned());
833 } else if let Some(rc) = env.root_constructs.get(op_name) {
834 deps.extend(rc.depends_on.iter().cloned());
835 }
836 deps.push("truth-table-fallback".to_string());
837 let mut seen = HashSet::new();
838 deps.into_iter()
839 .filter(|dep| seen.insert(dep.clone()))
840 .collect()
841}
842
843#[derive(Debug, Clone)]
847pub enum Op {
848 Not,
850 Agg(Aggregator),
852 Eq,
854 Neq,
856 Compose {
858 outer: String,
859 inner: String,
860 },
861 Add,
863 Sub,
864 Mul,
865 Div,
866 Less,
868 LessOrEqual,
869 TruthTable {
877 rows: Vec<TruthTableEntry>,
878 fallback: Option<Box<Op>>,
879 },
880}
881
882#[derive(Debug, Clone, PartialEq)]
885pub struct TruthTableEntry {
886 pub inputs: Vec<f64>,
887 pub output: f64,
888}
889
890#[derive(Debug, Clone)]
894pub struct EnvOptions {
895 pub lo: f64,
896 pub hi: f64,
897 pub valence: u32,
898}
899
900impl Default for EnvOptions {
901 fn default() -> Self {
902 Self {
903 lo: 0.0,
904 hi: 1.0,
905 valence: 0,
906 }
907 }
908}
909
910#[derive(Debug, Clone, Copy, Default)]
912pub struct ConvertOptions {
913 pub eta: bool,
916}
917
918#[derive(Debug, Clone)]
920pub struct Lambda {
921 pub param: String,
922 pub param_type: String,
923 pub body: Node,
924}
925
926#[derive(Debug, Clone)]
930pub struct TemplateDecl {
931 pub name: String,
932 pub params: Vec<String>,
933 pub body: Node,
934}
935
936pub type DomainPluginFn = fn(&[Node], &mut Env) -> Result<(), String>;
939
940#[derive(Debug, Clone, PartialEq)]
942pub struct AutomaticSequenceDecision {
943 pub theorem: String,
944 pub value: bool,
945 pub method: String,
946 pub certificate: Node,
947}
948
949pub struct Env {
951 pub terms: HashSet<String>,
952 pub assign: HashMap<String, f64>,
953 pub symbol_prob: HashMap<String, f64>,
954 pub lo: f64,
955 pub hi: f64,
956 pub valence: u32,
957 pub ops: HashMap<String, Op>,
958 pub types: HashMap<String, String>,
959 pub lambdas: HashMap<String, Lambda>,
960 pub templates: HashMap<String, TemplateDecl>,
961 pub trace_enabled: bool,
968 pub trace_events: Vec<TraceEvent>,
969 pub current_span: Option<Span>,
970 pub default_span: Span,
971 pub namespace: Option<String>,
979 pub aliases: HashMap<String, String>,
980 pub imported: HashSet<String>,
981 pub shadow_diagnostics: Vec<Diagnostic>,
982 pub file_namespaces: HashMap<PathBuf, String>,
983 pub modes: HashMap<String, Vec<ModeFlag>>,
988 pub relations: HashMap<String, Vec<Node>>,
994 pub worlds: HashMap<String, Vec<String>>,
1001 pub inductives: HashMap<String, InductiveDecl>,
1005 pub definitions: HashMap<String, DefineDecl>,
1011 pub coinductives: HashMap<String, CoinductiveDecl>,
1020 pub domain_plugins: HashMap<String, DomainPluginFn>,
1025 pub automatic_sequence_decisions: HashMap<String, AutomaticSequenceDecision>,
1027 pub root_constructs: HashMap<String, RootConstructDescriptor>,
1032 pub foundations: HashMap<String, FoundationDescriptor>,
1038 pub active_foundation: String,
1039 pub foundation_stack: Vec<FoundationFrame>,
1040 pub active_implementations: HashMap<String, ActiveImplementationDescriptor>,
1041 pub strict_carrier: bool,
1046 pub carrier: Option<Vec<f64>>,
1047 pub carrier_label: Option<String>,
1048 pub proof_rules: HashMap<String, ProofRule>,
1055 pub proof_assumptions: HashMap<String, ProofAssumption>,
1056 pub proof_objects: HashMap<String, ProofObject>,
1057 pub strict_pure_links: bool,
1064 pub allowed_host_primitives: HashSet<String>,
1065}
1066
1067#[derive(Debug, Clone)]
1075pub struct FoundationFrame {
1076 pub previous_active: String,
1077 pub snapshot: Vec<(String, Option<Op>)>,
1078 pub previous_active_implementations: Vec<(String, Option<ActiveImplementationDescriptor>)>,
1079 pub previous_strict_carrier: bool,
1080 pub previous_carrier: Option<Vec<f64>>,
1081 pub previous_carrier_label: Option<String>,
1082}
1083
1084#[derive(Debug, Clone, Default, PartialEq)]
1089pub struct RootConstructDescriptor {
1090 pub name: String,
1091 pub status: Option<String>,
1092 pub semantic_status: Option<String>,
1093 pub kind: Option<String>,
1094 pub depends_on: Vec<String>,
1095 pub encoded_as: Option<String>,
1096 pub pure_links_ready: Option<bool>,
1097 pub override_with: Option<String>,
1098 pub planned_as: Option<String>,
1099 pub foundation: Option<String>,
1100}
1101
1102#[derive(Debug, Clone, Default, PartialEq)]
1108pub struct FoundationDescriptor {
1109 pub name: String,
1110 pub description: Option<String>,
1111 pub uses: Vec<String>,
1112 pub defines: Vec<(String, String)>, pub extends: Option<String>,
1114 pub numeric_domain: Option<String>,
1115 pub truth_domain: Option<String>,
1116 pub carrier: Vec<String>,
1122 pub strict_carrier: bool,
1128 pub truth_tables: Vec<(String, Vec<TruthTableRow>)>,
1138 pub experimental: bool,
1145 pub root: Option<String>,
1146 pub abits: Vec<(String, String)>,
1147}
1148
1149#[derive(Debug, Clone, Default, PartialEq)]
1154pub struct ActiveImplementationDescriptor {
1155 pub construct: String,
1156 pub foundation: Option<String>,
1157 pub implementation: Option<String>,
1158 pub status: Option<String>,
1159 pub semantic_status: Option<String>,
1160 pub depends_on: Vec<String>,
1161}
1162
1163#[derive(Debug, Clone, Default, PartialEq)]
1166pub struct TruthTableRow {
1167 pub inputs: Vec<String>,
1168 pub output: String,
1169}
1170
1171#[derive(Debug, Clone, Default, PartialEq)]
1173pub struct FoundationReport {
1174 pub active_foundation: String,
1175 pub description: Option<String>,
1176 pub numeric_domain: Option<String>,
1177 pub truth_domain: Option<String>,
1178 pub root_constructs: Vec<RootConstructDescriptor>,
1179 pub by_status: Vec<(String, Vec<String>)>,
1180 pub by_semantic_status: Vec<(String, Vec<String>)>,
1181 pub foundations: Vec<FoundationDescriptor>,
1182 pub active_implementations: Vec<ActiveImplementationDescriptor>,
1183 pub proof_rules: Vec<ProofRuleSnapshot>,
1187 pub proof_assumptions: Vec<ProofAssumptionSnapshot>,
1188 pub proof_objects: Vec<ProofObjectSnapshot>,
1189 pub strict_pure_links: bool,
1193 pub allowed_host_primitives: Vec<String>,
1194 pub dependency_graph: Vec<(String, Vec<String>)>,
1199}
1200
1201#[derive(Debug, Clone, PartialEq)]
1206pub struct ProofRule {
1207 pub name: String,
1208 pub premises: Vec<Node>,
1209 pub conclusion: Node,
1210}
1211
1212#[derive(Debug, Clone, PartialEq)]
1215pub struct ProofAssumption {
1216 pub name: String,
1217 pub kind: String,
1218 pub judgement: Node,
1219}
1220
1221#[derive(Debug, Clone, PartialEq)]
1225pub struct ProofObject {
1226 pub name: String,
1227 pub rule: String,
1228 pub premises: Vec<Node>,
1229 pub premise_refs: Vec<String>,
1230 pub conclusion: Node,
1231}
1232
1233#[derive(Debug, Clone, Default, PartialEq)]
1237pub struct ProofRuleSnapshot {
1238 pub name: String,
1239 pub premises: Vec<String>,
1240 pub conclusion: String,
1241}
1242
1243#[derive(Debug, Clone, Default, PartialEq)]
1245pub struct ProofAssumptionSnapshot {
1246 pub name: String,
1247 pub kind: String,
1248 pub judgement: String,
1249}
1250
1251#[derive(Debug, Clone, Default, PartialEq)]
1254pub struct ProofObjectSnapshot {
1255 pub name: String,
1256 pub rule: String,
1257 pub premises: Vec<String>,
1258 pub premise_refs: Vec<String>,
1259 pub conclusion: String,
1260}
1261
1262#[derive(Debug, Clone, PartialEq)]
1265pub struct ProofReportVerdict {
1266 pub ok: bool,
1267 pub error: Option<String>,
1268}
1269
1270#[derive(Debug, Clone, PartialEq)]
1272pub struct ProofReportDependency {
1273 pub name: String,
1274 pub kind: String,
1275 pub rule: Option<String>,
1277 pub judgement: Option<String>,
1279}
1280
1281#[derive(Debug, Clone, PartialEq)]
1286pub struct ProofReport {
1287 pub name: String,
1288 pub rule: Option<String>,
1289 pub conclusion: Option<String>,
1290 pub premises: Vec<String>,
1291 pub premise_refs: Vec<String>,
1292 pub verdict: ProofReportVerdict,
1293 pub dependencies: Vec<ProofReportDependency>,
1294 pub rules: Vec<String>,
1295 pub root_constructs_used: Vec<String>,
1296 pub by_semantic_status: Vec<(String, Vec<String>)>,
1297 pub by_trust_status: Vec<(String, Vec<String>)>,
1298 pub active_foundation: String,
1299 pub strict_pure_links: bool,
1300}
1301
1302#[derive(Debug, Clone)]
1304pub struct ConstructorDecl {
1305 pub name: String,
1307 pub params: Vec<(String, Node)>,
1310 pub typ: Node,
1313}
1314
1315#[derive(Debug, Clone)]
1317pub struct InductiveDecl {
1318 pub name: String,
1320 pub constructors: Vec<ConstructorDecl>,
1322 pub elim_name: String,
1324 pub elim_type: Node,
1326}
1327
1328#[derive(Debug, Clone, PartialEq)]
1330pub struct DefineClause {
1331 pub pattern: Vec<Node>,
1334 pub body: Node,
1337}
1338
1339#[derive(Debug, Clone, PartialEq, Eq)]
1341pub enum DefineMeasure {
1342 Lex(Vec<usize>),
1346}
1347
1348#[derive(Debug, Clone, PartialEq)]
1350pub struct DefineDecl {
1351 pub name: String,
1353 pub measure: Option<DefineMeasure>,
1356 pub clauses: Vec<DefineClause>,
1358}
1359
1360#[derive(Debug, Clone)]
1364pub struct CoinductiveDecl {
1365 pub name: String,
1367 pub constructors: Vec<ConstructorDecl>,
1369 pub corec_name: String,
1371 pub corec_type: Node,
1373}
1374
1375#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1377pub enum ModeFlag {
1378 In,
1380 Out,
1382 Either,
1384}
1385
1386impl ModeFlag {
1387 pub fn from_token(token: &str) -> Option<Self> {
1388 match token {
1389 "+input" => Some(ModeFlag::In),
1390 "-output" => Some(ModeFlag::Out),
1391 "*either" => Some(ModeFlag::Either),
1392 _ => None,
1393 }
1394 }
1395}
1396
1397impl Env {
1398 pub fn new(options: Option<EnvOptions>) -> Self {
1399 let opts = options.unwrap_or_default();
1400 let mut ops = HashMap::new();
1401 ops.insert("not".to_string(), Op::Not);
1402 ops.insert("and".to_string(), Op::Agg(Aggregator::Avg));
1403 ops.insert("or".to_string(), Op::Agg(Aggregator::Max));
1404 ops.insert("both".to_string(), Op::Agg(Aggregator::Avg));
1409 ops.insert("neither".to_string(), Op::Agg(Aggregator::Prod));
1410 ops.insert("=".to_string(), Op::Eq);
1411 ops.insert("!=".to_string(), Op::Neq);
1412 ops.insert("+".to_string(), Op::Add);
1413 ops.insert("-".to_string(), Op::Sub);
1414 ops.insert("*".to_string(), Op::Mul);
1415 ops.insert("/".to_string(), Op::Div);
1416 ops.insert("<".to_string(), Op::Less);
1417 ops.insert("<=".to_string(), Op::LessOrEqual);
1418
1419 let mut env = Self {
1420 terms: HashSet::new(),
1421 assign: HashMap::new(),
1422 symbol_prob: HashMap::new(),
1423 lo: opts.lo,
1424 hi: opts.hi,
1425 valence: opts.valence,
1426 ops,
1427 types: HashMap::new(),
1428 lambdas: HashMap::new(),
1429 templates: HashMap::new(),
1430 trace_enabled: false,
1431 trace_events: Vec::new(),
1432 current_span: None,
1433 default_span: Span::unknown(),
1434 namespace: None,
1435 aliases: HashMap::new(),
1436 imported: HashSet::new(),
1437 shadow_diagnostics: Vec::new(),
1438 file_namespaces: HashMap::new(),
1439 modes: HashMap::new(),
1440 relations: HashMap::new(),
1441 worlds: HashMap::new(),
1442 inductives: HashMap::new(),
1443 definitions: HashMap::new(),
1444 coinductives: HashMap::new(),
1445 domain_plugins: HashMap::new(),
1446 automatic_sequence_decisions: HashMap::new(),
1447 root_constructs: HashMap::new(),
1448 foundations: HashMap::new(),
1449 active_foundation: "default-rml".to_string(),
1450 foundation_stack: Vec::new(),
1451 active_implementations: HashMap::new(),
1452 strict_carrier: false,
1453 carrier: None,
1454 carrier_label: None,
1455 proof_rules: HashMap::new(),
1456 proof_assumptions: HashMap::new(),
1457 proof_objects: HashMap::new(),
1458 strict_pure_links: false,
1459 allowed_host_primitives: HashSet::new(),
1460 };
1461
1462 env.init_truth_constants();
1468 env.register_domain_plugin("automatic-sequences", automatic_sequences_domain_plugin);
1469 env.register_default_foundation();
1470 env
1471 }
1472
1473 pub fn mid(&self) -> f64 {
1475 (self.lo + self.hi) / 2.0
1476 }
1477
1478 pub fn init_truth_constants(&mut self) {
1482 self.symbol_prob.insert("true".to_string(), self.hi);
1483 self.symbol_prob.insert("false".to_string(), self.lo);
1484 let mid = self.mid();
1485 self.symbol_prob.insert("unknown".to_string(), mid);
1486 self.symbol_prob.insert("undefined".to_string(), mid);
1487 }
1490
1491 pub fn clamp(&self, x: f64) -> f64 {
1493 let clamped = x.max(self.lo).min(self.hi);
1494 if self.valence >= 2 {
1495 quantize(clamped, self.valence, self.lo, self.hi)
1496 } else {
1497 clamped
1498 }
1499 }
1500
1501 pub fn to_num(&self, s: &str) -> f64 {
1503 self.clamp(s.parse::<f64>().unwrap_or(0.0))
1504 }
1505
1506 pub fn define_op(&mut self, name: &str, op: Op) {
1507 self.ops.insert(name.to_string(), op);
1508 }
1509
1510 pub fn register_domain_plugin(&mut self, name: &str, plugin: DomainPluginFn) {
1511 self.domain_plugins.insert(name.to_string(), plugin);
1512 }
1513
1514 pub fn get_domain_plugin(&self, name: &str) -> Option<DomainPluginFn> {
1515 self.domain_plugins.get(name).copied()
1516 }
1517
1518 pub fn register_default_foundation(&mut self) {
1523 let default = FoundationDescriptor {
1524 name: "default-rml".to_string(),
1525 description: Some(
1526 "Default RML foundation: host-implemented configurable kernel".to_string(),
1527 ),
1528 uses: Vec::new(),
1529 defines: Vec::new(),
1530 extends: None,
1531 numeric_domain: Some("decimal-12".to_string()),
1532 truth_domain: Some("default-truth".to_string()),
1533 carrier: Vec::new(),
1534 strict_carrier: false,
1535 truth_tables: Vec::new(),
1536 experimental: false,
1537 root: None,
1538 abits: Vec::new(),
1539 };
1540 self.foundations.insert(default.name.clone(), default);
1541 let mtc_anum = FoundationDescriptor {
1546 name: "mtc-anum".to_string(),
1547 description: Some(
1548 "experimental metatheory-of-links foundation (anum serialization)".to_string(),
1549 ),
1550 uses: Vec::new(),
1551 defines: Vec::new(),
1552 extends: None,
1553 numeric_domain: None,
1554 truth_domain: Some("mtc-abits".to_string()),
1555 carrier: Vec::new(),
1556 strict_carrier: false,
1557 truth_tables: Vec::new(),
1558 experimental: true,
1559 root: Some("∞".to_string()),
1560 abits: vec![
1561 ("[".to_string(), "start-of-meaning".to_string()),
1562 ("]".to_string(), "end-of-meaning".to_string()),
1563 ("1".to_string(), "unit-of-meaning".to_string()),
1564 ("0".to_string(), "zero-of-meaning".to_string()),
1565 ],
1566 };
1567 self.foundations.insert(mtc_anum.name.clone(), mtc_anum);
1568 let boolean_links = FoundationDescriptor {
1569 name: "boolean-links".to_string(),
1570 description: Some(
1571 "links-defined two-valued Boolean logic via finite truth tables".to_string(),
1572 ),
1573 uses: Vec::new(),
1574 defines: Vec::new(),
1575 extends: None,
1576 numeric_domain: Some("boolean-zero-one".to_string()),
1577 truth_domain: Some("boolean-two-valued".to_string()),
1578 carrier: vec!["0".to_string(), "1".to_string()],
1579 strict_carrier: true,
1580 truth_tables: vec![
1581 (
1582 "and".to_string(),
1583 vec![
1584 TruthTableRow {
1585 inputs: vec!["1".to_string(), "1".to_string()],
1586 output: "1".to_string(),
1587 },
1588 TruthTableRow {
1589 inputs: vec!["1".to_string(), "0".to_string()],
1590 output: "0".to_string(),
1591 },
1592 TruthTableRow {
1593 inputs: vec!["0".to_string(), "1".to_string()],
1594 output: "0".to_string(),
1595 },
1596 TruthTableRow {
1597 inputs: vec!["0".to_string(), "0".to_string()],
1598 output: "0".to_string(),
1599 },
1600 ],
1601 ),
1602 (
1603 "or".to_string(),
1604 vec![
1605 TruthTableRow {
1606 inputs: vec!["1".to_string(), "1".to_string()],
1607 output: "1".to_string(),
1608 },
1609 TruthTableRow {
1610 inputs: vec!["1".to_string(), "0".to_string()],
1611 output: "1".to_string(),
1612 },
1613 TruthTableRow {
1614 inputs: vec!["0".to_string(), "1".to_string()],
1615 output: "1".to_string(),
1616 },
1617 TruthTableRow {
1618 inputs: vec!["0".to_string(), "0".to_string()],
1619 output: "0".to_string(),
1620 },
1621 ],
1622 ),
1623 (
1624 "not".to_string(),
1625 vec![
1626 TruthTableRow {
1627 inputs: vec!["1".to_string()],
1628 output: "0".to_string(),
1629 },
1630 TruthTableRow {
1631 inputs: vec!["0".to_string()],
1632 output: "1".to_string(),
1633 },
1634 ],
1635 ),
1636 ],
1637 experimental: false,
1638 root: None,
1639 abits: Vec::new(),
1640 };
1641 self.foundations
1642 .insert(boolean_links.name.clone(), boolean_links);
1643 let typed_kernel_links = FoundationDescriptor {
1653 name: "typed-kernel-links".to_string(),
1654 description: Some(
1655 "links-defined typed-kernel fragment (Pi/lambda/apply/beta as proof rules)"
1656 .to_string(),
1657 ),
1658 uses: vec![
1659 "pi-formation".to_string(),
1660 "lambda-introduction".to_string(),
1661 "application-elimination".to_string(),
1662 "beta-conversion".to_string(),
1663 ],
1664 defines: Vec::new(),
1665 extends: Some("default-rml".to_string()),
1666 numeric_domain: Some("decimal-12".to_string()),
1667 truth_domain: Some("default-truth".to_string()),
1668 carrier: Vec::new(),
1669 strict_carrier: false,
1670 truth_tables: Vec::new(),
1671 experimental: false,
1672 root: None,
1673 abits: Vec::new(),
1674 };
1675 self.foundations
1676 .insert(typed_kernel_links.name.clone(), typed_kernel_links);
1677 let nat_links = FoundationDescriptor {
1684 name: "nat-links".to_string(),
1685 description: Some(
1686 "links-defined Peano naturals (zero/succ formation, add by recursion, induction with explicit forall/implication/predicate-application, nat-equality with reflexivity and successor congruence, nat-recursion/nat-eliminator, multiplication, rule-driven eval-nat normalizer)"
1687 .to_string(),
1688 ),
1689 uses: vec![
1690 "nat-zero-formation".to_string(),
1691 "nat-succ-formation".to_string(),
1692 "nat-add-zero".to_string(),
1693 "nat-add-succ".to_string(),
1694 "nat-induction".to_string(),
1695 "nat-equality".to_string(),
1696 "nat-refl".to_string(),
1697 "nat-cong-succ".to_string(),
1698 "forall".to_string(),
1699 "implication".to_string(),
1700 "predicate-application".to_string(),
1701 "nat-recursion".to_string(),
1702 "nat-eliminator".to_string(),
1703 "nat-rec-zero".to_string(),
1704 "nat-rec-succ".to_string(),
1705 "mul".to_string(),
1706 "nat-mul-zero".to_string(),
1707 "nat-mul-succ".to_string(),
1708 "eval-nat-normalize".to_string(),
1709 "eval-nat".to_string(),
1710 "nat-normal-form-to-host-number".to_string(),
1711 ],
1712 defines: Vec::new(),
1713 extends: Some("default-rml".to_string()),
1714 numeric_domain: Some("decimal-12".to_string()),
1715 truth_domain: Some("default-truth".to_string()),
1716 carrier: Vec::new(),
1717 strict_carrier: false,
1718 truth_tables: Vec::new(),
1719 experimental: false,
1720 root: None,
1721 abits: Vec::new(),
1722 };
1723 self.foundations
1724 .insert(nat_links.name.clone(), nat_links);
1725 seed_builtin_root_constructs(self);
1726 }
1727
1728 pub fn register_root_construct(
1729 &mut self,
1730 descriptor: RootConstructDescriptor,
1731 ) -> Result<RootConstructDescriptor, String> {
1732 if descriptor.name.is_empty() {
1733 return Err("root-construct descriptor requires a name".to_string());
1734 }
1735 let prev = self.root_constructs.get(&descriptor.name).cloned();
1736 let merged = merge_root_construct_descriptors(prev, descriptor);
1737 self.root_constructs
1738 .insert(merged.name.clone(), merged.clone());
1739 Ok(merged)
1740 }
1741
1742 pub fn get_root_construct(&self, name: &str) -> Option<&RootConstructDescriptor> {
1743 self.root_constructs.get(name)
1744 }
1745
1746 pub fn list_root_constructs(&self) -> Vec<RootConstructDescriptor> {
1747 let mut v: Vec<RootConstructDescriptor> = self.root_constructs.values().cloned().collect();
1748 v.sort_by(|a, b| a.name.cmp(&b.name));
1749 v
1750 }
1751
1752 pub fn register_foundation(
1753 &mut self,
1754 foundation: FoundationDescriptor,
1755 ) -> Result<FoundationDescriptor, String> {
1756 if foundation.name.is_empty() {
1757 return Err("foundation declaration requires a name".to_string());
1758 }
1759 let prev = self.foundations.get(&foundation.name).cloned();
1760 let merged = merge_foundation_descriptors(prev, foundation);
1761 self.foundations.insert(merged.name.clone(), merged.clone());
1762 Ok(merged)
1763 }
1764
1765 pub fn get_foundation(&self, name: &str) -> Option<&FoundationDescriptor> {
1766 self.foundations.get(name)
1767 }
1768
1769 pub fn enter_foundation(&mut self, name: &str) -> Result<(), String> {
1770 let foundation = match self.foundations.get(name) {
1771 Some(f) => f.clone(),
1772 None => return Err(format!("Unknown foundation: {}", name)),
1773 };
1774 let mut snapshot: Vec<(String, Option<Op>)> = Vec::new();
1779 let mut previous_active_implementations: Vec<(
1780 String,
1781 Option<ActiveImplementationDescriptor>,
1782 )> = Vec::new();
1783 let snapshot_impl = |env: &Env,
1784 store: &mut Vec<(String, Option<ActiveImplementationDescriptor>)>,
1785 op_name: &str| {
1786 if store.iter().any(|(n, _)| n == op_name) {
1787 return;
1788 }
1789 store.push((
1790 op_name.to_string(),
1791 env.active_implementations.get(op_name).cloned(),
1792 ));
1793 };
1794 for (op_name, impl_name) in &foundation.defines {
1795 if let Some(agg) = Aggregator::from_name(impl_name) {
1796 snapshot_impl(self, &mut previous_active_implementations, op_name);
1797 let prev = self.ops.get(op_name).cloned();
1798 snapshot.push((op_name.clone(), prev));
1799 self.ops.insert(op_name.clone(), Op::Agg(agg));
1800 self.active_implementations.insert(
1801 op_name.clone(),
1802 ActiveImplementationDescriptor {
1803 construct: op_name.clone(),
1804 foundation: Some(name.to_string()),
1805 implementation: Some(impl_name.clone()),
1806 status: Some("host-primitive".to_string()),
1807 semantic_status: Some("host-trusted".to_string()),
1808 depends_on: vec![impl_name.clone()],
1809 },
1810 );
1811 }
1812 }
1813 for (op_name, rows) in &foundation.truth_tables {
1818 let mut resolved: Vec<TruthTableEntry> = Vec::new();
1819 for row in rows {
1820 let mut inputs: Vec<f64> = Vec::with_capacity(row.inputs.len());
1821 let mut row_ok = true;
1822 for tok in &row.inputs {
1823 match resolve_truth_table_value(self, tok) {
1824 Some(v) => inputs.push(v),
1825 None => {
1826 row_ok = false;
1827 break;
1828 }
1829 }
1830 }
1831 if !row_ok {
1832 continue;
1833 }
1834 let output = match resolve_truth_table_value(self, &row.output) {
1835 Some(v) => v,
1836 None => continue,
1837 };
1838 resolved.push(TruthTableEntry { inputs, output });
1839 }
1840 if resolved.is_empty() {
1841 continue;
1842 }
1843 if !snapshot.iter().any(|(n, _)| n == op_name) {
1844 let prev = self.ops.get(op_name).cloned();
1845 snapshot.push((op_name.clone(), prev));
1846 }
1847 snapshot_impl(self, &mut previous_active_implementations, op_name);
1848 let previous_impl = self.active_implementations.get(op_name).cloned();
1849 let is_total = truth_table_rows_complete_for_carrier(self, rows, &foundation);
1850 let depends_on = if is_total {
1851 Vec::new()
1852 } else {
1853 truth_table_fallback_dependencies(self, op_name, previous_impl.as_ref())
1854 };
1855 let fallback = self.ops.get(op_name).cloned().map(Box::new);
1856 self.ops.insert(
1857 op_name.clone(),
1858 Op::TruthTable {
1859 rows: resolved,
1860 fallback,
1861 },
1862 );
1863 self.active_implementations.insert(
1864 op_name.clone(),
1865 ActiveImplementationDescriptor {
1866 construct: op_name.clone(),
1867 foundation: Some(name.to_string()),
1868 implementation: Some(format!("truth-table:{}/{}", name, op_name)),
1869 status: Some("links-defined".to_string()),
1870 semantic_status: Some("links-checked".to_string()),
1871 depends_on,
1872 },
1873 );
1874 }
1875 let previous_strict_carrier = self.strict_carrier;
1881 let previous_carrier = self.carrier.clone();
1882 let previous_carrier_label = self.carrier_label.clone();
1883 if foundation.strict_carrier && !foundation.carrier.is_empty() {
1884 let mut resolved: Vec<f64> = Vec::new();
1885 for tok in &foundation.carrier {
1886 if let Ok(num) = tok.parse::<f64>() {
1887 if num.is_finite() {
1888 resolved.push(num);
1889 continue;
1890 }
1891 }
1892 if let Some(p) = self.symbol_prob.get(tok) {
1893 resolved.push(*p);
1894 }
1895 }
1896 self.strict_carrier = true;
1897 self.carrier = Some(resolved);
1898 self.carrier_label = Some(foundation.carrier.join(" "));
1899 }
1900 let frame = FoundationFrame {
1901 previous_active: std::mem::take(&mut self.active_foundation),
1902 snapshot,
1903 previous_active_implementations,
1904 previous_strict_carrier,
1905 previous_carrier,
1906 previous_carrier_label,
1907 };
1908 self.foundation_stack.push(frame);
1909 self.active_foundation = name.to_string();
1910 Ok(())
1911 }
1912
1913 pub fn exit_foundation(&mut self) {
1914 if let Some(frame) = self.foundation_stack.pop() {
1915 for (op_name, prev) in frame.snapshot.into_iter().rev() {
1916 match prev {
1917 Some(op) => {
1918 self.ops.insert(op_name, op);
1919 }
1920 None => {
1921 self.ops.remove(&op_name);
1922 }
1923 }
1924 }
1925 for (op_name, prev) in frame.previous_active_implementations.into_iter().rev() {
1926 match prev {
1927 Some(implementation) => {
1928 self.active_implementations.insert(op_name, implementation);
1929 }
1930 None => {
1931 self.active_implementations.remove(&op_name);
1932 }
1933 }
1934 }
1935 self.active_foundation = frame.previous_active;
1936 self.strict_carrier = frame.previous_strict_carrier;
1937 self.carrier = frame.previous_carrier;
1938 self.carrier_label = frame.previous_carrier_label;
1939 } else {
1940 self.active_foundation = "default-rml".to_string();
1941 self.active_implementations.clear();
1942 self.strict_carrier = false;
1943 self.carrier = None;
1944 self.carrier_label = None;
1945 }
1946 }
1947
1948 pub fn check_carrier_value(&self, value: f64) -> Option<String> {
1953 if !self.strict_carrier {
1954 return None;
1955 }
1956 let carrier = self.carrier.as_ref()?;
1957 if carrier.is_empty() {
1958 return None;
1959 }
1960 if !value.is_finite() {
1961 return None;
1962 }
1963 if carrier.iter().any(|c| (*c - value).abs() < 1e-12) {
1964 return None;
1965 }
1966 let mut sorted = carrier.clone();
1967 sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1968 let allowed: Vec<String> = sorted.iter().map(|v| format_trace_value(*v)).collect();
1969 Some(format!(
1970 "value {} is not in active carrier {{{}}}",
1971 format_trace_value(value),
1972 allowed.join(", ")
1973 ))
1974 }
1975
1976 pub fn foundation_report(&self) -> FoundationReport {
1977 let active = if self.active_foundation.is_empty() {
1978 "default-rml".to_string()
1979 } else {
1980 self.active_foundation.clone()
1981 };
1982 let foundation = self.foundations.get(&active).cloned();
1983 let mut constructs = self.list_root_constructs();
1984 for rc in &mut constructs {
1985 if rc.semantic_status.is_none() {
1986 rc.semantic_status = semantic_status_for_trust_status(rc.status.as_deref());
1987 }
1988 }
1989 let mut by_status_map: std::collections::BTreeMap<String, Vec<String>> =
1990 std::collections::BTreeMap::new();
1991 let mut by_semantic_status_map: std::collections::BTreeMap<String, Vec<String>> =
1992 std::collections::BTreeMap::new();
1993 for rc in &constructs {
1994 let key = rc.status.clone().unwrap_or_else(|| "unknown".to_string());
1995 by_status_map.entry(key).or_default().push(rc.name.clone());
1996 let semantic_key =
1997 semantic_status_for_descriptor(rc).unwrap_or_else(|| "unknown".to_string());
1998 by_semantic_status_map
1999 .entry(semantic_key)
2000 .or_default()
2001 .push(rc.name.clone());
2002 }
2003 for v in by_status_map.values_mut() {
2004 v.sort();
2005 }
2006 for v in by_semantic_status_map.values_mut() {
2007 v.sort();
2008 }
2009 let by_status: Vec<(String, Vec<String>)> = by_status_map.into_iter().collect();
2010 let by_semantic_status: Vec<(String, Vec<String>)> =
2011 by_semantic_status_map.into_iter().collect();
2012 let mut foundations: Vec<FoundationDescriptor> =
2013 self.foundations.values().cloned().collect();
2014 foundations.sort_by(|a, b| a.name.cmp(&b.name));
2015 let mut active_implementations: Vec<ActiveImplementationDescriptor> =
2016 self.active_implementations.values().cloned().collect();
2017 for implementation in &mut active_implementations {
2018 if implementation.semantic_status.is_none() {
2019 implementation.semantic_status =
2020 semantic_status_for_trust_status(implementation.status.as_deref());
2021 }
2022 }
2023 active_implementations.sort_by(|a, b| a.construct.cmp(&b.construct));
2024 let mut proof_rules: Vec<ProofRuleSnapshot> = self
2025 .proof_rules
2026 .values()
2027 .map(|r| ProofRuleSnapshot {
2028 name: r.name.clone(),
2029 premises: r.premises.iter().map(key_of).collect(),
2030 conclusion: key_of(&r.conclusion),
2031 })
2032 .collect();
2033 proof_rules.sort_by(|a, b| a.name.cmp(&b.name));
2034 let mut proof_assumptions: Vec<ProofAssumptionSnapshot> = self
2035 .proof_assumptions
2036 .values()
2037 .map(|a| ProofAssumptionSnapshot {
2038 name: a.name.clone(),
2039 kind: a.kind.clone(),
2040 judgement: key_of(&a.judgement),
2041 })
2042 .collect();
2043 proof_assumptions.sort_by(|a, b| a.name.cmp(&b.name));
2044 let mut proof_objects: Vec<ProofObjectSnapshot> = self
2045 .proof_objects
2046 .values()
2047 .map(|po| ProofObjectSnapshot {
2048 name: po.name.clone(),
2049 rule: po.rule.clone(),
2050 premises: po.premises.iter().map(key_of).collect(),
2051 premise_refs: po.premise_refs.clone(),
2052 conclusion: key_of(&po.conclusion),
2053 })
2054 .collect();
2055 proof_objects.sort_by(|a, b| a.name.cmp(&b.name));
2056 let mut allowed: Vec<String> = self.allowed_host_primitives.iter().cloned().collect();
2057 allowed.sort();
2058 let dependency_graph = build_dependency_graph(self);
2059 FoundationReport {
2060 active_foundation: active,
2061 description: foundation.as_ref().and_then(|f| f.description.clone()),
2062 numeric_domain: foundation.as_ref().and_then(|f| f.numeric_domain.clone()),
2063 truth_domain: foundation.as_ref().and_then(|f| f.truth_domain.clone()),
2064 root_constructs: constructs,
2065 by_status,
2066 by_semantic_status,
2067 foundations,
2068 active_implementations,
2069 proof_rules,
2070 proof_assumptions,
2071 proof_objects,
2072 strict_pure_links: self.strict_pure_links,
2073 allowed_host_primitives: allowed,
2074 dependency_graph,
2075 }
2076 }
2077
2078 pub fn proof_report(&self, name: &str) -> ProofReport {
2086 let active = if self.active_foundation.is_empty() {
2087 "default-rml".to_string()
2088 } else {
2089 self.active_foundation.clone()
2090 };
2091 if name.is_empty() {
2092 return ProofReport {
2093 name: String::new(),
2094 rule: None,
2095 conclusion: None,
2096 premises: Vec::new(),
2097 premise_refs: Vec::new(),
2098 verdict: ProofReportVerdict {
2099 ok: false,
2100 error: Some("proof name required".to_string()),
2101 },
2102 dependencies: Vec::new(),
2103 rules: Vec::new(),
2104 root_constructs_used: Vec::new(),
2105 by_semantic_status: Vec::new(),
2106 by_trust_status: Vec::new(),
2107 active_foundation: active,
2108 strict_pure_links: self.strict_pure_links,
2109 };
2110 }
2111 let po = match self.get_proof_object(name) {
2112 Some(po) => po.clone(),
2113 None => {
2114 return ProofReport {
2115 name: name.to_string(),
2116 rule: None,
2117 conclusion: None,
2118 premises: Vec::new(),
2119 premise_refs: Vec::new(),
2120 verdict: ProofReportVerdict {
2121 ok: false,
2122 error: Some(format!("unknown proof-object {}", name)),
2123 },
2124 dependencies: Vec::new(),
2125 rules: Vec::new(),
2126 root_constructs_used: Vec::new(),
2127 by_semantic_status: Vec::new(),
2128 by_trust_status: Vec::new(),
2129 active_foundation: active,
2130 strict_pure_links: self.strict_pure_links,
2131 };
2132 }
2133 };
2134 let verdict = check_proof_object(self, name);
2135 let verdict = match verdict {
2136 CheckProofVerdict::Ok(_) => ProofReportVerdict {
2137 ok: true,
2138 error: None,
2139 },
2140 CheckProofVerdict::Err(msg) => ProofReportVerdict {
2141 ok: false,
2142 error: Some(msg),
2143 },
2144 };
2145 let mut dependencies: Vec<ProofReportDependency> = Vec::new();
2146 let mut seen: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
2147 let mut rules: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
2148 let mut stack: Vec<String> = po.premise_refs.iter().cloned().rev().collect();
2149 while let Some(refname) = stack.pop() {
2150 if seen.contains(&refname) {
2151 continue;
2152 }
2153 seen.insert(refname.clone());
2154 if let Some(ax) = self.get_proof_assumption(&refname) {
2155 dependencies.push(ProofReportDependency {
2156 name: ax.name.clone(),
2157 kind: ax.kind.clone(),
2158 rule: None,
2159 judgement: Some(key_of(&ax.judgement)),
2160 });
2161 continue;
2162 }
2163 if let Some(dep) = self.get_proof_object(&refname) {
2164 for sub in dep.premise_refs.iter().rev() {
2165 stack.push(sub.clone());
2166 }
2167 if !dep.rule.is_empty() {
2168 rules.insert(dep.rule.clone());
2169 }
2170 dependencies.push(ProofReportDependency {
2171 name: dep.name.clone(),
2172 kind: "proof-object".to_string(),
2173 rule: Some(dep.rule.clone()),
2174 judgement: Some(key_of(&dep.conclusion)),
2175 });
2176 continue;
2177 }
2178 dependencies.push(ProofReportDependency {
2179 name: refname.clone(),
2180 kind: "unknown".to_string(),
2181 rule: None,
2182 judgement: None,
2183 });
2184 }
2185 if !po.rule.is_empty() {
2186 rules.insert(po.rule.clone());
2187 }
2188 let mut root_names: std::collections::BTreeSet<String> = [
2189 "proof-replay",
2190 "structural-equality",
2191 "structural-matcher",
2192 "substitution",
2193 ]
2194 .iter()
2195 .map(|s| s.to_string())
2196 .collect();
2197 fn collect_terms(
2198 node: &Node,
2199 registry: &std::collections::HashMap<String, RootConstructDescriptor>,
2200 into: &mut std::collections::BTreeSet<String>,
2201 ) {
2202 match node {
2203 Node::Leaf(s) => {
2204 if registry.contains_key(s) {
2205 into.insert(s.clone());
2206 }
2207 }
2208 Node::List(children) => {
2209 for child in children {
2210 collect_terms(child, registry, into);
2211 }
2212 }
2213 }
2214 }
2215 collect_terms(&po.conclusion, &self.root_constructs, &mut root_names);
2216 for prem in &po.premises {
2217 collect_terms(prem, &self.root_constructs, &mut root_names);
2218 }
2219 for rule_name in &rules {
2220 if let Some(rule) = self.get_proof_rule(rule_name) {
2221 collect_terms(&rule.conclusion, &self.root_constructs, &mut root_names);
2222 for prem in &rule.premises {
2223 collect_terms(prem, &self.root_constructs, &mut root_names);
2224 }
2225 }
2226 }
2227 let root_constructs_used: Vec<String> = root_names.into_iter().collect();
2228 let mut by_semantic_status_map: std::collections::BTreeMap<String, Vec<String>> =
2229 std::collections::BTreeMap::new();
2230 let mut by_trust_status_map: std::collections::BTreeMap<String, Vec<String>> =
2231 std::collections::BTreeMap::new();
2232 for rc_name in &root_constructs_used {
2233 if let Some(rc) = self.root_constructs.get(rc_name) {
2234 let semantic =
2235 semantic_status_for_descriptor(rc).unwrap_or_else(|| "unknown".to_string());
2236 let trust = rc.status.clone().unwrap_or_else(|| "unknown".to_string());
2237 by_semantic_status_map
2238 .entry(semantic)
2239 .or_default()
2240 .push(rc_name.clone());
2241 by_trust_status_map
2242 .entry(trust)
2243 .or_default()
2244 .push(rc_name.clone());
2245 }
2246 }
2247 for v in by_semantic_status_map.values_mut() {
2248 v.sort();
2249 }
2250 for v in by_trust_status_map.values_mut() {
2251 v.sort();
2252 }
2253 ProofReport {
2254 name: name.to_string(),
2255 rule: Some(po.rule.clone()),
2256 conclusion: Some(key_of(&po.conclusion)),
2257 premises: po.premises.iter().map(key_of).collect(),
2258 premise_refs: po.premise_refs.clone(),
2259 verdict,
2260 dependencies,
2261 rules: rules.into_iter().collect(),
2262 root_constructs_used,
2263 by_semantic_status: by_semantic_status_map.into_iter().collect(),
2264 by_trust_status: by_trust_status_map.into_iter().collect(),
2265 active_foundation: active,
2266 strict_pure_links: self.strict_pure_links,
2267 }
2268 }
2269
2270 pub fn dependency_closure(&self, name: &str) -> Option<Vec<String>> {
2277 if name.is_empty() {
2278 return None;
2279 }
2280 if !self.root_constructs.contains_key(name) {
2281 return None;
2282 }
2283 Some(closure_for(self, name))
2284 }
2285
2286 pub fn register_proof_rule(&mut self, rule: ProofRule) {
2288 self.proof_rules.insert(rule.name.clone(), rule);
2289 }
2290
2291 pub fn register_proof_assumption(&mut self, assumption: ProofAssumption) {
2294 self.proof_assumptions
2295 .insert(assumption.name.clone(), assumption);
2296 }
2297
2298 pub fn register_proof_object(&mut self, po: ProofObject) {
2301 self.proof_objects.insert(po.name.clone(), po);
2302 }
2303
2304 pub fn get_proof_rule(&self, name: &str) -> Option<&ProofRule> {
2305 self.proof_rules.get(name)
2306 }
2307
2308 pub fn get_proof_assumption(&self, name: &str) -> Option<&ProofAssumption> {
2309 self.proof_assumptions.get(name)
2310 }
2311
2312 pub fn get_proof_object(&self, name: &str) -> Option<&ProofObject> {
2313 self.proof_objects.get(name)
2314 }
2315
2316 pub fn get_op(&self, name: &str) -> Option<&Op> {
2317 if let Some(op) = self.ops.get(name) {
2318 return Some(op);
2319 }
2320 let resolved = self.resolve_qualified(name);
2321 if resolved != name {
2322 return self.ops.get(&resolved);
2323 }
2324 None
2325 }
2326
2327 pub fn has_op(&self, name: &str) -> bool {
2328 if self.ops.contains_key(name) {
2329 return true;
2330 }
2331 let resolved = self.resolve_qualified(name);
2332 resolved != name && self.ops.contains_key(&resolved)
2333 }
2334
2335 pub fn qualify_name(&self, name: &str) -> String {
2340 if let Some(ns) = &self.namespace {
2341 if !name.contains('.') {
2342 return format!("{}.{}", ns, name);
2343 }
2344 }
2345 name.to_string()
2346 }
2347
2348 pub fn resolve_qualified(&self, name: &str) -> String {
2357 if let Some(dot_idx) = name.find('.') {
2358 if dot_idx > 0 {
2359 let prefix = &name[..dot_idx];
2360 let rest = &name[dot_idx + 1..];
2361 if let Some(target_ns) = self.aliases.get(prefix) {
2362 return format!("{}.{}", target_ns, rest);
2363 }
2364 }
2365 return name.to_string();
2366 }
2367 if let Some(ns) = &self.namespace {
2368 let qualified = format!("{}.{}", ns, name);
2369 if self.ops.contains_key(&qualified)
2370 || self.symbol_prob.contains_key(&qualified)
2371 || self.terms.contains(&qualified)
2372 || self.types.contains_key(&qualified)
2373 || self.lambdas.contains_key(&qualified)
2374 || self.templates.contains_key(&qualified)
2375 {
2376 return qualified;
2377 }
2378 }
2379 name.to_string()
2380 }
2381
2382 pub fn set_expr_prob(&mut self, expr_node: &Node, p: f64) {
2383 self.assign.insert(key_of(expr_node), self.clamp(p));
2384 }
2385
2386 pub fn set_symbol_prob(&mut self, sym: &str, p: f64) {
2387 self.symbol_prob.insert(sym.to_string(), self.clamp(p));
2388 }
2389
2390 pub fn get_symbol_prob(&self, sym: &str) -> f64 {
2391 if let Some(&v) = self.symbol_prob.get(sym) {
2392 return v;
2393 }
2394 let resolved = self.resolve_qualified(sym);
2395 if resolved != sym {
2396 if let Some(&v) = self.symbol_prob.get(&resolved) {
2397 return v;
2398 }
2399 }
2400 self.mid()
2401 }
2402
2403 pub fn trace(&mut self, kind: &str, detail: impl Into<String>) {
2407 if !self.trace_enabled {
2408 return;
2409 }
2410 let span = self
2411 .current_span
2412 .clone()
2413 .unwrap_or_else(|| self.default_span.clone());
2414 self.trace_events.push(TraceEvent::new(kind, detail, span));
2415 }
2416
2417 pub fn set_type(&mut self, expr: &str, type_expr: &str) {
2418 self.types.insert(expr.to_string(), type_expr.to_string());
2419 }
2420
2421 pub fn get_type(&self, expr: &str) -> Option<&String> {
2422 if let Some(recorded) = self.types.get(expr) {
2423 return Some(recorded);
2424 }
2425 let resolved = self.resolve_qualified(expr);
2426 if resolved != expr {
2427 return self.types.get(&resolved);
2428 }
2429 None
2430 }
2431
2432 pub fn set_lambda(&mut self, name: &str, lambda: Lambda) {
2433 self.lambdas.insert(name.to_string(), lambda);
2434 }
2435
2436 pub fn get_lambda(&self, name: &str) -> Option<&Lambda> {
2437 if let Some(l) = self.lambdas.get(name) {
2438 return Some(l);
2439 }
2440 let resolved = self.resolve_qualified(name);
2441 if resolved != name {
2442 return self.lambdas.get(&resolved);
2443 }
2444 None
2445 }
2446
2447 pub fn apply_op(&self, name: &str, vals: &[f64]) -> f64 {
2449 let op = match self.ops.get(name) {
2450 Some(op) => op.clone(),
2451 None => {
2452 let resolved = self.resolve_qualified(name);
2453 if resolved != name {
2454 match self.ops.get(&resolved) {
2455 Some(op) => op.clone(),
2456 None => panic!("Unknown op: {}", name),
2457 }
2458 } else {
2459 panic!("Unknown op: {}", name)
2460 }
2461 }
2462 };
2463 match op {
2464 Op::Not => {
2465 if vals.is_empty() {
2466 self.lo
2467 } else {
2468 self.hi - (vals[0] - self.lo)
2469 }
2470 }
2471 Op::Agg(agg) => dec_round(agg.apply(vals, self.lo)),
2472 Op::Eq | Op::Neq => self.lo,
2473 Op::Compose {
2474 ref outer,
2475 ref inner,
2476 } => {
2477 let inner_result = self.apply_op(inner, vals);
2478 self.apply_op(outer, &[inner_result])
2479 }
2480 Op::Add => {
2481 if vals.len() >= 2 {
2482 dec_round(vals[0] + vals[1])
2483 } else {
2484 0.0
2485 }
2486 }
2487 Op::Sub => {
2488 if vals.len() >= 2 {
2489 dec_round(vals[0] - vals[1])
2490 } else {
2491 0.0
2492 }
2493 }
2494 Op::Mul => {
2495 if vals.len() >= 2 {
2496 dec_round(vals[0] * vals[1])
2497 } else {
2498 0.0
2499 }
2500 }
2501 Op::Div => {
2502 if vals.len() >= 2 && vals[1] != 0.0 {
2503 dec_round(vals[0] / vals[1])
2504 } else {
2505 0.0
2506 }
2507 }
2508 Op::Less => {
2509 if vals.len() >= 2 && vals[0] < vals[1] {
2510 self.hi
2511 } else {
2512 self.lo
2513 }
2514 }
2515 Op::LessOrEqual => {
2516 if vals.len() >= 2 && vals[0] <= vals[1] {
2517 self.hi
2518 } else {
2519 self.lo
2520 }
2521 }
2522 Op::TruthTable {
2523 ref rows,
2524 ref fallback,
2525 } => {
2526 for row in rows {
2527 if row.inputs.len() != vals.len() {
2528 continue;
2529 }
2530 if row
2531 .inputs
2532 .iter()
2533 .zip(vals.iter())
2534 .all(|(a, b)| (*a - *b).abs() < 1e-12)
2535 {
2536 return row.output;
2537 }
2538 }
2539 match fallback {
2540 Some(prev) => self.apply_op_inner(prev, vals),
2541 None => self.lo,
2542 }
2543 }
2544 }
2545 }
2546
2547 fn apply_op_inner(&self, op: &Op, vals: &[f64]) -> f64 {
2551 let owned = op.clone();
2552 match owned {
2553 Op::Not => {
2554 if vals.is_empty() {
2555 self.lo
2556 } else {
2557 self.hi - (vals[0] - self.lo)
2558 }
2559 }
2560 Op::Agg(agg) => dec_round(agg.apply(vals, self.lo)),
2561 Op::Eq | Op::Neq => self.lo,
2562 Op::Compose { outer, inner } => {
2563 let inner_result = self.apply_op(&inner, vals);
2564 self.apply_op(&outer, &[inner_result])
2565 }
2566 Op::Add => {
2567 if vals.len() >= 2 {
2568 dec_round(vals[0] + vals[1])
2569 } else {
2570 0.0
2571 }
2572 }
2573 Op::Sub => {
2574 if vals.len() >= 2 {
2575 dec_round(vals[0] - vals[1])
2576 } else {
2577 0.0
2578 }
2579 }
2580 Op::Mul => {
2581 if vals.len() >= 2 {
2582 dec_round(vals[0] * vals[1])
2583 } else {
2584 0.0
2585 }
2586 }
2587 Op::Div => {
2588 if vals.len() >= 2 && vals[1] != 0.0 {
2589 dec_round(vals[0] / vals[1])
2590 } else {
2591 0.0
2592 }
2593 }
2594 Op::Less => {
2595 if vals.len() >= 2 && vals[0] < vals[1] {
2596 self.hi
2597 } else {
2598 self.lo
2599 }
2600 }
2601 Op::LessOrEqual => {
2602 if vals.len() >= 2 && vals[0] <= vals[1] {
2603 self.hi
2604 } else {
2605 self.lo
2606 }
2607 }
2608 Op::TruthTable { rows, fallback } => {
2609 for row in &rows {
2610 if row.inputs.len() != vals.len() {
2611 continue;
2612 }
2613 if row
2614 .inputs
2615 .iter()
2616 .zip(vals.iter())
2617 .all(|(a, b)| (*a - *b).abs() < 1e-12)
2618 {
2619 return row.output;
2620 }
2621 }
2622 match fallback {
2623 Some(prev) => self.apply_op_inner(&prev, vals),
2624 None => self.lo,
2625 }
2626 }
2627 }
2628 }
2629
2630 pub fn apply_eq(&mut self, left: &Node, right: &Node) -> f64 {
2633 if let Some(value) = lookup_assigned_infix(self, "=", left, right) {
2634 return self.clamp(value);
2635 }
2636 let options = ConvertOptions::default();
2637 let left_term = normalize_term(left, self, options);
2638 let right_term = normalize_term(right, self, options);
2639 equality_truth_value(left, right, &left_term, &right_term, self, options)
2640 }
2641
2642 pub fn apply_neq(&mut self, left: &Node, right: &Node) -> f64 {
2644 let eq_val = self.apply_eq(left, right);
2645 self.apply_op("not", &[eq_val])
2646 }
2647
2648 pub fn reinit_ops(&mut self) {
2650 self.ops.insert("not".to_string(), Op::Not);
2651 self.ops.insert("and".to_string(), Op::Agg(Aggregator::Avg));
2652 self.ops.insert("or".to_string(), Op::Agg(Aggregator::Max));
2653 self.ops
2654 .insert("both".to_string(), Op::Agg(Aggregator::Avg));
2655 self.ops
2656 .insert("neither".to_string(), Op::Agg(Aggregator::Prod));
2657 self.ops.insert("=".to_string(), Op::Eq);
2658 self.ops.insert("!=".to_string(), Op::Neq);
2659 self.ops.insert("+".to_string(), Op::Add);
2660 self.ops.insert("-".to_string(), Op::Sub);
2661 self.ops.insert("*".to_string(), Op::Mul);
2662 self.ops.insert("/".to_string(), Op::Div);
2663 self.ops.insert("<".to_string(), Op::Less);
2664 self.ops.insert("<=".to_string(), Op::LessOrEqual);
2665 self.init_truth_constants();
2667 }
2668}
2669
2670#[derive(Debug, Clone)]
2674pub enum EvalResult {
2675 Value(f64),
2676 Query(f64),
2677 TypeQuery(String),
2678 Term(Node),
2679}
2680
2681impl EvalResult {
2682 pub fn as_f64(&self) -> f64 {
2683 match self {
2684 EvalResult::Value(v) | EvalResult::Query(v) => *v,
2685 EvalResult::TypeQuery(_) | EvalResult::Term(_) => 0.0,
2686 }
2687 }
2688
2689 pub fn is_query(&self) -> bool {
2690 matches!(self, EvalResult::Query(_) | EvalResult::TypeQuery(_))
2691 }
2692
2693 pub fn is_type_query(&self) -> bool {
2694 matches!(self, EvalResult::TypeQuery(_))
2695 }
2696
2697 pub fn type_string(&self) -> Option<&str> {
2698 match self {
2699 EvalResult::TypeQuery(s) => Some(s),
2700 _ => None,
2701 }
2702 }
2703}
2704
2705pub fn parse_binding(binding: &Node) -> Option<(String, String)> {
2713 if let Node::List(children) = binding {
2714 if children.len() == 2 {
2715 if let Node::Leaf(ref s) = children[0] {
2717 if s.ends_with(':') {
2718 let param_name = s[..s.len() - 1].to_string();
2719 let param_type = match &children[1] {
2720 Node::Leaf(s) => s.clone(),
2721 other => key_of(other),
2722 };
2723 return Some((param_name, param_type));
2724 }
2725 }
2726 if let (Node::Leaf(ref type_name), Node::Leaf(ref var_name)) =
2728 (&children[0], &children[1])
2729 {
2730 if type_name.starts_with(|c: char| c.is_uppercase()) && !var_name.ends_with(':') {
2731 return Some((var_name.clone(), type_name.clone()));
2732 }
2733 }
2734 if let (Node::List(_), Node::Leaf(ref var_name)) = (&children[0], &children[1]) {
2738 if !var_name.ends_with(':') {
2739 return Some((var_name.clone(), key_of(&children[0])));
2740 }
2741 }
2742 }
2743 }
2744 None
2745}
2746
2747pub fn parse_bindings(binding: &Node) -> Option<Vec<(String, String)>> {
2750 if let Some(single) = parse_binding(binding) {
2752 return Some(vec![single]);
2753 }
2754 if let Node::List(children) = binding {
2756 let mut tokens: Vec<String> = Vec::new();
2757 for child in children {
2758 if let Node::Leaf(ref s) = child {
2759 if s.ends_with(',') {
2760 tokens.push(s[..s.len() - 1].to_string());
2761 tokens.push(",".to_string());
2762 } else {
2763 tokens.push(s.clone());
2764 }
2765 } else {
2766 return None;
2767 }
2768 }
2769 let mut bindings = Vec::new();
2770 let mut i = 0;
2771 while i < tokens.len() {
2772 if tokens[i] == "," {
2773 i += 1;
2774 continue;
2775 }
2776 if i + 1 < tokens.len() && tokens[i + 1] != "," {
2777 let type_name = &tokens[i];
2778 let var_name = &tokens[i + 1];
2779 if type_name.starts_with(|c: char| c.is_uppercase()) {
2780 bindings.push((var_name.clone(), type_name.clone()));
2781 i += 2;
2782 continue;
2783 }
2784 }
2785 return None;
2786 }
2787 if !bindings.is_empty() {
2788 return Some(bindings);
2789 }
2790 }
2791 None
2792}
2793
2794#[derive(Debug, Clone, PartialEq)]
2799enum BinderKind {
2800 Lambda,
2801 Pi,
2802 Fresh,
2803}
2804
2805#[derive(Debug, Clone)]
2806struct BinderInfo {
2807 kind: BinderKind,
2808 params: Vec<String>,
2809 body_index: usize,
2810 binding_index: usize,
2811}
2812
2813fn non_variable_token(s: &str) -> bool {
2814 matches!(
2815 s,
2816 "lambda"
2817 | "Pi"
2818 | "fresh"
2819 | "in"
2820 | "subst"
2821 | "apply"
2822 | "type"
2823 | "of"
2824 | "has"
2825 | "probability"
2826 | "with"
2827 | "proof"
2828 | "range"
2829 | "valence"
2830 | "namespace"
2831 | "import"
2832 | "as"
2833 | "is"
2834 | "?"
2835 | "mode"
2836 | "relation"
2837 | "total"
2838 | "coverage"
2839 | "world"
2840 | "inductive"
2841 | "coinductive"
2842 | "constructor"
2843 | "define"
2844 | "case"
2845 | "measure"
2846 | "lex"
2847 | "terminating"
2848 | "whnf"
2849 | "nf"
2850 | "normal-form"
2851 | "template"
2852 | "+"
2853 | "-"
2854 | "*"
2855 | "/"
2856 | "<"
2857 | "<="
2858 | "="
2859 | "!="
2860 | "and"
2861 | "or"
2862 | "not"
2863 | "both"
2864 | "neither"
2865 | "nor"
2866 )
2867}
2868
2869fn token_base_name(token: &str) -> String {
2870 token.trim_end_matches(|c| c == ':' || c == ',').to_string()
2871}
2872
2873fn is_variable_token(token: &str) -> bool {
2874 let base = token_base_name(token);
2875 !base.is_empty() && base == token && !is_num(&base) && !non_variable_token(&base)
2876}
2877
2878fn binding_param_names(binding: &Node) -> Vec<String> {
2879 parse_bindings(binding)
2880 .map(|bindings| bindings.into_iter().map(|(name, _)| name).collect())
2881 .unwrap_or_default()
2882}
2883
2884fn binder_info(expr: &Node) -> Option<BinderInfo> {
2885 if let Node::List(children) = expr {
2886 if children.len() == 3 {
2887 if let Node::Leaf(head) = &children[0] {
2888 if head == "lambda" || head == "Pi" {
2889 let params = binding_param_names(&children[1]);
2890 if !params.is_empty() {
2891 return Some(BinderInfo {
2892 kind: if head == "lambda" {
2893 BinderKind::Lambda
2894 } else {
2895 BinderKind::Pi
2896 },
2897 params,
2898 body_index: 2,
2899 binding_index: 1,
2900 });
2901 }
2902 }
2903 }
2904 }
2905 if children.len() == 4 {
2906 if let (Node::Leaf(head), Node::Leaf(var_name), Node::Leaf(in_kw)) =
2907 (&children[0], &children[1], &children[2])
2908 {
2909 if head == "fresh" && in_kw == "in" {
2910 return Some(BinderInfo {
2911 kind: BinderKind::Fresh,
2912 params: vec![var_name.clone()],
2913 body_index: 3,
2914 binding_index: 1,
2915 });
2916 }
2917 }
2918 }
2919 }
2920 None
2921}
2922
2923fn free_variables(expr: &Node) -> HashSet<String> {
2924 fn walk(expr: &Node, bound: &HashSet<String>, out: &mut HashSet<String>) {
2925 match expr {
2926 Node::Leaf(s) => {
2927 if is_variable_token(s) && !bound.contains(s) {
2928 out.insert(s.clone());
2929 }
2930 }
2931 Node::List(children) => {
2932 if let Some(binder) = binder_info(expr) {
2933 if binder.kind != BinderKind::Fresh {
2934 let params: HashSet<String> = binder.params.iter().cloned().collect();
2935 if let Node::List(binding_children) = &children[binder.binding_index] {
2936 for child in binding_children {
2937 if let Node::Leaf(s) = child {
2938 if params.contains(&token_base_name(s)) {
2939 continue;
2940 }
2941 }
2942 walk(child, bound, out);
2943 }
2944 }
2945 }
2946 let mut nested = bound.clone();
2947 for param in binder.params {
2948 nested.insert(param);
2949 }
2950 walk(&children[binder.body_index], &nested, out);
2951 return;
2952 }
2953 for child in children {
2954 walk(child, bound, out);
2955 }
2956 }
2957 }
2958 }
2959
2960 let mut out = HashSet::new();
2961 walk(expr, &HashSet::new(), &mut out);
2962 out
2963}
2964
2965fn contains_free(expr: &Node, name: &str) -> bool {
2966 free_variables(expr).contains(name)
2967}
2968
2969fn env_can_evaluate_name(env: &Env, name: &str) -> bool {
2970 if env.symbol_prob.contains_key(name)
2971 || env.terms.contains(name)
2972 || env.types.contains_key(name)
2973 || env.lambdas.contains_key(name)
2974 || env.ops.contains_key(name)
2975 || env.templates.contains_key(name)
2976 {
2977 return true;
2978 }
2979 let resolved = env.resolve_qualified(name);
2980 resolved != name
2981 && (env.symbol_prob.contains_key(&resolved)
2982 || env.terms.contains(&resolved)
2983 || env.types.contains_key(&resolved)
2984 || env.lambdas.contains_key(&resolved)
2985 || env.ops.contains_key(&resolved)
2986 || env.templates.contains_key(&resolved))
2987}
2988
2989fn has_unresolved_free_variables(expr: &Node, env: &Env) -> bool {
2990 free_variables(expr)
2991 .iter()
2992 .any(|name| !env_can_evaluate_name(env, name))
2993}
2994
2995fn collect_names(expr: &Node, out: &mut HashSet<String>) {
2996 match expr {
2997 Node::Leaf(s) => {
2998 let base = token_base_name(s);
2999 if !base.is_empty() && !is_num(&base) && !non_variable_token(&base) {
3000 out.insert(base);
3001 }
3002 }
3003 Node::List(children) => {
3004 for child in children {
3005 collect_names(child, out);
3006 }
3007 }
3008 }
3009}
3010
3011fn fresh_name(base: &str, avoid: &HashSet<String>) -> String {
3012 let mut i = 1;
3013 loop {
3014 let candidate = format!("{}_{}", base, i);
3015 if !avoid.contains(&candidate) {
3016 return candidate;
3017 }
3018 i += 1;
3019 }
3020}
3021
3022fn rename_binding_param(binding: &Node, old_name: &str, new_name: &str) -> Node {
3023 if let Node::List(children) = binding {
3024 return Node::List(
3025 children
3026 .iter()
3027 .map(|child| match child {
3028 Node::Leaf(s) if s == old_name => Node::Leaf(new_name.to_string()),
3029 Node::Leaf(s) if s == &format!("{},", old_name) => {
3030 Node::Leaf(format!("{},", new_name))
3031 }
3032 Node::Leaf(s) if s == &format!("{}:", old_name) => {
3033 Node::Leaf(format!("{}:", new_name))
3034 }
3035 _ => child.clone(),
3036 })
3037 .collect(),
3038 );
3039 }
3040 binding.clone()
3041}
3042
3043fn rename_bound_occurrences(expr: &Node, old_name: &str, new_name: &str) -> Node {
3044 match expr {
3045 Node::Leaf(s) => {
3046 if s == old_name {
3047 Node::Leaf(new_name.to_string())
3048 } else {
3049 expr.clone()
3050 }
3051 }
3052 Node::List(children) => {
3053 if let Some(binder) = binder_info(expr) {
3054 if binder.params.iter().any(|param| param == old_name) {
3055 return expr.clone();
3056 }
3057 }
3058 Node::List(
3059 children
3060 .iter()
3061 .map(|child| rename_bound_occurrences(child, old_name, new_name))
3062 .collect(),
3063 )
3064 }
3065 }
3066}
3067
3068fn rename_binder(expr: &Node, binder: &BinderInfo, old_name: &str, new_name: &str) -> Node {
3069 if let Node::List(children) = expr {
3070 let mut out = children.clone();
3071 if binder.kind == BinderKind::Fresh {
3072 out[binder.binding_index] = Node::Leaf(new_name.to_string());
3073 } else {
3074 out[binder.binding_index] =
3075 rename_binding_param(&out[binder.binding_index], old_name, new_name);
3076 }
3077 out[binder.body_index] =
3078 rename_bound_occurrences(&out[binder.body_index], old_name, new_name);
3079 Node::List(out)
3080 } else {
3081 expr.clone()
3082 }
3083}
3084
3085pub fn subst(expr: &Node, name: &str, replacement: &Node) -> Node {
3087 match expr {
3088 Node::Leaf(s) => {
3089 if s == name {
3090 replacement.clone()
3091 } else {
3092 expr.clone()
3093 }
3094 }
3095 Node::List(children) => {
3096 if let Some(binder) = binder_info(expr) {
3097 if binder.params.iter().any(|param| param == name) {
3098 return expr.clone(); }
3100 let mut current = expr.clone();
3101 let replacement_free = free_variables(replacement);
3102 if contains_free(&children[binder.body_index], name) {
3103 let mut avoid = HashSet::new();
3104 collect_names(¤t, &mut avoid);
3105 collect_names(replacement, &mut avoid);
3106 avoid.insert(name.to_string());
3107 for param in &binder.params {
3108 if replacement_free.contains(param) {
3109 let next = fresh_name(param, &avoid);
3110 avoid.insert(next.clone());
3111 let current_binder = binder_info(¤t).expect("renamed binder");
3112 current = rename_binder(¤t, ¤t_binder, param, &next);
3113 }
3114 }
3115 }
3116 if let Node::List(current_children) = current {
3117 return Node::List(
3118 current_children
3119 .iter()
3120 .map(|child| subst(child, name, replacement))
3121 .collect(),
3122 );
3123 }
3124 }
3125 Node::List(
3126 children
3127 .iter()
3128 .map(|child| subst(child, name, replacement))
3129 .collect(),
3130 )
3131 }
3132 }
3133}
3134
3135pub fn substitute(expr: &Node, name: &str, replacement: &Node) -> Node {
3137 subst(expr, name, replacement)
3138}
3139
3140fn template_key_for(env: &Env, name: &str) -> Option<String> {
3143 if env.templates.contains_key(name) {
3144 return Some(name.to_string());
3145 }
3146 let resolved = env.resolve_qualified(name);
3147 if resolved != name && env.templates.contains_key(&resolved) {
3148 return Some(resolved);
3149 }
3150 None
3151}
3152
3153fn validate_template_pattern(pattern: &Node) -> Result<(String, Vec<String>), String> {
3154 let children = match pattern {
3155 Node::List(items) if !items.is_empty() => items,
3156 _ => {
3157 return Err(
3158 "Template declaration must be `(template (<name> <param>...) <body>)`".to_string(),
3159 );
3160 }
3161 };
3162 let name = match &children[0] {
3163 Node::Leaf(s) if is_variable_token(s) => s.clone(),
3164 Node::Leaf(s) => {
3165 return Err(format!(
3166 "Template name must be a bare identifier (got \"{}\")",
3167 s
3168 ));
3169 }
3170 other => {
3171 return Err(format!(
3172 "Template name must be a bare identifier (got \"{}\")",
3173 key_of(other)
3174 ));
3175 }
3176 };
3177
3178 let mut params = Vec::new();
3179 let mut seen = HashSet::new();
3180 for param in &children[1..] {
3181 let p = match param {
3182 Node::Leaf(s) if is_variable_token(s) => s.clone(),
3183 Node::Leaf(s) => {
3184 return Err(format!(
3185 "Template parameter must be a bare identifier (got \"{}\")",
3186 s
3187 ));
3188 }
3189 other => {
3190 return Err(format!(
3191 "Template parameter must be a bare identifier (got \"{}\")",
3192 key_of(other)
3193 ));
3194 }
3195 };
3196 if !seen.insert(p.clone()) {
3197 return Err(format!(
3198 "Template parameter \"{}\" is declared more than once",
3199 p
3200 ));
3201 }
3202 params.push(p);
3203 }
3204 Ok((name, params))
3205}
3206
3207fn merge_root_construct_descriptors(
3213 previous: Option<RootConstructDescriptor>,
3214 next: RootConstructDescriptor,
3215) -> RootConstructDescriptor {
3216 let mut base = previous.unwrap_or_else(|| RootConstructDescriptor {
3217 name: next.name.clone(),
3218 ..Default::default()
3219 });
3220 base.name = next.name;
3221 if next.status.is_some() {
3222 base.status = next.status;
3223 }
3224 if next.semantic_status.is_some() {
3225 base.semantic_status = next.semantic_status;
3226 }
3227 if next.kind.is_some() {
3228 base.kind = next.kind;
3229 }
3230 if !next.depends_on.is_empty() {
3231 let mut seen: std::collections::HashSet<String> =
3232 base.depends_on.iter().cloned().collect();
3233 for d in next.depends_on {
3234 if !seen.contains(&d) {
3235 seen.insert(d.clone());
3236 base.depends_on.push(d);
3237 }
3238 }
3239 }
3240 if next.encoded_as.is_some() {
3241 base.encoded_as = next.encoded_as;
3242 }
3243 if next.pure_links_ready.is_some() {
3244 base.pure_links_ready = next.pure_links_ready;
3245 }
3246 if next.override_with.is_some() {
3247 base.override_with = next.override_with;
3248 }
3249 if next.planned_as.is_some() {
3250 base.planned_as = next.planned_as;
3251 }
3252 if next.foundation.is_some() {
3253 base.foundation = next.foundation;
3254 }
3255 base
3256}
3257
3258const SEMANTIC_STATUS_ORDER: [&str; 5] = [
3259 "host-trusted",
3260 "links-described",
3261 "links-checked",
3262 "links-evaluated",
3263 "self-hosted",
3264];
3265
3266fn semantic_status_for_trust_status(status: Option<&str>) -> Option<String> {
3267 match status {
3268 Some("host-primitive")
3269 | Some("host-derived")
3270 | Some("external-trusted")
3271 | Some("user-configurable")
3272 | Some("user-overridden") => Some("host-trusted".to_string()),
3273 Some("links-encoded") | Some("planned") => Some("links-described".to_string()),
3274 Some("links-defined") => Some("links-checked".to_string()),
3275 _ => None,
3276 }
3277}
3278
3279fn semantic_status_for_descriptor(descriptor: &RootConstructDescriptor) -> Option<String> {
3280 descriptor
3281 .semantic_status
3282 .clone()
3283 .or_else(|| semantic_status_for_trust_status(descriptor.status.as_deref()))
3284}
3285
3286fn merge_foundation_descriptors(
3287 previous: Option<FoundationDescriptor>,
3288 next: FoundationDescriptor,
3289) -> FoundationDescriptor {
3290 let mut base = previous.unwrap_or_else(|| FoundationDescriptor {
3291 name: next.name.clone(),
3292 ..Default::default()
3293 });
3294 base.name = next.name;
3295 if next.description.is_some() {
3296 base.description = next.description;
3297 }
3298 if !next.uses.is_empty() {
3299 let mut seen: std::collections::HashSet<String> = base.uses.iter().cloned().collect();
3300 for u in next.uses {
3301 if !seen.contains(&u) {
3302 seen.insert(u.clone());
3303 base.uses.push(u);
3304 }
3305 }
3306 }
3307 if !next.defines.is_empty() {
3308 for (k, v) in next.defines {
3309 if let Some(existing) = base.defines.iter_mut().find(|(name, _)| name == &k) {
3310 existing.1 = v;
3311 } else {
3312 base.defines.push((k, v));
3313 }
3314 }
3315 }
3316 if next.extends.is_some() {
3317 base.extends = next.extends;
3318 }
3319 if next.numeric_domain.is_some() {
3320 base.numeric_domain = next.numeric_domain;
3321 }
3322 if next.truth_domain.is_some() {
3323 base.truth_domain = next.truth_domain;
3324 }
3325 if !next.carrier.is_empty() {
3329 base.carrier = next.carrier;
3330 }
3331 if next.strict_carrier {
3332 base.strict_carrier = true;
3333 }
3334 for (op_name, rows) in next.truth_tables {
3338 if let Some(existing) = base
3339 .truth_tables
3340 .iter_mut()
3341 .find(|(name, _)| name == &op_name)
3342 {
3343 existing.1 = rows;
3344 } else {
3345 base.truth_tables.push((op_name, rows));
3346 }
3347 }
3348 if next.experimental {
3352 base.experimental = true;
3353 }
3354 if next.root.is_some() {
3355 base.root = next.root;
3356 }
3357 if !next.abits.is_empty() {
3358 let mut seen: std::collections::HashSet<String> =
3359 base.abits.iter().map(|(s, _)| s.clone()).collect();
3360 for (symbol, meaning) in next.abits {
3361 if !seen.contains(&symbol) {
3362 seen.insert(symbol.clone());
3363 base.abits.push((symbol, meaning));
3364 }
3365 }
3366 }
3367 base
3368}
3369
3370fn closure_for(env: &Env, name: &str) -> Vec<String> {
3379 let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
3380 let mut order: Vec<String> = Vec::new();
3381 let mut queue: std::collections::VecDeque<String> = std::collections::VecDeque::new();
3382 queue.push_back(name.to_string());
3383 while let Some(next) = queue.pop_front() {
3384 if seen.contains(&next) {
3385 continue;
3386 }
3387 seen.insert(next.clone());
3388 if next != name {
3389 order.push(next.clone());
3390 }
3391 if let Some(rc) = env.root_constructs.get(&next) {
3392 let mut deps = rc.depends_on.clone();
3393 deps.sort();
3394 for dep in deps {
3395 if !seen.contains(&dep) {
3396 queue.push_back(dep);
3397 }
3398 }
3399 }
3400 }
3401 order.sort();
3402 order
3403}
3404
3405pub fn build_dependency_graph(env: &Env) -> Vec<(String, Vec<String>)> {
3411 let mut names: Vec<String> = env.root_constructs.keys().cloned().collect();
3412 names.sort();
3413 let mut out: Vec<(String, Vec<String>)> = Vec::with_capacity(names.len());
3414 for name in names {
3415 let closure = closure_for(env, &name);
3416 out.push((name, closure));
3417 }
3418 out
3419}
3420
3421pub fn encode_anum(node: &Node) -> String {
3430 let mut out = String::new();
3431 encode_anum_into(node, &mut out);
3432 out
3433}
3434
3435fn encode_anum_into(node: &Node, out: &mut String) {
3436 match node {
3437 Node::Leaf(s) => {
3438 out.push('[');
3439 out.push('0');
3440 for byte in s.as_bytes() {
3441 for shift in (0..8).rev() {
3442 out.push(if (byte >> shift) & 1 == 1 { '1' } else { '0' });
3443 }
3444 }
3445 out.push(']');
3446 }
3447 Node::List(children) => {
3448 out.push('[');
3449 out.push('1');
3450 for child in children {
3451 encode_anum_into(child, out);
3452 }
3453 out.push(']');
3454 }
3455 }
3456}
3457
3458pub fn decode_anum(s: &str) -> Result<Node, String> {
3463 let bytes = s.as_bytes();
3464 let (node, pos) = decode_anum_at(bytes, 0)?;
3465 if pos != bytes.len() {
3466 return Err(format!("anum-decode: trailing data at position {}", pos));
3467 }
3468 Ok(node)
3469}
3470
3471fn decode_anum_at(bytes: &[u8], mut pos: usize) -> Result<(Node, usize), String> {
3472 if pos >= bytes.len() || bytes[pos] != b'[' {
3473 return Err(format!("anum-decode: expected '[' at position {}", pos));
3474 }
3475 pos += 1;
3476 if pos >= bytes.len() {
3477 return Err("anum-decode: truncated input after '['".to_string());
3478 }
3479 let tag = bytes[pos];
3480 if tag == b'0' {
3481 pos += 1;
3482 let mut bits = String::new();
3483 while pos < bytes.len() && bytes[pos] != b']' {
3484 let b = bytes[pos];
3485 if b != b'0' && b != b'1' {
3486 return Err(format!(
3487 "anum-decode: leaf payload may only contain '0' or '1' (got '{}' at {})",
3488 b as char, pos
3489 ));
3490 }
3491 bits.push(b as char);
3492 pos += 1;
3493 }
3494 if pos >= bytes.len() || bytes[pos] != b']' {
3495 return Err(format!(
3496 "anum-decode: unterminated leaf starting before position {}",
3497 pos
3498 ));
3499 }
3500 pos += 1;
3501 if bits.len() % 8 != 0 {
3502 return Err(format!(
3503 "anum-decode: leaf bit-count {} is not byte-aligned",
3504 bits.len()
3505 ));
3506 }
3507 let mut payload: Vec<u8> = Vec::with_capacity(bits.len() / 8);
3508 for chunk in bits.as_bytes().chunks(8) {
3509 let mut byte: u8 = 0;
3510 for &c in chunk {
3511 byte = (byte << 1) | (if c == b'1' { 1 } else { 0 });
3512 }
3513 payload.push(byte);
3514 }
3515 let s = String::from_utf8(payload)
3516 .map_err(|e| format!("anum-decode: invalid UTF-8 in leaf ({})", e))?;
3517 Ok((Node::Leaf(s), pos))
3518 } else if tag == b'1' {
3519 pos += 1;
3520 let mut items: Vec<Node> = Vec::new();
3521 while pos < bytes.len() && bytes[pos] != b']' {
3522 if bytes[pos] != b'[' {
3523 return Err(format!(
3524 "anum-decode: list child must start with '[' (got '{}' at {})",
3525 bytes[pos] as char, pos
3526 ));
3527 }
3528 let (child, next) = decode_anum_at(bytes, pos)?;
3529 items.push(child);
3530 pos = next;
3531 }
3532 if pos >= bytes.len() || bytes[pos] != b']' {
3533 return Err(format!(
3534 "anum-decode: unterminated list starting before position {}",
3535 pos
3536 ));
3537 }
3538 pos += 1;
3539 Ok((Node::List(items), pos))
3540 } else {
3541 Err(format!(
3542 "anum-decode: expected tag '0' or '1' after '[' at position {}",
3543 pos
3544 ))
3545 }
3546}
3547
3548fn seed_builtin_root_constructs(env: &mut Env) {
3553 let seeds: Vec<(&str, &str, &str, Vec<&str>, Option<&str>, Option<bool>)> = vec![
3554 ("lino-parser", "parser", "external-trusted", vec![], Some("links-notation"), Some(false)),
3556 ("canonical-printer", "printer", "host-primitive", vec![], Some("keyOf"), None),
3557 ("structural-equality", "equality-layer", "host-primitive", vec![], Some("isStructurallySame"), None),
3558 ("structural-matcher", "matcher", "external-trusted", vec![], Some("match_proof_pattern"), None),
3559 ("decimal-12-arithmetic", "numeric-domain", "host-primitive", vec![], Some("decRound"), Some(false)),
3560 ("+", "arithmetic-operator", "host-primitive", vec!["decimal-12-arithmetic"], None, None),
3561 ("-", "arithmetic-operator", "host-primitive", vec!["decimal-12-arithmetic"], None, None),
3562 ("*", "arithmetic-operator", "host-primitive", vec!["decimal-12-arithmetic"], None, None),
3563 ("/", "arithmetic-operator", "host-primitive", vec!["decimal-12-arithmetic"], None, None),
3564 ("<", "comparison-operator", "host-primitive", vec!["decimal-12-arithmetic"], None, None),
3565 ("<=", "comparison-operator", "host-primitive", vec!["decimal-12-arithmetic"], None, None),
3566 ("truth-range", "truth-domain", "user-configurable", vec![], Some("Env.lo/Env.hi"), None),
3567 ("valence", "truth-domain", "user-configurable", vec![], Some("Env.valence"), None),
3568 ("clamp", "truth-normalization", "host-primitive", vec![], Some("Env.clamp"), None),
3569 ("quantize", "truth-normalization", "host-primitive", vec![], Some("quantize"), None),
3570 ("true", "truth-constant", "user-configurable", vec![], None, None),
3571 ("false", "truth-constant", "user-configurable", vec![], None, None),
3572 ("unknown", "truth-constant", "user-configurable", vec![], None, None),
3573 ("undefined", "truth-constant", "user-configurable", vec![], None, None),
3574 ("avg", "aggregator", "host-primitive", vec![], None, None),
3575 ("min", "aggregator", "host-primitive", vec![], None, None),
3576 ("max", "aggregator", "host-primitive", vec![], None, None),
3577 ("product", "aggregator", "host-primitive", vec![], None, None),
3578 ("probabilistic_sum", "aggregator", "host-primitive", vec![], None, None),
3579 ("truth-table-fallback", "truth-table-fallback", "host-derived", vec![], None, None),
3580 ("not", "truth-operator", "user-configurable", vec!["truth-range", "decimal-12-arithmetic"], None, None),
3581 ("and", "truth-operator", "user-configurable", vec!["avg"], None, None),
3582 ("or", "truth-operator", "user-configurable", vec!["max"], None, None),
3583 ("both", "truth-operator", "user-configurable", vec!["avg"], None, None),
3584 ("neither", "truth-operator", "user-configurable", vec!["product"], None, None),
3585 ("=", "equality-layer", "host-primitive", vec!["structural-equality", "decimal-12-arithmetic"], None, None),
3586 ("!=", "equality-layer", "host-derived", vec!["=", "not"], None, None),
3587 ("assigned-equality", "equality-layer", "host-primitive", vec![], None, None),
3588 ("numeric-equality", "equality-layer", "host-primitive", vec!["decimal-12-arithmetic"], None, None),
3589 ("definitional-equality", "equality-layer", "host-primitive", vec!["beta-reduction", "structural-equality"], None, None),
3590 ("Type", "universe-form", "host-primitive", vec![], None, Some(false)),
3591 ("Prop", "universe-form", "host-primitive", vec!["Type"], None, None),
3592 ("Pi", "binder", "host-primitive", vec!["Type", "substitution", "freshness"], None, None),
3593 ("lambda", "binder", "host-primitive", vec!["Pi", "substitution"], None, None),
3594 ("apply", "eliminator", "host-primitive", vec!["lambda", "beta-reduction"], None, None),
3595 ("beta-reduction", "reduction-rule", "host-primitive", vec!["substitution", "freshness", "alpha-renaming"], None, None),
3596 ("substitution", "meta-operation", "host-primitive", vec![], Some("substitute"), None),
3597 ("freshness", "meta-operation", "host-primitive", vec![], Some("evalFresh"), None),
3598 ("alpha-renaming", "meta-operation", "host-primitive", vec![], None, None),
3599 ("normalization", "reduction-rule", "host-primitive", vec!["beta-reduction"], Some("normalizeTerm"), None),
3600 ("whnf", "reduction-rule", "host-primitive", vec!["beta-reduction"], Some("whnfTerm"), None),
3601 ("conversion", "equality-layer", "host-primitive", vec!["beta-reduction", "normalization", "structural-equality"], None, None),
3602 ("pi-formation", "typing-rule", "links-defined", vec!["Pi"], None, None),
3603 ("lambda-introduction", "typing-rule", "links-defined", vec!["lambda"], None, None),
3604 ("application-elimination", "typing-rule", "links-defined", vec!["apply"], None, None),
3605 ("beta-conversion", "reduction-rule", "links-defined", vec!["beta-reduction"], None, None),
3606 ("inductive", "declaration", "host-primitive", vec!["Type", "Pi"], None, None),
3607 ("coinductive", "declaration", "host-primitive", vec!["Type", "Pi"], None, None),
3608 ("proof-replay", "replay-checker", "host-primitive", vec![], Some("check.mjs"), None),
3609 ("proof-object", "proof-data", "links-encoded", vec![], Some("proof-object"), None),
3610 ("proof-rule-declaration", "proof-data", "links-encoded", vec![], Some("rule"), None),
3611 ("proof-checking-relation", "checking-relation", "links-defined", vec!["proof-replay", "structural-equality", "proof-object"], None, None),
3612 ("rule-application-check", "checking-relation", "links-defined", vec!["proof-replay", "structural-equality", "proof-rule-declaration"], None, None),
3613 ("by", "proof-rule", "host-primitive", vec![], None, None),
3614 ("Nat", "inductive-type", "links-defined", vec![], None, None),
3615 ("zero", "constructor", "links-defined", vec!["Nat"], None, None),
3616 ("succ", "constructor", "links-defined", vec!["Nat"], None, None),
3617 ("nat-equality", "equality-layer", "links-defined", vec!["Nat", "structural-equality"], Some("nat-equals"), None),
3618 ("nat-recursion", "recursor", "links-defined", vec!["Nat", "zero", "succ", "nat-equality", "proof-replay", "structural-equality"], None, None),
3619 ("add", "derived-operation", "links-defined", vec!["Nat", "zero", "succ", "nat-recursion", "nat-equality"], None, None),
3620 ("nat-add-zero", "computation-rule", "links-defined", vec!["add", "zero", "nat-recursion", "nat-equality"], None, None),
3621 ("nat-add-succ", "computation-rule", "links-defined", vec!["add", "succ", "nat-recursion", "nat-equality"], None, None),
3622 ("nat-zero-formation", "typing-rule", "links-defined", vec!["Nat", "zero"], None, None),
3623 ("nat-succ-formation", "typing-rule", "links-defined", vec!["Nat", "succ"], None, None),
3624 ("forall", "universal-quantifier", "links-defined", vec!["Nat"], None, None),
3625 ("implication", "logical-connective", "links-defined", vec![], Some("implies"), None),
3626 ("predicate-application", "logical-form", "links-defined", vec![], Some("at"), None),
3627 ("nat-induction", "proof-principle", "links-defined", vec!["Nat", "forall", "implication", "predicate-application", "substitution", "freshness", "proof-replay", "structural-equality"], None, None),
3628 ("nat-refl", "equality-rule", "links-defined", vec!["Nat", "nat-equality"], None, None),
3629 ("nat-cong-succ", "equality-rule", "links-defined", vec!["Nat", "succ", "nat-equality"], None, None),
3630 ("nat-eliminator", "eliminator", "links-defined", vec!["Nat", "nat-recursion", "nat-induction"], None, None),
3631 ("nat-rec-zero", "computation-rule", "links-defined", vec!["nat-recursion", "zero", "nat-equality"], None, None),
3632 ("nat-rec-succ", "computation-rule", "links-defined", vec!["nat-recursion", "succ", "nat-equality"], None, None),
3633 ("mul", "derived-operation", "links-defined", vec!["Nat", "zero", "succ", "add", "nat-recursion", "nat-equality"], None, None),
3634 ("nat-mul-zero", "computation-rule", "links-defined", vec!["mul", "zero", "nat-recursion", "nat-equality"], None, None),
3635 ("nat-mul-succ", "computation-rule", "links-defined", vec!["mul", "succ", "add", "nat-recursion", "nat-equality"], None, None),
3636 ("eval-nat-normalize", "evaluator-fragment", "links-defined", vec!["Nat", "zero", "succ", "add", "mul", "nat-add-zero", "nat-add-succ", "nat-mul-zero", "nat-mul-succ", "structural-matcher"], None, None),
3637 ("eval-nat", "evaluator", "links-defined", vec!["eval-nat-normalize", "nat-normal-form-to-host-number"], None, None),
3638 ("nat-normal-form-to-host-number", "renderer", "host-derived", vec!["eval-nat-normalize"], None, None),
3639 ("smt-trusted", "external-decision", "external-trusted", vec![], None, None),
3640 ("atp-trusted", "external-decision", "external-trusted", vec![], None, None),
3641 ("mode", "mode-declaration", "host-primitive", vec![], None, None),
3642 ("totality-check", "metatheorem", "host-primitive", vec![], None, None),
3643 ("coverage-check", "metatheorem", "host-primitive", vec![], None, None),
3644 ("termination-check", "metatheorem", "host-primitive", vec![], None, None),
3645 ("self.evaluator", "self-bootstrap", "links-encoded", vec![], Some("lib/self/evaluator.lino"), None),
3646 ("self.grammar", "self-bootstrap", "links-encoded", vec![], Some("lib/self/grammar.lino"), None),
3647 ("self.types", "self-bootstrap", "links-encoded", vec![], Some("lib/self/types.lino"), None),
3648 ("self.operators", "self-bootstrap", "links-encoded", vec![], Some("lib/self/operators.lino"), None),
3649 ("self.metatheorem", "self-bootstrap", "links-encoded", vec![], Some("lib/self/metatheorem.lino"), None),
3650 ];
3651 for (name, kind, status, depends_on, encoded_as, pure_links_ready) in seeds {
3653 let descriptor = RootConstructDescriptor {
3654 name: name.to_string(),
3655 status: Some(status.to_string()),
3656 semantic_status: None,
3657 kind: Some(kind.to_string()),
3658 depends_on: depends_on.into_iter().map(String::from).collect(),
3659 encoded_as: encoded_as.map(String::from),
3660 pure_links_ready,
3661 override_with: None,
3662 planned_as: if name == "Type" { Some("links-defined".to_string()) } else { None },
3663 foundation: None,
3664 };
3665 env.root_constructs.insert(descriptor.name.clone(), descriptor);
3666 }
3667 for name in ["eval-nat-normalize", "eval-nat"] {
3668 if let Some(descriptor) = env.root_constructs.get_mut(name) {
3669 descriptor.semantic_status = Some("links-evaluated".to_string());
3670 }
3671 }
3672 if let Some(descriptor) = env.root_constructs.get_mut("structural-matcher") {
3673 descriptor.semantic_status = Some("host-trusted".to_string());
3674 }
3675 if let Some(descriptor) = env.root_constructs.get_mut("nat-normal-form-to-host-number") {
3676 descriptor.semantic_status = Some("host-trusted".to_string());
3677 }
3678}
3679
3680fn parse_root_construct_form(node: &Node) -> Result<RootConstructDescriptor, String> {
3683 let children = match node {
3684 Node::List(items) => items,
3685 _ => return Err("root-construct form must be `(root-construct <name> …)`".to_string()),
3686 };
3687 if children.len() < 2 {
3688 return Err("root-construct form must be `(root-construct <name> …)`".to_string());
3689 }
3690 let head = match &children[0] {
3691 Node::Leaf(s) if s == "root-construct" => s,
3692 _ => return Err("root-construct form must be `(root-construct <name> …)`".to_string()),
3693 };
3694 let _ = head;
3695 let name = match &children[1] {
3696 Node::Leaf(s) if !s.is_empty() => s.clone(),
3697 _ => return Err("root-construct name must be a non-empty identifier".to_string()),
3698 };
3699 let mut descriptor = RootConstructDescriptor {
3700 name,
3701 ..Default::default()
3702 };
3703 for child in &children[2..] {
3704 let clause = match child {
3705 Node::List(items) => items,
3706 _ => {
3707 return Err(
3708 "root-construct child clauses must be lists led by a keyword".to_string(),
3709 );
3710 }
3711 };
3712 if clause.is_empty() {
3713 return Err(
3714 "root-construct child clauses must be lists led by a keyword".to_string(),
3715 );
3716 }
3717 let key = match &clause[0] {
3718 Node::Leaf(s) => s.as_str(),
3719 _ => {
3720 return Err(
3721 "root-construct child clauses must be lists led by a keyword".to_string(),
3722 );
3723 }
3724 };
3725 let rest: Vec<&Node> = clause.iter().skip(1).collect();
3726 match key {
3727 "status" => {
3728 if rest.len() != 1 {
3729 return Err("(status …) requires one symbol".to_string());
3730 }
3731 if let Node::Leaf(v) = rest[0] {
3732 descriptor.status = Some(v.clone());
3733 } else {
3734 return Err("(status …) requires one symbol".to_string());
3735 }
3736 }
3737 "semantic-status" => {
3738 if rest.len() != 1 {
3739 return Err("(semantic-status …) requires one symbol".to_string());
3740 }
3741 if let Node::Leaf(v) = rest[0] {
3742 descriptor.semantic_status = Some(v.clone());
3743 } else {
3744 return Err("(semantic-status …) requires one symbol".to_string());
3745 }
3746 }
3747 "kind" => {
3748 if rest.len() != 1 {
3749 return Err("(kind …) requires one symbol".to_string());
3750 }
3751 if let Node::Leaf(v) = rest[0] {
3752 descriptor.kind = Some(v.clone());
3753 } else {
3754 return Err("(kind …) requires one symbol".to_string());
3755 }
3756 }
3757 "depends-on" => {
3758 for r in &rest {
3759 descriptor.depends_on.push(key_of(r));
3760 }
3761 }
3762 "encoded-as" | "implemented-by" => {
3763 let joined = rest
3764 .iter()
3765 .map(|n| key_of(n))
3766 .collect::<Vec<_>>()
3767 .join(" ");
3768 descriptor.encoded_as = Some(joined);
3769 }
3770 "pure-links-ready" => {
3771 if rest.len() != 1 {
3772 return Err("(pure-links-ready …) must be `yes` or `no`".to_string());
3773 }
3774 if let Node::Leaf(v) = rest[0] {
3775 descriptor.pure_links_ready = Some(match v.as_str() {
3776 "yes" => true,
3777 "no" => false,
3778 _ => {
3779 return Err(
3780 "(pure-links-ready …) must be `yes` or `no`".to_string()
3781 );
3782 }
3783 });
3784 } else {
3785 return Err("(pure-links-ready …) must be `yes` or `no`".to_string());
3786 }
3787 }
3788 "override" => {
3789 let joined = rest
3790 .iter()
3791 .map(|n| key_of(n))
3792 .collect::<Vec<_>>()
3793 .join(" ");
3794 descriptor.override_with = Some(joined);
3795 }
3796 "planned-as" => {
3797 let joined = rest
3798 .iter()
3799 .map(|n| key_of(n))
3800 .collect::<Vec<_>>()
3801 .join(" ");
3802 descriptor.planned_as = Some(joined);
3803 }
3804 "foundation" => {
3805 if rest.len() != 1 {
3806 return Err("(foundation …) must be a single name".to_string());
3807 }
3808 if let Node::Leaf(v) = rest[0] {
3809 descriptor.foundation = Some(v.clone());
3810 } else {
3811 return Err("(foundation …) must be a single name".to_string());
3812 }
3813 }
3814 "surface" | "description" | "used-by" => {
3815 }
3817 _ => {
3818 }
3820 }
3821 }
3822 Ok(descriptor)
3823}
3824
3825fn parse_foundation_form(node: &Node) -> Result<FoundationDescriptor, String> {
3828 let children = match node {
3829 Node::List(items) => items,
3830 _ => return Err("foundation form must be `(foundation <name> …)`".to_string()),
3831 };
3832 if children.len() < 2 {
3833 return Err("foundation form must be `(foundation <name> …)`".to_string());
3834 }
3835 let head = match &children[0] {
3836 Node::Leaf(s) if s == "foundation" => s,
3837 _ => return Err("foundation form must be `(foundation <name> …)`".to_string()),
3838 };
3839 let _ = head;
3840 let name = match &children[1] {
3841 Node::Leaf(s) if !s.is_empty() => s.clone(),
3842 _ => return Err("foundation name must be a non-empty identifier".to_string()),
3843 };
3844 let mut foundation = FoundationDescriptor {
3845 name,
3846 ..Default::default()
3847 };
3848 for child in &children[2..] {
3849 let (key, rest): (&str, Vec<&Node>) = match child {
3854 Node::Leaf(s) if !s.is_empty() => (s.as_str(), Vec::new()),
3855 Node::List(items) if !items.is_empty() => match &items[0] {
3856 Node::Leaf(s) => (s.as_str(), items.iter().skip(1).collect()),
3857 _ => {
3858 return Err(
3859 "foundation child clauses must be lists led by a keyword".to_string(),
3860 );
3861 }
3862 },
3863 _ => {
3864 return Err(
3865 "foundation child clauses must be lists led by a keyword".to_string(),
3866 );
3867 }
3868 };
3869 match key {
3870 "uses" => {
3871 for r in &rest {
3872 foundation.uses.push(key_of(r));
3873 }
3874 }
3875 "defines" => {
3876 if rest.is_empty() {
3877 return Err(
3878 "(defines <construct> <implementation>) requires a construct name"
3879 .to_string(),
3880 );
3881 }
3882 let construct = match rest[0] {
3883 Node::Leaf(s) => s.clone(),
3884 _ => {
3885 return Err(
3886 "(defines <construct> <implementation>) requires a construct name"
3887 .to_string(),
3888 );
3889 }
3890 };
3891 let impl_str = if rest.len() > 1 {
3892 rest.iter()
3893 .skip(1)
3894 .map(|n| key_of(n))
3895 .collect::<Vec<_>>()
3896 .join(" ")
3897 } else {
3898 "links-defined".to_string()
3899 };
3900 foundation.defines.push((construct, impl_str));
3901 }
3902 "extends" => {
3903 if rest.len() != 1 {
3904 return Err("(extends …) requires one foundation name".to_string());
3905 }
3906 if let Node::Leaf(v) = rest[0] {
3907 foundation.extends = Some(v.clone());
3908 } else {
3909 return Err("(extends …) requires one foundation name".to_string());
3910 }
3911 }
3912 "numeric-domain" => {
3913 if rest.len() != 1 {
3914 return Err("(numeric-domain …) requires one name".to_string());
3915 }
3916 if let Node::Leaf(v) = rest[0] {
3917 foundation.numeric_domain = Some(v.clone());
3918 } else {
3919 return Err("(numeric-domain …) requires one name".to_string());
3920 }
3921 }
3922 "truth-domain" => {
3923 if rest.len() != 1 {
3924 return Err("(truth-domain …) requires one name".to_string());
3925 }
3926 if let Node::Leaf(v) = rest[0] {
3927 foundation.truth_domain = Some(v.clone());
3928 } else {
3929 return Err("(truth-domain …) requires one name".to_string());
3930 }
3931 }
3932 "carrier" => {
3933 if rest.is_empty() {
3939 return Err("(carrier ...) requires at least one value".to_string());
3940 }
3941 foundation.carrier = rest.iter().map(|n| key_of(n)).collect();
3942 }
3943 "strict-carrier" => {
3944 foundation.strict_carrier = true;
3949 }
3950 "truth-table" => {
3951 if rest.is_empty() {
3957 return Err(
3958 "(truth-table <op> ...) requires an operator name".to_string(),
3959 );
3960 }
3961 let op_name = match rest[0] {
3962 Node::Leaf(ref s) if !s.is_empty() => s.clone(),
3963 _ => {
3964 return Err(
3965 "(truth-table <op> ...) requires an operator name".to_string(),
3966 );
3967 }
3968 };
3969 let mut table_rows: Vec<TruthTableRow> = Vec::new();
3970 for raw in rest.iter().skip(1) {
3971 let row_items = match raw {
3972 Node::List(items) => items,
3973 _ => {
3974 return Err(format!(
3975 "(truth-table {} ...) rows must be lists like (in1 in2 -> out)",
3976 op_name
3977 ));
3978 }
3979 };
3980 let arrow_at = row_items
3981 .iter()
3982 .position(|n| matches!(n, Node::Leaf(s) if s == "->"));
3983 let arrow_at = match arrow_at {
3984 Some(idx) if idx >= 1 && idx == row_items.len() - 2 => idx,
3985 _ => {
3986 return Err(format!(
3987 "(truth-table {} ...) row must be (input ... -> output)",
3988 op_name
3989 ));
3990 }
3991 };
3992 let inputs: Vec<String> = row_items[..arrow_at]
3993 .iter()
3994 .map(|n| key_of(n))
3995 .collect();
3996 let output = key_of(&row_items[arrow_at + 1]);
3997 table_rows.push(TruthTableRow { inputs, output });
3998 }
3999 if table_rows.is_empty() {
4000 return Err(format!(
4001 "(truth-table {} ...) requires at least one row",
4002 op_name
4003 ));
4004 }
4005 foundation
4006 .truth_tables
4007 .push((op_name, table_rows));
4008 }
4009 "description" => {
4010 foundation.description = Some(
4011 rest.iter()
4012 .map(|n| key_of(n))
4013 .collect::<Vec<_>>()
4014 .join(" "),
4015 );
4016 }
4017 "experimental" => {
4018 foundation.experimental = true;
4022 }
4023 "root" => {
4024 if rest.len() != 1 {
4028 return Err("(root <symbol>) requires exactly one value".to_string());
4029 }
4030 foundation.root = Some(key_of(rest[0]));
4031 }
4032 "abit" => {
4033 if rest.is_empty() {
4038 return Err("(abit <symbol> <meaning>) requires a symbol".to_string());
4039 }
4040 let symbol = key_of(rest[0]);
4041 let meaning = rest
4042 .iter()
4043 .skip(1)
4044 .map(|n| key_of(n))
4045 .collect::<Vec<_>>()
4046 .join(" ");
4047 foundation.abits.push((symbol, meaning));
4048 }
4049 _ => {
4050 }
4052 }
4053 }
4054 Ok(foundation)
4055}
4056
4057fn is_proof_rule_shape(children: &[Node]) -> bool {
4065 if children.len() < 3 {
4066 return false;
4067 }
4068 if !matches!(&children[1], Node::Leaf(s) if !s.is_empty()) {
4069 return false;
4070 }
4071 let mut saw_conclusion = false;
4072 for c in &children[2..] {
4073 let clause = match c {
4074 Node::List(items) => items,
4075 _ => return false,
4076 };
4077 let key = match clause.first() {
4078 Some(Node::Leaf(k)) => k.as_str(),
4079 _ => return false,
4080 };
4081 match key {
4082 "premise" => {}
4083 "conclusion" => {
4084 saw_conclusion = true;
4085 }
4086 _ => return false,
4087 }
4088 }
4089 saw_conclusion
4090}
4091
4092pub fn parse_rule_form(node: &Node) -> Result<ProofRule, String> {
4100 let children = match node {
4101 Node::List(items) => items,
4102 _ => {
4103 return Err(
4104 "rule form must be `(rule <name> (premise <pat>)... (conclusion <pat>))`".to_string(),
4105 );
4106 }
4107 };
4108 if children.len() < 3 || !matches!(children.first(), Some(Node::Leaf(h)) if h == "rule") {
4109 return Err(
4110 "rule form must be `(rule <name> (premise <pat>)... (conclusion <pat>))`".to_string(),
4111 );
4112 }
4113 let name = match &children[1] {
4114 Node::Leaf(s) if !s.is_empty() => s.clone(),
4115 _ => return Err("rule name must be a non-empty identifier".to_string()),
4116 };
4117 let mut premises: Vec<Node> = Vec::new();
4118 let mut conclusion: Option<Node> = None;
4119 for child in &children[2..] {
4120 let clause = match child {
4121 Node::List(c) => c,
4122 _ => {
4123 return Err(format!(
4124 "rule {}: clauses must be lists led by a keyword",
4125 name
4126 ));
4127 }
4128 };
4129 let key = match clause.first() {
4130 Some(Node::Leaf(k)) => k.as_str(),
4131 _ => {
4132 return Err(format!(
4133 "rule {}: clauses must be lists led by a keyword",
4134 name
4135 ));
4136 }
4137 };
4138 match key {
4139 "premise" => {
4140 if clause.len() != 2 {
4141 return Err(format!(
4142 "rule {}: (premise <pat>) requires exactly one pattern",
4143 name
4144 ));
4145 }
4146 premises.push(clause[1].clone());
4147 }
4148 "conclusion" => {
4149 if clause.len() != 2 {
4150 return Err(format!(
4151 "rule {}: (conclusion <pat>) requires exactly one pattern",
4152 name
4153 ));
4154 }
4155 if conclusion.is_some() {
4156 return Err(format!(
4157 "rule {}: only one (conclusion ...) clause is allowed",
4158 name
4159 ));
4160 }
4161 conclusion = Some(clause[1].clone());
4162 }
4163 other => {
4164 return Err(format!(
4165 "rule {}: unknown clause keyword {}",
4166 name, other
4167 ));
4168 }
4169 }
4170 }
4171 let conclusion = conclusion.ok_or_else(|| {
4172 format!(
4173 "rule {}: at least one (conclusion <pat>) clause is required",
4174 name
4175 )
4176 })?;
4177 Ok(ProofRule {
4178 name,
4179 premises,
4180 conclusion,
4181 })
4182}
4183
4184pub fn parse_proof_assumption_form(node: &Node) -> Result<ProofAssumption, String> {
4187 let children = match node {
4188 Node::List(items) => items,
4189 _ => {
4190 return Err(
4191 "proof assumption form must be `(assumption <name> (judgement <j>))` or `(axiom <name> (judgement <j>))`".to_string(),
4192 );
4193 }
4194 };
4195 if children.len() < 2 {
4196 return Err(
4197 "proof assumption form must be `(assumption <name> (judgement <j>))` or `(axiom <name> (judgement <j>))`".to_string(),
4198 );
4199 }
4200 let kind = match &children[0] {
4201 Node::Leaf(s) if s == "assumption" || s == "axiom" => s.clone(),
4202 _ => {
4203 return Err(
4204 "proof assumption form must be `(assumption <name> (judgement <j>))` or `(axiom <name> (judgement <j>))`".to_string(),
4205 );
4206 }
4207 };
4208 let name = match &children[1] {
4209 Node::Leaf(s) if !s.is_empty() => s.clone(),
4210 _ => return Err(format!("{} name must be a non-empty identifier", kind)),
4211 };
4212 let mut judgement: Option<Node> = None;
4213 for child in &children[2..] {
4214 let clause = match child {
4215 Node::List(c) => c,
4216 _ => {
4217 return Err(format!(
4218 "{} {}: clauses must be lists led by a keyword",
4219 kind, name
4220 ));
4221 }
4222 };
4223 let key = match clause.first() {
4224 Some(Node::Leaf(k)) => k.as_str(),
4225 _ => {
4226 return Err(format!(
4227 "{} {}: clauses must be lists led by a keyword",
4228 kind, name
4229 ));
4230 }
4231 };
4232 match key {
4233 "judgement" => {
4234 if clause.len() != 2 {
4235 return Err(format!(
4236 "{} {}: (judgement <j>) requires one argument",
4237 kind, name
4238 ));
4239 }
4240 if judgement.is_some() {
4241 return Err(format!(
4242 "{} {}: only one (judgement ...) clause is allowed",
4243 kind, name
4244 ));
4245 }
4246 judgement = Some(clause[1].clone());
4247 }
4248 other => {
4249 return Err(format!(
4250 "{} {}: unknown clause keyword {}",
4251 kind, name, other
4252 ));
4253 }
4254 }
4255 }
4256 let judgement = judgement
4257 .ok_or_else(|| format!("{} {}: (judgement <j>) clause is required", kind, name))?;
4258 Ok(ProofAssumption {
4259 name,
4260 kind,
4261 judgement,
4262 })
4263}
4264
4265pub fn parse_proof_object_form(node: &Node) -> Result<ProofObject, String> {
4269 let children = match node {
4270 Node::List(items) => items,
4271 _ => {
4272 return Err(
4273 "proof-object form must be `(proof-object <name> (applies <rule>) ...)`"
4274 .to_string(),
4275 );
4276 }
4277 };
4278 if children.len() < 2 || !matches!(children.first(), Some(Node::Leaf(h)) if h == "proof-object")
4279 {
4280 return Err(
4281 "proof-object form must be `(proof-object <name> (applies <rule>) ...)`".to_string(),
4282 );
4283 }
4284 let name = match &children[1] {
4285 Node::Leaf(s) if !s.is_empty() => s.clone(),
4286 _ => return Err("proof-object name must be a non-empty identifier".to_string()),
4287 };
4288 let mut rule: Option<String> = None;
4289 let mut premises: Vec<Node> = Vec::new();
4290 let mut premise_refs: Vec<String> = Vec::new();
4291 let mut conclusion: Option<Node> = None;
4292 for child in &children[2..] {
4293 let clause = match child {
4294 Node::List(c) => c,
4295 _ => {
4296 return Err(format!(
4297 "proof-object {}: clauses must be lists led by a keyword",
4298 name
4299 ));
4300 }
4301 };
4302 let key = match clause.first() {
4303 Some(Node::Leaf(k)) => k.as_str(),
4304 _ => {
4305 return Err(format!(
4306 "proof-object {}: clauses must be lists led by a keyword",
4307 name
4308 ));
4309 }
4310 };
4311 match key {
4312 "applies" => {
4313 if clause.len() != 2 {
4314 return Err(format!(
4315 "proof-object {}: (applies <rule>) requires a rule name",
4316 name
4317 ));
4318 }
4319 rule = match &clause[1] {
4320 Node::Leaf(s) if !s.is_empty() => Some(s.clone()),
4321 _ => {
4322 return Err(format!(
4323 "proof-object {}: (applies <rule>) requires a rule name",
4324 name
4325 ));
4326 }
4327 };
4328 }
4329 "premise" => {
4330 if clause.len() != 2 {
4331 return Err(format!(
4332 "proof-object {}: (premise <judgement>) requires one argument",
4333 name
4334 ));
4335 }
4336 premises.push(clause[1].clone());
4337 }
4338 "premise-by" => {
4339 if clause.len() != 2 {
4340 return Err(format!(
4341 "proof-object {}: (premise-by <name>) requires a dependency name",
4342 name
4343 ));
4344 }
4345 match &clause[1] {
4346 Node::Leaf(s) if !s.is_empty() => premise_refs.push(s.clone()),
4347 _ => {
4348 return Err(format!(
4349 "proof-object {}: (premise-by <name>) requires a dependency name",
4350 name
4351 ));
4352 }
4353 }
4354 }
4355 "uses" => {
4356 if clause.len() < 2 {
4357 return Err(format!(
4358 "proof-object {}: (uses <name>...) requires at least one dependency name",
4359 name
4360 ));
4361 }
4362 for dep in &clause[1..] {
4363 match dep {
4364 Node::Leaf(s) if !s.is_empty() => premise_refs.push(s.clone()),
4365 _ => {
4366 return Err(format!(
4367 "proof-object {}: (uses ...) dependencies must be names",
4368 name
4369 ));
4370 }
4371 }
4372 }
4373 }
4374 "conclusion" => {
4375 if clause.len() != 2 {
4376 return Err(format!(
4377 "proof-object {}: (conclusion <judgement>) requires one argument",
4378 name
4379 ));
4380 }
4381 if conclusion.is_some() {
4382 return Err(format!(
4383 "proof-object {}: only one (conclusion ...) clause is allowed",
4384 name
4385 ));
4386 }
4387 conclusion = Some(clause[1].clone());
4388 }
4389 other => {
4390 return Err(format!(
4391 "proof-object {}: unknown clause keyword {}",
4392 name, other
4393 ));
4394 }
4395 }
4396 }
4397 let rule =
4398 rule.ok_or_else(|| format!("proof-object {}: (applies <rule>) clause is required", name))?;
4399 let conclusion = conclusion.ok_or_else(|| {
4400 format!(
4401 "proof-object {}: (conclusion <judgement>) clause is required",
4402 name
4403 )
4404 })?;
4405 Ok(ProofObject {
4406 name,
4407 rule,
4408 premises,
4409 premise_refs,
4410 conclusion,
4411 })
4412}
4413
4414pub fn match_proof_pattern(
4419 pattern: &Node,
4420 candidate: &Node,
4421 subs: &mut HashMap<String, Node>,
4422) -> bool {
4423 match pattern {
4424 Node::Leaf(token) if token.starts_with('?') => {
4425 if let Some(prev) = subs.get(token) {
4426 key_of(prev) == key_of(candidate)
4427 } else {
4428 subs.insert(token.clone(), candidate.clone());
4429 true
4430 }
4431 }
4432 Node::Leaf(token) => matches!(candidate, Node::Leaf(c) if c == token),
4433 Node::List(pat_children) => match candidate {
4434 Node::List(cand_children) => {
4435 if pat_children.len() != cand_children.len() {
4436 return false;
4437 }
4438 for (p, c) in pat_children.iter().zip(cand_children.iter()) {
4439 if !match_proof_pattern(p, c, subs) {
4440 return false;
4441 }
4442 }
4443 true
4444 }
4445 _ => false,
4446 },
4447 }
4448}
4449
4450pub enum CheckProofVerdict {
4454 Ok(HashMap<String, Node>),
4455 Err(String),
4456}
4457
4458pub fn check_proof_object(env: &Env, name: &str) -> CheckProofVerdict {
4459 match check_proof_object_inner(env, name, &[]) {
4460 Ok(subs) => CheckProofVerdict::Ok(subs),
4461 Err(message) => CheckProofVerdict::Err(message),
4462 }
4463}
4464
4465fn resolve_proof_dependency(env: &Env, name: &str, stack: &[String]) -> Result<Node, String> {
4466 if let Some(assumption) = env.get_proof_assumption(name) {
4467 return Ok(assumption.judgement.clone());
4468 }
4469 let po = env
4470 .get_proof_object(name)
4471 .ok_or_else(|| format!("unknown proof dependency {}", name))?;
4472 check_proof_object_inner(env, name, stack)?;
4473 Ok(po.conclusion.clone())
4474}
4475
4476fn check_proof_object_inner(
4477 env: &Env,
4478 name: &str,
4479 stack: &[String],
4480) -> Result<HashMap<String, Node>, String> {
4481 if stack.iter().any(|n| n == name) {
4482 let mut cycle = stack.to_vec();
4483 cycle.push(name.to_string());
4484 return Err(format!("cyclic proof dependency: {}", cycle.join(" -> ")));
4485 }
4486 let po = match env.get_proof_object(name) {
4487 Some(po) => po,
4488 None => return Err(format!("unknown proof-object {}", name)),
4489 };
4490 let rule = match env.get_proof_rule(&po.rule) {
4491 Some(r) => r,
4492 None => {
4493 return Err(format!(
4494 "proof-object {} references unknown rule {}",
4495 name, po.rule
4496 ));
4497 }
4498 };
4499
4500 let mut effective_premises = po.premises.clone();
4501 if !po.premise_refs.is_empty() {
4502 effective_premises.clear();
4503 let mut dependency_stack = stack.to_vec();
4504 dependency_stack.push(name.to_string());
4505 for (idx, dep) in po.premise_refs.iter().enumerate() {
4506 let judgement = resolve_proof_dependency(env, dep, &dependency_stack)?;
4507 if let Some(explicit) = po.premises.get(idx) {
4508 if key_of(explicit) != key_of(&judgement) {
4509 return Err(format!(
4510 "proof-object {}: premise {} does not match referenced judgement {}",
4511 name,
4512 idx + 1,
4513 dep
4514 ));
4515 }
4516 }
4517 effective_premises.push(judgement);
4518 }
4519 if !po.premises.is_empty() && po.premises.len() != po.premise_refs.len() {
4520 return Err(format!(
4521 "proof-object {}: has {} explicit premise(s) but {} proof dependency reference(s)",
4522 name,
4523 po.premises.len(),
4524 po.premise_refs.len()
4525 ));
4526 }
4527 } else if !po.premises.is_empty() {
4528 return Err(format!(
4529 "proof-object {}: premise 1 is unjustified; use (premise-by <name>) or declare an assumption/axiom",
4530 name
4531 ));
4532 }
4533
4534 if effective_premises.len() != rule.premises.len() {
4535 return Err(format!(
4536 "proof-object {}: expected {} premise(s) for rule {}, got {}",
4537 name,
4538 rule.premises.len(),
4539 po.rule,
4540 effective_premises.len()
4541 ));
4542 }
4543 let mut subs: HashMap<String, Node> = HashMap::new();
4544 for (i, (pat, cand)) in rule
4545 .premises
4546 .iter()
4547 .zip(effective_premises.iter())
4548 .enumerate()
4549 {
4550 if !match_proof_pattern(pat, cand, &mut subs) {
4551 return Err(format!(
4552 "proof-object {}: premise {} does not match rule {}",
4553 name,
4554 i + 1,
4555 po.rule
4556 ));
4557 }
4558 }
4559 if !match_proof_pattern(&rule.conclusion, &po.conclusion, &mut subs) {
4560 return Err(format!(
4561 "proof-object {}: conclusion does not match rule {}",
4562 name, po.rule
4563 ));
4564 }
4565 Ok(subs)
4566}
4567
4568#[derive(Debug, Clone, PartialEq, Eq)]
4579pub struct StrictFoundationDecl {
4580 pub profile: String,
4581}
4582
4583#[derive(Debug, Clone, PartialEq, Eq)]
4585pub struct AllowHostPrimitiveDecl {
4586 pub names: Vec<String>,
4587}
4588
4589pub fn parse_strict_foundation_form(node: &Node) -> Result<StrictFoundationDecl, String> {
4590 let children = match node {
4591 Node::List(items) => items,
4592 _ => return Err("(strict-foundation <profile>) is required".to_string()),
4593 };
4594 if children.is_empty() || !matches!(children.first(), Some(Node::Leaf(h)) if h == "strict-foundation") {
4595 return Err("(strict-foundation <profile>) is required".to_string());
4596 }
4597 if children.len() != 2 {
4598 return Err("(strict-foundation <profile>) requires a single profile name".to_string());
4599 }
4600 let profile = match &children[1] {
4601 Node::Leaf(s) if !s.is_empty() => s.clone(),
4602 _ => return Err("(strict-foundation <profile>) requires a single profile name".to_string()),
4603 };
4604 if profile != "pure-links" {
4605 return Err(format!(
4606 "unknown strict-foundation profile: {} (expected pure-links)",
4607 profile
4608 ));
4609 }
4610 Ok(StrictFoundationDecl { profile })
4611}
4612
4613pub fn parse_allow_host_primitive_form(node: &Node) -> Result<AllowHostPrimitiveDecl, String> {
4614 let children = match node {
4615 Node::List(items) => items,
4616 _ => return Err("(allow-host-primitive <name>...) is required".to_string()),
4617 };
4618 if children.is_empty() || !matches!(children.first(), Some(Node::Leaf(h)) if h == "allow-host-primitive") {
4619 return Err("(allow-host-primitive <name>...) is required".to_string());
4620 }
4621 if children.len() < 2 {
4622 return Err(
4623 "(allow-host-primitive <name>...) requires at least one construct name".to_string(),
4624 );
4625 }
4626 let mut names = Vec::new();
4627 for child in &children[1..] {
4628 match child {
4629 Node::Leaf(s) if !s.is_empty() => names.push(s.clone()),
4630 _ => {
4631 return Err(
4632 "(allow-host-primitive ...) names must be non-empty identifiers".to_string()
4633 );
4634 }
4635 }
4636 }
4637 Ok(AllowHostPrimitiveDecl { names })
4638}
4639
4640fn pure_links_scanner_ignored(name: &str) -> bool {
4644 matches!(
4645 name,
4646 "?" | "with"
4647 | "proof"
4648 | "by"
4649 | "because"
4650 | "let"
4651 | "in"
4652 | "where"
4653 | ":"
4654 | "::"
4655 | "has"
4656 | "probability"
4657 | "is"
4658 | "a"
4659 | "an"
4660 | "sequence"
4661 | "normalizes-to"
4662 | "applies"
4663 | "premise"
4664 | "premise-by"
4665 | "conclusion"
4666 | "uses"
4667 | "judgement"
4668 | "assumption"
4669 | "axiom"
4670 | "rule"
4671 | "proof-object"
4672 | "check-proof"
4673 | "proof-report"
4674 | "foundation"
4675 | "with-foundation"
4676 | "foundation-report"
4677 | "foundation-report?"
4678 | "root-construct"
4679 | "strict-carrier"
4680 | "truth-table"
4681 | "strict-foundation"
4682 | "allow-host-primitive"
4683 )
4684}
4685
4686fn is_strictly_offending_status(status: Option<&String>) -> bool {
4687 match status {
4688 Some(s) => s == "host-primitive" || s == "host-derived",
4689 None => false,
4690 }
4691}
4692
4693fn strict_dependency_offenders(env: &Env, name: &str, path: &[String]) -> Vec<String> {
4694 if env.allowed_host_primitives.contains(name) {
4695 return Vec::new();
4696 }
4697 if path.iter().any(|n| n == name) {
4698 return Vec::new();
4699 }
4700 let mut current_path = path.to_vec();
4701 current_path.push(name.to_string());
4702 let active = env.active_implementations.get(name);
4703 let rc = env.root_constructs.get(name);
4704 let status = active
4705 .and_then(|i| i.status.as_ref())
4706 .or_else(|| rc.and_then(|r| r.status.as_ref()));
4707 let deps: Vec<String> = active
4708 .map(|i| i.depends_on.clone())
4709 .or_else(|| rc.map(|r| r.depends_on.clone()))
4710 .unwrap_or_default();
4711
4712 if matches!(
4713 active.and_then(|i| i.status.as_deref()),
4714 Some("links-defined")
4715 ) && deps.is_empty()
4716 {
4717 return Vec::new();
4718 }
4719 if is_strictly_offending_status(status) && deps.is_empty() {
4720 return vec![format!(
4721 "{} -> {}",
4722 current_path.join(" -> "),
4723 status.cloned().unwrap_or_default()
4724 )];
4725 }
4726
4727 let mut offenders: Vec<String> = Vec::new();
4728 for dep in deps {
4729 if env.allowed_host_primitives.contains(&dep) {
4730 continue;
4731 }
4732 offenders.extend(strict_dependency_offenders(env, &dep, ¤t_path));
4733 }
4734 if is_strictly_offending_status(status) && offenders.is_empty() {
4735 offenders.push(format!(
4736 "{} -> {}",
4737 current_path.join(" -> "),
4738 status.cloned().unwrap_or_default()
4739 ));
4740 }
4741 offenders
4742}
4743
4744pub fn scan_pure_links_offenders(node: &Node, env: &Env) -> Vec<String> {
4748 if !env.strict_pure_links {
4749 return Vec::new();
4750 }
4751 let mut offenders: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
4752 fn check(name: &str, env: &Env, offenders: &mut std::collections::BTreeSet<String>) {
4753 if pure_links_scanner_ignored(name) || env.allowed_host_primitives.contains(name) {
4754 return;
4755 }
4756 for offender in strict_dependency_offenders(env, name, &[]) {
4757 offenders.insert(offender);
4758 }
4759 }
4760 fn visit(node: &Node, env: &Env, offenders: &mut std::collections::BTreeSet<String>) {
4761 match node {
4762 Node::List(children) => {
4763 if let Some(Node::Leaf(head)) = children.first() {
4764 check(head, env, offenders);
4765 }
4766 if children.len() == 3 {
4768 if let Node::Leaf(op) = &children[1] {
4769 check(op, env, offenders);
4770 }
4771 }
4772 for c in children {
4773 visit(c, env, offenders);
4774 }
4775 }
4776 Node::Leaf(s) => {
4777 check(s, env, offenders);
4778 }
4779 }
4780 }
4781 visit(node, env, &mut offenders);
4782 offenders.into_iter().collect()
4783}
4784
4785pub fn format_foundation_report(report: &FoundationReport) -> String {
4788 let mut lines: Vec<String> = Vec::new();
4789 lines.push("Foundation report:".to_string());
4790 lines.push(format!(" active foundation: {}", report.active_foundation));
4791 if let Some(d) = &report.description {
4792 lines.push(format!(" description: {}", d));
4793 }
4794 if let Some(n) = &report.numeric_domain {
4795 lines.push(format!(" numeric domain: {}", n));
4796 }
4797 if let Some(t) = &report.truth_domain {
4798 lines.push(format!(" truth domain: {}", t));
4799 }
4800 let ordered_statuses = [
4801 "host-primitive",
4802 "host-derived",
4803 "external-trusted",
4804 "user-configurable",
4805 "links-encoded",
4806 "links-defined",
4807 "user-overridden",
4808 "planned",
4809 ];
4810 let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
4811 for status in ordered_statuses.iter() {
4812 if let Some((_, names)) = report.by_status.iter().find(|(s, _)| s == status) {
4813 if !names.is_empty() {
4814 lines.push(String::new());
4815 lines.push(format!("{}:", status));
4816 for n in names {
4817 lines.push(format!(" - {}", n));
4818 }
4819 seen.insert((*status).to_string());
4820 }
4821 }
4822 }
4823 for (status, names) in &report.by_status {
4824 if seen.contains(status) || names.is_empty() {
4825 continue;
4826 }
4827 lines.push(String::new());
4828 lines.push(format!("{}:", status));
4829 for n in names {
4830 lines.push(format!(" - {}", n));
4831 }
4832 }
4833 if !report.by_semantic_status.is_empty() {
4834 lines.push(String::new());
4835 lines.push("semantic statuses:".to_string());
4836 let mut seen_semantic: std::collections::HashSet<String> =
4837 std::collections::HashSet::new();
4838 for status in SEMANTIC_STATUS_ORDER.iter() {
4839 if let Some((_, names)) = report
4840 .by_semantic_status
4841 .iter()
4842 .find(|(s, _)| s == status)
4843 {
4844 if !names.is_empty() {
4845 lines.push(format!(" {}: {}", status, names.join(", ")));
4846 seen_semantic.insert((*status).to_string());
4847 }
4848 }
4849 }
4850 for (status, names) in &report.by_semantic_status {
4851 if seen_semantic.contains(status) || names.is_empty() {
4852 continue;
4853 }
4854 lines.push(format!(" {}: {}", status, names.join(", ")));
4855 }
4856 }
4857 if !report.active_implementations.is_empty() {
4858 lines.push(String::new());
4859 lines.push("active implementations:".to_string());
4860 for implementation in &report.active_implementations {
4861 let mut parts: Vec<String> = Vec::new();
4862 if let Some(status) = &implementation.status {
4863 parts.push(status.clone());
4864 }
4865 if let Some(semantic_status) = &implementation.semantic_status {
4866 parts.push(format!("semantic {}", semantic_status));
4867 }
4868 if let Some(implementation_name) = &implementation.implementation {
4869 parts.push(format!("via {}", implementation_name));
4870 }
4871 if let Some(foundation) = &implementation.foundation {
4872 parts.push(format!("foundation {}", foundation));
4873 }
4874 if !implementation.depends_on.is_empty() {
4875 parts.push(format!(
4876 "depends on {}",
4877 implementation.depends_on.join(", ")
4878 ));
4879 }
4880 lines.push(format!(
4881 " - {}: {}",
4882 implementation.construct,
4883 parts.join("; ")
4884 ));
4885 }
4886 }
4887 if !report.proof_rules.is_empty() {
4888 lines.push(String::new());
4889 lines.push("proof rules:".to_string());
4890 for r in &report.proof_rules {
4891 lines.push(format!(
4892 " - {} ({} premises → {})",
4893 r.name,
4894 r.premises.len(),
4895 r.conclusion
4896 ));
4897 }
4898 }
4899 if !report.proof_assumptions.is_empty() {
4900 lines.push(String::new());
4901 lines.push("proof assumptions:".to_string());
4902 for a in &report.proof_assumptions {
4903 lines.push(format!(" - {} [{}] : {}", a.name, a.kind, a.judgement));
4904 }
4905 }
4906 if !report.proof_objects.is_empty() {
4907 lines.push(String::new());
4908 lines.push("proof objects:".to_string());
4909 for po in &report.proof_objects {
4910 let refs = if po.premise_refs.is_empty() {
4911 String::new()
4912 } else {
4913 format!(" using {}", po.premise_refs.join(", "))
4914 };
4915 lines.push(format!(
4916 " - {} : applies {} ({} premises{} → {})",
4917 po.name,
4918 po.rule,
4919 po.premises.len(),
4920 refs,
4921 po.conclusion
4922 ));
4923 }
4924 }
4925 if !report.foundations.is_empty() {
4926 lines.push(String::new());
4927 lines.push("foundations:".to_string());
4928 for f in &report.foundations {
4929 let suffix = f
4930 .description
4931 .as_ref()
4932 .map(|d| format!(" — {}", d))
4933 .unwrap_or_default();
4934 let tag = if f.experimental {
4935 " [experimental]"
4936 } else {
4937 ""
4938 };
4939 lines.push(format!(" - {}{}{}", f.name, tag, suffix));
4940 if let Some(n) = &f.numeric_domain {
4941 lines.push(format!(" numeric domain: {}", n));
4942 }
4943 if let Some(t) = &f.truth_domain {
4944 lines.push(format!(" truth domain: {}", t));
4945 }
4946 if let Some(r) = &f.root {
4947 lines.push(format!(" root: {}", r));
4948 }
4949 if !f.abits.is_empty() {
4950 let parts: Vec<String> = f
4951 .abits
4952 .iter()
4953 .map(|(s, m)| format!("{}={}", s, m))
4954 .collect();
4955 lines.push(format!(" abits: {}", parts.join(", ")));
4956 }
4957 if !f.uses.is_empty() {
4958 lines.push(format!(" uses: {}", f.uses.join(", ")));
4959 }
4960 if !f.defines.is_empty() {
4961 let parts: Vec<String> = f
4962 .defines
4963 .iter()
4964 .map(|(k, v)| format!("{}={}", k, v))
4965 .collect();
4966 lines.push(format!(" defines: {}", parts.join(", ")));
4967 }
4968 if !f.truth_tables.is_empty() {
4969 let mut sorted: Vec<&(String, Vec<TruthTableRow>)> =
4970 f.truth_tables.iter().collect();
4971 sorted.sort_by(|a, b| a.0.cmp(&b.0));
4972 let parts: Vec<String> = sorted
4973 .iter()
4974 .map(|(op, rows)| format!("{}({} rows)", op, rows.len()))
4975 .collect();
4976 lines.push(format!(" truth tables: {}", parts.join(", ")));
4977 }
4978 }
4979 }
4980 if report.strict_pure_links {
4981 lines.push(String::new());
4982 lines.push("pure-links strict mode: on".to_string());
4983 if !report.allowed_host_primitives.is_empty() {
4984 lines.push(format!(
4985 " allowed host primitives: {}",
4986 report.allowed_host_primitives.join(", ")
4987 ));
4988 }
4989 }
4990 if !report.dependency_graph.is_empty() {
4991 let non_empty: Vec<&(String, Vec<String>)> = report
4992 .dependency_graph
4993 .iter()
4994 .filter(|(_, deps)| !deps.is_empty())
4995 .collect();
4996 if !non_empty.is_empty() {
4997 lines.push(String::new());
4998 lines.push("dependency graph (transitive):".to_string());
4999 for (name, deps) in non_empty {
5000 lines.push(format!(" - {} → {}", name, deps.join(", ")));
5001 }
5002 }
5003 }
5004 lines.join("\n")
5005}
5006
5007pub fn format_proof_report(report: &ProofReport) -> String {
5010 let mut lines: Vec<String> = Vec::new();
5011 lines.push(format!("Proof report for {}:", report.name));
5012 lines.push(format!(
5013 " verdict: {}",
5014 if report.verdict.ok { "ok" } else { "error" }
5015 ));
5016 if let Some(err) = &report.verdict.error {
5017 lines.push(format!(" error: {}", err));
5018 }
5019 if let Some(rule) = &report.rule {
5020 lines.push(format!(" rule: {}", rule));
5021 }
5022 if let Some(conc) = &report.conclusion {
5023 lines.push(format!(" conclusion: {}", conc));
5024 }
5025 if !report.premises.is_empty() {
5026 lines.push(format!(" premises: {}", report.premises.join(", ")));
5027 }
5028 if !report.premise_refs.is_empty() {
5029 lines.push(format!(
5030 " premise refs: {}",
5031 report.premise_refs.join(", ")
5032 ));
5033 }
5034 if !report.dependencies.is_empty() {
5035 lines.push(String::new());
5036 lines.push("dependencies (transitive):".to_string());
5037 for d in &report.dependencies {
5038 let extra = match (&d.rule, &d.judgement) {
5039 (Some(r), Some(j)) => format!(" — applies {} → {}", r, j),
5040 (Some(r), None) => format!(" — applies {}", r),
5041 (None, Some(j)) => format!(" — {}", j),
5042 (None, None) => String::new(),
5043 };
5044 lines.push(format!(" - {} [{}]{}", d.name, d.kind, extra));
5045 }
5046 }
5047 if !report.rules.is_empty() {
5048 lines.push(String::new());
5049 lines.push(format!("rules applied: {}", report.rules.join(", ")));
5050 }
5051 if !report.root_constructs_used.is_empty() {
5052 lines.push(String::new());
5053 lines.push(format!(
5054 "root constructs used: {}",
5055 report.root_constructs_used.join(", ")
5056 ));
5057 }
5058 if !report.by_semantic_status.is_empty() {
5059 lines.push(String::new());
5060 lines.push("semantic statuses:".to_string());
5061 let mut seen: std::collections::HashSet<String> =
5062 std::collections::HashSet::new();
5063 for status in SEMANTIC_STATUS_ORDER.iter() {
5064 if let Some((_, names)) = report
5065 .by_semantic_status
5066 .iter()
5067 .find(|(s, _)| s == status)
5068 {
5069 if !names.is_empty() {
5070 lines.push(format!(" {}: {}", status, names.join(", ")));
5071 seen.insert((*status).to_string());
5072 }
5073 }
5074 }
5075 for (status, names) in &report.by_semantic_status {
5076 if seen.contains(status) || names.is_empty() {
5077 continue;
5078 }
5079 lines.push(format!(" {}: {}", status, names.join(", ")));
5080 }
5081 }
5082 if !report.by_trust_status.is_empty() {
5083 lines.push(String::new());
5084 lines.push("trust statuses:".to_string());
5085 for (status, names) in &report.by_trust_status {
5086 if names.is_empty() {
5087 continue;
5088 }
5089 lines.push(format!(" {}: {}", status, names.join(", ")));
5090 }
5091 }
5092 lines.push(String::new());
5093 lines.push(format!(" active foundation: {}", report.active_foundation));
5094 if report.strict_pure_links {
5095 lines.push(" pure-links strict mode: on".to_string());
5096 }
5097 lines.join("\n")
5098}
5099
5100#[derive(Debug, Clone, PartialEq)]
5103pub struct EvalNatResult {
5104 pub value: f64,
5105 pub normal_form: Node,
5106 pub steps: Vec<String>,
5107}
5108
5109fn leaf_node(s: &str) -> Node {
5110 Node::Leaf(s.to_string())
5111}
5112
5113fn list_node(items: Vec<Node>) -> Node {
5114 Node::List(items)
5115}
5116
5117fn default_eval_nat_rule(name: &str) -> Option<ProofRule> {
5118 match name {
5119 "nat-add-zero" => Some(ProofRule {
5120 name: name.to_string(),
5121 premises: vec![list_node(vec![
5122 leaf_node("?n"),
5123 leaf_node("has-type"),
5124 leaf_node("Nat"),
5125 ])],
5126 conclusion: list_node(vec![
5127 list_node(vec![leaf_node("add"), leaf_node("zero"), leaf_node("?n")]),
5128 leaf_node("nat-equals"),
5129 leaf_node("?n"),
5130 ]),
5131 }),
5132 "nat-add-succ" => Some(ProofRule {
5133 name: name.to_string(),
5134 premises: vec![list_node(vec![
5135 list_node(vec![leaf_node("add"), leaf_node("?m"), leaf_node("?n")]),
5136 leaf_node("nat-equals"),
5137 leaf_node("?k"),
5138 ])],
5139 conclusion: list_node(vec![
5140 list_node(vec![
5141 leaf_node("add"),
5142 list_node(vec![leaf_node("succ"), leaf_node("?m")]),
5143 leaf_node("?n"),
5144 ]),
5145 leaf_node("nat-equals"),
5146 list_node(vec![leaf_node("succ"), leaf_node("?k")]),
5147 ]),
5148 }),
5149 "nat-mul-zero" => Some(ProofRule {
5150 name: name.to_string(),
5151 premises: vec![list_node(vec![
5152 leaf_node("?n"),
5153 leaf_node("has-type"),
5154 leaf_node("Nat"),
5155 ])],
5156 conclusion: list_node(vec![
5157 list_node(vec![leaf_node("mul"), leaf_node("zero"), leaf_node("?n")]),
5158 leaf_node("nat-equals"),
5159 leaf_node("zero"),
5160 ]),
5161 }),
5162 "nat-mul-succ" => Some(ProofRule {
5163 name: name.to_string(),
5164 premises: vec![
5165 list_node(vec![
5166 list_node(vec![leaf_node("mul"), leaf_node("?m"), leaf_node("?n")]),
5167 leaf_node("nat-equals"),
5168 leaf_node("?k"),
5169 ]),
5170 list_node(vec![
5171 list_node(vec![leaf_node("add"), leaf_node("?n"), leaf_node("?k")]),
5172 leaf_node("nat-equals"),
5173 leaf_node("?s"),
5174 ]),
5175 ],
5176 conclusion: list_node(vec![
5177 list_node(vec![
5178 leaf_node("mul"),
5179 list_node(vec![leaf_node("succ"), leaf_node("?m")]),
5180 leaf_node("?n"),
5181 ]),
5182 leaf_node("nat-equals"),
5183 leaf_node("?s"),
5184 ]),
5185 }),
5186 _ => None,
5187 }
5188}
5189
5190fn instantiate_proof_pattern(pattern: &Node, subs: &HashMap<String, Node>) -> Node {
5191 match pattern {
5192 Node::Leaf(token) if token.starts_with('?') => {
5193 subs.get(token).cloned().unwrap_or_else(|| pattern.clone())
5194 }
5195 Node::Leaf(_) => pattern.clone(),
5196 Node::List(children) => Node::List(
5197 children
5198 .iter()
5199 .map(|child| instantiate_proof_pattern(child, subs))
5200 .collect(),
5201 ),
5202 }
5203}
5204
5205fn eval_nat_foundation_uses(
5206 env: &Env,
5207 foundation_name: &str,
5208 rule_name: &str,
5209 seen: &mut HashSet<String>,
5210) -> bool {
5211 if !seen.insert(foundation_name.to_string()) {
5212 return false;
5213 }
5214 let Some(foundation) = env.get_foundation(foundation_name) else {
5215 return false;
5216 };
5217 if foundation.uses.iter().any(|u| u == rule_name) {
5218 return true;
5219 }
5220 foundation
5221 .extends
5222 .as_deref()
5223 .map(|parent| eval_nat_foundation_uses(env, parent, rule_name, seen))
5224 .unwrap_or(false)
5225}
5226
5227fn eval_nat_active_foundation_uses(env: &Env, name: &str) -> bool {
5228 let active = if env.active_foundation.is_empty() {
5229 "default-rml"
5230 } else {
5231 env.active_foundation.as_str()
5232 };
5233 if active == "default-rml" {
5234 return true;
5235 }
5236 eval_nat_foundation_uses(env, active, name, &mut HashSet::new())
5237}
5238
5239fn eval_nat_rule(env: &Env, name: &str) -> Result<ProofRule, String> {
5240 if !eval_nat_active_foundation_uses(env, name) {
5241 return Err(format!(
5242 "eval-nat requires {}, but it is not available in active foundation {}",
5243 name,
5244 if env.active_foundation.is_empty() {
5245 "default-rml"
5246 } else {
5247 env.active_foundation.as_str()
5248 }
5249 ));
5250 }
5251 env.get_proof_rule(name)
5252 .cloned()
5253 .or_else(|| default_eval_nat_rule(name))
5254 .ok_or_else(|| {
5255 format!(
5256 "eval-nat requires {}, but no links-level rule is registered",
5257 name
5258 )
5259 })
5260}
5261
5262fn eval_nat_equality_conclusion<'a>(
5263 rule: &'a ProofRule,
5264 rule_name: &str,
5265) -> Result<(&'a Node, &'a Node), String> {
5266 let Node::List(children) = &rule.conclusion else {
5267 return Err(format!(
5268 "eval-nat rule {} must conclude (<term> nat-equals <term>)",
5269 rule_name
5270 ));
5271 };
5272 if children.len() != 3 || !matches!(&children[1], Node::Leaf(mid) if mid == "nat-equals") {
5273 return Err(format!(
5274 "eval-nat rule {} must conclude (<term> nat-equals <term>)",
5275 rule_name
5276 ));
5277 }
5278 Ok((&children[0], &children[2]))
5279}
5280
5281fn process_eval_nat_premises(
5282 env: &Env,
5283 rule: &ProofRule,
5284 subs: &mut HashMap<String, Node>,
5285 steps: &mut Vec<String>,
5286 depth: usize,
5287) -> Result<(), String> {
5288 for premise in &rule.premises {
5289 if let Node::List(children) = premise {
5290 if children.len() == 3 && matches!(&children[1], Node::Leaf(mid) if mid == "nat-equals")
5291 {
5292 let premise_input = instantiate_proof_pattern(&children[0], subs);
5293 let premise_normal =
5294 normalize_eval_nat_term(env, &premise_input, steps, depth + 1)?;
5295 if !match_proof_pattern(&children[2], &premise_normal, subs) {
5296 return Err(format!(
5297 "eval-nat rule {} premise {} did not match normal form {}",
5298 rule.name,
5299 key_of(premise),
5300 key_of(&premise_normal)
5301 ));
5302 }
5303 continue;
5304 }
5305 if children.len() == 3
5306 && matches!(&children[1], Node::Leaf(mid) if mid == "has-type")
5307 && matches!(&children[2], Node::Leaf(ty) if ty == "Nat")
5308 {
5309 continue;
5310 }
5311 }
5312 return Err(format!(
5313 "eval-nat rule {} has unsupported premise {}",
5314 rule.name,
5315 key_of(premise)
5316 ));
5317 }
5318 Ok(())
5319}
5320
5321fn apply_eval_nat_rule(
5322 env: &Env,
5323 rule_name: &str,
5324 term: &Node,
5325 steps: &mut Vec<String>,
5326 depth: usize,
5327) -> Result<Node, String> {
5328 let rule = eval_nat_rule(env, rule_name)?;
5329 let (left, right) = eval_nat_equality_conclusion(&rule, rule_name)?;
5330 let mut subs: HashMap<String, Node> = HashMap::new();
5331 if !match_proof_pattern(left, term, &mut subs) {
5332 return Err(format!(
5333 "eval-nat rule {} does not apply to {}",
5334 rule_name,
5335 key_of(term)
5336 ));
5337 }
5338 steps.push(rule.name.clone());
5339 process_eval_nat_premises(env, &rule, &mut subs, steps, depth)?;
5340 let next = instantiate_proof_pattern(right, &subs);
5341 normalize_eval_nat_term(env, &next, steps, depth + 1)
5342}
5343
5344fn normalize_eval_nat_term(
5345 env: &Env,
5346 node: &Node,
5347 steps: &mut Vec<String>,
5348 depth: usize,
5349) -> Result<Node, String> {
5350 if depth > 10_000 {
5351 return Err("eval-nat exceeded its structural rewrite limit".to_string());
5352 }
5353 if let Node::Leaf(s) = node {
5354 if s == "zero" {
5355 return Ok(leaf_node("zero"));
5356 }
5357 }
5358 if let Node::List(children) = node {
5359 if children.len() == 2 {
5360 if let Node::Leaf(head) = &children[0] {
5361 if head == "succ" {
5362 let inner = normalize_eval_nat_term(env, &children[1], steps, depth + 1)?;
5363 return Ok(list_node(vec![leaf_node("succ"), inner]));
5364 }
5365 }
5366 }
5367 if children.len() == 3 {
5368 if let Node::Leaf(head) = &children[0] {
5369 if head == "add" {
5370 let left = normalize_eval_nat_term(env, &children[1], steps, depth + 1)?;
5371 let current =
5372 list_node(vec![leaf_node("add"), left.clone(), children[2].clone()]);
5373 if matches!(&left, Node::Leaf(s) if s == "zero") {
5374 return apply_eval_nat_rule(
5375 env,
5376 "nat-add-zero",
5377 ¤t,
5378 steps,
5379 depth + 1,
5380 );
5381 }
5382 if matches!(&left, Node::List(items) if items.len() == 2 && matches!(&items[0], Node::Leaf(h) if h == "succ"))
5383 {
5384 return apply_eval_nat_rule(
5385 env,
5386 "nat-add-succ",
5387 ¤t,
5388 steps,
5389 depth + 1,
5390 );
5391 }
5392 }
5393 if head == "mul" {
5394 let left = normalize_eval_nat_term(env, &children[1], steps, depth + 1)?;
5395 let current =
5396 list_node(vec![leaf_node("mul"), left.clone(), children[2].clone()]);
5397 if matches!(&left, Node::Leaf(s) if s == "zero") {
5398 return apply_eval_nat_rule(
5399 env,
5400 "nat-mul-zero",
5401 ¤t,
5402 steps,
5403 depth + 1,
5404 );
5405 }
5406 if matches!(&left, Node::List(items) if items.len() == 2 && matches!(&items[0], Node::Leaf(h) if h == "succ"))
5407 {
5408 return apply_eval_nat_rule(
5409 env,
5410 "nat-mul-succ",
5411 ¤t,
5412 steps,
5413 depth + 1,
5414 );
5415 }
5416 }
5417 }
5418 }
5419 }
5420 Err(format!(
5421 "eval-nat: not a closed Peano term: {}",
5422 key_of(node)
5423 ))
5424}
5425
5426fn peano_normal_form_to_host_number(node: &Node) -> Result<f64, String> {
5427 if let Node::Leaf(s) = node {
5428 if s == "zero" {
5429 return Ok(0.0);
5430 }
5431 }
5432 if let Node::List(children) = node {
5433 if children.len() == 2 && matches!(&children[0], Node::Leaf(head) if head == "succ") {
5434 return Ok(1.0 + peano_normal_form_to_host_number(&children[1])?);
5435 }
5436 }
5437 Err(format!(
5438 "eval-nat produced a non-Peano normal form: {}",
5439 key_of(node)
5440 ))
5441}
5442
5443pub fn eval_nat_term(env: &Env, node: &Node) -> Result<EvalNatResult, String> {
5446 let mut steps: Vec<String> = Vec::new();
5447 let normal_form = normalize_eval_nat_term(env, node, &mut steps, 0)?;
5448 let value = peano_normal_form_to_host_number(&normal_form)?;
5449 Ok(EvalNatResult {
5450 value,
5451 normal_form,
5452 steps,
5453 })
5454}
5455
5456fn register_template_form(form: &Node, env: &mut Env) -> Result<String, String> {
5457 let children = match form {
5458 Node::List(items) => items,
5459 _ => {
5460 return Err(
5461 "Template declaration must be `(template (<name> <param>...) <body>)`".to_string(),
5462 );
5463 }
5464 };
5465 if children.len() != 3 || !matches!(children.first(), Some(Node::Leaf(h)) if h == "template") {
5466 return Err(
5467 "Template declaration must be `(template (<name> <param>...) <body>)`".to_string(),
5468 );
5469 }
5470 let (name, params) = validate_template_pattern(&children[1])?;
5471 let store_name = env.qualify_name(&name);
5472 maybe_warn_shadow(env, &store_name);
5473 env.templates.insert(
5474 store_name.clone(),
5475 TemplateDecl {
5476 name: store_name.clone(),
5477 params,
5478 body: children[2].clone(),
5479 },
5480 );
5481 Ok(store_name)
5482}
5483
5484fn substitute_template_placeholders(body: &Node, params: &[String], args: &[Node]) -> Node {
5485 let mut current = body.clone();
5486 let mut avoid = HashSet::new();
5487 collect_names(¤t, &mut avoid);
5488 for arg in args {
5489 collect_names(arg, &mut avoid);
5490 }
5491 let mut sentinels = Vec::new();
5492 for param in params {
5493 let sentinel = fresh_name(&format!("__template_{}", param), &avoid);
5494 avoid.insert(sentinel.clone());
5495 sentinels.push(sentinel);
5496 }
5497 for (param, sentinel) in params.iter().zip(sentinels.iter()) {
5498 current = subst(¤t, param, &Node::Leaf(sentinel.clone()));
5499 }
5500 for (sentinel, arg) in sentinels.iter().zip(args.iter()) {
5501 current = subst(¤t, sentinel, arg);
5502 }
5503 current
5504}
5505
5506fn expand_templates(node: &Node, env: &Env, stack: &mut Vec<String>) -> Node {
5507 match node {
5508 Node::Leaf(_) => node.clone(),
5509 Node::List(children) => {
5510 if children.is_empty() {
5511 return node.clone();
5512 }
5513 if let Some(Node::Leaf(head)) = children.first() {
5514 if let Some(key) = template_key_for(env, head) {
5515 let decl = env
5516 .templates
5517 .get(&key)
5518 .cloned()
5519 .expect("template key resolved to declaration");
5520 let arg_count = children.len().saturating_sub(1);
5521 if arg_count != decl.params.len() {
5522 panic!(
5523 "Template expansion error: Template \"{}\" expects {} argument{}, got {}",
5524 head,
5525 decl.params.len(),
5526 if decl.params.len() == 1 { "" } else { "s" },
5527 arg_count
5528 );
5529 }
5530 if let Some(pos) = stack.iter().position(|item| item == &key) {
5531 let mut cycle = stack[pos..].to_vec();
5532 cycle.push(key.clone());
5533 panic!(
5534 "Template expansion error: Template expansion cycle detected: {}",
5535 cycle.join(" -> ")
5536 );
5537 }
5538
5539 let expanded_args: Vec<Node> = children[1..]
5540 .iter()
5541 .map(|arg| expand_templates(arg, env, stack))
5542 .collect();
5543 stack.push(key.clone());
5544 let instantiated =
5545 substitute_template_placeholders(&decl.body, &decl.params, &expanded_args);
5546 let expanded = expand_templates(&instantiated, env, stack);
5547 stack.pop();
5548 return expanded;
5549 }
5550 }
5551 Node::List(
5552 children
5553 .iter()
5554 .map(|child| expand_templates(child, env, stack))
5555 .collect(),
5556 )
5557 }
5558 }
5559}
5560
5561fn eval_arith(node: &Node, env: &mut Env) -> f64 {
5565 if let Node::Leaf(ref s) = node {
5566 if is_num(s) {
5567 return s.parse::<f64>().unwrap_or(0.0);
5568 }
5569 }
5570 match eval_node(node, env) {
5571 EvalResult::Term(term) => eval_arith(&term, env),
5572 other => other.as_f64(),
5573 }
5574}
5575
5576fn eval_term_node(node: &Node, env: &mut Env) -> Node {
5577 if let Node::List(children) = node {
5578 if children.len() == 4 {
5579 if let (Node::Leaf(head), Node::Leaf(var_name)) = (&children[0], &children[2]) {
5580 if head == "subst" {
5581 let term = eval_term_node(&children[1], env);
5582 let replacement = eval_term_node(&children[3], env);
5583 let reduced = subst(&term, var_name, &replacement);
5584 return eval_term_node(&reduced, env);
5585 }
5586 }
5587 }
5588
5589 if children.len() == 3 {
5590 if let Node::Leaf(head) = &children[0] {
5591 if head == "apply" {
5592 let fn_node = &children[1];
5593 let arg = eval_term_node(&children[2], env);
5594 if let Node::List(fn_children) = fn_node {
5595 if fn_children.len() == 3 {
5596 if let Node::Leaf(fn_head) = &fn_children[0] {
5597 if fn_head == "lambda" {
5598 if let Some((param_name, _)) = parse_binding(&fn_children[1]) {
5599 let reduced = subst(&fn_children[2], ¶m_name, &arg);
5600 return eval_term_node(&reduced, env);
5601 }
5602 }
5603 }
5604 }
5605 }
5606 if let Node::Leaf(fn_name) = fn_node {
5607 if let Some(lambda) = env.get_lambda(fn_name).cloned() {
5608 let reduced = subst(&lambda.body, &lambda.param, &arg);
5609 return eval_term_node(&reduced, env);
5610 }
5611 }
5612 }
5613 }
5614 }
5615
5616 if children.len() >= 2 {
5617 if let Node::List(head_children) = &children[0] {
5618 if head_children.len() == 3 {
5619 if let Node::Leaf(fn_head) = &head_children[0] {
5620 if fn_head == "lambda" {
5621 if let Some((param_name, _)) = parse_binding(&head_children[1]) {
5622 let arg = eval_term_node(&children[1], env);
5623 let reduced = subst(&head_children[2], ¶m_name, &arg);
5624 if children.len() == 2 {
5625 return eval_term_node(&reduced, env);
5626 }
5627 let mut next = vec![reduced];
5628 next.extend_from_slice(&children[2..]);
5629 return eval_term_node(&Node::List(next), env);
5630 }
5631 }
5632 }
5633 }
5634 }
5635 }
5636 }
5637 node.clone()
5638}
5639
5640fn normalize_term(node: &Node, env: &mut Env, options: ConvertOptions) -> Node {
5641 if let Node::List(children) = node {
5642 if children.is_empty() {
5643 return Node::List(vec![]);
5644 }
5645
5646 if children.len() == 4 {
5647 if let (Node::Leaf(head), Node::Leaf(var_name)) = (&children[0], &children[2]) {
5648 if head == "subst" {
5649 let term = normalize_term(&children[1], env, options);
5650 let replacement = normalize_term(&children[3], env, options);
5651 let reduced = subst(&term, var_name, &replacement);
5652 return normalize_term(&reduced, env, options);
5653 }
5654 }
5655 }
5656
5657 if children.len() == 3 {
5658 if let Node::Leaf(head) = &children[0] {
5659 if head == "apply" {
5660 let fn_node = normalize_term(&children[1], env, options);
5665 let arg = normalize_term(&children[2], env, options);
5666 if let Node::List(fn_children) = &fn_node {
5667 if fn_children.len() == 3 {
5668 if let Node::Leaf(fn_head) = &fn_children[0] {
5669 if fn_head == "lambda" {
5670 if let Some((param_name, _)) = parse_binding(&fn_children[1]) {
5671 let reduced = subst(&fn_children[2], ¶m_name, &arg);
5672 return normalize_term(&reduced, env, options);
5673 }
5674 }
5675 }
5676 }
5677 }
5678 if let Node::Leaf(fn_name) = &fn_node {
5679 let resolved = env.resolve_qualified(fn_name);
5680 let lambda = env
5681 .get_lambda(fn_name)
5682 .cloned()
5683 .or_else(|| env.get_lambda(&resolved).cloned());
5684 if let Some(lambda) = lambda {
5685 let reduced = subst(&lambda.body, &lambda.param, &arg);
5686 return normalize_term(&reduced, env, options);
5687 }
5688 }
5689 return Node::List(vec![Node::Leaf("apply".into()), fn_node, arg]);
5690 }
5691
5692 if head == "lambda" {
5693 let candidate = Node::List(vec![
5694 Node::Leaf("lambda".into()),
5695 normalize_term(&children[1], env, options),
5696 normalize_term(&children[2], env, options),
5697 ]);
5698 return eta_contract(&candidate, env, options);
5699 }
5700 }
5701 }
5702
5703 if children.len() >= 2 {
5704 if let Node::List(head_children) = &children[0] {
5705 if head_children.len() == 3 {
5706 if let Node::Leaf(fn_head) = &head_children[0] {
5707 if fn_head == "lambda" {
5708 if let Some((param_name, _)) = parse_binding(&head_children[1]) {
5709 let arg = normalize_term(&children[1], env, options);
5710 let reduced = subst(&head_children[2], ¶m_name, &arg);
5711 if children.len() == 2 {
5712 return normalize_term(&reduced, env, options);
5713 }
5714 let mut next = vec![reduced];
5715 next.extend_from_slice(&children[2..]);
5716 return normalize_term(&Node::List(next), env, options);
5717 }
5718 }
5719 }
5720 }
5721 }
5722 }
5723
5724 if children.len() >= 2 {
5725 if let Node::Leaf(head) = &children[0] {
5726 let resolved = env.resolve_qualified(head);
5727 let lambda = env
5728 .get_lambda(head)
5729 .cloned()
5730 .or_else(|| env.get_lambda(&resolved).cloned());
5731 if let Some(lambda) = lambda {
5732 let arg = normalize_term(&children[1], env, options);
5733 let reduced = subst(&lambda.body, &lambda.param, &arg);
5734 if children.len() == 2 {
5735 return normalize_term(&reduced, env, options);
5736 }
5737 let mut next = vec![reduced];
5738 next.extend_from_slice(&children[2..]);
5739 return normalize_term(&Node::List(next), env, options);
5740 }
5741 }
5742 }
5743
5744 return Node::List(
5745 children
5746 .iter()
5747 .map(|child| normalize_term(child, env, options))
5748 .collect(),
5749 );
5750 }
5751 node.clone()
5752}
5753
5754pub fn whnf_term(node: &Node, env: &mut Env, options: ConvertOptions) -> Node {
5763 if let Node::List(children) = node {
5764 if children.is_empty() {
5765 return Node::List(vec![]);
5766 }
5767 if children.len() == 4 {
5768 if let (Node::Leaf(head), Node::Leaf(var_name)) = (&children[0], &children[2]) {
5769 if head == "subst" {
5770 let term = whnf_term(&children[1], env, options);
5771 let replacement = children[3].clone();
5772 let reduced = subst(&term, var_name, &replacement);
5773 return whnf_term(&reduced, env, options);
5774 }
5775 }
5776 }
5777 }
5778
5779 let mut spine_args: Vec<Node> = Vec::new();
5784 let mut head_node = node.clone();
5785 loop {
5786 if let Node::List(children) = &head_node {
5787 if children.len() == 3 {
5788 if let Node::Leaf(h) = &children[0] {
5789 if h == "apply" {
5790 spine_args.insert(0, children[2].clone());
5791 head_node = children[1].clone();
5792 continue;
5793 }
5794 }
5795 }
5796 }
5797 break;
5798 }
5799
5800 if spine_args.is_empty() {
5803 if let Node::List(children) = head_node.clone() {
5804 if children.len() > 1 {
5805 let head_is_lambda = matches!(
5806 &children[0],
5807 Node::List(lc)
5808 if lc.len() == 3
5809 && matches!(&lc[0], Node::Leaf(h) if h == "lambda")
5810 );
5811 let head_is_name = matches!(
5812 &children[0],
5813 Node::Leaf(name)
5814 if name != "apply"
5815 && name != "lambda"
5816 && name != "Pi"
5817 && name != "fresh"
5818 && name != "subst"
5819 );
5820 if head_is_lambda || head_is_name {
5821 head_node = children[0].clone();
5822 spine_args.extend(children[1..].iter().cloned());
5823 }
5824 }
5825 }
5826 }
5827
5828 while !spine_args.is_empty() {
5832 let lambda_match = if let Node::List(hc) = &head_node {
5833 if hc.len() == 3 {
5834 if let Node::Leaf(h) = &hc[0] {
5835 if h == "lambda" {
5836 parse_binding(&hc[1]).map(|(p, _)| (p, hc[2].clone()))
5837 } else {
5838 None
5839 }
5840 } else {
5841 None
5842 }
5843 } else {
5844 None
5845 }
5846 } else {
5847 None
5848 };
5849 if let Some((param, body)) = lambda_match {
5850 let arg = spine_args.remove(0);
5851 head_node = subst(&body, ¶m, &arg);
5852 continue;
5853 }
5854 if let Node::Leaf(name) = &head_node {
5855 let resolved = env.resolve_qualified(name);
5856 let lambda = env
5857 .get_lambda(name)
5858 .cloned()
5859 .or_else(|| env.get_lambda(&resolved).cloned());
5860 if let Some(lambda) = lambda {
5861 let arg = spine_args.remove(0);
5862 head_node = subst(&lambda.body, &lambda.param, &arg);
5863 continue;
5864 }
5865 }
5866 break;
5867 }
5868
5869 if spine_args.is_empty() {
5870 return head_node;
5871 }
5872 let mut stuck = head_node;
5874 for arg in spine_args {
5875 stuck = Node::List(vec![Node::Leaf("apply".into()), stuck, arg]);
5876 }
5877 stuck
5878}
5879
5880fn is_neutral_apply(node: &Node, env: &Env) -> bool {
5886 if let Node::List(children) = node {
5887 if children.len() == 3 {
5888 if let Node::Leaf(head) = &children[0] {
5889 if head == "apply" {
5890 if let Node::Leaf(name) = &children[1] {
5891 if env.lambdas.contains_key(name) {
5892 return false;
5893 }
5894 return is_variable_token(name);
5895 }
5896 }
5897 }
5898 }
5899 }
5900 false
5901}
5902
5903pub fn flatten_neutral_applies(node: &Node, env: &Env) -> Node {
5909 if let Node::List(children) = node {
5910 if children.is_empty() {
5911 return node.clone();
5912 }
5913 if let Some(binder) = binder_info(node) {
5914 let mut out = children.clone();
5915 out[binder.body_index] =
5916 flatten_neutral_applies(&children[binder.body_index], env);
5917 return Node::List(out);
5918 }
5919 let flattened: Vec<Node> = children
5920 .iter()
5921 .map(|child| flatten_neutral_applies(child, env))
5922 .collect();
5923 let candidate = Node::List(flattened.clone());
5924 if is_neutral_apply(&candidate, env) {
5925 return Node::List(vec![flattened[1].clone(), flattened[2].clone()]);
5926 }
5927 return candidate;
5928 }
5929 node.clone()
5930}
5931
5932pub fn whnf(term: &Node, env: &mut Env) -> Node {
5935 whnf_with_options(term, env, ConvertOptions::default())
5936}
5937
5938pub fn whnf_with_options(term: &Node, env: &mut Env, options: ConvertOptions) -> Node {
5940 whnf_term(term, env, options)
5941}
5942
5943pub fn nf(term: &Node, env: &mut Env) -> Node {
5949 nf_with_options(term, env, ConvertOptions::default())
5950}
5951
5952pub fn nf_with_options(term: &Node, env: &mut Env, options: ConvertOptions) -> Node {
5954 let normalized = normalize_term(term, env, options);
5955 flatten_neutral_applies(&normalized, env)
5956}
5957
5958fn eta_contract(term: &Node, env: &mut Env, options: ConvertOptions) -> Node {
5959 if !options.eta {
5960 return term.clone();
5961 }
5962 let children = match term {
5963 Node::List(children) if children.len() == 3 => children,
5964 _ => return term.clone(),
5965 };
5966 if !matches!(&children[0], Node::Leaf(head) if head == "lambda") {
5967 return term.clone();
5968 }
5969 let bindings = parse_bindings(&children[1]).unwrap_or_default();
5970 if bindings.len() != 1 {
5971 return term.clone();
5972 }
5973 let param = &bindings[0].0;
5974 let body = &children[2];
5975 let fn_node = match body {
5976 Node::List(body_children) if body_children.len() == 3 => {
5977 if matches!(&body_children[0], Node::Leaf(head) if head == "apply")
5978 && is_structurally_same(&body_children[2], &Node::Leaf(param.clone()))
5979 {
5980 Some(body_children[1].clone())
5981 } else {
5982 None
5983 }
5984 }
5985 Node::List(body_children) if body_children.len() == 2 => {
5986 if is_structurally_same(&body_children[1], &Node::Leaf(param.clone())) {
5987 Some(body_children[0].clone())
5988 } else {
5989 None
5990 }
5991 }
5992 _ => None,
5993 };
5994 if let Some(fn_node) = fn_node {
5995 if !free_variables(&fn_node).contains(param) {
5996 return normalize_term(&fn_node, env, options);
5997 }
5998 }
5999 term.clone()
6000}
6001
6002fn lookup_assigned_infix(env: &mut Env, op: &str, left: &Node, right: &Node) -> Option<f64> {
6003 let candidates = [
6004 Node::List(vec![
6005 Node::Leaf(op.to_string()),
6006 left.clone(),
6007 right.clone(),
6008 ]),
6009 Node::List(vec![
6010 left.clone(),
6011 Node::Leaf(op.to_string()),
6012 right.clone(),
6013 ]),
6014 ];
6015 for candidate in candidates {
6016 let key = key_of(&candidate);
6017 if let Some(&value) = env.assign.get(&key) {
6018 env.trace("lookup", format!("{} → {}", key, format_trace_value(value)));
6019 return Some(value);
6020 }
6021 }
6022 None
6023}
6024
6025fn same_normalized_input(left: &Node, right: &Node, left_term: &Node, right_term: &Node) -> bool {
6026 is_structurally_same(left, left_term) && is_structurally_same(right, right_term)
6027}
6028
6029fn explicit_symbol_number(node: &Node, env: &Env) -> Option<f64> {
6030 if let Node::Leaf(name) = node {
6031 if let Some(value) = env.symbol_prob.get(name) {
6032 return Some(*value);
6033 }
6034 let resolved = env.resolve_qualified(name);
6035 if resolved != *name {
6036 return env.symbol_prob.get(&resolved).copied();
6037 }
6038 }
6039 None
6040}
6041
6042fn try_eval_numeric(node: &Node, env: &mut Env, options: ConvertOptions) -> Option<f64> {
6043 let term = normalize_term(node, env, options);
6044 match &term {
6045 Node::Leaf(s) if is_num(s) => s.parse::<f64>().ok(),
6046 Node::Leaf(_) => explicit_symbol_number(&term, env),
6047 Node::List(children) if children.is_empty() => None,
6048 Node::List(children) => {
6049 if children.len() == 3 {
6050 if let Node::Leaf(op) = &children[1] {
6051 if matches!(op.as_str(), "+" | "-" | "*" | "/") {
6052 let left = try_eval_numeric(&children[0], env, options)?;
6053 let right = try_eval_numeric(&children[2], env, options)?;
6054 return Some(env.apply_op(op, &[left, right]));
6055 }
6056 if matches!(op.as_str(), "and" | "or" | "both" | "neither") {
6057 let left = try_eval_numeric(&children[0], env, options)?;
6058 let right = try_eval_numeric(&children[2], env, options)?;
6059 let value = env.apply_op(op, &[left, right]);
6060 return Some(env.clamp(value));
6061 }
6062 }
6063 }
6064 if let Node::Leaf(head) = &children[0] {
6065 if head != "=" && head != "!=" && env.has_op(head) {
6066 let mut values = Vec::new();
6067 for arg in &children[1..] {
6068 values.push(try_eval_numeric(arg, env, options)?);
6069 }
6070 let value = env.apply_op(head, &values);
6071 return Some(env.clamp(value));
6072 }
6073 }
6074 None
6075 }
6076 }
6077}
6078
6079fn equality_truth_value(
6080 left: &Node,
6081 right: &Node,
6082 left_term: &Node,
6083 right_term: &Node,
6084 env: &mut Env,
6085 options: ConvertOptions,
6086) -> f64 {
6087 if let Some(value) = lookup_assigned_infix(env, "=", left, right) {
6088 return env.clamp(value);
6089 }
6090 if !same_normalized_input(left, right, left_term, right_term) {
6091 if let Some(value) = lookup_assigned_infix(env, "=", left_term, right_term) {
6092 return env.clamp(value);
6093 }
6094 }
6095 if is_structurally_same(left_term, right_term) {
6096 return env.hi;
6097 }
6098 let left_num = try_eval_numeric(left_term, env, options);
6099 let right_num = try_eval_numeric(right_term, env, options);
6100 if let (Some(left_num), Some(right_num)) = (left_num, right_num) {
6101 if dec_round(left_num) == dec_round(right_num) {
6102 env.hi
6103 } else {
6104 env.lo
6105 }
6106 } else {
6107 env.lo
6108 }
6109}
6110
6111fn eval_equality_node(left: &Node, op: &str, right: &Node, env: &mut Env) -> EvalResult {
6112 let options = ConvertOptions::default();
6113 if let Some(value) = lookup_assigned_infix(env, op, left, right) {
6114 return EvalResult::Value(env.clamp(value));
6115 }
6116 let left_term = normalize_term(left, env, options);
6117 let right_term = normalize_term(right, env, options);
6118 if !same_normalized_input(left, right, &left_term, &right_term) {
6119 if let Some(value) = lookup_assigned_infix(env, op, &left_term, &right_term) {
6120 return EvalResult::Value(env.clamp(value));
6121 }
6122 }
6123 if op == "=" {
6124 let value = equality_truth_value(left, right, &left_term, &right_term, env, options);
6125 EvalResult::Value(env.clamp(value))
6126 } else {
6127 let eq = equality_truth_value(left, right, &left_term, &right_term, env, options);
6128 let value = env.apply_op("not", &[eq]);
6129 EvalResult::Value(env.clamp(value))
6130 }
6131}
6132
6133pub fn is_convertible(left: &Node, right: &Node, env: &mut Env) -> bool {
6136 is_convertible_with_options(left, right, env, ConvertOptions::default())
6137}
6138
6139pub fn is_convertible_with_options(
6141 left: &Node,
6142 right: &Node,
6143 env: &mut Env,
6144 options: ConvertOptions,
6145) -> bool {
6146 if let Some(value) = lookup_assigned_infix(env, "=", left, right) {
6147 return env.clamp(value) == env.hi;
6148 }
6149 let left_term = normalize_term(left, env, options);
6150 let right_term = normalize_term(right, env, options);
6151 if !same_normalized_input(left, right, &left_term, &right_term) {
6152 if let Some(value) = lookup_assigned_infix(env, "=", &left_term, &right_term) {
6153 return env.clamp(value) == env.hi;
6154 }
6155 }
6156 is_structurally_same(&left_term, &right_term)
6157}
6158
6159fn eval_reduced_term(reduced: &Node, env: &mut Env) -> EvalResult {
6160 let term = normalize_term(reduced, env, ConvertOptions::default());
6161 if has_unresolved_free_variables(&term, env) {
6162 EvalResult::Term(term)
6163 } else {
6164 eval_node(&term, env)
6165 }
6166}
6167
6168fn context_has_name(env: &Env, name: &str) -> bool {
6169 if env.terms.contains(name)
6170 || env.types.contains_key(name)
6171 || env.lambdas.contains_key(name)
6172 || env.symbol_prob.contains_key(name)
6173 || env.ops.contains_key(name)
6174 || env.templates.contains_key(name)
6175 {
6176 return true;
6177 }
6178 let resolved = env.resolve_qualified(name);
6179 resolved != name
6180 && (env.terms.contains(&resolved)
6181 || env.types.contains_key(&resolved)
6182 || env.lambdas.contains_key(&resolved)
6183 || env.symbol_prob.contains_key(&resolved)
6184 || env.ops.contains_key(&resolved)
6185 || env.templates.contains_key(&resolved))
6186}
6187
6188fn eval_fresh(var_name: &str, body: &Node, env: &mut Env) -> EvalResult {
6189 if context_has_name(env, var_name) {
6190 panic!(
6191 "Freshness error: fresh variable \"{}\" already appears in context",
6192 var_name
6193 );
6194 }
6195 let had_term = env.terms.contains(var_name);
6196 let previous_type = env.types.get(var_name).cloned();
6197 let previous_lambda = env.lambdas.get(var_name).cloned();
6198 let previous_symbol = env.symbol_prob.get(var_name).copied();
6199 env.terms.insert(var_name.to_string());
6200 let result = catch_unwind(AssertUnwindSafe(|| eval_node(body, env)));
6201 if !had_term {
6202 env.terms.remove(var_name);
6203 }
6204 if let Some(value) = previous_type {
6205 env.types.insert(var_name.to_string(), value);
6206 } else {
6207 env.types.remove(var_name);
6208 }
6209 if let Some(value) = previous_lambda {
6210 env.lambdas.insert(var_name.to_string(), value);
6211 } else {
6212 env.lambdas.remove(var_name);
6213 }
6214 if let Some(value) = previous_symbol {
6215 env.symbol_prob.insert(var_name.to_string(), value);
6216 } else {
6217 env.symbol_prob.remove(var_name);
6218 }
6219 match result {
6220 Ok(value) => value,
6221 Err(payload) => std::panic::resume_unwind(payload),
6222 }
6223}
6224
6225#[derive(Debug, Clone, Default)]
6249pub struct SynthResult {
6250 pub typ: Option<Node>,
6251 pub diagnostics: Vec<Diagnostic>,
6252}
6253
6254#[derive(Debug, Clone, Default)]
6257pub struct CheckResult {
6258 pub ok: bool,
6259 pub diagnostics: Vec<Diagnostic>,
6260}
6261
6262fn synth_span(env: &Env) -> Span {
6263 env.current_span.clone().unwrap_or_else(|| env.default_span.clone())
6264}
6265
6266fn type_key_to_node(type_key: &str) -> Node {
6267 let trimmed = type_key.trim();
6268 if trimmed.starts_with('(') {
6269 let toks = tokenize_one(trimmed);
6270 if let Ok(parsed) = parse_one(&toks) {
6271 return parsed;
6272 }
6273 }
6274 Node::Leaf(type_key.to_string())
6275}
6276
6277fn parse_term_input_str(s: &str) -> Node {
6278 let trimmed = s.trim();
6279 if trimmed.starts_with('(') {
6280 let toks = tokenize_one(trimmed);
6281 if let Ok(parsed) = parse_one(&toks) {
6282 return desugar_hoas(parsed);
6283 }
6284 }
6285 Node::Leaf(s.to_string())
6286}
6287
6288struct TypeBindingSnapshot {
6289 name: String,
6290 had_term: bool,
6291 previous_type: Option<String>,
6292}
6293
6294fn snapshot_type_binding(env: &Env, name: &str) -> TypeBindingSnapshot {
6295 TypeBindingSnapshot {
6296 name: name.to_string(),
6297 had_term: env.terms.contains(name),
6298 previous_type: env.types.get(name).cloned(),
6299 }
6300}
6301
6302fn extend_type_binding(env: &mut Env, name: &str, type_key: &str) {
6303 env.terms.insert(name.to_string());
6304 env.types.insert(name.to_string(), type_key.to_string());
6305}
6306
6307fn restore_type_binding(env: &mut Env, snap: TypeBindingSnapshot) {
6308 if !snap.had_term {
6309 env.terms.remove(&snap.name);
6310 }
6311 if let Some(value) = snap.previous_type {
6312 env.types.insert(snap.name, value);
6313 } else {
6314 env.types.remove(&snap.name);
6315 }
6316}
6317
6318fn is_forall_node(node: &Node) -> bool {
6323 if let Node::List(children) = node {
6324 if children.len() == 3 {
6325 if let (Node::Leaf(head), Node::Leaf(_)) = (&children[0], &children[1]) {
6326 return head == "forall";
6327 }
6328 }
6329 }
6330 false
6331}
6332
6333fn expand_forall(node: &Node) -> Node {
6334 if !is_forall_node(node) {
6335 return node.clone();
6336 }
6337 if let Node::List(children) = node {
6338 let var_name = match &children[1] {
6339 Node::Leaf(s) => s.clone(),
6340 _ => return node.clone(),
6341 };
6342 return Node::List(vec![
6343 Node::Leaf("Pi".to_string()),
6344 Node::List(vec![
6345 Node::Leaf("Type".to_string()),
6346 Node::Leaf(var_name),
6347 ]),
6348 children[2].clone(),
6349 ]);
6350 }
6351 node.clone()
6352}
6353
6354fn types_agree(a: &Node, b: &Node, env: &mut Env) -> bool {
6355 let a_n = expand_forall(a);
6356 let b_n = expand_forall(b);
6357 if is_structurally_same(&a_n, &b_n) {
6358 return true;
6359 }
6360 let result = catch_unwind(AssertUnwindSafe(|| is_convertible(&a_n, &b_n, env)));
6361 matches!(result, Ok(true))
6362}
6363
6364fn synth_leaf(name: &str, env: &mut Env) -> Option<Node> {
6365 if is_num(name) {
6366 return None;
6367 }
6368 let leaf = Node::Leaf(name.to_string());
6369 if let Some(recorded) = infer_type_key(&leaf, env) {
6370 return Some(type_key_to_node(&recorded));
6371 }
6372 let resolved = env.resolve_qualified(name);
6373 if resolved != name {
6374 if let Some(recorded) = env.types.get(&resolved).cloned() {
6375 return Some(type_key_to_node(&recorded));
6376 }
6377 }
6378 None
6379}
6380
6381fn synth_apply(children: &[Node], env: &mut Env, span: &Span, diagnostics: &mut Vec<Diagnostic>) -> Option<Node> {
6382 let head = &children[1];
6383 let arg = &children[2];
6384 let inner = synth(head, env);
6385 diagnostics.extend(inner.diagnostics);
6386 let fn_type = match inner.typ {
6387 Some(t) => t,
6388 None => {
6389 diagnostics.push(Diagnostic::new(
6390 "E020",
6391 format!(
6392 "Cannot synthesize type of `{}` in `{}`",
6393 key_of(head),
6394 key_of(&Node::List(children.to_vec()))
6395 ),
6396 span.clone(),
6397 ));
6398 return None;
6399 }
6400 };
6401 let fn_type = expand_forall(&fn_type);
6405 let pi_children = match &fn_type {
6406 Node::List(c) if c.len() == 3 && matches!(&c[0], Node::Leaf(s) if s == "Pi") => c.clone(),
6407 _ => {
6408 diagnostics.push(Diagnostic::new(
6409 "E022",
6410 format!(
6411 "Application head `{}` has type `{}`, expected a Pi-type",
6412 key_of(head),
6413 key_of(&fn_type)
6414 ),
6415 span.clone(),
6416 ));
6417 return None;
6418 }
6419 };
6420 let (param_name, param_type_key) = match parse_binding(&pi_children[1]) {
6421 Some(b) => b,
6422 None => {
6423 diagnostics.push(Diagnostic::new(
6424 "E022",
6425 format!(
6426 "Application head has malformed Pi binder `{}`",
6427 key_of(&pi_children[1])
6428 ),
6429 span.clone(),
6430 ));
6431 return None;
6432 }
6433 };
6434 let domain_node = type_key_to_node(¶m_type_key);
6435 let arg_check = check(arg, &domain_node, env);
6436 diagnostics.extend(arg_check.diagnostics);
6437 if !arg_check.ok {
6438 return None;
6439 }
6440 Some(subst(&pi_children[2], ¶m_name, arg))
6441}
6442
6443fn synth_lambda(children: &[Node], env: &mut Env, span: &Span, diagnostics: &mut Vec<Diagnostic>) -> Option<Node> {
6444 let (param_name, param_type_key) = match parse_binding(&children[1]) {
6445 Some(b) => b,
6446 None => {
6447 diagnostics.push(Diagnostic::new(
6448 "E024",
6449 format!("Lambda has malformed binder `{}`", key_of(&children[1])),
6450 span.clone(),
6451 ));
6452 return None;
6453 }
6454 };
6455 let snap = snapshot_type_binding(env, ¶m_name);
6456 extend_type_binding(env, ¶m_name, ¶m_type_key);
6457 let body_synth = synth(&children[2], env);
6458 restore_type_binding(env, snap);
6459 diagnostics.extend(body_synth.diagnostics);
6460 let body_type = body_synth.typ?;
6461 Some(Node::List(vec![
6462 Node::Leaf("Pi".to_string()),
6463 Node::List(vec![
6464 Node::Leaf(param_type_key),
6465 Node::Leaf(param_name),
6466 ]),
6467 body_type,
6468 ]))
6469}
6470
6471fn synth_of_membership(children: &[Node], env: &mut Env, _span: &Span, diagnostics: &mut Vec<Diagnostic>) -> Option<Node> {
6472 let result = check(&children[0], &children[2], env);
6473 diagnostics.extend(result.diagnostics);
6474 if !result.ok {
6475 return None;
6476 }
6477 Some(Node::List(vec![
6478 Node::Leaf("Type".to_string()),
6479 Node::Leaf("0".to_string()),
6480 ]))
6481}
6482
6483pub fn synth(term: &Node, env: &mut Env) -> SynthResult {
6489 let span = synth_span(env);
6490 let mut diagnostics: Vec<Diagnostic> = Vec::new();
6491
6492 match term {
6493 Node::Leaf(name) => {
6494 if let Some(t) = synth_leaf(name, env) {
6495 return SynthResult { typ: Some(t), diagnostics };
6496 }
6497 if !is_num(name) {
6498 diagnostics.push(Diagnostic::new(
6499 "E020",
6500 format!("Cannot synthesize type of symbol `{}`", name),
6501 span,
6502 ));
6503 }
6504 SynthResult { typ: None, diagnostics }
6505 }
6506 Node::List(children) => {
6507 if children.len() == 2 {
6509 if let Node::Leaf(head) = &children[0] {
6510 if head == "Type" {
6511 if let Some(univ) = universe_type_key(term) {
6512 return SynthResult {
6513 typ: Some(type_key_to_node(&univ)),
6514 diagnostics,
6515 };
6516 }
6517 diagnostics.push(Diagnostic::new(
6518 "E020",
6519 format!(
6520 "Universe `{}` has invalid level token `{}`",
6521 key_of(term),
6522 key_of(&children[1])
6523 ),
6524 span,
6525 ));
6526 return SynthResult { typ: None, diagnostics };
6527 }
6528 }
6529 }
6530
6531 if children.len() == 1 {
6533 if let Node::Leaf(head) = &children[0] {
6534 if head == "Prop" {
6535 return SynthResult {
6536 typ: Some(Node::List(vec![
6537 Node::Leaf("Type".to_string()),
6538 Node::Leaf("1".to_string()),
6539 ])),
6540 diagnostics,
6541 };
6542 }
6543 }
6544 }
6545
6546 if children.len() == 3 {
6547 if let Node::Leaf(head) = &children[0] {
6548 match head.as_str() {
6549 "forall" => {
6550 let expanded = expand_forall(term);
6554 let inner = synth(&expanded, env);
6555 diagnostics.extend(inner.diagnostics);
6556 return SynthResult { typ: inner.typ, diagnostics };
6557 }
6558 "Pi" => {
6559 if parse_binding(&children[1]).is_none() {
6560 diagnostics.push(Diagnostic::new(
6561 "E024",
6562 format!("Pi has malformed binder `{}`", key_of(&children[1])),
6563 span,
6564 ));
6565 return SynthResult { typ: None, diagnostics };
6566 }
6567 return SynthResult {
6568 typ: Some(Node::List(vec![
6569 Node::Leaf("Type".to_string()),
6570 Node::Leaf("0".to_string()),
6571 ])),
6572 diagnostics,
6573 };
6574 }
6575 "lambda" => {
6576 let t = synth_lambda(children, env, &span, &mut diagnostics);
6577 return SynthResult { typ: t, diagnostics };
6578 }
6579 "apply" => {
6580 let t = synth_apply(children, env, &span, &mut diagnostics);
6581 return SynthResult { typ: t, diagnostics };
6582 }
6583 "type" => {
6584 if let Node::Leaf(of_kw) = &children[1] {
6585 if of_kw == "of" {
6586 let inner = synth(&children[2], env);
6587 diagnostics.extend(inner.diagnostics);
6588 if inner.typ.is_some() {
6589 return SynthResult {
6590 typ: Some(Node::List(vec![
6591 Node::Leaf("Type".to_string()),
6592 Node::Leaf("0".to_string()),
6593 ])),
6594 diagnostics,
6595 };
6596 }
6597 diagnostics.push(Diagnostic::new(
6598 "E020",
6599 format!(
6600 "Cannot synthesize type referenced by `{}`",
6601 key_of(term)
6602 ),
6603 span,
6604 ));
6605 return SynthResult { typ: None, diagnostics };
6606 }
6607 }
6608 }
6609 _ => {}
6610 }
6611 }
6612 if let Node::Leaf(of_kw) = &children[1] {
6614 if of_kw == "of" {
6615 let t = synth_of_membership(children, env, &span, &mut diagnostics);
6616 return SynthResult { typ: t, diagnostics };
6617 }
6618 }
6619 }
6620
6621 if children.len() == 4 {
6623 if let (Node::Leaf(head), Node::Leaf(name)) = (&children[0], &children[2]) {
6624 if head == "subst" {
6625 let reduced = subst(&children[1], name, &children[3]);
6626 let inner = synth(&reduced, env);
6627 diagnostics.extend(inner.diagnostics);
6628 return SynthResult { typ: inner.typ, diagnostics };
6629 }
6630 }
6631 }
6632
6633 if let Some(recorded) = infer_type_key(term, env) {
6635 return SynthResult {
6636 typ: Some(type_key_to_node(&recorded)),
6637 diagnostics,
6638 };
6639 }
6640
6641 diagnostics.push(Diagnostic::new(
6642 "E020",
6643 format!("Cannot synthesize type of `{}`", key_of(term)),
6644 span,
6645 ));
6646 SynthResult { typ: None, diagnostics }
6647 }
6648 }
6649}
6650
6651pub fn check(term: &Node, expected_type: &Node, env: &mut Env) -> CheckResult {
6657 let span = synth_span(env);
6658 let mut diagnostics: Vec<Diagnostic> = Vec::new();
6659
6660 let expanded;
6663 let expected_type = if is_forall_node(expected_type) {
6664 expanded = expand_forall(expected_type);
6665 &expanded
6666 } else {
6667 expected_type
6668 };
6669
6670 if let (Node::List(lc), Node::List(ec)) = (term, expected_type) {
6672 if lc.len() == 3 && ec.len() == 3 {
6673 let lambda_head = matches!(&lc[0], Node::Leaf(s) if s == "lambda");
6674 let pi_head = matches!(&ec[0], Node::Leaf(s) if s == "Pi");
6675 if lambda_head && pi_head {
6676 let lambda_binding = parse_binding(&lc[1]);
6677 let pi_binding = parse_binding(&ec[1]);
6678 if let (Some((lname, ltype)), Some((pname, ptype))) = (lambda_binding, pi_binding) {
6679 let lparam_node = parse_term_input_str(<ype);
6680 let pparam_node = parse_term_input_str(&ptype);
6681 if !types_agree(&lparam_node, &pparam_node, env) {
6682 diagnostics.push(Diagnostic::new(
6683 "E021",
6684 format!(
6685 "Lambda parameter type `{}` does not match Pi domain `{}`",
6686 ltype, ptype
6687 ),
6688 span,
6689 ));
6690 return CheckResult { ok: false, diagnostics };
6691 }
6692 let codomain = subst(&ec[2], &pname, &Node::Leaf(lname.clone()));
6693 let snap = snapshot_type_binding(env, &lname);
6694 extend_type_binding(env, &lname, <ype);
6695 let body_result = check(&lc[2], &codomain, env);
6696 restore_type_binding(env, snap);
6697 diagnostics.extend(body_result.diagnostics);
6698 return CheckResult {
6699 ok: body_result.ok,
6700 diagnostics,
6701 };
6702 }
6703 }
6704 }
6705 }
6706
6707 if let Node::List(lc) = term {
6709 if lc.len() == 3 && matches!(&lc[0], Node::Leaf(s) if s == "lambda") {
6710 let expected_is_pi = matches!(
6711 expected_type,
6712 Node::List(ec) if ec.len() == 3 && matches!(&ec[0], Node::Leaf(s) if s == "Pi")
6713 );
6714 if !expected_is_pi {
6715 diagnostics.push(Diagnostic::new(
6716 "E023",
6717 format!(
6718 "Lambda `{}` cannot check against non-Pi type `{}`",
6719 key_of(term),
6720 key_of(expected_type)
6721 ),
6722 span,
6723 ));
6724 return CheckResult { ok: false, diagnostics };
6725 }
6726 }
6727 }
6728
6729 if let Node::Leaf(name) = term {
6731 if is_num(name) {
6732 return CheckResult { ok: true, diagnostics };
6733 }
6734 }
6735
6736 let synth_result = synth(term, env);
6738 diagnostics.extend(synth_result.diagnostics);
6739 let actual = match synth_result.typ {
6740 Some(t) => t,
6741 None => return CheckResult { ok: false, diagnostics },
6742 };
6743 let ok = types_agree(&actual, expected_type, env);
6744 if !ok {
6745 diagnostics.push(Diagnostic::new(
6746 "E021",
6747 format!(
6748 "Type mismatch: `{}` has type `{}`, expected `{}`",
6749 key_of(term),
6750 key_of(&actual),
6751 key_of(expected_type)
6752 ),
6753 span,
6754 ));
6755 }
6756 CheckResult { ok, diagnostics }
6757}
6758
6759fn wrap_proof(rule: &str, subs: Vec<Node>) -> Node {
6773 let mut out = Vec::with_capacity(subs.len() + 2);
6774 out.push(Node::Leaf("by".to_string()));
6775 out.push(Node::Leaf(rule.to_string()));
6776 out.extend(subs);
6777 Node::List(out)
6778}
6779
6780fn leaf(s: &str) -> Node {
6781 Node::Leaf(s.to_string())
6782}
6783
6784fn strip_with_proof(parts: &[Node]) -> &[Node] {
6788 if parts.len() >= 3 {
6789 if let (Node::Leaf(w), Node::Leaf(p)) =
6790 (&parts[parts.len() - 2], &parts[parts.len() - 1])
6791 {
6792 if w == "with" && p == "proof" {
6793 return &parts[..parts.len() - 2];
6794 }
6795 }
6796 }
6797 parts
6798}
6799
6800fn query_requests_proof(node: &Node) -> bool {
6805 if let Node::List(children) = node {
6806 if let Some(Node::Leaf(head)) = children.first() {
6807 if head == "?" {
6808 let parts = &children[1..];
6809 if parts.len() >= 3 {
6810 if let (Node::Leaf(w), Node::Leaf(p)) =
6811 (&parts[parts.len() - 2], &parts[parts.len() - 1])
6812 {
6813 return w == "with" && p == "proof";
6814 }
6815 }
6816 }
6817 }
6818 }
6819 false
6820}
6821
6822fn pure_beta_normalize(node: &Node) -> Node {
6828 if let Node::List(children) = node {
6829 if children.len() == 3 {
6830 if let Node::Leaf(head) = &children[0] {
6831 if head == "apply" {
6832 let fn_n = pure_beta_normalize(&children[1]);
6833 let arg = pure_beta_normalize(&children[2]);
6834 if let Node::List(fn_children) = &fn_n {
6835 if fn_children.len() == 3 {
6836 if let Node::Leaf(fn_head) = &fn_children[0] {
6837 if fn_head == "lambda" {
6838 if let Some((param, _)) =
6839 parse_binding(&fn_children[1])
6840 {
6841 let reduced =
6842 subst(&fn_children[2], ¶m, &arg);
6843 return pure_beta_normalize(&reduced);
6844 }
6845 }
6846 }
6847 }
6848 }
6849 return Node::List(vec![Node::Leaf("apply".into()), fn_n, arg]);
6850 }
6851 }
6852 }
6853 let normalized: Vec<Node> = children.iter().map(pure_beta_normalize).collect();
6854 return Node::List(normalized);
6855 }
6856 node.clone()
6857}
6858
6859fn contains_lambda_or_apply(node: &Node) -> bool {
6860 if let Node::List(children) = node {
6861 if let Some(Node::Leaf(head)) = children.first() {
6862 if head == "lambda" || head == "apply" {
6863 return true;
6864 }
6865 }
6866 return children.iter().any(contains_lambda_or_apply);
6867 }
6868 false
6869}
6870
6871pub fn classify_equality_rule(l: &Node, r: &Node, op: &str, env: &Env) -> &'static str {
6876 let is_inequality = op == "!=";
6877 let k_prefix = key_of(&Node::List(vec![leaf("="), l.clone(), r.clone()]));
6878 let k_infix = key_of(&Node::List(vec![l.clone(), leaf("="), r.clone()]));
6879 if env.assign.contains_key(&k_prefix) || env.assign.contains_key(&k_infix) {
6880 return if is_inequality {
6881 "assigned-inequality"
6882 } else {
6883 "assigned-equality"
6884 };
6885 }
6886 if is_structurally_same(l, r) {
6887 return if is_inequality {
6888 "structural-inequality"
6889 } else {
6890 "structural-equality"
6891 };
6892 }
6893 if contains_lambda_or_apply(l) || contains_lambda_or_apply(r) {
6897 let ln = pure_beta_normalize(l);
6898 let rn = pure_beta_normalize(r);
6899 if is_structurally_same(&ln, &rn) && !is_structurally_same(l, r) {
6900 return if is_inequality {
6901 "definitional-inequality"
6902 } else {
6903 "definitional-equality"
6904 };
6905 }
6906 }
6907 if is_inequality {
6908 "numeric-inequality"
6909 } else {
6910 "numeric-equality"
6911 }
6912}
6913
6914fn query_body_for_provenance(form: &Node) -> Option<Node> {
6917 if let Node::List(children) = form {
6918 if let Some(Node::Leaf(head)) = children.first() {
6919 if head == "?" {
6920 let stripped = strip_with_proof(&children[1..]);
6921 let mut body: Node = if stripped.len() == 1 {
6922 stripped[0].clone()
6923 } else {
6924 Node::List(stripped.to_vec())
6925 };
6926 loop {
6927 match body {
6928 Node::List(ref inner) if inner.len() == 1 => {
6929 if matches!(&inner[0], Node::List(_)) {
6930 body = inner[0].clone();
6931 } else {
6932 break;
6933 }
6934 }
6935 _ => break,
6936 }
6937 }
6938 return Some(body);
6939 }
6940 }
6941 }
6942 None
6943}
6944
6945pub fn equality_provenance_for_query(form: &Node, env: &Env) -> Option<String> {
6951 let body = query_body_for_provenance(form)?;
6952 if let Node::List(children) = &body {
6953 if children.len() == 3 {
6954 if let Node::Leaf(op) = &children[1] {
6955 if op == "=" || op == "!=" {
6956 return Some(
6957 classify_equality_rule(&children[0], &children[2], op, env)
6958 .to_string(),
6959 );
6960 }
6961 }
6962 }
6963 }
6964 None
6965}
6966
6967pub fn build_proof(node: &Node, env: &Env) -> Node {
6979 match node {
6980 Node::Leaf(s) => {
6982 if is_num(s) {
6983 wrap_proof("literal", vec![leaf(s)])
6984 } else {
6985 wrap_proof("symbol", vec![leaf(s)])
6986 }
6987 }
6988 Node::List(children) => {
6989 if let Some(Node::Leaf(s)) = children.first() {
6991 if s.ends_with(':') {
6992 return wrap_proof("definition", vec![node.clone()]);
6993 }
6994 }
6995
6996 if children.len() == 4 {
6998 if let (Node::Leaf(w1), Node::Leaf(w2), Node::Leaf(w3)) =
6999 (&children[1], &children[2], &children[3])
7000 {
7001 if w1 == "has" && w2 == "probability" && is_num(w3) {
7002 return wrap_proof(
7003 "assigned-probability",
7004 vec![children[0].clone(), leaf(w3)],
7005 );
7006 }
7007 }
7008 }
7009
7010 if children.len() == 3 {
7012 if let (Node::Leaf(h), Node::Leaf(lo_s), Node::Leaf(hi_s)) =
7013 (&children[0], &children[1], &children[2])
7014 {
7015 if h == "range" && is_num(lo_s) && is_num(hi_s) {
7016 return wrap_proof(
7017 "configuration",
7018 vec![leaf("range"), leaf(lo_s), leaf(hi_s)],
7019 );
7020 }
7021 }
7022 }
7023 if children.len() == 2 {
7024 if let (Node::Leaf(h), Node::Leaf(v)) = (&children[0], &children[1]) {
7025 if h == "valence" && is_num(v) {
7026 return wrap_proof(
7027 "configuration",
7028 vec![leaf("valence"), leaf(v)],
7029 );
7030 }
7031 }
7032 }
7033
7034 if let Some(Node::Leaf(head)) = children.first() {
7036 if head == "?" {
7037 let parts = &children[1..];
7038 let inner = strip_with_proof(parts);
7039 let target = if inner.len() == 1 {
7040 inner[0].clone()
7041 } else {
7042 Node::List(inner.to_vec())
7043 };
7044 return wrap_proof("query", vec![build_proof(&target, env)]);
7045 }
7046 }
7047
7048 if children.len() == 3 {
7050 if let Node::Leaf(op_name) = &children[1] {
7051 if matches!(op_name.as_str(), "+" | "-" | "*" | "/") {
7052 let rule = match op_name.as_str() {
7053 "+" => "sum",
7054 "-" => "difference",
7055 "*" => "product",
7056 "/" => "quotient",
7057 _ => unreachable!(),
7058 };
7059 return wrap_proof(
7060 rule,
7061 vec![build_proof(&children[0], env), build_proof(&children[2], env)],
7062 );
7063 }
7064 }
7065 }
7066
7067 if children.len() == 3 {
7069 if let Node::Leaf(op_name) = &children[1] {
7070 if matches!(op_name.as_str(), "and" | "or" | "both" | "neither") {
7071 return wrap_proof(
7072 op_name,
7073 vec![build_proof(&children[0], env), build_proof(&children[2], env)],
7074 );
7075 }
7076 }
7077 }
7078
7079 if children.len() >= 4 && children.len() % 2 == 0 {
7082 if let Node::Leaf(head) = &children[0] {
7083 if head == "both" || head == "neither" {
7084 let sep = if head == "both" { "and" } else { "nor" };
7085 let mut valid = true;
7086 for i in (2..children.len()).step_by(2) {
7087 if let Node::Leaf(s) = &children[i] {
7088 if s != sep {
7089 valid = false;
7090 break;
7091 }
7092 } else {
7093 valid = false;
7094 break;
7095 }
7096 }
7097 if valid {
7098 let subs: Vec<Node> = (1..children.len())
7099 .step_by(2)
7100 .map(|i| build_proof(&children[i], env))
7101 .collect();
7102 return wrap_proof(head, subs);
7103 }
7104 }
7105 }
7106 }
7107
7108 if children.len() == 3 {
7110 if let Node::Leaf(op_name) = &children[1] {
7111 if op_name == "=" || op_name == "!=" {
7112 let l = &children[0];
7113 let r = &children[2];
7114 let rule = classify_equality_rule(l, r, op_name, env);
7115 let pair = Node::List(vec![l.clone(), r.clone()]);
7119 return wrap_proof(rule, vec![pair]);
7120 }
7121 }
7122 }
7123
7124 if children.len() == 2 {
7126 if let (Node::Leaf(h), level) = (&children[0], &children[1]) {
7127 if h == "Type" {
7128 return wrap_proof("type-universe", vec![level.clone()]);
7129 }
7130 }
7131 }
7132 if children.len() == 1 {
7133 if let Node::Leaf(h) = &children[0] {
7134 if h == "Prop" {
7135 return wrap_proof("prop", vec![]);
7136 }
7137 }
7138 }
7139 if children.len() == 3 {
7140 if let Node::Leaf(h) = &children[0] {
7141 if h == "Pi" {
7142 return wrap_proof(
7143 "pi-formation",
7144 vec![children[1].clone(), children[2].clone()],
7145 );
7146 }
7147 if h == "lambda" {
7148 return wrap_proof(
7149 "lambda-formation",
7150 vec![children[1].clone(), children[2].clone()],
7151 );
7152 }
7153 if h == "apply" {
7154 return wrap_proof(
7155 "beta-reduction",
7156 vec![build_proof(&children[1], env), build_proof(&children[2], env)],
7157 );
7158 }
7159 }
7160 }
7161 if children.len() == 2 {
7165 if let Node::Leaf(h) = &children[0] {
7166 if h == "whnf" {
7167 return wrap_proof("whnf-reduction", vec![children[1].clone()]);
7168 }
7169 if h == "nf" || h == "normal-form" {
7170 return wrap_proof("nf-reduction", vec![children[1].clone()]);
7171 }
7172 }
7173 }
7174 if children.len() == 4 {
7175 if let Node::Leaf(h) = &children[0] {
7176 if h == "subst" {
7177 return wrap_proof(
7178 "substitution",
7179 vec![
7180 children[1].clone(),
7181 children[2].clone(),
7182 children[3].clone(),
7183 ],
7184 );
7185 }
7186 if h == "fresh" {
7187 if let Node::Leaf(in_kw) = &children[2] {
7188 if in_kw == "in" {
7189 return wrap_proof(
7190 "fresh",
7191 vec![children[1].clone(), children[3].clone()],
7192 );
7193 }
7194 }
7195 }
7196 }
7197 }
7198 if children.len() == 3 {
7199 if let (Node::Leaf(h), Node::Leaf(m)) = (&children[0], &children[1]) {
7200 if h == "type" && m == "of" {
7201 return wrap_proof(
7202 "type-query",
7203 vec![children[2].clone()],
7204 );
7205 }
7206 }
7207 if let Node::Leaf(m) = &children[1] {
7208 if m == "of" {
7209 return wrap_proof(
7210 "type-check",
7211 vec![children[0].clone(), children[2].clone()],
7212 );
7213 }
7214 }
7215 }
7216
7217 if let Node::Leaf(head) = &children[0] {
7219 if env.has_op(head) {
7220 let subs: Vec<Node> = children[1..]
7221 .iter()
7222 .map(|arg| build_proof(arg, env))
7223 .collect();
7224 return wrap_proof(head, subs);
7225 }
7226 }
7227
7228 wrap_proof("reduce", vec![node.clone()])
7230 }
7231 }
7232}
7233
7234#[derive(Debug, Clone, PartialEq)]
7241pub struct ProofGoal {
7242 pub goal: Node,
7243 pub context: Vec<Node>,
7244}
7245
7246impl ProofGoal {
7247 pub fn new(goal: Node) -> Self {
7248 Self {
7249 goal,
7250 context: Vec::new(),
7251 }
7252 }
7253}
7254
7255#[derive(Debug, Clone, Default, PartialEq)]
7257pub struct ProofState {
7258 pub goals: Vec<ProofGoal>,
7259 pub proof: Vec<Node>,
7260}
7261
7262impl ProofState {
7263 pub fn from_goals(goals: Vec<Node>) -> Self {
7264 Self {
7265 goals: goals.into_iter().map(ProofGoal::new).collect(),
7266 proof: Vec::new(),
7267 }
7268 }
7269}
7270
7271#[derive(Debug, Clone, PartialEq)]
7273pub struct TacticRunResult {
7274 pub state: ProofState,
7275 pub diagnostics: Vec<Diagnostic>,
7276}
7277
7278const DEFAULT_SIMPLIFY_MAX_STEPS: usize = 100;
7279const DEFAULT_ATP_TIMEOUT_MS: u64 = 5000;
7280const DEFAULT_SMT_TIMEOUT_MS: u64 = 5000;
7281const ATP_PROVED_STATUSES: &[&str] = &["Theorem", "Unsatisfiable", "ContradictoryAxioms"];
7282const ATP_UNKNOWN_STATUSES: &[&str] = &["Unknown", "GaveUp"];
7283const ATP_TIMEOUT_STATUSES: &[&str] = &["Timeout", "ResourceOut"];
7284
7285#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7287pub enum RewriteDirection {
7288 Forward,
7289 Backward,
7290}
7291
7292#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7294pub enum RewriteOccurrence {
7295 All,
7296 Index(usize),
7297}
7298
7299#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7301pub struct RewriteOptions {
7302 pub direction: RewriteDirection,
7303 pub occurrence: RewriteOccurrence,
7304}
7305
7306impl Default for RewriteOptions {
7307 fn default() -> Self {
7308 Self {
7309 direction: RewriteDirection::Forward,
7310 occurrence: RewriteOccurrence::All,
7311 }
7312 }
7313}
7314
7315#[derive(Debug, Clone, PartialEq)]
7317pub struct RewriteResult {
7318 pub node: Node,
7319 pub changed: bool,
7320 pub count: usize,
7321}
7322
7323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7325pub struct SimplifyOptions {
7326 pub max_steps: usize,
7327}
7328
7329impl Default for SimplifyOptions {
7330 fn default() -> Self {
7331 Self {
7332 max_steps: DEFAULT_SIMPLIFY_MAX_STEPS,
7333 }
7334 }
7335}
7336
7337#[derive(Debug, Clone, PartialEq)]
7339pub struct SimplifyResult {
7340 pub node: Node,
7341 pub changed: bool,
7342 pub steps: usize,
7343}
7344
7345#[derive(Debug, Clone, PartialEq, Eq)]
7347pub struct AtpOptions {
7348 pub path: Option<String>,
7349 pub args: Vec<String>,
7350 pub name: Option<String>,
7351 pub timeout_ms: u64,
7352}
7353
7354impl Default for AtpOptions {
7355 fn default() -> Self {
7356 Self {
7357 path: None,
7358 args: Vec::new(),
7359 name: None,
7360 timeout_ms: DEFAULT_ATP_TIMEOUT_MS,
7361 }
7362 }
7363}
7364
7365#[derive(Debug, Clone, PartialEq, Eq)]
7367pub enum AtpStatusKind {
7368 Proved,
7369 Unknown,
7370 Timeout,
7371 Failure,
7372}
7373
7374#[derive(Debug, Clone, PartialEq, Eq)]
7376pub struct AtpStatus {
7377 pub status: String,
7378 pub kind: AtpStatusKind,
7379}
7380
7381#[derive(Debug, Clone, PartialEq)]
7383pub struct TacticOptions {
7384 pub rewrite_rules: Vec<Node>,
7385 pub simplify_max_steps: usize,
7386 pub atp: AtpOptions,
7387 pub smt_solver: Option<String>,
7388 pub smt_solver_args: Vec<String>,
7389 pub smt_timeout_ms: u64,
7390}
7391
7392impl Default for TacticOptions {
7393 fn default() -> Self {
7394 Self {
7395 rewrite_rules: Vec::new(),
7396 simplify_max_steps: DEFAULT_SIMPLIFY_MAX_STEPS,
7397 atp: AtpOptions::default(),
7398 smt_solver: std::env::var("RML_SMT_SOLVER").ok(),
7399 smt_solver_args: std::env::var("RML_SMT_ARGS")
7400 .ok()
7401 .map(|args| {
7402 args.split_whitespace()
7403 .map(|arg| arg.to_string())
7404 .collect()
7405 })
7406 .unwrap_or_default(),
7407 smt_timeout_ms: std::env::var("RML_SMT_TIMEOUT_MS")
7408 .ok()
7409 .and_then(|raw| raw.parse::<u64>().ok())
7410 .unwrap_or(DEFAULT_SMT_TIMEOUT_MS),
7411 }
7412 }
7413}
7414
7415fn tactic_name(tactic: &Node) -> Option<&str> {
7416 match tactic {
7417 Node::Leaf(s) => Some(s.as_str()),
7418 Node::List(children) => match children.first() {
7419 Some(Node::Leaf(s)) => Some(s.as_str()),
7420 _ => None,
7421 },
7422 }
7423}
7424
7425fn tactic_args(tactic: &Node) -> &[Node] {
7426 match tactic {
7427 Node::List(children) if !children.is_empty() => &children[1..],
7428 _ => &[],
7429 }
7430}
7431
7432fn as_equality(node: &Node) -> Option<(&Node, &Node)> {
7433 if let Node::List(children) = node {
7434 if children.len() == 3 {
7435 if let Node::Leaf(op) = &children[1] {
7436 if op == "=" {
7437 return Some((&children[0], &children[2]));
7438 }
7439 }
7440 }
7441 }
7442 None
7443}
7444
7445fn tactic_diagnostic(
7446 tactic: &Node,
7447 goal: Option<&ProofGoal>,
7448 reason: impl AsRef<str>,
7449) -> Diagnostic {
7450 let goal_text = goal
7451 .map(|g| key_of(&g.goal))
7452 .unwrap_or_else(|| "<none>".to_string());
7453 Diagnostic::new(
7454 "E039",
7455 format!(
7456 "Tactic {} failed: {}; current goal: {}",
7457 key_of(tactic),
7458 reason.as_ref(),
7459 goal_text
7460 ),
7461 Span::unknown(),
7462 )
7463}
7464
7465fn goal_with_context(current: &ProofGoal, goal: Node) -> ProofGoal {
7466 ProofGoal {
7467 goal,
7468 context: current.context.clone(),
7469 }
7470}
7471
7472fn replace_current_goal(
7473 state: &ProofState,
7474 replacement_goals: Vec<ProofGoal>,
7475 record_tactic: &Node,
7476) -> ProofState {
7477 let mut goals = replacement_goals;
7478 goals.extend(state.goals.iter().skip(1).cloned());
7479 let mut proof = state.proof.clone();
7480 proof.push(record_tactic.clone());
7481 ProofState { goals, proof }
7482}
7483
7484fn rewrite_diagnostic(message: impl Into<String>) -> Diagnostic {
7485 Diagnostic::new("E039", message, Span::unknown())
7486}
7487
7488#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7489enum SmtSort {
7490 Bool,
7491 Real,
7492}
7493
7494impl SmtSort {
7495 fn as_str(self) -> &'static str {
7496 match self {
7497 SmtSort::Bool => "Bool",
7498 SmtSort::Real => "Real",
7499 }
7500 }
7501}
7502
7503#[derive(Debug, Default)]
7504struct SmtContext {
7505 declarations: BTreeMap<String, SmtSort>,
7506}
7507
7508#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7509enum SmtStatus {
7510 Unsat,
7511 Sat,
7512 Unknown,
7513 Timeout,
7514 Error,
7515}
7516
7517#[derive(Debug, Clone, PartialEq, Eq)]
7518struct SmtRunResult {
7519 status: SmtStatus,
7520 reason: String,
7521}
7522
7523fn smt_escape_symbol(raw: &str) -> String {
7524 format!("|{}|", raw.replace('\\', "\\\\").replace('|', "\\|"))
7525}
7526
7527fn smt_declare(ctx: &mut SmtContext, raw: String, sort: SmtSort) -> Result<String, String> {
7528 if let Some(existing) = ctx.declarations.get(&raw) {
7529 if *existing != sort {
7530 return Err(format!(
7531 "SMT symbol {} is used as both {} and {}",
7532 raw,
7533 existing.as_str(),
7534 sort.as_str()
7535 ));
7536 }
7537 }
7538 ctx.declarations.insert(raw.clone(), sort);
7539 Ok(smt_escape_symbol(&raw))
7540}
7541
7542fn smt_number(raw: &str) -> String {
7543 if let Some(rest) = raw.strip_prefix('-') {
7544 format!("(- {})", rest)
7545 } else {
7546 raw.to_string()
7547 }
7548}
7549
7550fn smt_infix<'a>(node: &'a Node, operators: &[&str]) -> Option<&'a str> {
7551 let Node::List(children) = node else {
7552 return None;
7553 };
7554 if children.len() != 3 {
7555 return None;
7556 }
7557 let Node::Leaf(op) = &children[1] else {
7558 return None;
7559 };
7560 if operators.contains(&op.as_str()) {
7561 Some(op.as_str())
7562 } else {
7563 None
7564 }
7565}
7566
7567fn smt_is_boolish(node: &Node) -> bool {
7568 match node {
7569 Node::Leaf(s) => s == "true" || s == "false",
7570 Node::List(children) => {
7571 if children.is_empty() {
7572 return false;
7573 }
7574 if smt_infix(node, &["=", "!=", "and", "or", "=>", "implies"]).is_some() {
7575 return true;
7576 }
7577 matches!(
7578 &children[0],
7579 Node::Leaf(head)
7580 if matches!(head.as_str(), "not" | "and" | "or" | "=>" | "implies")
7581 )
7582 }
7583 }
7584}
7585
7586fn smt_term(node: &Node, ctx: &mut SmtContext) -> Result<String, String> {
7587 match node {
7588 Node::Leaf(s) => {
7589 if is_num(s) {
7590 return Ok(smt_number(s));
7591 }
7592 if s == "true" || s == "false" {
7593 return Err(format!(
7594 "SMT bridge cannot use Boolean constant {} as a Real term",
7595 s
7596 ));
7597 }
7598 smt_declare(ctx, s.clone(), SmtSort::Real)
7599 }
7600 Node::List(children) => {
7601 if children.is_empty() {
7602 return Err(format!("SMT bridge cannot translate term {}", key_of(node)));
7603 }
7604 if let Some(op) = smt_infix(node, &["+", "-", "*", "/"]) {
7605 return Ok(format!(
7606 "({} {} {})",
7607 op,
7608 smt_term(&children[0], ctx)?,
7609 smt_term(&children[2], ctx)?
7610 ));
7611 }
7612 if let Node::Leaf(head) = &children[0] {
7613 if ["+", "-", "*", "/"].contains(&head.as_str()) && children.len() >= 3 {
7614 let args = children[1..]
7615 .iter()
7616 .map(|arg| smt_term(arg, ctx))
7617 .collect::<Result<Vec<_>, _>>()?;
7618 return Ok(format!("({} {})", head, args.join(" ")));
7619 }
7620 }
7621 smt_declare(ctx, key_of(node), SmtSort::Real)
7622 }
7623 }
7624}
7625
7626fn smt_equality(left: &Node, right: &Node, ctx: &mut SmtContext) -> Result<String, String> {
7627 if smt_is_boolish(left) || smt_is_boolish(right) {
7628 return Ok(format!(
7629 "(= {} {})",
7630 smt_formula(left, ctx)?,
7631 smt_formula(right, ctx)?
7632 ));
7633 }
7634 Ok(format!(
7635 "(= {} {})",
7636 smt_term(left, ctx)?,
7637 smt_term(right, ctx)?
7638 ))
7639}
7640
7641fn smt_formula(node: &Node, ctx: &mut SmtContext) -> Result<String, String> {
7642 match node {
7643 Node::Leaf(s) => {
7644 if s == "true" {
7645 return Ok("true".to_string());
7646 }
7647 if s == "false" {
7648 return Ok("false".to_string());
7649 }
7650 if is_num(s) {
7651 return Err(format!(
7652 "SMT bridge cannot use numeric literal {} as a Boolean formula",
7653 s
7654 ));
7655 }
7656 smt_declare(ctx, s.clone(), SmtSort::Bool)
7657 }
7658 Node::List(children) => {
7659 if children.is_empty() {
7660 return Err(format!("SMT bridge cannot translate formula {}", key_of(node)));
7661 }
7662 match smt_infix(node, &["=", "!=", "and", "or", "=>", "implies"]) {
7663 Some("=") => return smt_equality(&children[0], &children[2], ctx),
7664 Some("!=") => {
7665 return Ok(format!(
7666 "(not {})",
7667 smt_equality(&children[0], &children[2], ctx)?
7668 ));
7669 }
7670 Some("and") | Some("or") => {
7671 let op = smt_infix(node, &["and", "or"]).unwrap();
7672 return Ok(format!(
7673 "({} {} {})",
7674 op,
7675 smt_formula(&children[0], ctx)?,
7676 smt_formula(&children[2], ctx)?
7677 ));
7678 }
7679 Some("=>") | Some("implies") => {
7680 return Ok(format!(
7681 "(=> {} {})",
7682 smt_formula(&children[0], ctx)?,
7683 smt_formula(&children[2], ctx)?
7684 ));
7685 }
7686 _ => {}
7687 }
7688
7689 if let Node::Leaf(head) = &children[0] {
7690 match head.as_str() {
7691 "not" if children.len() == 2 => {
7692 return Ok(format!("(not {})", smt_formula(&children[1], ctx)?));
7693 }
7694 "and" | "or" => {
7695 if children.len() == 1 {
7696 return Ok(if head == "and" {
7697 "true".to_string()
7698 } else {
7699 "false".to_string()
7700 });
7701 }
7702 let args = children[1..]
7703 .iter()
7704 .map(|arg| smt_formula(arg, ctx))
7705 .collect::<Result<Vec<_>, _>>()?;
7706 return Ok(format!("({} {})", head, args.join(" ")));
7707 }
7708 "=>" | "implies" if children.len() == 3 => {
7709 return Ok(format!(
7710 "(=> {} {})",
7711 smt_formula(&children[1], ctx)?,
7712 smt_formula(&children[2], ctx)?
7713 ));
7714 }
7715 _ => {}
7716 }
7717 }
7718
7719 smt_declare(ctx, key_of(node), SmtSort::Bool)
7720 }
7721 }
7722}
7723
7724fn smt_lib_for_goal(goal: &Node) -> Result<String, String> {
7725 let mut ctx = SmtContext::default();
7726 let formula = smt_formula(goal, &mut ctx)?;
7727 let mut lines = Vec::new();
7728 for (name, sort) in ctx.declarations {
7729 lines.push(format!(
7730 "(declare-const {} {})",
7731 smt_escape_symbol(&name),
7732 sort.as_str()
7733 ));
7734 }
7735 lines.push(format!("(assert (not {}))", formula));
7736 lines.push("(check-sat)".to_string());
7737 lines.push("(exit)".to_string());
7738 lines.push(String::new());
7739 Ok(lines.join("\n"))
7740}
7741
7742fn smt_solver_proof_name(options: &TacticOptions) -> String {
7743 let Some(solver) = options.smt_solver.as_deref() else {
7744 return "unconfigured".to_string();
7745 };
7746 let base = Path::new(solver)
7747 .file_name()
7748 .and_then(|name| name.to_str())
7749 .unwrap_or(solver);
7750 let safe: String = base
7751 .chars()
7752 .map(|c| if c.is_whitespace() { '_' } else { c })
7753 .collect();
7754 if safe.is_empty() {
7755 "solver".to_string()
7756 } else {
7757 safe
7758 }
7759}
7760
7761fn smt_trusted_node(options: &TacticOptions) -> Node {
7762 Node::List(vec![
7763 leaf("by"),
7764 leaf("smt-trusted"),
7765 Node::Leaf(smt_solver_proof_name(options)),
7766 ])
7767}
7768
7769fn smt_process_summary(stdout: &[u8], stderr: &[u8]) -> String {
7770 let stderr_text = String::from_utf8_lossy(stderr);
7771 let stdout_text = String::from_utf8_lossy(stdout);
7772 let text = if stderr_text.trim().is_empty() {
7773 stdout_text.trim()
7774 } else {
7775 stderr_text.trim()
7776 };
7777 if text.is_empty() {
7778 return "<no output>".to_string();
7779 }
7780 let first = text.lines().next().unwrap_or(text);
7781 if first.chars().count() > 200 {
7782 format!("{}...", first.chars().take(200).collect::<String>())
7783 } else {
7784 first.to_string()
7785 }
7786}
7787
7788fn parse_smt_check_sat(stdout: &[u8], stderr: &[u8]) -> Option<SmtStatus> {
7789 let stdout_text = String::from_utf8_lossy(stdout);
7790 let stderr_text = String::from_utf8_lossy(stderr);
7791 for line in stdout_text.lines().chain(stderr_text.lines()) {
7792 match line.trim() {
7793 "unsat" => return Some(SmtStatus::Unsat),
7794 "sat" => return Some(SmtStatus::Sat),
7795 "unknown" => return Some(SmtStatus::Unknown),
7796 _ => {}
7797 }
7798 }
7799 None
7800}
7801
7802fn run_smt_solver(smt_lib: &str, options: &TacticOptions) -> SmtRunResult {
7803 let Some(solver) = options
7804 .smt_solver
7805 .as_deref()
7806 .filter(|solver| !solver.trim().is_empty())
7807 else {
7808 return SmtRunResult {
7809 status: SmtStatus::Error,
7810 reason: "SMT solver path is not configured".to_string(),
7811 };
7812 };
7813 let solver_name = smt_solver_proof_name(options);
7814 let mut child = match Command::new(solver)
7815 .args(&options.smt_solver_args)
7816 .stdin(Stdio::piped())
7817 .stdout(Stdio::piped())
7818 .stderr(Stdio::piped())
7819 .spawn()
7820 {
7821 Ok(child) => child,
7822 Err(err) => {
7823 return SmtRunResult {
7824 status: SmtStatus::Error,
7825 reason: format!("SMT solver {} failed to start: {}", solver_name, err),
7826 };
7827 }
7828 };
7829
7830 let mut stdin_error = None;
7831 if let Some(mut stdin) = child.stdin.take() {
7832 if let Err(err) = stdin.write_all(smt_lib.as_bytes()) {
7833 stdin_error = Some(err.to_string());
7834 }
7835 }
7836
7837 let started = Instant::now();
7838 let timeout = Duration::from_millis(options.smt_timeout_ms);
7839 loop {
7840 match child.try_wait() {
7841 Ok(Some(_)) => {
7842 let output = match child.wait_with_output() {
7843 Ok(output) => output,
7844 Err(err) => {
7845 return SmtRunResult {
7846 status: SmtStatus::Error,
7847 reason: format!(
7848 "SMT solver {} output collection failed: {}",
7849 solver_name, err
7850 ),
7851 };
7852 }
7853 };
7854 if !output.status.success() {
7855 return SmtRunResult {
7856 status: SmtStatus::Error,
7857 reason: format!(
7858 "SMT solver {} exited with status {}: {}",
7859 solver_name,
7860 output.status,
7861 smt_process_summary(&output.stdout, &output.stderr)
7862 ),
7863 };
7864 }
7865 let Some(status) = parse_smt_check_sat(&output.stdout, &output.stderr) else {
7866 let reason = stdin_error
7867 .map(|err| {
7868 format!(
7869 "SMT solver {} did not accept SMT-LIB input: {}",
7870 solver_name, err
7871 )
7872 })
7873 .unwrap_or_else(|| {
7874 format!(
7875 "SMT solver {} did not return sat, unsat, or unknown",
7876 solver_name
7877 )
7878 });
7879 return SmtRunResult {
7880 status: SmtStatus::Error,
7881 reason,
7882 };
7883 };
7884 return SmtRunResult {
7885 status,
7886 reason: format!(
7887 "SMT solver {} returned {}",
7888 solver_name,
7889 match status {
7890 SmtStatus::Unsat => "unsat",
7891 SmtStatus::Sat => "sat",
7892 SmtStatus::Unknown => "unknown",
7893 SmtStatus::Timeout | SmtStatus::Error => unreachable!(),
7894 }
7895 ),
7896 };
7897 }
7898 Ok(None) => {
7899 if started.elapsed() >= timeout {
7900 let _ = child.kill();
7901 let _ = child.wait();
7902 return SmtRunResult {
7903 status: SmtStatus::Timeout,
7904 reason: format!(
7905 "SMT solver {} timed out after {} ms",
7906 solver_name, options.smt_timeout_ms
7907 ),
7908 };
7909 }
7910 thread::sleep(Duration::from_millis(10));
7911 }
7912 Err(err) => {
7913 return SmtRunResult {
7914 status: SmtStatus::Error,
7915 reason: format!("SMT solver {} wait failed: {}", solver_name, err),
7916 };
7917 }
7918 }
7919 }
7920}
7921
7922fn tptp_identifier(raw: &str, role: &str) -> String {
7923 let mut cleaned: String = raw
7924 .chars()
7925 .map(|c| {
7926 if c.is_ascii_alphanumeric() || c == '_' {
7927 c
7928 } else {
7929 '_'
7930 }
7931 })
7932 .collect();
7933 if cleaned.is_empty() {
7934 cleaned = if role == "var" {
7935 "X".to_string()
7936 } else {
7937 "rml_symbol".to_string()
7938 };
7939 }
7940 if role == "var" {
7941 let mut chars = cleaned.chars();
7942 if let Some(first) = chars.next() {
7943 cleaned = first.to_ascii_uppercase().to_string() + chars.as_str();
7944 }
7945 if !cleaned
7946 .chars()
7947 .next()
7948 .map(|c| c.is_ascii_uppercase())
7949 .unwrap_or(false)
7950 {
7951 cleaned = format!("V_{}", cleaned);
7952 }
7953 return cleaned;
7954 }
7955 cleaned = cleaned.to_ascii_lowercase();
7956 if !cleaned
7957 .chars()
7958 .next()
7959 .map(|c| c.is_ascii_lowercase())
7960 .unwrap_or(false)
7961 {
7962 cleaned = format!("rml_{}", cleaned);
7963 }
7964 cleaned
7965}
7966
7967fn tptp_term(node: &Node, bound_vars: &HashSet<String>) -> Result<String, Diagnostic> {
7968 match node {
7969 Node::Leaf(raw) => {
7970 if bound_vars.contains(raw) {
7971 Ok(tptp_identifier(raw, "var"))
7972 } else if is_num(raw) {
7973 Ok(tptp_identifier(&format!("num_{}", raw), "term"))
7974 } else {
7975 Ok(tptp_identifier(raw, "term"))
7976 }
7977 }
7978 Node::List(children) if !children.is_empty() => {
7979 let Node::Leaf(head) = &children[0] else {
7980 return Err(rewrite_diagnostic(format!(
7981 "TPTP export supports first-order terms only (got {})",
7982 key_of(node)
7983 )));
7984 };
7985 let args = children[1..]
7986 .iter()
7987 .map(|arg| tptp_term(arg, bound_vars))
7988 .collect::<Result<Vec<_>, _>>()?
7989 .join(", ");
7990 Ok(format!("{}({})", tptp_identifier(head, "term"), args))
7991 }
7992 _ => Err(rewrite_diagnostic(format!(
7993 "TPTP export supports first-order terms only (got {})",
7994 key_of(node)
7995 ))),
7996 }
7997}
7998
7999fn infix_operands<'a>(node: &'a Node, op: &str) -> Option<Vec<&'a Node>> {
8000 let Node::List(children) = node else {
8001 return None;
8002 };
8003 if children.len() < 3 || children.len() % 2 == 0 {
8004 return None;
8005 }
8006 let mut operands = Vec::new();
8007 let mut index = 0;
8008 while index < children.len() {
8009 if index > 0 && !matches!(&children[index - 1], Node::Leaf(mid) if mid == op) {
8010 return None;
8011 }
8012 operands.push(&children[index]);
8013 index += 2;
8014 }
8015 Some(operands)
8016}
8017
8018fn tptp_join_formula(
8019 op: &str,
8020 operands: &[&Node],
8021 bound_vars: &HashSet<String>,
8022) -> Result<String, Diagnostic> {
8023 let parts = operands
8024 .iter()
8025 .map(|part| tptp_formula(part, bound_vars).map(|s| format!("({})", s)))
8026 .collect::<Result<Vec<_>, _>>()?;
8027 Ok(parts.join(&format!(" {} ", op)))
8028}
8029
8030fn quantifier_parts<'a>(node: &'a Node) -> Result<Option<(&'a str, String, &'a Node)>, Diagnostic> {
8031 let Node::List(children) = node else {
8032 return Ok(None);
8033 };
8034 if children.len() != 3 {
8035 return Ok(None);
8036 }
8037 let Node::Leaf(head) = &children[0] else {
8038 return Ok(None);
8039 };
8040 if head != "forall" && head != "exists" && head != "Pi" {
8041 return Ok(None);
8042 }
8043 let Some((variable, _)) = parse_binding(&children[1]) else {
8044 return Err(rewrite_diagnostic(format!(
8045 "TPTP export could not parse quantifier binder {}",
8046 key_of(&children[1])
8047 )));
8048 };
8049 let quantifier = if head == "exists" { "?" } else { "!" };
8050 Ok(Some((quantifier, variable, &children[2])))
8051}
8052
8053fn tptp_formula(node: &Node, bound_vars: &HashSet<String>) -> Result<String, Diagnostic> {
8054 match node {
8055 Node::Leaf(raw) => {
8056 if raw == "true" {
8057 return Ok("$true".to_string());
8058 }
8059 if raw == "false" {
8060 return Ok("$false".to_string());
8061 }
8062 if bound_vars.contains(raw) {
8063 return Ok(tptp_identifier(raw, "var"));
8064 }
8065 return Ok(tptp_identifier(raw, "pred"));
8066 }
8067 Node::List(children) if children.is_empty() => {
8068 return Err(rewrite_diagnostic(format!(
8069 "TPTP export supports first-order formulas only (got {})",
8070 key_of(node)
8071 )));
8072 }
8073 Node::List(_) => {}
8074 }
8075
8076 if let Some((quantifier, variable, body)) = quantifier_parts(node)? {
8077 let mut next_bound = bound_vars.clone();
8078 next_bound.insert(variable.clone());
8079 return Ok(format!(
8080 "{}[{}] : ({})",
8081 quantifier,
8082 tptp_identifier(&variable, "var"),
8083 tptp_formula(body, &next_bound)?
8084 ));
8085 }
8086
8087 if let Some((term, typ)) = type_ascription(node) {
8088 return Ok(format!(
8089 "{}({})",
8090 tptp_identifier(&key_of(typ), "pred"),
8091 tptp_term(term, bound_vars)?
8092 ));
8093 }
8094
8095 if let Some((left, right)) = as_equality(node) {
8096 return Ok(format!(
8097 "{} = {}",
8098 tptp_term(left, bound_vars)?,
8099 tptp_term(right, bound_vars)?
8100 ));
8101 }
8102 if let Node::List(children) = node {
8103 if children.len() == 3 && matches!(&children[1], Node::Leaf(op) if op == "!=") {
8104 return Ok(format!(
8105 "{} != {}",
8106 tptp_term(&children[0], bound_vars)?,
8107 tptp_term(&children[2], bound_vars)?
8108 ));
8109 }
8110 }
8111
8112 if let Some(operands) = infix_operands(node, "and") {
8113 return tptp_join_formula("&", &operands, bound_vars);
8114 }
8115 if let Some(operands) = infix_operands(node, "or") {
8116 return tptp_join_formula("|", &operands, bound_vars);
8117 }
8118 if let Some(operands) = infix_operands(node, "=>").or_else(|| infix_operands(node, "implies")) {
8119 if operands.len() == 2 {
8120 return tptp_join_formula("=>", &operands, bound_vars);
8121 }
8122 }
8123 if let Some(operands) = infix_operands(node, "<=>").or_else(|| infix_operands(node, "iff")) {
8124 if operands.len() == 2 {
8125 return tptp_join_formula("<=>", &operands, bound_vars);
8126 }
8127 }
8128
8129 let Node::List(children) = node else {
8130 unreachable!();
8131 };
8132 let Node::Leaf(head) = &children[0] else {
8133 return Err(rewrite_diagnostic(format!(
8134 "TPTP export supports first-order formulas only (got {})",
8135 key_of(node)
8136 )));
8137 };
8138 match head.as_str() {
8139 "not" if children.len() == 2 => {
8140 Ok(format!("~({})", tptp_formula(&children[1], bound_vars)?))
8141 }
8142 "and" if children.len() >= 2 => {
8143 let operands: Vec<&Node> = children[1..].iter().collect();
8144 tptp_join_formula("&", &operands, bound_vars)
8145 }
8146 "or" if children.len() >= 2 => {
8147 let operands: Vec<&Node> = children[1..].iter().collect();
8148 tptp_join_formula("|", &operands, bound_vars)
8149 }
8150 "=>" | "implies" if children.len() == 3 => {
8151 let operands: Vec<&Node> = children[1..].iter().collect();
8152 tptp_join_formula("=>", &operands, bound_vars)
8153 }
8154 "<=>" | "iff" if children.len() == 3 => {
8155 let operands: Vec<&Node> = children[1..].iter().collect();
8156 tptp_join_formula("<=>", &operands, bound_vars)
8157 }
8158 _ => {
8159 let predicate = tptp_identifier(head, "pred");
8160 if children.len() == 1 {
8161 return Ok(predicate);
8162 }
8163 let args = children[1..]
8164 .iter()
8165 .map(|arg| tptp_term(arg, bound_vars))
8166 .collect::<Result<Vec<_>, _>>()?
8167 .join(", ");
8168 Ok(format!("{}({})", predicate, args))
8169 }
8170 }
8171}
8172
8173pub fn goal_to_tptp(goal: &ProofGoal) -> Result<String, Diagnostic> {
8175 let bound_vars = HashSet::new();
8176 let mut lines = Vec::new();
8177 for (index, ctx) in goal.context.iter().enumerate() {
8178 lines.push(format!(
8179 "fof(rml_context_{}, axiom, ({})).",
8180 index + 1,
8181 tptp_formula(ctx, &bound_vars)?
8182 ));
8183 }
8184 lines.push(format!(
8185 "fof(rml_goal, conjecture, ({})).",
8186 tptp_formula(&goal.goal, &bound_vars)?
8187 ));
8188 Ok(format!("{}\n", lines.join("\n")))
8189}
8190
8191pub fn parse_atp_status(output: &str) -> Option<AtpStatus> {
8193 let tokens: Vec<&str> = output.split_whitespace().collect();
8194 for window in tokens.windows(3) {
8195 if window[0] == "SZS" && window[1] == "status" {
8196 let status = window[2].to_string();
8197 let kind = if ATP_PROVED_STATUSES.contains(&window[2]) {
8198 AtpStatusKind::Proved
8199 } else if ATP_UNKNOWN_STATUSES.contains(&window[2]) {
8200 AtpStatusKind::Unknown
8201 } else if ATP_TIMEOUT_STATUSES.contains(&window[2]) {
8202 AtpStatusKind::Timeout
8203 } else {
8204 AtpStatusKind::Failure
8205 };
8206 return Some(AtpStatus { status, kind });
8207 }
8208 }
8209 None
8210}
8211
8212fn atp_solver_name(options: &AtpOptions) -> String {
8213 let raw = options
8214 .name
8215 .clone()
8216 .or_else(|| {
8217 options.path.as_ref().and_then(|p| {
8218 Path::new(p)
8219 .file_name()
8220 .and_then(|name| name.to_str())
8221 .map(|name| name.to_string())
8222 })
8223 })
8224 .unwrap_or_else(|| "atp".to_string());
8225 let cleaned = raw
8226 .chars()
8227 .map(|c| {
8228 if c.is_whitespace() || c == '(' || c == ')' {
8229 '_'
8230 } else {
8231 c
8232 }
8233 })
8234 .collect::<String>();
8235 if cleaned.is_empty() {
8236 "atp".to_string()
8237 } else {
8238 cleaned
8239 }
8240}
8241
8242struct AtpRunSuccess {
8243 solver: String,
8244}
8245
8246fn read_atp_pipe<R>(mut pipe: R) -> JoinHandle<Result<Vec<u8>, String>>
8247where
8248 R: Read + Send + 'static,
8249{
8250 std::thread::spawn(move || {
8251 let mut bytes = Vec::new();
8252 pipe.read_to_end(&mut bytes)
8253 .map_err(|err| err.to_string())?;
8254 Ok(bytes)
8255 })
8256}
8257
8258fn collect_atp_pipe(
8259 handle: Option<JoinHandle<Result<Vec<u8>, String>>>,
8260 label: &str,
8261) -> Result<Vec<u8>, String> {
8262 match handle {
8263 Some(handle) => handle
8264 .join()
8265 .map_err(|_| format!("ATP {} reader failed", label))?,
8266 None => Ok(Vec::new()),
8267 }
8268}
8269
8270fn run_atp_process(tptp: &str, options: &AtpOptions) -> Result<AtpRunSuccess, String> {
8271 let Some(path) = options.path.as_ref().filter(|p| !p.is_empty()) else {
8272 return Err("ATP path is not configured".to_string());
8273 };
8274 if options.timeout_ms == 0 {
8275 return Err("ATP timeout must be a positive integer".to_string());
8276 }
8277 let mut child = Command::new(path)
8278 .args(&options.args)
8279 .stdin(Stdio::piped())
8280 .stdout(Stdio::piped())
8281 .stderr(Stdio::piped())
8282 .spawn()
8283 .map_err(|err| format!("ATP invocation failed: {}", err))?;
8284 let stdout_reader = child.stdout.take().map(read_atp_pipe);
8285 let stderr_reader = child.stderr.take().map(read_atp_pipe);
8286
8287 if let Some(mut stdin) = child.stdin.take() {
8288 stdin
8289 .write_all(tptp.as_bytes())
8290 .map_err(|err| format!("ATP invocation failed: {}", err))?;
8291 }
8292
8293 let deadline = Instant::now() + Duration::from_millis(options.timeout_ms);
8294 loop {
8295 match child.try_wait() {
8296 Ok(Some(_)) => break,
8297 Ok(None) => {
8298 if Instant::now() >= deadline {
8299 let _ = child.kill();
8300 let _ = child.wait();
8301 let _ = collect_atp_pipe(stdout_reader, "stdout");
8302 let _ = collect_atp_pipe(stderr_reader, "stderr");
8303 return Err(format!("ATP timed out after {} ms", options.timeout_ms));
8304 }
8305 sleep(Duration::from_millis(5));
8306 }
8307 Err(err) => return Err(format!("ATP invocation failed: {}", err)),
8308 }
8309 }
8310
8311 let status = child
8312 .wait()
8313 .map_err(|err| format!("ATP invocation failed: {}", err))?;
8314 let stdout_bytes = collect_atp_pipe(stdout_reader, "stdout")?;
8315 let stderr_bytes = collect_atp_pipe(stderr_reader, "stderr")?;
8316 let stdout = String::from_utf8_lossy(&stdout_bytes);
8317 let stderr = String::from_utf8_lossy(&stderr_bytes);
8318 let combined = format!("{}\n{}", stdout, stderr);
8319
8320 if !status.success() {
8321 let detail = if !stderr.trim().is_empty() {
8322 stderr.trim().to_string()
8323 } else if !stdout.trim().is_empty() {
8324 stdout.trim().to_string()
8325 } else {
8326 status
8327 .code()
8328 .map(|code| format!("exit status {}", code))
8329 .unwrap_or_else(|| "terminated by signal".to_string())
8330 };
8331 return Err(format!("ATP exited with status {}: {}", status, detail));
8332 }
8333
8334 let Some(status) = parse_atp_status(&combined) else {
8335 return Err("ATP output did not contain an SZS status".to_string());
8336 };
8337 match status.kind {
8338 AtpStatusKind::Proved => Ok(AtpRunSuccess {
8339 solver: atp_solver_name(options),
8340 }),
8341 AtpStatusKind::Timeout | AtpStatusKind::Unknown => {
8342 Err(format!("ATP returned {}", status.status))
8343 }
8344 AtpStatusKind::Failure => Err(format!("ATP returned non-proving status {}", status.status)),
8345 }
8346}
8347
8348fn rewrite_sides(
8349 eq: &Node,
8350 direction: RewriteDirection,
8351) -> Result<(&Node, &Node), Diagnostic> {
8352 let Some((left, right)) = as_equality(eq) else {
8353 return Err(rewrite_diagnostic("rewrite expects an equality link"));
8354 };
8355 Ok(match direction {
8356 RewriteDirection::Forward => (left, right),
8357 RewriteDirection::Backward => (right, left),
8358 })
8359}
8360
8361fn rewrite_node(
8362 node: &Node,
8363 from: &Node,
8364 to: &Node,
8365 occurrence: RewriteOccurrence,
8366 seen: &mut usize,
8367 count: &mut usize,
8368) -> Node {
8369 if is_structurally_same(node, from) {
8370 *seen += 1;
8371 let selected = match occurrence {
8372 RewriteOccurrence::All => true,
8373 RewriteOccurrence::Index(index) => *seen == index,
8374 };
8375 if selected {
8376 *count += 1;
8377 return to.clone();
8378 }
8379 }
8380 match node {
8381 Node::Leaf(_) => node.clone(),
8382 Node::List(children) => Node::List(
8383 children
8384 .iter()
8385 .map(|child| rewrite_node(child, from, to, occurrence, seen, count))
8386 .collect(),
8387 ),
8388 }
8389}
8390
8391pub fn rewrite_with_options(
8393 goal: &Node,
8394 eq: &Node,
8395 options: RewriteOptions,
8396) -> Result<RewriteResult, Diagnostic> {
8397 let (from, to) = rewrite_sides(eq, options.direction)?;
8398 let mut seen = 0;
8399 let mut count = 0;
8400 let node = rewrite_node(
8401 goal,
8402 from,
8403 to,
8404 options.occurrence,
8405 &mut seen,
8406 &mut count,
8407 );
8408 Ok(RewriteResult {
8409 node,
8410 changed: count > 0,
8411 count,
8412 })
8413}
8414
8415pub fn rewrite(goal: &Node, eq: &Node) -> Result<Node, Diagnostic> {
8417 rewrite_with_options(goal, eq, RewriteOptions::default()).map(|result| result.node)
8418}
8419
8420pub fn simplify_with_options(
8422 goal: &Node,
8423 rules: &[Node],
8424 options: SimplifyOptions,
8425) -> Result<SimplifyResult, Diagnostic> {
8426 let mut node = goal.clone();
8427 let mut changed = false;
8428 let mut steps = 0;
8429 loop {
8430 let mut applied = false;
8431 for rule in rules {
8432 let rewritten = rewrite_with_options(&node, rule, RewriteOptions::default())?;
8433 if !rewritten.changed {
8434 continue;
8435 }
8436 if steps >= options.max_steps {
8437 return Err(rewrite_diagnostic(format!(
8438 "simplify termination guard reached after {} rewrite steps",
8439 options.max_steps
8440 )));
8441 }
8442 node = rewritten.node;
8443 steps += 1;
8444 changed = true;
8445 applied = true;
8446 break;
8447 }
8448 if !applied {
8449 return Ok(SimplifyResult {
8450 node,
8451 changed,
8452 steps,
8453 });
8454 }
8455 }
8456}
8457
8458pub fn simplify(goal: &Node, rules: &[Node]) -> Result<Node, Diagnostic> {
8460 simplify_with_options(goal, rules, SimplifyOptions::default()).map(|result| result.node)
8461}
8462
8463fn type_ascription(node: &Node) -> Option<(&Node, &Node)> {
8464 if let Node::List(children) = node {
8465 if children.len() == 3 {
8466 if let Node::Leaf(mid) = &children[1] {
8467 if mid == "of" {
8468 return Some((&children[0], &children[2]));
8469 }
8470 }
8471 }
8472 }
8473 None
8474}
8475
8476fn exact_closes_goal(arg: &Node, goal: &ProofGoal) -> bool {
8477 if is_structurally_same(arg, &goal.goal) {
8478 return true;
8479 }
8480 if let Some((_, typ)) = type_ascription(arg) {
8481 if is_structurally_same(typ, &goal.goal) {
8482 return true;
8483 }
8484 }
8485 goal.context.iter().any(|ctx| {
8486 if is_structurally_same(ctx, arg) && is_structurally_same(arg, &goal.goal) {
8487 return true;
8488 }
8489 if is_structurally_same(ctx, &goal.goal) && is_structurally_same(arg, &goal.goal) {
8490 return true;
8491 }
8492 if let Some((term, typ)) = type_ascription(ctx) {
8493 return is_structurally_same(term, arg) && is_structurally_same(typ, &goal.goal);
8494 }
8495 false
8496 })
8497}
8498
8499fn is_leaf(node: &Node, value: &str) -> bool {
8500 matches!(node, Node::Leaf(s) if s == value)
8501}
8502
8503fn parse_rewrite_direction(node: &Node) -> Option<RewriteDirection> {
8504 match node {
8505 Node::Leaf(s) if s == "->" => Some(RewriteDirection::Forward),
8506 Node::Leaf(s) if s == "<-" => Some(RewriteDirection::Backward),
8507 _ => None,
8508 }
8509}
8510
8511fn parse_rewrite_occurrence(node: &Node) -> Result<RewriteOccurrence, String> {
8512 let Node::Leaf(raw) = node else {
8513 return Err(format!(
8514 "rewrite occurrence must be \"all\", \"first\", or a positive integer (got {})",
8515 key_of(node)
8516 ));
8517 };
8518 if raw == "all" {
8519 return Ok(RewriteOccurrence::All);
8520 }
8521 if raw == "first" {
8522 return Ok(RewriteOccurrence::Index(1));
8523 }
8524 let Ok(index) = raw.parse::<usize>() else {
8525 return Err(format!(
8526 "rewrite occurrence must be \"all\", \"first\", or a positive integer (got {})",
8527 key_of(node)
8528 ));
8529 };
8530 if index == 0 {
8531 return Err(format!(
8532 "rewrite occurrence must be \"all\", \"first\", or a positive integer (got {})",
8533 key_of(node)
8534 ));
8535 }
8536 Ok(RewriteOccurrence::Index(index))
8537}
8538
8539struct ParsedRewriteTactic<'a> {
8540 eq: &'a Node,
8541 direction: RewriteDirection,
8542 occurrence: RewriteOccurrence,
8543}
8544
8545fn parse_rewrite_tactic(args: &[Node]) -> Result<ParsedRewriteTactic<'_>, String> {
8546 let mut index = 0;
8547 let mut direction = RewriteDirection::Forward;
8548 if let Some(next_direction) = args.first().and_then(parse_rewrite_direction) {
8549 direction = next_direction;
8550 index += 1;
8551 }
8552 if args.len() < index + 3
8553 || !is_leaf(&args[index + 1], "in")
8554 || !is_leaf(&args[index + 2], "goal")
8555 {
8556 return Err("rewrite expects `(rewrite [->|<-] (L = R) in goal [at N])`".to_string());
8557 }
8558 let eq = &args[index];
8559 index += 3;
8560 let mut occurrence = RewriteOccurrence::All;
8561 if index < args.len() {
8562 if !is_leaf(&args[index], "at") || index + 2 != args.len() {
8563 return Err("rewrite expects optional occurrence selector `at N`".to_string());
8564 }
8565 occurrence = parse_rewrite_occurrence(&args[index + 1])?;
8566 }
8567 Ok(ParsedRewriteTactic {
8568 eq,
8569 direction,
8570 occurrence,
8571 })
8572}
8573
8574fn rewrite_rules_from_node(node: &Node) -> Result<Vec<Node>, String> {
8575 if as_equality(node).is_some() {
8576 return Ok(vec![node.clone()]);
8577 }
8578 let Node::List(children) = node else {
8579 return Err(format!(
8580 "simplify expects equality rewrite rules (got {})",
8581 key_of(node)
8582 ));
8583 };
8584 let mut rules = Vec::with_capacity(children.len());
8585 for child in children {
8586 if as_equality(child).is_none() {
8587 return Err(format!(
8588 "simplify expects equality rewrite rules (got {})",
8589 key_of(child)
8590 ));
8591 }
8592 rules.push(child.clone());
8593 }
8594 Ok(rules)
8595}
8596
8597struct ParsedSimplifyTactic {
8598 rules: Option<Vec<Node>>,
8599 max_steps: Option<usize>,
8600}
8601
8602fn parse_simplify_tactic(args: &[Node]) -> Result<ParsedSimplifyTactic, String> {
8603 if args.len() < 2 || !is_leaf(&args[0], "in") || !is_leaf(&args[1], "goal") {
8604 return Err("simplify expects `(simplify in goal)`".to_string());
8605 }
8606 let mut index = 2;
8607 let mut rules = None;
8608 let mut max_steps = None;
8609 while index < args.len() {
8610 if is_leaf(&args[index], "using") && index + 1 < args.len() {
8611 rules = Some(rewrite_rules_from_node(&args[index + 1])?);
8612 index += 2;
8613 continue;
8614 }
8615 if (is_leaf(&args[index], "max") || is_leaf(&args[index], "limit"))
8616 && index + 1 < args.len()
8617 {
8618 let Node::Leaf(raw) = &args[index + 1] else {
8619 return Err("simplify max step count must be a non-negative integer".to_string());
8620 };
8621 let Ok(parsed) = raw.parse::<usize>() else {
8622 return Err("simplify max step count must be a non-negative integer".to_string());
8623 };
8624 max_steps = Some(parsed);
8625 index += 2;
8626 continue;
8627 }
8628 return Err("simplify expects optional `using <rules>` and `max <steps>` clauses".to_string());
8629 }
8630 Ok(ParsedSimplifyTactic { rules, max_steps })
8631}
8632
8633fn apply_tactic(
8634 state: &ProofState,
8635 tactic: &Node,
8636 record_tactic: &Node,
8637 tactic_options: &TacticOptions,
8638) -> Result<ProofState, Diagnostic> {
8639 let name = tactic_name(tactic);
8640 let args = tactic_args(tactic);
8641
8642 if name == Some("by") {
8643 if args.len() == 1 {
8644 return apply_tactic(state, &args[0], record_tactic, tactic_options);
8645 }
8646 if args.len() > 1 {
8647 return apply_tactic(state, &Node::List(args.to_vec()), record_tactic, tactic_options);
8648 }
8649 return Err(tactic_diagnostic(
8650 record_tactic,
8651 state.goals.first(),
8652 "`by` requires an inner tactic",
8653 ));
8654 }
8655
8656 let Some(current) = state.goals.first() else {
8657 return Err(tactic_diagnostic(record_tactic, None, "no open goals"));
8658 };
8659
8660 match name {
8661 Some("reflexivity") => {
8662 let Some((left, right)) = as_equality(¤t.goal) else {
8663 return Err(tactic_diagnostic(
8664 record_tactic,
8665 Some(current),
8666 "reflexivity expects an equality goal",
8667 ));
8668 };
8669 if !is_structurally_same(left, right) {
8670 return Err(tactic_diagnostic(
8671 record_tactic,
8672 Some(current),
8673 "both sides are not structurally equal",
8674 ));
8675 }
8676 Ok(replace_current_goal(state, Vec::new(), record_tactic))
8677 }
8678 Some("symmetry") => {
8679 let Some((left, right)) = as_equality(¤t.goal) else {
8680 return Err(tactic_diagnostic(
8681 record_tactic,
8682 Some(current),
8683 "symmetry expects an equality goal",
8684 ));
8685 };
8686 Ok(replace_current_goal(
8687 state,
8688 vec![goal_with_context(
8689 current,
8690 Node::List(vec![right.clone(), leaf("="), left.clone()]),
8691 )],
8692 record_tactic,
8693 ))
8694 }
8695 Some("transitivity") => {
8696 let Some((left, right)) = as_equality(¤t.goal) else {
8697 return Err(tactic_diagnostic(
8698 record_tactic,
8699 Some(current),
8700 "transitivity expects an equality goal and one intermediate term",
8701 ));
8702 };
8703 if args.len() != 1 {
8704 return Err(tactic_diagnostic(
8705 record_tactic,
8706 Some(current),
8707 "transitivity expects an equality goal and one intermediate term",
8708 ));
8709 }
8710 let mid = args[0].clone();
8711 Ok(replace_current_goal(
8712 state,
8713 vec![
8714 goal_with_context(
8715 current,
8716 Node::List(vec![left.clone(), leaf("="), mid.clone()]),
8717 ),
8718 goal_with_context(
8719 current,
8720 Node::List(vec![mid, leaf("="), right.clone()]),
8721 ),
8722 ],
8723 record_tactic,
8724 ))
8725 }
8726 Some("suppose") => {
8727 if args.len() != 1 {
8728 return Err(tactic_diagnostic(
8729 record_tactic,
8730 Some(current),
8731 "suppose expects one hypothesis link",
8732 ));
8733 }
8734 let mut next = state.clone();
8735 next.goals[0].context.push(args[0].clone());
8736 next.proof.push(record_tactic.clone());
8737 Ok(next)
8738 }
8739 Some("introduce") => {
8740 if args.len() != 1 {
8741 return Err(tactic_diagnostic(
8742 record_tactic,
8743 Some(current),
8744 "introduce expects one variable name",
8745 ));
8746 }
8747 let Node::Leaf(variable) = &args[0] else {
8748 return Err(tactic_diagnostic(
8749 record_tactic,
8750 Some(current),
8751 "introduce expects one variable name",
8752 ));
8753 };
8754 let Node::List(goal_children) = ¤t.goal else {
8755 return Err(tactic_diagnostic(
8756 record_tactic,
8757 Some(current),
8758 "introduce expects a Pi goal",
8759 ));
8760 };
8761 if goal_children.len() != 3 || !matches!(&goal_children[0], Node::Leaf(h) if h == "Pi") {
8762 return Err(tactic_diagnostic(
8763 record_tactic,
8764 Some(current),
8765 "introduce expects a Pi goal",
8766 ));
8767 }
8768 let Some((param, param_type_key)) = parse_binding(&goal_children[1]) else {
8769 return Err(tactic_diagnostic(
8770 record_tactic,
8771 Some(current),
8772 "introduce could not parse the Pi binder",
8773 ));
8774 };
8775 let body = subst(&goal_children[2], ¶m, &Node::Leaf(variable.clone()));
8776 let mut introduced = goal_with_context(current, body);
8777 introduced.context.push(Node::List(vec![
8778 Node::Leaf(variable.clone()),
8779 leaf("of"),
8780 type_key_to_node(¶m_type_key),
8781 ]));
8782 Ok(replace_current_goal(
8783 state,
8784 vec![introduced],
8785 record_tactic,
8786 ))
8787 }
8788 Some("rewrite") => {
8789 let parsed = parse_rewrite_tactic(args)
8790 .map_err(|reason| tactic_diagnostic(record_tactic, Some(current), reason))?;
8791 let rewritten = rewrite_with_options(
8792 ¤t.goal,
8793 parsed.eq,
8794 RewriteOptions {
8795 direction: parsed.direction,
8796 occurrence: parsed.occurrence,
8797 },
8798 )
8799 .map_err(|diag| tactic_diagnostic(record_tactic, Some(current), diag.message))?;
8800 if !rewritten.changed {
8801 let (from, _) = rewrite_sides(parsed.eq, parsed.direction)
8802 .map_err(|diag| tactic_diagnostic(record_tactic, Some(current), diag.message))?;
8803 return Err(tactic_diagnostic(
8804 record_tactic,
8805 Some(current),
8806 format!("rewrite did not find {} in the current goal", key_of(from)),
8807 ));
8808 }
8809 Ok(replace_current_goal(
8810 state,
8811 vec![goal_with_context(current, rewritten.node)],
8812 record_tactic,
8813 ))
8814 }
8815 Some("simplify") => {
8816 let parsed = parse_simplify_tactic(args)
8817 .map_err(|reason| tactic_diagnostic(record_tactic, Some(current), reason))?;
8818 let rules = parsed
8819 .rules
8820 .as_deref()
8821 .unwrap_or_else(|| tactic_options.rewrite_rules.as_slice());
8822 if rules.is_empty() {
8823 return Err(tactic_diagnostic(
8824 record_tactic,
8825 Some(current),
8826 "simplify expects at least one configured rewrite rule",
8827 ));
8828 }
8829 let max_steps = parsed
8830 .max_steps
8831 .unwrap_or(tactic_options.simplify_max_steps);
8832 let simplified = simplify_with_options(
8833 ¤t.goal,
8834 rules,
8835 SimplifyOptions { max_steps },
8836 )
8837 .map_err(|diag| tactic_diagnostic(record_tactic, Some(current), diag.message))?;
8838 Ok(replace_current_goal(
8839 state,
8840 vec![goal_with_context(current, simplified.node)],
8841 record_tactic,
8842 ))
8843 }
8844 Some("smt") => {
8845 if !args.is_empty() {
8846 return Err(tactic_diagnostic(
8847 record_tactic,
8848 Some(current),
8849 "smt expects no arguments; configure the solver through tactic options",
8850 ));
8851 }
8852 let smt_lib = smt_lib_for_goal(¤t.goal)
8853 .map_err(|reason| tactic_diagnostic(record_tactic, Some(current), reason))?;
8854 let checked = run_smt_solver(&smt_lib, tactic_options);
8855 if checked.status != SmtStatus::Unsat {
8856 return Err(tactic_diagnostic(
8857 record_tactic,
8858 Some(current),
8859 checked.reason,
8860 ));
8861 }
8862 Ok(replace_current_goal(
8863 state,
8864 Vec::new(),
8865 &smt_trusted_node(tactic_options),
8866 ))
8867 }
8868 Some("atp") => {
8869 if !args.is_empty() {
8870 return Err(tactic_diagnostic(
8871 record_tactic,
8872 Some(current),
8873 "atp expects no tactic arguments",
8874 ));
8875 }
8876 let tptp = goal_to_tptp(current)
8877 .map_err(|diag| tactic_diagnostic(record_tactic, Some(current), diag.message))?;
8878 let proved = run_atp_process(&tptp, &tactic_options.atp)
8879 .map_err(|reason| tactic_diagnostic(record_tactic, Some(current), reason))?;
8880 Ok(replace_current_goal(
8881 state,
8882 Vec::new(),
8883 &Node::List(vec![
8884 leaf("by"),
8885 leaf("atp-trusted"),
8886 Node::Leaf(proved.solver),
8887 ]),
8888 ))
8889 }
8890 Some("exact") => {
8891 if args.len() != 1 {
8892 return Err(tactic_diagnostic(
8893 record_tactic,
8894 Some(current),
8895 "exact expects one term or hypothesis",
8896 ));
8897 }
8898 if !exact_closes_goal(&args[0], current) {
8899 return Err(tactic_diagnostic(
8900 record_tactic,
8901 Some(current),
8902 format!("{} does not prove the current goal", key_of(&args[0])),
8903 ));
8904 }
8905 Ok(replace_current_goal(state, Vec::new(), record_tactic))
8906 }
8907 Some("induction") => {
8908 if args.len() < 2 {
8909 return Err(tactic_diagnostic(
8910 record_tactic,
8911 Some(current),
8912 "induction expects a variable and at least one case",
8913 ));
8914 }
8915 let Node::Leaf(variable) = &args[0] else {
8916 return Err(tactic_diagnostic(
8917 record_tactic,
8918 Some(current),
8919 "induction expects a variable and at least one case",
8920 ));
8921 };
8922 let mut open_goals = Vec::new();
8923 let mut nested_proofs = Vec::new();
8924 for case_node in &args[1..] {
8925 let Node::List(case_children) = case_node else {
8926 return Err(tactic_diagnostic(
8927 record_tactic,
8928 Some(current),
8929 "induction cases must be `(case <pattern> <tactic>...)` links",
8930 ));
8931 };
8932 if case_children.len() < 2
8933 || !matches!(&case_children[0], Node::Leaf(h) if h == "case")
8934 {
8935 return Err(tactic_diagnostic(
8936 record_tactic,
8937 Some(current),
8938 "induction cases must be `(case <pattern> <tactic>...)` links",
8939 ));
8940 }
8941 let pattern = &case_children[1];
8942 let case_goal =
8943 goal_with_context(current, subst(¤t.goal, variable, pattern));
8944 let case_tactics = &case_children[2..];
8945 if case_tactics.is_empty() {
8946 open_goals.push(case_goal);
8947 continue;
8948 }
8949 let nested = run_tactics_with_options(
8950 ProofState {
8951 goals: vec![case_goal],
8952 proof: Vec::new(),
8953 },
8954 case_tactics,
8955 tactic_options.clone(),
8956 );
8957 if let Some(diag) = nested.diagnostics.first() {
8958 return Err(diag.clone());
8959 }
8960 open_goals.extend(nested.state.goals);
8961 nested_proofs.extend(nested.state.proof);
8962 }
8963 let mut goals = open_goals;
8964 goals.extend(state.goals.iter().skip(1).cloned());
8965 let mut proof = state.proof.clone();
8966 proof.push(record_tactic.clone());
8967 proof.extend(nested_proofs);
8968 Ok(ProofState { goals, proof })
8969 }
8970 Some(other) => Err(tactic_diagnostic(
8971 record_tactic,
8972 Some(current),
8973 format!("unknown tactic \"{}\"", other),
8974 )),
8975 None => Err(tactic_diagnostic(
8976 record_tactic,
8977 Some(current),
8978 "tactic head must be a symbol",
8979 )),
8980 }
8981}
8982
8983pub fn parse_tactic_links(text: &str) -> Vec<Node> {
8985 parse_lino(text)
8986 .iter()
8987 .filter(|link_str| {
8988 let s = link_str.trim();
8989 !(s.starts_with("(#") && s.chars().nth(2).map_or(false, |c| c.is_whitespace()))
8990 })
8991 .filter_map(|link_str| {
8992 let toks = tokenize_one(link_str);
8993 let toks = if toks.len() == 1 && toks[0] != "(" && toks[0] != ")" {
8994 vec!["(".to_string(), toks[0].clone(), ")".to_string()]
8995 } else {
8996 toks
8997 };
8998 parse_one(&toks).ok().map(desugar_hoas)
8999 })
9000 .collect()
9001}
9002
9003pub fn run_tactics_with_options(
9005 state: ProofState,
9006 tactics: &[Node],
9007 options: TacticOptions,
9008) -> TacticRunResult {
9009 let mut next = state;
9010 let mut diagnostics = Vec::new();
9011 for tactic in tactics {
9012 match apply_tactic(&next, tactic, tactic, &options) {
9013 Ok(applied) => next = applied,
9014 Err(diag) => {
9015 diagnostics.push(diag);
9016 break;
9017 }
9018 }
9019 }
9020 TacticRunResult {
9021 state: next,
9022 diagnostics,
9023 }
9024}
9025
9026pub fn run_tactics(state: ProofState, tactics: &[Node]) -> TacticRunResult {
9028 run_tactics_with_options(state, tactics, TacticOptions::default())
9029}
9030
9031fn parse_mode_form(children: &[Node]) -> Option<(String, Vec<ModeFlag>)> {
9040 if children.len() < 2 {
9042 return None;
9043 }
9044 let name = match &children[1] {
9045 Node::Leaf(s) => s.clone(),
9046 _ => panic!("Mode declaration error: relation name must be a bare symbol"),
9047 };
9048 if children.len() < 3 {
9049 panic!(
9050 "Mode declaration error: declaration for \"{}\" must list at least one mode flag",
9051 name
9052 );
9053 }
9054 let mut flags = Vec::with_capacity(children.len() - 2);
9055 for child in &children[2..] {
9056 match child {
9057 Node::Leaf(token) => match ModeFlag::from_token(token) {
9058 Some(flag) => flags.push(flag),
9059 None => panic!(
9060 "Mode declaration error: declaration for \"{}\": unknown flag \"{}\" (expected +input, -output, or *either)",
9061 name, token
9062 ),
9063 },
9064 _ => panic!(
9065 "Mode declaration error: declaration for \"{}\" contains a non-token flag",
9066 name
9067 ),
9068 }
9069 }
9070 Some((name, flags))
9071}
9072
9073fn is_ground_for_mode(arg: &Node, env: &Env) -> bool {
9074 match arg {
9075 Node::Leaf(s) => {
9076 if is_num(s) {
9077 return true;
9078 }
9079 env_can_evaluate_name(env, s)
9080 }
9081 Node::List(_) => !has_unresolved_free_variables(arg, env),
9082 }
9083}
9084
9085fn check_mode_at_call(name: &str, args: &[Node], env: &Env) {
9086 let flags = match env.modes.get(name) {
9087 Some(f) => f.clone(),
9088 None => return,
9089 };
9090 if args.len() != flags.len() {
9091 panic!(
9092 "Mode mismatch: \"{}\" expected {} argument{}, got {}",
9093 name,
9094 flags.len(),
9095 if flags.len() == 1 { "" } else { "s" },
9096 args.len()
9097 );
9098 }
9099 for (i, flag) in flags.iter().enumerate() {
9100 if *flag == ModeFlag::In && !is_ground_for_mode(&args[i], env) {
9101 panic!(
9102 "Mode mismatch: \"{}\" argument {} (+input) is not ground",
9103 name,
9104 i + 1
9105 );
9106 }
9107 }
9108}
9109
9110fn is_relation_clause_head(node: &Node, name: &str) -> bool {
9119 match node {
9120 Node::List(items) if items.len() >= 2 => match &items[0] {
9121 Node::Leaf(head) => head == name,
9122 _ => false,
9123 },
9124 _ => false,
9125 }
9126}
9127
9128fn parse_relation_form(children: &[Node]) -> (String, Vec<Node>) {
9129 if children.len() < 2 {
9131 panic!("Relation declaration error: relation name must be a bare symbol");
9132 }
9133 let name = match &children[1] {
9134 Node::Leaf(s) => s.clone(),
9135 _ => panic!("Relation declaration error: relation name must be a bare symbol"),
9136 };
9137 if children.len() < 3 {
9138 panic!(
9139 "Relation declaration error: declaration for \"{}\" must list at least one clause",
9140 name
9141 );
9142 }
9143 if children.len() == 4 {
9144 let pattern = &children[2];
9145 let body = &children[3];
9146 if is_relation_clause_head(pattern, &name) && !is_relation_clause_head(body, &name) {
9147 if let Node::List(items) = pattern {
9148 let mut clause = items.clone();
9149 clause.push(body.clone());
9150 return (name, vec![Node::List(clause)]);
9151 }
9152 }
9153 }
9154 let mut clauses = Vec::with_capacity(children.len() - 2);
9155 for (idx, clause) in children[2..].iter().enumerate() {
9156 match clause {
9157 Node::List(items) if items.len() >= 2 => match &items[0] {
9158 Node::Leaf(head) if *head == name => {
9159 clauses.push(clause.clone());
9160 }
9161 _ => panic!(
9162 "Relation declaration error: declaration for \"{}\": clause {} must be a list whose head is \"{}\"",
9163 name,
9164 idx + 1,
9165 name
9166 ),
9167 },
9168 _ => panic!(
9169 "Relation declaration error: declaration for \"{}\": clause {} must be a list whose head is \"{}\"",
9170 name,
9171 idx + 1,
9172 name
9173 ),
9174 }
9175 }
9176 (name, clauses)
9177}
9178
9179fn is_strict_subterm(inner: &Node, outer: &Node) -> bool {
9180 if let Node::List(children) = outer {
9181 for child in children {
9182 if inner == child {
9183 return true;
9184 }
9185 if is_strict_subterm(inner, child) {
9186 return true;
9187 }
9188 }
9189 }
9190 false
9191}
9192
9193fn collect_recursive_calls(node: &Node, rel_name: &str, is_head: bool, out: &mut Vec<Node>) {
9194 if let Node::List(children) = node {
9195 if !is_head {
9196 if let Some(Node::Leaf(head)) = children.first() {
9197 if head == rel_name {
9198 out.push(node.clone());
9199 }
9200 }
9201 }
9202 for (i, child) in children.iter().enumerate() {
9203 if i == 0 {
9205 if let Node::Leaf(_) = child {
9206 continue;
9207 }
9208 }
9209 collect_recursive_calls(child, rel_name, false, out);
9210 }
9211 }
9212}
9213
9214#[derive(Debug, Clone, PartialEq, Eq)]
9216pub struct TotalityDiagnostic {
9217 pub code: String,
9218 pub message: String,
9219}
9220
9221#[derive(Debug, Clone, PartialEq, Eq)]
9223pub struct TotalityResult {
9224 pub ok: bool,
9225 pub diagnostics: Vec<TotalityDiagnostic>,
9226}
9227
9228fn check_recursive_decrease(
9229 call: &Node,
9230 head_args: &[Node],
9231 flags: &[ModeFlag],
9232 rel_name: &str,
9233) -> Option<String> {
9234 let call_args: Vec<Node> = match call {
9235 Node::List(items) if !items.is_empty() => items[1..].to_vec(),
9236 _ => return Some(format!("recursive call `{}` has no arguments", key_of(call))),
9237 };
9238 let input_indices: Vec<usize> = flags
9239 .iter()
9240 .enumerate()
9241 .filter(|(_, f)| **f == ModeFlag::In)
9242 .map(|(i, _)| i)
9243 .collect();
9244
9245 let pairs: Vec<(&Node, &Node)> = if call_args.len() == flags.len() {
9246 input_indices
9247 .iter()
9248 .map(|&i| (&call_args[i], &head_args[i]))
9249 .collect()
9250 } else if call_args.len() == input_indices.len() {
9251 input_indices
9252 .iter()
9253 .enumerate()
9254 .map(|(j, &i)| (&call_args[j], &head_args[i]))
9255 .collect()
9256 } else {
9257 return Some(format!(
9258 "recursive call `{}` has {} argument{}, expected {} (or {} input{})",
9259 key_of(call),
9260 call_args.len(),
9261 if call_args.len() == 1 { "" } else { "s" },
9262 flags.len(),
9263 input_indices.len(),
9264 if input_indices.len() == 1 { "" } else { "s" },
9265 ));
9266 };
9267
9268 if input_indices.is_empty() {
9269 return Some(format!(
9270 "relation \"{}\" has no `+input` slot, so structural decrease is unverifiable",
9271 rel_name
9272 ));
9273 }
9274 for (call_arg, head_arg) in &pairs {
9275 if is_strict_subterm(call_arg, head_arg) {
9276 return None;
9277 }
9278 }
9279 let head_with_args = {
9280 let mut items = Vec::with_capacity(head_args.len() + 1);
9281 items.push(Node::Leaf(rel_name.to_string()));
9282 items.extend(head_args.iter().cloned());
9283 Node::List(items)
9284 };
9285 Some(format!(
9286 "recursive call `{}` does not structurally decrease any `+input` slot of `{}`",
9287 key_of(call),
9288 key_of(&head_with_args)
9289 ))
9290}
9291
9292pub fn is_total(env: &Env, rel_name: &str) -> TotalityResult {
9298 let mut diagnostics: Vec<TotalityDiagnostic> = Vec::new();
9299 let flags = match env.modes.get(rel_name) {
9300 Some(f) => f.clone(),
9301 None => {
9302 diagnostics.push(TotalityDiagnostic {
9303 code: "E032".to_string(),
9304 message: format!(
9305 "Totality check for \"{}\": no `(mode {} ...)` declaration found",
9306 rel_name, rel_name
9307 ),
9308 });
9309 return TotalityResult {
9310 ok: false,
9311 diagnostics,
9312 };
9313 }
9314 };
9315 let clauses: Vec<Node> = match env.relations.get(rel_name) {
9316 Some(c) if !c.is_empty() => c.clone(),
9317 _ => {
9318 diagnostics.push(TotalityDiagnostic {
9319 code: "E032".to_string(),
9320 message: format!(
9321 "Totality check for \"{}\": no `(relation {} ...)` clauses found",
9322 rel_name, rel_name
9323 ),
9324 });
9325 return TotalityResult {
9326 ok: false,
9327 diagnostics,
9328 };
9329 }
9330 };
9331 for (ci, clause) in clauses.iter().enumerate() {
9332 let head_args: Vec<Node> = match clause {
9333 Node::List(items) if !items.is_empty() => items[1..].to_vec(),
9334 _ => continue,
9335 };
9336 if head_args.len() != flags.len() {
9337 diagnostics.push(TotalityDiagnostic {
9338 code: "E032".to_string(),
9339 message: format!(
9340 "Totality check for \"{}\": clause {} `{}` has {} argument{}, mode declares {}",
9341 rel_name,
9342 ci + 1,
9343 key_of(clause),
9344 head_args.len(),
9345 if head_args.len() == 1 { "" } else { "s" },
9346 flags.len(),
9347 ),
9348 });
9349 continue;
9350 }
9351 let mut calls: Vec<Node> = Vec::new();
9352 collect_recursive_calls(clause, rel_name, true, &mut calls);
9353 for call in &calls {
9354 if let Some(reason) = check_recursive_decrease(call, &head_args, &flags, rel_name) {
9355 diagnostics.push(TotalityDiagnostic {
9356 code: "E032".to_string(),
9357 message: format!(
9358 "Totality check for \"{}\": clause {} `{}` — {}",
9359 rel_name,
9360 ci + 1,
9361 key_of(clause),
9362 reason
9363 ),
9364 });
9365 }
9366 }
9367 }
9368 TotalityResult {
9369 ok: diagnostics.is_empty(),
9370 diagnostics,
9371 }
9372}
9373
9374#[derive(Debug, Clone, PartialEq, Eq)]
9383pub struct TerminationDiagnostic {
9384 pub code: String,
9385 pub message: String,
9386}
9387
9388#[derive(Debug, Clone, PartialEq, Eq)]
9390pub struct TerminationResult {
9391 pub ok: bool,
9392 pub diagnostics: Vec<TerminationDiagnostic>,
9393}
9394
9395fn parse_define_form(children: &[Node]) -> DefineDecl {
9396 if children.len() < 2 {
9398 panic!("Termination check error: Define declaration: name must be a bare symbol");
9399 }
9400 let name = match &children[1] {
9401 Node::Leaf(s) => s.clone(),
9402 _ => panic!("Termination check error: Define declaration: name must be a bare symbol"),
9403 };
9404 if children.len() < 3 {
9405 panic!(
9406 "Termination check error: Define declaration for \"{}\" must list at least one `(case ...)` clause",
9407 name
9408 );
9409 }
9410 let mut measure: Option<DefineMeasure> = None;
9411 let mut clauses: Vec<DefineClause> = Vec::new();
9412 for child in &children[2..] {
9413 match child {
9414 Node::List(items) if !items.is_empty() => match &items[0] {
9415 Node::Leaf(head) if head == "measure" => {
9416 if measure.is_some() {
9417 panic!(
9418 "Termination check error: Define declaration for \"{}\": only one `(measure ...)` clause is allowed",
9419 name
9420 );
9421 }
9422 if items.len() != 2 {
9423 panic!(
9424 "Termination check error: Define declaration for \"{}\": `(measure ...)` body must be `(lex <slot>...)`",
9425 name
9426 );
9427 }
9428 let body = &items[1];
9429 let lex_items = match body {
9430 Node::List(b) if b.len() >= 2 => match &b[0] {
9431 Node::Leaf(h) if h == "lex" => &b[1..],
9432 _ => panic!(
9433 "Termination check error: Define declaration for \"{}\": `(measure ...)` body must be `(lex <slot>...)`",
9434 name
9435 ),
9436 },
9437 _ => panic!(
9438 "Termination check error: Define declaration for \"{}\": `(measure ...)` body must be `(lex <slot>...)`",
9439 name
9440 ),
9441 };
9442 let mut slots: Vec<usize> = Vec::with_capacity(lex_items.len());
9443 for item in lex_items {
9444 let raw = match item {
9445 Node::Leaf(s) => s.clone(),
9446 _ => panic!(
9447 "Termination check error: Define declaration for \"{}\": measure slot must be a positive integer",
9448 name
9449 ),
9450 };
9451 let parsed: Result<usize, _> = raw.parse();
9452 match parsed {
9453 Ok(n) if n >= 1 => slots.push(n - 1),
9454 _ => panic!(
9455 "Termination check error: Define declaration for \"{}\": measure slot must be a positive integer",
9456 name
9457 ),
9458 }
9459 }
9460 measure = Some(DefineMeasure::Lex(slots));
9461 }
9462 Node::Leaf(head) if head == "case" => {
9463 if items.len() != 3 {
9464 panic!(
9465 "Termination check error: Define declaration for \"{}\": `(case <pattern-args> <body>)` clause must have exactly two children",
9466 name
9467 );
9468 }
9469 let pattern = match &items[1] {
9470 Node::List(p) => p.clone(),
9471 Node::Leaf(_) => vec![items[1].clone()],
9478 };
9479 clauses.push(DefineClause {
9480 pattern,
9481 body: items[2].clone(),
9482 });
9483 }
9484 _ => panic!(
9485 "Termination check error: Define declaration for \"{}\": unexpected clause `{}` (expected `(measure ...)` or `(case ...)`)",
9486 name,
9487 key_of(child)
9488 ),
9489 },
9490 _ => panic!(
9491 "Termination check error: Define declaration for \"{}\": unexpected clause `{}` (expected `(measure ...)` or `(case ...)`)",
9492 name,
9493 key_of(child)
9494 ),
9495 }
9496 }
9497 if clauses.is_empty() {
9498 panic!(
9499 "Termination check error: Define declaration for \"{}\" must list at least one `(case ...)` clause",
9500 name
9501 );
9502 }
9503 DefineDecl {
9504 name,
9505 measure,
9506 clauses,
9507 }
9508}
9509
9510fn check_define_decrease(
9511 call: &Node,
9512 pattern: &[Node],
9513 measure: &Option<DefineMeasure>,
9514 def_name: &str,
9515) -> Option<String> {
9516 let call_args: Vec<Node> = match call {
9517 Node::List(items) if !items.is_empty() => items[1..].to_vec(),
9518 _ => return Some(format!("recursive call `{}` has no arguments", key_of(call))),
9519 };
9520 if call_args.len() != pattern.len() {
9521 return Some(format!(
9522 "recursive call `{}` has {} argument{}, clause pattern declares {}",
9523 key_of(call),
9524 call_args.len(),
9525 if call_args.len() == 1 { "" } else { "s" },
9526 pattern.len(),
9527 ));
9528 }
9529 if let Some(DefineMeasure::Lex(slots)) = measure {
9530 for &slot in slots {
9531 if slot >= pattern.len() {
9532 return Some(format!(
9533 "measure slot {} is out of range for {}-argument clause",
9534 slot + 1,
9535 pattern.len(),
9536 ));
9537 }
9538 }
9539 for &slot in slots {
9540 let call_arg = &call_args[slot];
9541 let pat_arg = &pattern[slot];
9542 if is_strict_subterm(call_arg, pat_arg) {
9543 return None;
9544 }
9545 if !is_node_equal(call_arg, pat_arg) {
9546 return Some(format!(
9547 "recursive call `{}` does not lexicographically decrease the declared measure",
9548 key_of(call),
9549 ));
9550 }
9551 }
9552 return Some(format!(
9553 "recursive call `{}` does not lexicographically decrease the declared measure",
9554 key_of(call),
9555 ));
9556 }
9557 if pattern.is_empty() {
9558 return Some(format!(
9559 "definition \"{}\" has no arguments, so structural decrease is unverifiable",
9560 def_name
9561 ));
9562 }
9563 if is_strict_subterm(&call_args[0], &pattern[0]) {
9564 return None;
9565 }
9566 let head_with_pattern = {
9567 let mut items = Vec::with_capacity(pattern.len() + 1);
9568 items.push(Node::Leaf(def_name.to_string()));
9569 items.extend(pattern.iter().cloned());
9570 Node::List(items)
9571 };
9572 Some(format!(
9573 "recursive call `{}` does not structurally decrease the first argument of `{}`",
9574 key_of(call),
9575 key_of(&head_with_pattern)
9576 ))
9577}
9578
9579fn is_node_equal(a: &Node, b: &Node) -> bool {
9580 a == b
9581}
9582
9583pub fn is_terminating(env: &Env, def_name: &str) -> TerminationResult {
9588 let mut diagnostics: Vec<TerminationDiagnostic> = Vec::new();
9589 let decl = match env.definitions.get(def_name) {
9590 Some(d) => d.clone(),
9591 None => {
9592 diagnostics.push(TerminationDiagnostic {
9593 code: "E035".to_string(),
9594 message: format!(
9595 "Termination check for \"{}\": no `(define {} ...)` declaration found",
9596 def_name, def_name
9597 ),
9598 });
9599 return TerminationResult {
9600 ok: false,
9601 diagnostics,
9602 };
9603 }
9604 };
9605 for (ci, clause) in decl.clauses.iter().enumerate() {
9606 let mut calls: Vec<Node> = Vec::new();
9607 collect_recursive_calls(&clause.body, def_name, false, &mut calls);
9608 for call in &calls {
9609 if let Some(reason) =
9610 check_define_decrease(call, &clause.pattern, &decl.measure, def_name)
9611 {
9612 let case_node = Node::List(vec![
9613 Node::Leaf("case".to_string()),
9614 Node::List(clause.pattern.clone()),
9615 clause.body.clone(),
9616 ]);
9617 diagnostics.push(TerminationDiagnostic {
9618 code: "E035".to_string(),
9619 message: format!(
9620 "Termination check for \"{}\": clause {} `{}` — {}",
9621 def_name,
9622 ci + 1,
9623 key_of(&case_node),
9624 reason
9625 ),
9626 });
9627 }
9628 }
9629 }
9630 TerminationResult {
9631 ok: diagnostics.is_empty(),
9632 diagnostics,
9633 }
9634}
9635
9636#[derive(Debug, Clone, PartialEq, Eq)]
9646pub struct CoverageDiagnostic {
9647 pub code: String,
9648 pub message: String,
9649}
9650
9651#[derive(Debug, Clone, PartialEq, Eq)]
9653pub struct CoverageResult {
9654 pub ok: bool,
9655 pub diagnostics: Vec<CoverageDiagnostic>,
9656}
9657
9658fn inductive_type_of_constructor(env: &Env, ctor_name: &str) -> Option<String> {
9659 for (type_name, decl) in &env.inductives {
9660 for ctor in &decl.constructors {
9661 if ctor.name == ctor_name {
9662 return Some(type_name.clone());
9663 }
9664 }
9665 }
9666 None
9667}
9668
9669fn is_wildcard_pattern(pat: &Node, env: &Env) -> bool {
9670 match pat {
9671 Node::Leaf(s) => {
9672 if is_num(s) {
9673 return false;
9674 }
9675 if non_variable_token(s) {
9676 return false;
9677 }
9678 inductive_type_of_constructor(env, s).is_none()
9679 }
9680 _ => false,
9681 }
9682}
9683
9684fn pattern_constructor_head(pat: &Node, env: &Env) -> Option<String> {
9685 match pat {
9686 Node::Leaf(s) => {
9687 if inductive_type_of_constructor(env, s).is_some() {
9688 Some(s.clone())
9689 } else {
9690 None
9691 }
9692 }
9693 Node::List(items) => {
9694 if let Some(Node::Leaf(head)) = items.first() {
9695 if inductive_type_of_constructor(env, head).is_some() {
9696 return Some(head.clone());
9697 }
9698 }
9699 None
9700 }
9701 }
9702}
9703
9704fn infer_slot_type(env: &Env, clauses: &[Node], slot_index: usize) -> Option<String> {
9705 for clause in clauses {
9706 if let Node::List(items) = clause {
9707 if let Some(pat) = items.get(slot_index + 1) {
9708 if let Some(head) = pattern_constructor_head(pat, env) {
9709 return inductive_type_of_constructor(env, &head);
9710 }
9711 }
9712 }
9713 }
9714 None
9715}
9716
9717fn example_constructor_pattern(ctor: &ConstructorDecl) -> String {
9718 if ctor.params.is_empty() {
9719 ctor.name.clone()
9720 } else {
9721 let placeholders = " _".repeat(ctor.params.len());
9722 format!("({}{})", ctor.name, placeholders)
9723 }
9724}
9725
9726pub fn is_covered(env: &Env, rel_name: &str) -> CoverageResult {
9730 let mut diagnostics: Vec<CoverageDiagnostic> = Vec::new();
9731 let flags = match env.modes.get(rel_name) {
9732 Some(f) => f.clone(),
9733 None => {
9734 diagnostics.push(CoverageDiagnostic {
9735 code: "E037".to_string(),
9736 message: format!(
9737 "Coverage check for \"{}\": no `(mode {} ...)` declaration found",
9738 rel_name, rel_name
9739 ),
9740 });
9741 return CoverageResult {
9742 ok: false,
9743 diagnostics,
9744 };
9745 }
9746 };
9747 let clauses: Vec<Node> = match env.relations.get(rel_name) {
9748 Some(c) if !c.is_empty() => c.clone(),
9749 _ => {
9750 diagnostics.push(CoverageDiagnostic {
9751 code: "E037".to_string(),
9752 message: format!(
9753 "Coverage check for \"{}\": no `(relation {} ...)` clauses found",
9754 rel_name, rel_name
9755 ),
9756 });
9757 return CoverageResult {
9758 ok: false,
9759 diagnostics,
9760 };
9761 }
9762 };
9763 for (i, flag) in flags.iter().enumerate() {
9764 if *flag != ModeFlag::In {
9765 continue;
9766 }
9767 let slot_patterns: Vec<Node> = clauses
9768 .iter()
9769 .filter_map(|c| match c {
9770 Node::List(items) => items.get(i + 1).cloned(),
9771 _ => None,
9772 })
9773 .collect();
9774 if slot_patterns.iter().any(|p| is_wildcard_pattern(p, env)) {
9775 continue;
9776 }
9777 let type_name = match infer_slot_type(env, &clauses, i) {
9778 Some(t) => t,
9779 None => continue,
9780 };
9781 let decl = match env.inductives.get(&type_name) {
9782 Some(d) => d.clone(),
9783 None => continue,
9784 };
9785 let mut covered: Vec<String> = Vec::new();
9786 for pat in &slot_patterns {
9787 if let Some(head) = pattern_constructor_head(pat, env) {
9788 if !covered.contains(&head) {
9789 covered.push(head);
9790 }
9791 }
9792 }
9793 let missing: Vec<&ConstructorDecl> = decl
9794 .constructors
9795 .iter()
9796 .filter(|c| !covered.contains(&c.name))
9797 .collect();
9798 if missing.is_empty() {
9799 continue;
9800 }
9801 let examples: Vec<String> = missing
9802 .iter()
9803 .map(|c| example_constructor_pattern(c))
9804 .collect();
9805 let plural = if missing.len() == 1 { "" } else { "s" };
9806 diagnostics.push(CoverageDiagnostic {
9807 code: "E037".to_string(),
9808 message: format!(
9809 "Coverage check for \"{}\": +input slot {} (type \"{}\") missing case{} for constructor{} {}",
9810 rel_name,
9811 i + 1,
9812 type_name,
9813 plural,
9814 plural,
9815 examples.join(", ")
9816 ),
9817 });
9818 }
9819 CoverageResult {
9820 ok: diagnostics.is_empty(),
9821 diagnostics,
9822 }
9823}
9824
9825fn parse_world_form(children: &[Node]) -> Option<(String, Vec<String>)> {
9836 if children.len() < 2 {
9838 return None;
9839 }
9840 let name = match &children[1] {
9841 Node::Leaf(s) => s.clone(),
9842 _ => panic!("World declaration error: relation name must be a bare symbol"),
9843 };
9844 if children.len() != 3 {
9845 panic!(
9846 "World declaration error: declaration for \"{}\" must have shape `(world {} (<const>...))`",
9847 name, name
9848 );
9849 }
9850 let allowed: Vec<String> = match &children[2] {
9851 Node::List(items) => {
9852 let mut consts = Vec::with_capacity(items.len());
9853 for item in items {
9854 match item {
9855 Node::Leaf(s) => consts.push(s.clone()),
9856 _ => panic!(
9857 "World declaration error: declaration for \"{}\": each allowed constant must be a bare symbol",
9858 name
9859 ),
9860 }
9861 }
9862 consts
9863 }
9864 Node::Leaf(s) => vec![s.clone()],
9868 };
9869 Some((name, allowed))
9870}
9871
9872fn collect_free_constants(node: &Node, bound: &mut HashSet<String>, out: &mut Vec<String>) {
9878 match node {
9879 Node::Leaf(s) => {
9880 if is_num(s) || non_variable_token(s) {
9881 return;
9882 }
9883 if bound.contains(s) {
9884 return;
9885 }
9886 if !out.contains(s) {
9887 out.push(s.clone());
9888 }
9889 }
9890 Node::List(items) => {
9891 if items.len() >= 3 {
9894 if let Node::Leaf(head) = &items[0] {
9895 if head == "lambda" || head == "Pi" {
9896 if let Node::List(binder) = &items[1] {
9897 if binder.len() == 2 {
9898 if let Node::Leaf(var) = &binder[1] {
9899 let was_bound = bound.contains(var);
9900 if let Node::Leaf(ty) = &binder[0] {
9901 if !is_num(ty) && !non_variable_token(ty) && !bound.contains(ty) && !out.contains(ty) {
9902 out.push(ty.clone());
9903 }
9904 } else {
9905 collect_free_constants(&binder[0], bound, out);
9906 }
9907 bound.insert(var.clone());
9908 for child in &items[2..] {
9909 collect_free_constants(child, bound, out);
9910 }
9911 if !was_bound {
9912 bound.remove(var);
9913 }
9914 return;
9915 }
9916 }
9917 }
9918 }
9919 if head == "fresh" && items.len() == 4 {
9920 if let (Node::Leaf(var), Node::Leaf(in_kw)) = (&items[1], &items[2]) {
9921 if in_kw == "in" {
9922 let was_bound = bound.contains(var);
9923 bound.insert(var.clone());
9924 collect_free_constants(&items[3], bound, out);
9925 if !was_bound {
9926 bound.remove(var);
9927 }
9928 return;
9929 }
9930 }
9931 }
9932 }
9933 }
9934 for child in items {
9935 collect_free_constants(child, bound, out);
9936 }
9937 }
9938 }
9939}
9940
9941fn check_world_at_call(name: &str, args: &[Node], env: &Env) {
9942 let allowed = match env.worlds.get(name) {
9943 Some(a) => a.clone(),
9944 None => return,
9945 };
9946 let mut violations: Vec<String> = Vec::new();
9949 for arg in args {
9950 let mut bound: HashSet<String> = HashSet::new();
9951 let mut found: Vec<String> = Vec::new();
9952 collect_free_constants(arg, &mut bound, &mut found);
9953 for sym in found {
9954 if sym == name {
9955 continue;
9956 }
9957 if allowed.iter().any(|a| a == &sym) {
9958 continue;
9959 }
9960 if !violations.contains(&sym) {
9966 violations.push(sym);
9967 }
9968 }
9969 }
9970 if !violations.is_empty() {
9971 let listed = violations
9972 .iter()
9973 .map(|s| format!("\"{}\"", s))
9974 .collect::<Vec<_>>()
9975 .join(", ");
9976 panic!(
9977 "World violation: \"{}\" argument contains free constant{} {} not in declared world",
9978 name,
9979 if violations.len() == 1 { "" } else { "s" },
9980 listed
9981 );
9982 }
9983}
9984
9985fn is_pi_sig(node: &Node) -> bool {
9994 matches!(node, Node::List(items)
9995 if items.len() == 3
9996 && matches!(&items[0], Node::Leaf(h) if h == "Pi"))
9997}
9998
9999fn flatten_pi(type_node: &Node) -> Option<(Vec<(String, Node)>, Node)> {
10001 let mut params: Vec<(String, Node)> = Vec::new();
10002 let mut current = type_node.clone();
10003 while is_pi_sig(¤t) {
10004 let items = match ¤t {
10005 Node::List(items) => items.clone(),
10006 _ => return None,
10007 };
10008 let bindings = parse_bindings(&items[1])?;
10009 if bindings.is_empty() {
10010 return None;
10011 }
10012 for (name, type_str) in bindings {
10013 let binding_node = &items[1];
10017 let type_node = recover_binding_type(binding_node, &name).unwrap_or(Node::Leaf(type_str));
10018 params.push((name, type_node));
10019 }
10020 current = items[2].clone();
10021 }
10022 Some((params, current))
10023}
10024
10025fn recover_binding_type(binding: &Node, param_name: &str) -> Option<Node> {
10029 match binding {
10030 Node::List(items) if items.len() == 2 => {
10031 if let Node::Leaf(name) = &items[1] {
10032 if name == param_name {
10033 return Some(items[0].clone());
10034 }
10035 }
10036 if let Node::Leaf(name) = &items[0] {
10037 if name == param_name {
10038 return Some(items[1].clone());
10039 }
10040 }
10041 None
10042 }
10043 _ => None,
10044 }
10045}
10046
10047fn build_pi(params: &[(String, Node)], result: Node) -> Node {
10049 let mut out = result;
10050 for (name, ty) in params.iter().rev() {
10051 out = Node::List(vec![
10052 Node::Leaf("Pi".to_string()),
10053 Node::List(vec![ty.clone(), Node::Leaf(name.clone())]),
10054 out,
10055 ]);
10056 }
10057 out
10058}
10059
10060fn parse_constructor_clause(clause: &Node, type_name: &str) -> ConstructorDecl {
10061 let items = match clause {
10062 Node::List(items) if items.len() == 2 => items,
10063 _ => panic!(
10064 "Inductive declaration error: each clause must be `(constructor <name>)` or `(constructor (<name> <pi-type>))`"
10065 ),
10066 };
10067 match &items[0] {
10068 Node::Leaf(h) if h == "constructor" => {}
10069 _ => panic!(
10070 "Inductive declaration error: each clause must be `(constructor <name>)` or `(constructor (<name> <pi-type>))`"
10071 ),
10072 }
10073 match &items[1] {
10074 Node::Leaf(name) => ConstructorDecl {
10075 name: name.clone(),
10076 params: Vec::new(),
10077 typ: Node::Leaf(type_name.to_string()),
10078 },
10079 Node::List(inner) if inner.len() == 2 => {
10080 let name = match &inner[0] {
10081 Node::Leaf(s) => s.clone(),
10082 _ => panic!(
10083 "Inductive declaration error: malformed constructor clause `{}`",
10084 key_of(clause)
10085 ),
10086 };
10087 if !is_pi_sig(&inner[1]) {
10088 panic!(
10089 "Inductive declaration error: malformed constructor clause `{}`",
10090 key_of(clause)
10091 );
10092 }
10093 let (params, result) = match flatten_pi(&inner[1]) {
10094 Some(parts) => parts,
10095 None => panic!(
10096 "Inductive declaration error: constructor \"{}\" has malformed Pi-type `{}`",
10097 name,
10098 key_of(&inner[1])
10099 ),
10100 };
10101 match &result {
10102 Node::Leaf(r) if r == type_name => {}
10103 other => panic!(
10104 "Inductive declaration error: constructor \"{}\" must return \"{}\" (got \"{}\")",
10105 name,
10106 type_name,
10107 key_of(other)
10108 ),
10109 }
10110 ConstructorDecl {
10111 name,
10112 params,
10113 typ: inner[1].clone(),
10114 }
10115 }
10116 _ => panic!(
10117 "Inductive declaration error: malformed constructor clause `{}`",
10118 key_of(clause)
10119 ),
10120 }
10121}
10122
10123pub fn parse_inductive_form(node: &Node) -> Option<InductiveDecl> {
10128 let children = match node {
10129 Node::List(items) => items,
10130 _ => return None,
10131 };
10132 if children.is_empty() {
10133 return None;
10134 }
10135 match &children[0] {
10136 Node::Leaf(h) if h == "inductive" => {}
10137 _ => return None,
10138 }
10139 let name = match children.get(1) {
10140 Some(Node::Leaf(s)) => s.clone(),
10141 _ => panic!("Inductive declaration error: type name must be a bare symbol"),
10142 };
10143 if !name.chars().next().map_or(false, |c| c.is_ascii_uppercase()) {
10144 panic!(
10145 "Inductive declaration error: declaration for \"{}\": type name must start with an uppercase letter",
10146 name
10147 );
10148 }
10149 if children.len() < 3 {
10150 panic!(
10151 "Inductive declaration error: declaration for \"{}\" must list at least one constructor",
10152 name
10153 );
10154 }
10155 let mut constructors: Vec<ConstructorDecl> = Vec::new();
10156 let mut seen: HashSet<String> = HashSet::new();
10157 for clause in &children[2..] {
10158 let ctor = parse_constructor_clause(clause, &name);
10159 if seen.contains(&ctor.name) {
10160 panic!(
10161 "Inductive declaration error: declaration for \"{}\": constructor \"{}\" is declared more than once",
10162 name, ctor.name
10163 );
10164 }
10165 seen.insert(ctor.name.clone());
10166 constructors.push(ctor);
10167 }
10168 let elim_name = format!("{}-rec", name);
10169 let elim_type = build_eliminator_type(&name, &constructors);
10170 Some(InductiveDecl {
10171 name,
10172 constructors,
10173 elim_name,
10174 elim_type,
10175 })
10176}
10177
10178fn build_case_type(ctor: &ConstructorDecl, type_name: &str, motive_var: &str) -> Node {
10179 let mut rec_binders: Vec<(String, Node)> = Vec::new();
10180 for (pname, ptype) in &ctor.params {
10181 if let Node::Leaf(s) = ptype {
10182 if s == type_name {
10183 rec_binders.push((
10184 format!("ih_{}", pname),
10185 Node::List(vec![
10186 Node::Leaf("apply".to_string()),
10187 Node::Leaf(motive_var.to_string()),
10188 Node::Leaf(pname.clone()),
10189 ]),
10190 ));
10191 }
10192 }
10193 }
10194 let ctor_applied = if ctor.params.is_empty() {
10195 Node::Leaf(ctor.name.clone())
10196 } else {
10197 let mut items = vec![Node::Leaf(ctor.name.clone())];
10198 for (pname, _) in &ctor.params {
10199 items.push(Node::Leaf(pname.clone()));
10200 }
10201 Node::List(items)
10202 };
10203 let motive_on_target = Node::List(vec![
10204 Node::Leaf("apply".to_string()),
10205 Node::Leaf(motive_var.to_string()),
10206 ctor_applied,
10207 ]);
10208 let inner = build_pi(&rec_binders, motive_on_target);
10209 build_pi(&ctor.params, inner)
10210}
10211
10212pub fn build_eliminator_type(type_name: &str, constructors: &[ConstructorDecl]) -> Node {
10216 let motive_var = "_motive";
10217 let motive_type = Node::List(vec![
10218 Node::Leaf("Pi".to_string()),
10219 Node::List(vec![
10220 Node::Leaf(type_name.to_string()),
10221 Node::Leaf("_".to_string()),
10222 ]),
10223 Node::List(vec![
10224 Node::Leaf("Type".to_string()),
10225 Node::Leaf("0".to_string()),
10226 ]),
10227 ]);
10228 let case_params: Vec<(String, Node)> = constructors
10229 .iter()
10230 .map(|c| (format!("case_{}", c.name), build_case_type(c, type_name, motive_var)))
10231 .collect();
10232 let target_var = "_target";
10233 let final_node = Node::List(vec![
10234 Node::Leaf("apply".to_string()),
10235 Node::Leaf(motive_var.to_string()),
10236 Node::Leaf(target_var.to_string()),
10237 ]);
10238 let inner = build_pi(
10239 &[(target_var.to_string(), Node::Leaf(type_name.to_string()))],
10240 final_node,
10241 );
10242 let with_cases = build_pi(&case_params, inner);
10243 build_pi(
10244 &[(motive_var.to_string(), motive_type)],
10245 with_cases,
10246 )
10247}
10248
10249pub fn register_inductive(env: &mut Env, decl: InductiveDecl) {
10253 let store_type = env.qualify_name(&decl.name);
10254 env.terms.insert(store_type.clone());
10255 let type0 = Node::List(vec![
10256 Node::Leaf("Type".to_string()),
10257 Node::Leaf("0".to_string()),
10258 ]);
10259 env.set_type(&store_type, &key_of(&type0));
10260 eval_node(&type0, env);
10261
10262 for ctor in &decl.constructors {
10263 let store_name = env.qualify_name(&ctor.name);
10264 env.terms.insert(store_name.clone());
10265 env.set_type(&store_name, &key_of(&ctor.typ));
10266 if matches!(ctor.typ, Node::List(_)) {
10267 eval_node(&ctor.typ, env);
10268 }
10269 }
10270
10271 let store_elim = env.qualify_name(&decl.elim_name);
10272 env.terms.insert(store_elim.clone());
10273 env.set_type(&store_elim, &key_of(&decl.elim_type));
10274 eval_node(&decl.elim_type, env);
10275
10276 env.inductives.insert(decl.name.clone(), decl);
10277}
10278
10279fn parse_coinductive_constructor_clause(clause: &Node, type_name: &str) -> ConstructorDecl {
10291 let items = match clause {
10292 Node::List(items) if items.len() == 2 => items,
10293 _ => panic!(
10294 "Coinductive declaration error: each clause must be `(constructor <name>)` or `(constructor (<name> <pi-type>))`"
10295 ),
10296 };
10297 match &items[0] {
10298 Node::Leaf(h) if h == "constructor" => {}
10299 _ => panic!(
10300 "Coinductive declaration error: each clause must be `(constructor <name>)` or `(constructor (<name> <pi-type>))`"
10301 ),
10302 }
10303 match &items[1] {
10304 Node::Leaf(name) => ConstructorDecl {
10305 name: name.clone(),
10306 params: Vec::new(),
10307 typ: Node::Leaf(type_name.to_string()),
10308 },
10309 Node::List(inner) if inner.len() == 2 => {
10310 let name = match &inner[0] {
10311 Node::Leaf(s) => s.clone(),
10312 _ => panic!(
10313 "Coinductive declaration error: malformed constructor clause `{}`",
10314 key_of(clause)
10315 ),
10316 };
10317 if !is_pi_sig(&inner[1]) {
10318 panic!(
10319 "Coinductive declaration error: malformed constructor clause `{}`",
10320 key_of(clause)
10321 );
10322 }
10323 let (params, result) = match flatten_pi(&inner[1]) {
10324 Some(parts) => parts,
10325 None => panic!(
10326 "Coinductive declaration error: constructor \"{}\" has malformed Pi-type `{}`",
10327 name,
10328 key_of(&inner[1])
10329 ),
10330 };
10331 match &result {
10332 Node::Leaf(r) if r == type_name => {}
10333 other => panic!(
10334 "Coinductive declaration error: constructor \"{}\" must return \"{}\" (got \"{}\")",
10335 name,
10336 type_name,
10337 key_of(other)
10338 ),
10339 }
10340 ConstructorDecl {
10341 name,
10342 params,
10343 typ: inner[1].clone(),
10344 }
10345 }
10346 _ => panic!(
10347 "Coinductive declaration error: malformed constructor clause `{}`",
10348 key_of(clause)
10349 ),
10350 }
10351}
10352
10353fn ctor_has_recursive_param(ctor: &ConstructorDecl, type_name: &str) -> bool {
10356 ctor.params.iter().any(|(_, ty)| {
10357 if let Node::Leaf(s) = ty {
10358 s == type_name
10359 } else {
10360 false
10361 }
10362 })
10363}
10364
10365pub fn parse_coinductive_form(node: &Node) -> Option<CoinductiveDecl> {
10370 let children = match node {
10371 Node::List(items) => items,
10372 _ => return None,
10373 };
10374 if children.is_empty() {
10375 return None;
10376 }
10377 match &children[0] {
10378 Node::Leaf(h) if h == "coinductive" => {}
10379 _ => return None,
10380 }
10381 let name = match children.get(1) {
10382 Some(Node::Leaf(s)) => s.clone(),
10383 _ => panic!("Coinductive declaration error: type name must be a bare symbol"),
10384 };
10385 if !name.chars().next().map_or(false, |c| c.is_ascii_uppercase()) {
10386 panic!(
10387 "Coinductive declaration error: declaration for \"{}\": type name must start with an uppercase letter",
10388 name
10389 );
10390 }
10391 if children.len() < 3 {
10392 panic!(
10393 "Coinductive declaration error: declaration for \"{}\" must list at least one constructor",
10394 name
10395 );
10396 }
10397 let mut constructors: Vec<ConstructorDecl> = Vec::new();
10398 let mut seen: HashSet<String> = HashSet::new();
10399 for clause in &children[2..] {
10400 let ctor = parse_coinductive_constructor_clause(clause, &name);
10401 if seen.contains(&ctor.name) {
10402 panic!(
10403 "Coinductive declaration error: declaration for \"{}\": constructor \"{}\" is declared more than once",
10404 name, ctor.name
10405 );
10406 }
10407 seen.insert(ctor.name.clone());
10408 constructors.push(ctor);
10409 }
10410 let any_recursive = constructors.iter().any(|c| ctor_has_recursive_param(c, &name));
10411 if !any_recursive {
10412 panic!(
10413 "Coinductive declaration error: declaration for \"{}\" is non-productive: at least one constructor must take a recursive \"{}\" argument",
10414 name, name
10415 );
10416 }
10417 let corec_name = format!("{}-corec", name);
10418 let corec_type = build_corecursor_type(&name, &constructors);
10419 Some(CoinductiveDecl {
10420 name,
10421 constructors,
10422 corec_name,
10423 corec_type,
10424 })
10425}
10426
10427fn build_corec_case_type(ctor: &ConstructorDecl, type_name: &str, state_var: &str) -> Node {
10428 let dual_params: Vec<(String, Node)> = ctor
10429 .params
10430 .iter()
10431 .map(|(pname, ptype)| {
10432 let new_type = match ptype {
10433 Node::Leaf(s) if s == type_name => Node::Leaf(state_var.to_string()),
10434 other => other.clone(),
10435 };
10436 (pname.clone(), new_type)
10437 })
10438 .collect();
10439 let inner = build_pi(&dual_params, Node::Leaf(type_name.to_string()));
10440 build_pi(
10441 &[(
10442 "_state".to_string(),
10443 Node::Leaf(state_var.to_string()),
10444 )],
10445 inner,
10446 )
10447}
10448
10449pub fn build_corecursor_type(type_name: &str, constructors: &[ConstructorDecl]) -> Node {
10453 let state_var = "_state_type";
10454 let state_type = Node::List(vec![
10455 Node::Leaf("Type".to_string()),
10456 Node::Leaf("0".to_string()),
10457 ]);
10458 let case_params: Vec<(String, Node)> = constructors
10459 .iter()
10460 .map(|c| {
10461 (
10462 format!("case_{}", c.name),
10463 build_corec_case_type(c, type_name, state_var),
10464 )
10465 })
10466 .collect();
10467 let seed_var = "_seed";
10468 let final_node = Node::Leaf(type_name.to_string());
10469 let inner = build_pi(
10470 &[(seed_var.to_string(), Node::Leaf(state_var.to_string()))],
10471 final_node,
10472 );
10473 let with_cases = build_pi(&case_params, inner);
10474 build_pi(
10475 &[(state_var.to_string(), state_type)],
10476 with_cases,
10477 )
10478}
10479
10480pub fn register_coinductive(env: &mut Env, decl: CoinductiveDecl) {
10484 let store_type = env.qualify_name(&decl.name);
10485 env.terms.insert(store_type.clone());
10486 let type0 = Node::List(vec![
10487 Node::Leaf("Type".to_string()),
10488 Node::Leaf("0".to_string()),
10489 ]);
10490 env.set_type(&store_type, &key_of(&type0));
10491 eval_node(&type0, env);
10492
10493 for ctor in &decl.constructors {
10494 let store_name = env.qualify_name(&ctor.name);
10495 env.terms.insert(store_name.clone());
10496 env.set_type(&store_name, &key_of(&ctor.typ));
10497 if matches!(ctor.typ, Node::List(_)) {
10498 eval_node(&ctor.typ, env);
10499 }
10500 }
10501
10502 let store_corec = env.qualify_name(&decl.corec_name);
10503 env.terms.insert(store_corec.clone());
10504 env.set_type(&store_corec, &key_of(&decl.corec_type));
10505 eval_node(&decl.corec_type, env);
10506
10507 env.coinductives.insert(decl.name.clone(), decl);
10508}
10509
10510pub fn decide_automatic_sequence_theorem(name: &str) -> Option<AutomaticSequenceDecision> {
10511 if name == "thue-morse-cube-free" {
10512 return Some(AutomaticSequenceDecision {
10513 theorem: name.to_string(),
10514 value: true,
10515 method: "built-in Buchi emptiness certificate".to_string(),
10516 certificate: Node::List(vec![
10517 Node::Leaf("buchi-emptiness".to_string()),
10518 Node::Leaf("thue-morse".to_string()),
10519 Node::Leaf("cube-free".to_string()),
10520 ]),
10521 });
10522 }
10523 None
10524}
10525
10526pub fn automatic_sequences_domain_plugin(forms: &[Node], env: &mut Env) -> Result<(), String> {
10527 if forms.is_empty() {
10528 return Err("automatic-sequences domain requires at least one request".to_string());
10529 }
10530 let theorem_shape_error = "automatic-sequences entries must be `(theorem <name>)`".to_string();
10531 for form in forms {
10532 let theorem_name = match form {
10533 Node::List(children) if children.len() == 2 => {
10534 if let (Node::Leaf(head), Node::Leaf(name)) = (&children[0], &children[1]) {
10535 if head == "theorem" {
10536 name.clone()
10537 } else {
10538 return Err(theorem_shape_error.clone());
10539 }
10540 } else {
10541 return Err(theorem_shape_error.clone());
10542 }
10543 }
10544 _ => {
10545 return Err(theorem_shape_error.clone());
10546 }
10547 };
10548 let mut decision = decide_automatic_sequence_theorem(&theorem_name)
10549 .ok_or_else(|| format!("unknown automatic-sequences theorem \"{}\"", theorem_name))?;
10550 let store_name = env.qualify_name(&decision.theorem);
10551 let truth_value = if decision.value { env.hi } else { env.lo };
10552 env.terms.insert(store_name.clone());
10553 env.set_type(&store_name, "Theorem");
10554 env.set_symbol_prob(&store_name, truth_value);
10555 decision.theorem = store_name.clone();
10556 env.automatic_sequence_decisions
10557 .insert(store_name.clone(), decision);
10558 env.trace(
10559 "domain",
10560 format!("{} decided by automatic-sequences", store_name),
10561 );
10562 }
10563 Ok(())
10564}
10565
10566fn eval_domain_form(children: &[Node], env: &mut Env) -> EvalResult {
10567 if children.len() < 3 {
10568 panic!("Domain plugin error: Domain form must be `(domain <name> <request>...)`");
10569 }
10570 let plugin_name = match &children[1] {
10571 Node::Leaf(name) => name.clone(),
10572 _ => {
10573 panic!("Domain plugin error: Domain form must be `(domain <name> <request>...)`");
10574 }
10575 };
10576 let plugin = env.get_domain_plugin(&plugin_name).unwrap_or_else(|| {
10577 panic!(
10578 "Domain plugin error: Unknown domain plugin \"{}\"",
10579 plugin_name
10580 )
10581 });
10582 if let Err(message) = plugin(&children[2..], env) {
10583 panic!("Domain plugin error: {}", message);
10584 }
10585 EvalResult::Value(1.0)
10586}
10587
10588pub fn eval_node(node: &Node, env: &mut Env) -> EvalResult {
10590 let desugared;
10596 let node = if matches!(node, Node::List(_)) {
10597 desugared = desugar_hoas(node.clone());
10598 &desugared
10599 } else {
10600 node
10601 };
10602 match node {
10603 Node::Leaf(s) => {
10604 if is_num(s) {
10605 EvalResult::Value(env.to_num(s))
10606 } else {
10607 EvalResult::Value(env.get_symbol_prob(s))
10608 }
10609 }
10610 Node::List(children) => {
10611 if children.is_empty() {
10612 return EvalResult::Value(0.0);
10613 }
10614
10615 if let Node::Leaf(ref s) = children[0] {
10617 if s.ends_with(':') {
10618 let head = &s[..s.len() - 1];
10619 return define_form(head, &children[1..], env);
10620 }
10621 }
10622
10623 if let Node::Leaf(ref head) = children[0] {
10632 if head == "mode" {
10633 if let Some((name, flags)) = parse_mode_form(children) {
10634 env.modes.insert(name, flags);
10635 return EvalResult::Value(1.0);
10636 }
10637 }
10638 }
10639
10640 if let Node::Leaf(ref head) = children[0] {
10645 if head == "relation" {
10646 let (name, clauses) = parse_relation_form(children);
10647 env.relations.insert(name, clauses);
10648 return EvalResult::Value(1.0);
10649 }
10650 }
10651
10652 if let Node::Leaf(ref head) = children[0] {
10656 if head == "total" {
10657 if children.len() == 2 {
10658 if let Node::Leaf(ref rel_name) = children[1] {
10659 let result = is_total(env, rel_name);
10660 if !result.ok {
10661 if let Some(first) = result.diagnostics.first() {
10662 panic!("Totality check error: {}", first.message);
10663 }
10664 }
10665 return EvalResult::Value(1.0);
10666 }
10667 }
10668 panic!(
10669 "Totality check error: Totality declaration must be `(total <relation-name>)`"
10670 );
10671 }
10672 }
10673
10674 if let Node::Leaf(ref head) = children[0] {
10681 if head == "define" {
10682 let decl = parse_define_form(children);
10683 env.definitions.insert(decl.name.clone(), decl);
10684 return EvalResult::Value(1.0);
10685 }
10686 }
10687
10688 if let Node::Leaf(ref head) = children[0] {
10693 if head == "terminating" {
10694 if children.len() == 2 {
10695 if let Node::Leaf(ref def_name) = children[1] {
10696 let result = is_terminating(env, def_name);
10697 if !result.ok {
10698 if let Some(first) = result.diagnostics.first() {
10699 panic!("Termination check error: {}", first.message);
10700 }
10701 }
10702 return EvalResult::Value(1.0);
10703 }
10704 }
10705 panic!(
10706 "Termination check error: Termination declaration must be `(terminating <definition-name>)`"
10707 );
10708 }
10709 }
10710
10711 if let Node::Leaf(ref head) = children[0] {
10716 if head == "coverage" {
10717 if children.len() == 2 {
10718 if let Node::Leaf(ref rel_name) = children[1] {
10719 let result = is_covered(env, rel_name);
10720 if !result.ok {
10721 let span = env
10722 .current_span
10723 .clone()
10724 .unwrap_or_else(|| env.default_span.clone());
10725 if result.diagnostics.len() > 1 {
10726 for d in result.diagnostics.iter().skip(1) {
10727 env.shadow_diagnostics.push(Diagnostic::new(
10728 &d.code,
10729 d.message.clone(),
10730 span.clone(),
10731 ));
10732 }
10733 }
10734 if let Some(first) = result.diagnostics.first() {
10735 panic!("Coverage check error: {}", first.message);
10736 }
10737 }
10738 return EvalResult::Value(1.0);
10739 }
10740 }
10741 panic!(
10742 "Coverage check error: Coverage declaration must be `(coverage <relation-name>)`"
10743 );
10744 }
10745 }
10746
10747 if let Node::Leaf(ref head) = children[0] {
10753 if head == "world" {
10754 if let Some((name, allowed)) = parse_world_form(children) {
10755 env.worlds.insert(name, allowed);
10756 return EvalResult::Value(1.0);
10757 }
10758 }
10759 }
10760
10761 if let Node::Leaf(ref head) = children[0] {
10770 if head == "inductive" {
10771 if let Some(decl) = parse_inductive_form(node) {
10772 register_inductive(env, decl);
10773 return EvalResult::Value(1.0);
10774 }
10775 }
10776 }
10777
10778 if let Node::Leaf(ref head) = children[0] {
10790 if head == "coinductive" {
10791 if let Some(decl) = parse_coinductive_form(node) {
10792 register_coinductive(env, decl);
10793 return EvalResult::Value(1.0);
10794 }
10795 }
10796 }
10797
10798 if let Node::Leaf(ref head) = children[0] {
10803 if head == "domain" {
10804 return eval_domain_form(children, env);
10805 }
10806 }
10807
10808 if let Node::Leaf(ref head) = children[0] {
10813 if env.modes.contains_key(head) {
10814 let head_owned = head.clone();
10815 let args: Vec<Node> = children[1..].to_vec();
10816 check_mode_at_call(&head_owned, &args, env);
10817 }
10818 }
10819
10820 if let Node::Leaf(ref head) = children[0] {
10825 if env.worlds.contains_key(head) {
10826 let head_owned = head.clone();
10827 let args: Vec<Node> = children[1..].to_vec();
10828 check_world_at_call(&head_owned, &args, env);
10829 }
10830 }
10831
10832 if children.len() == 4 {
10834 if let (Node::Leaf(ref w1), Node::Leaf(ref w2), Node::Leaf(ref w3)) =
10835 (&children[1], &children[2], &children[3])
10836 {
10837 if w1 == "has" && w2 == "probability" && is_num(w3) {
10838 let p: f64 = w3.parse().unwrap_or(0.0);
10839 let clamped = env.clamp(p);
10847 if let Some(msg) = env.check_carrier_value(clamped) {
10848 panic!(
10849 "Carrier violation: Probability assignment {} = {} violates active foundation carrier: {}",
10850 key_of(&children[0]),
10851 format_trace_value(clamped),
10852 msg
10853 );
10854 }
10855 env.set_expr_prob(&children[0], p);
10856 let key = key_of(&children[0]);
10857 env.trace(
10858 "assign",
10859 format!("{} ← {}", key, format_trace_value(clamped)),
10860 );
10861 return EvalResult::Value(env.to_num(w3));
10862 }
10863 }
10864 }
10865
10866 if children.len() == 3 {
10868 if let Node::Leaf(ref first) = children[0] {
10869 if first == "range" {
10870 if let (Node::Leaf(ref lo_s), Node::Leaf(ref hi_s)) =
10871 (&children[1], &children[2])
10872 {
10873 if is_num(lo_s) && is_num(hi_s) {
10874 env.lo = lo_s.parse().unwrap_or(0.0);
10875 env.hi = hi_s.parse().unwrap_or(1.0);
10876 env.reinit_ops();
10877 return EvalResult::Value(1.0);
10878 }
10879 }
10880 }
10881 }
10882 }
10883
10884 if children.len() == 2 {
10886 if let Node::Leaf(ref first) = children[0] {
10887 if first == "valence" {
10888 if let Node::Leaf(ref val_s) = children[1] {
10889 if is_num(val_s) {
10890 env.valence = val_s.parse::<f64>().unwrap_or(0.0) as u32;
10891 return EvalResult::Value(1.0);
10892 }
10893 }
10894 }
10895 }
10896 }
10897
10898 if let Node::Leaf(ref first) = children[0] {
10904 if first == "?" {
10905 let parts = strip_with_proof(&children[1..]);
10906 let target: Node = if parts.len() == 1 {
10907 parts[0].clone()
10908 } else {
10909 Node::List(parts.to_vec())
10910 };
10911 let result = eval_node(&target, env);
10912 if result.is_type_query() {
10914 return result;
10915 }
10916 if let EvalResult::Term(term) = result {
10917 return EvalResult::TypeQuery(key_of(&term));
10918 }
10919 let v = result.as_f64();
10920 return EvalResult::Query(env.clamp(v));
10921 }
10922 }
10923
10924 if children.len() == 4 {
10926 if let (Node::Leaf(ref head), Node::Leaf(ref var_name)) =
10927 (&children[0], &children[2])
10928 {
10929 if head == "subst" {
10930 let term = eval_term_node(node, env);
10931 let _ = var_name;
10932 return EvalResult::Term(term);
10933 }
10934 }
10935 }
10936
10937 if children.len() == 4 {
10939 if let (Node::Leaf(ref head), Node::Leaf(ref var_name), Node::Leaf(ref in_kw)) =
10940 (&children[0], &children[1], &children[2])
10941 {
10942 if head == "fresh" && in_kw == "in" {
10943 return eval_fresh(var_name, &children[3], env);
10944 }
10945 }
10946 }
10947
10948 if children.len() == 3 {
10951 if let Node::Leaf(ref op_name) = children[1] {
10952 if op_name == "+" || op_name == "-" || op_name == "*" || op_name == "/" {
10953 let l = eval_arith(&children[0], env);
10954 let r = eval_arith(&children[2], env);
10955 return EvalResult::Value(env.apply_op(op_name, &[l, r]));
10956 }
10957 }
10958 }
10959
10960 if children.len() == 3 {
10962 if let Node::Leaf(ref op_name) = children[1] {
10963 if op_name == "<" || op_name == "<=" {
10964 let l = eval_arith(&children[0], env);
10965 let r = eval_arith(&children[2], env);
10966 return EvalResult::Value(env.clamp(env.apply_op(op_name, &[l, r])));
10967 }
10968 }
10969 }
10970
10971 if children.len() == 3 {
10973 if let Node::Leaf(ref op_name) = children[1] {
10974 if op_name == "and"
10975 || op_name == "or"
10976 || op_name == "both"
10977 || op_name == "neither"
10978 {
10979 let l = eval_node(&children[0], env).as_f64();
10980 let r = eval_node(&children[2], env).as_f64();
10981 return EvalResult::Value(env.clamp(env.apply_op(op_name, &[l, r])));
10982 }
10983 }
10984 }
10985
10986 if children.len() >= 4 && children.len() % 2 == 0 {
10988 if let Node::Leaf(ref head) = children[0] {
10989 if head == "both" || head == "neither" {
10990 let sep = if head == "both" { "and" } else { "nor" };
10991 let mut valid = true;
10992 for i in (2..children.len()).step_by(2) {
10993 if let Node::Leaf(ref s) = children[i] {
10994 if s != sep {
10995 valid = false;
10996 break;
10997 }
10998 } else {
10999 valid = false;
11000 break;
11001 }
11002 }
11003 if valid {
11004 let head_str = head.clone();
11005 let vals: Vec<f64> = (1..children.len())
11006 .step_by(2)
11007 .map(|i| eval_node(&children[i], env).as_f64())
11008 .collect();
11009 return EvalResult::Value(env.clamp(env.apply_op(&head_str, &vals)));
11010 }
11011 }
11012 }
11013 }
11014
11015 if children.len() == 3 {
11017 if let Node::Leaf(ref op_name) = children[1] {
11018 if op_name == "=" {
11019 return eval_equality_node(&children[0], "=", &children[2], env);
11020 }
11021 if op_name == "!=" {
11022 return eval_equality_node(&children[0], "!=", &children[2], env);
11023 }
11024 }
11025 }
11026
11027 if children.len() == 2 {
11031 if let Node::Leaf(ref first) = children[0] {
11032 if first == "Type" {
11033 if let Node::Leaf(ref level_s) = children[1] {
11034 if let Some(level) = parse_universe_level_token(level_s) {
11035 if let Some(next_level) = level.checked_add(1) {
11036 let key = key_of(&Node::List(children.clone()));
11037 env.set_type(&key, &format!("(Type {})", next_level));
11038 return EvalResult::Value(1.0);
11039 }
11040 }
11041 }
11042 }
11043 }
11044 }
11045
11046 if children.len() == 1 {
11048 if let Node::Leaf(ref first) = children[0] {
11049 if first == "Prop" {
11050 env.set_type("(Prop)", "(Type 1)");
11051 return EvalResult::Value(1.0);
11052 }
11053 }
11054 }
11055
11056 if children.len() == 3 {
11058 if let Node::Leaf(ref first) = children[0] {
11059 if first == "Pi" {
11060 if let Some((param_name, param_type)) = parse_binding(&children[1]) {
11061 env.terms.insert(param_name.clone());
11062 env.set_type(¶m_name, ¶m_type);
11063 let key = key_of(&Node::List(children.clone()));
11064 env.set_type(&key, "(Type 0)");
11065 }
11066 return EvalResult::Value(1.0);
11067 }
11068 }
11069 }
11070
11071 if children.len() == 3 {
11074 if let Node::Leaf(ref first) = children[0] {
11075 if first == "lambda" {
11076 if let Some(bindings) = parse_bindings(&children[1]) {
11077 if !bindings.is_empty() {
11078 let (ref param_name, ref param_type) = bindings[0];
11079 env.terms.insert(param_name.clone());
11080 env.set_type(param_name, param_type);
11081 for binding in &bindings[1..] {
11083 env.terms.insert(binding.0.clone());
11084 env.set_type(&binding.0, &binding.1);
11085 }
11086 let body_key = key_of(&children[2]);
11087 let body_type = env
11088 .get_type(&body_key)
11089 .cloned()
11090 .unwrap_or_else(|| "unknown".to_string());
11091 let key = key_of(&Node::List(children.clone()));
11092 env.set_type(
11093 &key,
11094 &format!("(Pi ({} {}) {})", param_type, param_name, body_type),
11095 );
11096 }
11097 }
11098 return EvalResult::Value(1.0);
11099 }
11100 }
11101 }
11102
11103 if let Node::Leaf(ref first) = children[0] {
11112 if first == "whnf" {
11113 if children.len() == 2 {
11114 let opts = ConvertOptions::default();
11115 let reduced = whnf_term(&children[1], env, opts);
11116 return EvalResult::Term(reduced);
11117 }
11118 panic!("Normalization error: Normalization form must be `(whnf <expr>)`");
11119 }
11120 if first == "nf" {
11121 if children.len() == 2 {
11122 let opts = ConvertOptions::default();
11123 let normalized = normalize_term(&children[1], env, opts);
11124 let flat = flatten_neutral_applies(&normalized, env);
11125 return EvalResult::Term(flat);
11126 }
11127 panic!("Normalization error: Normalization form must be `(nf <expr>)`");
11128 }
11129 if first == "normal-form" {
11130 if children.len() == 2 {
11131 let opts = ConvertOptions::default();
11132 let normalized = normalize_term(&children[1], env, opts);
11133 let flat = flatten_neutral_applies(&normalized, env);
11134 return EvalResult::Term(flat);
11135 }
11136 panic!("Normalization error: Normalization form must be `(normal-form <expr>)`");
11137 }
11138 }
11139
11140 if children.len() == 3 {
11142 if let Node::Leaf(ref first) = children[0] {
11143 if first == "apply" {
11144 let fn_node = &children[1];
11145 let arg = &children[2];
11146
11147 if let Node::List(ref fn_children) = fn_node {
11149 if fn_children.len() == 3 {
11150 if let Node::Leaf(ref fn_head) = fn_children[0] {
11151 if fn_head == "lambda" {
11152 if let Some((param_name, _)) =
11153 parse_binding(&fn_children[1])
11154 {
11155 let body = &fn_children[2];
11156 let result = subst(body, ¶m_name, arg);
11157 return eval_reduced_term(&result, env);
11158 }
11159 }
11160 }
11161 }
11162 }
11163
11164 if let Node::Leaf(ref fn_name) = fn_node {
11166 if let Some(lambda) = env.get_lambda(fn_name).cloned() {
11167 let result = subst(&lambda.body, &lambda.param, arg);
11168 return eval_reduced_term(&result, env);
11169 }
11170 }
11171
11172 let f_val = eval_node(fn_node, env).as_f64();
11174 return EvalResult::Value(f_val);
11175 }
11176 }
11177 }
11178
11179 if children.len() == 3 {
11182 if let (Node::Leaf(ref first), Node::Leaf(ref mid)) = (&children[0], &children[1]) {
11183 if first == "type" && mid == "of" {
11184 let type_str = infer_type_key(&children[2], env)
11185 .unwrap_or_else(|| "unknown".to_string());
11186 return EvalResult::TypeQuery(type_str);
11187 }
11188 }
11189 }
11190
11191 if children.len() == 3 {
11194 if let Node::Leaf(ref mid) = children[1] {
11195 if mid == "of" {
11196 let expected_key = match &children[2] {
11197 Node::Leaf(s) => s.clone(),
11198 other => key_of(other),
11199 };
11200 if let Some(actual) = infer_type_key(&children[0], env) {
11201 return EvalResult::Value(if actual == expected_key {
11202 env.hi
11203 } else {
11204 env.lo
11205 });
11206 }
11207 return EvalResult::Value(env.lo);
11208 }
11209 }
11210 }
11211
11212 if let Node::Leaf(ref head) = children[0] {
11214 let head_str = head.clone();
11215 if (head_str == "=" || head_str == "!=") && children.len() == 3 {
11216 return eval_equality_node(&children[1], &head_str, &children[2], env);
11217 }
11218 if env.has_op(&head_str) {
11219 let vals: Vec<f64> = children[1..]
11220 .iter()
11221 .map(|a| eval_node(a, env).as_f64())
11222 .collect();
11223 return EvalResult::Value(env.clamp(env.apply_op(&head_str, &vals)));
11224 }
11225
11226 if children.len() >= 2 {
11228 if let Some(lambda) = env.get_lambda(&head_str).cloned() {
11229 let result = subst(&lambda.body, &lambda.param, &children[1]);
11230 if children.len() == 2 {
11231 return eval_reduced_term(&result, env);
11232 }
11233 let mut next = vec![result];
11234 next.extend_from_slice(&children[2..]);
11235 return eval_reduced_term(&Node::List(next), env);
11236 }
11237 }
11238 }
11239
11240 if children.len() >= 2 {
11242 if let Node::List(head_children) = &children[0] {
11243 if head_children.len() == 3 {
11244 if let Node::Leaf(fn_head) = &head_children[0] {
11245 if fn_head == "lambda" {
11246 if let Some((param_name, _)) = parse_binding(&head_children[1]) {
11247 let result =
11248 subst(&head_children[2], ¶m_name, &children[1]);
11249 if children.len() == 2 {
11250 return eval_reduced_term(&result, env);
11251 }
11252 let mut next = vec![result];
11253 next.extend_from_slice(&children[2..]);
11254 return eval_reduced_term(&Node::List(next), env);
11255 }
11256 }
11257 }
11258 }
11259 }
11260 }
11261
11262 EvalResult::Value(0.0)
11263 }
11264 }
11265}
11266
11267fn define_form(head: &str, rhs: &[Node], env: &mut Env) -> EvalResult {
11269 if head == "range" && rhs.len() == 2 {
11272 if let (Node::Leaf(ref lo_s), Node::Leaf(ref hi_s)) = (&rhs[0], &rhs[1]) {
11273 if is_num(lo_s) && is_num(hi_s) {
11274 env.lo = lo_s.parse().unwrap_or(0.0);
11275 env.hi = hi_s.parse().unwrap_or(1.0);
11276 env.reinit_ops();
11277 return EvalResult::Value(1.0);
11278 }
11279 }
11280 }
11281
11282 if head == "valence" && rhs.len() == 1 {
11284 if let Node::Leaf(ref val_s) = rhs[0] {
11285 if is_num(val_s) {
11286 env.valence = val_s.parse::<f64>().unwrap_or(0.0) as u32;
11287 return EvalResult::Value(1.0);
11288 }
11289 }
11290 }
11291
11292 let store_name = env.qualify_name(head);
11296 if store_name != head || env.namespace.is_none() {
11298 maybe_warn_shadow(env, &store_name);
11299 } else {
11300 maybe_warn_shadow(env, head);
11301 }
11302
11303 if rhs.len() == 3 {
11305 if let (Node::Leaf(ref r0), Node::Leaf(ref r1), Node::Leaf(ref r2)) =
11306 (&rhs[0], &rhs[1], &rhs[2])
11307 {
11308 if r1 == "is" && r0 == head && r2 == head {
11309 env.terms.insert(store_name.clone());
11310 return EvalResult::Value(1.0);
11311 }
11312 }
11313 }
11314
11315 if rhs.len() == 2 {
11318 if let Node::Leaf(ref last) = rhs[1] {
11319 if last == head {
11320 match &rhs[0] {
11321 Node::Leaf(ref type_name)
11322 if type_name.starts_with(|c: char| c.is_uppercase()) =>
11323 {
11324 env.terms.insert(store_name.clone());
11325 env.types.insert(store_name.clone(), type_name.clone());
11326 return EvalResult::Value(1.0);
11327 }
11328 Node::List(_) => {
11329 env.terms.insert(store_name.clone());
11330 let type_key = key_of(&rhs[0]);
11331 env.types.insert(store_name.clone(), type_key);
11332 eval_node(&rhs[0], env);
11333 return EvalResult::Value(1.0);
11334 }
11335 _ => {}
11336 }
11337 }
11338 }
11339 }
11340
11341 if rhs.len() == 1 {
11343 if let Node::Leaf(ref val_s) = rhs[0] {
11344 if is_num(val_s) {
11345 let p: f64 = val_s.parse().unwrap_or(0.0);
11346 env.set_symbol_prob(&store_name, p);
11347 return EvalResult::Value(env.to_num(val_s));
11348 }
11349 }
11350 }
11351
11352 let is_op_name = head == "="
11354 || head == "!="
11355 || head == "and"
11356 || head == "or"
11357 || head == "both"
11358 || head == "neither"
11359 || head == "not"
11360 || head == "is"
11361 || head == "?:"
11362 || head.contains('=')
11363 || head.contains('!');
11364
11365 if is_op_name {
11366 if rhs.len() == 1 {
11369 if let Node::Leaf(ref target) = rhs[0] {
11370 if let Some(op) = env.get_op(target.as_str()).cloned() {
11371 env.define_op(&store_name, op);
11372 env.trace("resolve", format!("({}: {})", store_name, target));
11373 return EvalResult::Value(1.0);
11374 }
11375 }
11376 }
11377
11378 if rhs.len() == 2 {
11380 if let (Node::Leaf(ref outer), Node::Leaf(ref inner)) = (&rhs[0], &rhs[1]) {
11381 if env.has_op(outer.as_str()) && env.has_op(inner.as_str()) {
11382 env.define_op(
11383 &store_name,
11384 Op::Compose {
11385 outer: outer.clone(),
11386 inner: inner.clone(),
11387 },
11388 );
11389 env.trace("resolve", format!("({}: {} {})", store_name, outer, inner));
11390 return EvalResult::Value(1.0);
11391 }
11392 if !env.has_op(outer.as_str()) {
11394 panic!("Unknown op: {}", outer);
11395 }
11396 if !env.has_op(inner.as_str()) {
11397 panic!("Unknown op: {}", inner);
11398 }
11399 }
11400 }
11401
11402 if (head == "and" || head == "or" || head == "both" || head == "neither") && rhs.len() == 1
11404 {
11405 if let Node::Leaf(ref sel) = rhs[0] {
11406 if let Some(agg) = Aggregator::from_name(sel) {
11407 env.define_op(&store_name, Op::Agg(agg));
11408 env.trace("resolve", format!("({}: {})", store_name, sel));
11409 return EvalResult::Value(1.0);
11410 } else {
11411 panic!("Unknown aggregator \"{}\"", sel);
11412 }
11413 }
11414 }
11415 }
11416
11417 if rhs.len() >= 2 {
11419 if let Node::Leaf(ref first) = rhs[0] {
11420 if first == "lambda" && rhs.len() == 3 {
11421 if let Some((param_name, param_type)) = parse_binding(&rhs[1]) {
11422 let body = rhs[2].clone();
11423 env.terms.insert(store_name.clone());
11424 let had_param_term = env.terms.contains(¶m_name);
11425 let previous_param_type = env.get_type(¶m_name).cloned();
11426 env.terms.insert(param_name.clone());
11427 env.set_type(¶m_name, ¶m_type);
11428 let body_key = key_of(&body);
11429 let body_type =
11430 env.get_type(&body_key)
11431 .cloned()
11432 .unwrap_or_else(|| match &body {
11433 Node::Leaf(s) => s.clone(),
11434 other => key_of(other),
11435 });
11436 if !had_param_term {
11437 env.terms.remove(¶m_name);
11438 }
11439 if let Some(previous) = previous_param_type {
11440 env.set_type(¶m_name, &previous);
11441 } else {
11442 env.types.remove(¶m_name);
11443 }
11444 env.set_type(
11445 &store_name,
11446 &format!("(Pi ({} {}) {})", param_type, param_name, body_type),
11447 );
11448 env.set_lambda(
11449 &store_name,
11450 Lambda {
11451 param: param_name,
11452 param_type,
11453 body,
11454 },
11455 );
11456 return EvalResult::Value(1.0);
11457 }
11458 }
11459 }
11460 }
11461
11462 if rhs.len() == 1 {
11466 let is_op = head == "="
11467 || head == "!="
11468 || head == "and"
11469 || head == "or"
11470 || head == "both"
11471 || head == "neither"
11472 || head == "not"
11473 || head == "is"
11474 || head == "?:"
11475 || head.contains('=')
11476 || head.contains('!');
11477
11478 if !is_op {
11479 if let Node::List(_) = &rhs[0] {
11480 env.terms.insert(store_name.clone());
11481 let type_key = key_of(&rhs[0]);
11482 env.set_type(&store_name, &type_key);
11483 eval_node(&rhs[0], env);
11484 return EvalResult::Value(1.0);
11485 }
11486 }
11487 }
11488
11489 if rhs.len() == 1 {
11491 if let Node::Leaf(ref sym) = rhs[0] {
11492 let prob = env.get_symbol_prob(sym);
11493 env.set_symbol_prob(&store_name, prob);
11494 return EvalResult::Value(env.get_symbol_prob(&store_name));
11495 }
11496 }
11497
11498 EvalResult::Value(0.0)
11500}
11501
11502fn maybe_warn_shadow(env: &mut Env, name: &str) {
11509 let key = if env.imported.contains(name) {
11512 name.to_string()
11513 } else {
11514 let resolved = env.resolve_qualified(name);
11515 if resolved != name && env.imported.contains(&resolved) {
11516 resolved
11517 } else {
11518 return;
11519 }
11520 };
11521 env.imported.remove(&key);
11524 let span = env
11525 .current_span
11526 .clone()
11527 .unwrap_or_else(|| env.default_span.clone());
11528 let diag = Diagnostic::new(
11529 "E008",
11530 format!("Definition of \"{}\" shadows an imported binding", name),
11531 span,
11532 );
11533 env.shadow_diagnostics.push(diag);
11534}
11535
11536#[derive(Debug, Clone, PartialEq)]
11540pub struct Interpretation {
11541 pub kind: String,
11542 pub expression: Option<String>,
11543 pub summary: Option<String>,
11544 pub lino: Option<String>,
11545}
11546
11547impl Interpretation {
11548 pub fn arithmetic_equality(expression: &str) -> Self {
11549 Self {
11550 kind: "arithmetic-equality".to_string(),
11551 expression: Some(expression.to_string()),
11552 summary: None,
11553 lino: None,
11554 }
11555 }
11556
11557 pub fn arithmetic_question(expression: &str) -> Self {
11558 Self {
11559 kind: "arithmetic-question".to_string(),
11560 expression: Some(expression.to_string()),
11561 summary: None,
11562 lino: None,
11563 }
11564 }
11565
11566 pub fn real_world_claim(summary: &str) -> Self {
11567 Self {
11568 kind: "real-world-claim".to_string(),
11569 expression: None,
11570 summary: Some(summary.to_string()),
11571 lino: None,
11572 }
11573 }
11574
11575 pub fn lino(expression: &str) -> Self {
11576 Self {
11577 kind: "lino".to_string(),
11578 expression: None,
11579 summary: None,
11580 lino: Some(expression.to_string()),
11581 }
11582 }
11583}
11584
11585#[derive(Debug, Clone, PartialEq)]
11587pub struct Dependency {
11588 pub id: String,
11589 pub status: String,
11590 pub description: String,
11591}
11592
11593impl Dependency {
11594 pub fn missing(id: &str, description: &str) -> Self {
11595 Self {
11596 id: id.to_string(),
11597 status: "missing".to_string(),
11598 description: description.to_string(),
11599 }
11600 }
11601}
11602
11603#[derive(Debug, Clone, PartialEq)]
11605pub struct FormalizationRequest {
11606 pub text: String,
11607 pub interpretation: Interpretation,
11608 pub formal_system: String,
11609 pub dependencies: Vec<Dependency>,
11610}
11611
11612#[derive(Debug, Clone, PartialEq)]
11614pub struct Formalization {
11615 pub source_text: String,
11616 pub interpretation: Interpretation,
11617 pub formal_system: String,
11618 pub dependencies: Vec<Dependency>,
11619 pub computable: bool,
11620 pub formalization_level: u8,
11621 pub unknowns: Vec<String>,
11622 pub value_kind: String,
11623 pub ast: Option<Node>,
11624 pub lino: Option<String>,
11625}
11626
11627#[derive(Debug, Clone, PartialEq)]
11629pub enum FormalizationResultValue {
11630 Number(f64),
11631 TruthValue(f64),
11632 Type(String),
11633 Partial(String),
11634}
11635
11636#[derive(Debug, Clone, PartialEq)]
11638pub struct FormalizationEvaluation {
11639 pub computable: bool,
11640 pub formalization_level: u8,
11641 pub unknowns: Vec<String>,
11642 pub result: FormalizationResultValue,
11643}
11644
11645fn normalize_question_expression(text: &str) -> String {
11646 let mut out = text.trim().trim_end_matches('?').trim().to_string();
11647 let lower = out.to_lowercase();
11648 if lower.starts_with("what is ") {
11649 out = out[8..].trim().to_string();
11650 }
11651 out
11652}
11653
11654fn split_top_level_equals(expression: &str) -> Option<(String, String)> {
11655 let mut depth: i32 = 0;
11656 let chars: Vec<char> = expression.chars().collect();
11657 for (i, c) in chars.iter().enumerate() {
11658 match c {
11659 '(' => depth += 1,
11660 ')' => depth -= 1,
11661 '=' if depth == 0 => {
11662 if i > 0 && chars[i - 1] == '!' {
11663 continue;
11664 }
11665 if i + 1 < chars.len() && chars[i + 1] == '=' {
11666 continue;
11667 }
11668 let left: String = chars[..i].iter().collect();
11669 let right: String = chars[i + 1..].iter().collect();
11670 return Some((left.trim().to_string(), right.trim().to_string()));
11671 }
11672 _ => {}
11673 }
11674 }
11675 None
11676}
11677
11678fn parse_expression_shape(expression: &str, unwrap_single: bool) -> Result<Node, String> {
11679 let trimmed = expression.trim();
11680 if trimmed.is_empty() {
11681 return Err("empty expression".to_string());
11682 }
11683 let source = if trimmed.starts_with('(') && trimmed.ends_with(')') {
11684 trimmed.to_string()
11685 } else {
11686 format!("({})", trimmed)
11687 };
11688 let mut ast = parse_one(&tokenize_one(&source))?;
11689 loop {
11690 match ast {
11691 Node::List(ref children) if children.len() == 1 => {
11692 if unwrap_single || matches!(&children[0], Node::List(_)) {
11693 ast = children[0].clone();
11694 continue;
11695 }
11696 return Ok(ast);
11697 }
11698 _ => return Ok(ast),
11699 }
11700 }
11701}
11702
11703fn unique_unknowns(unknowns: Vec<String>) -> Vec<String> {
11704 let mut out = Vec::new();
11705 for unknown in unknowns {
11706 if !out.contains(&unknown) {
11707 out.push(unknown);
11708 }
11709 }
11710 out
11711}
11712
11713fn partial_formalization(
11714 request: FormalizationRequest,
11715 unknowns: Vec<String>,
11716 formalization_level: u8,
11717) -> Formalization {
11718 Formalization {
11719 source_text: request.text,
11720 interpretation: request.interpretation,
11721 formal_system: request.formal_system,
11722 dependencies: request.dependencies,
11723 computable: false,
11724 formalization_level,
11725 unknowns: unique_unknowns(unknowns),
11726 value_kind: "partial".to_string(),
11727 ast: None,
11728 lino: None,
11729 }
11730}
11731
11732fn build_arithmetic_formalization(
11733 expression: &str,
11734 value_kind: &str,
11735) -> Result<(Node, String), String> {
11736 let ast = if value_kind == "truth-value" {
11737 if let Some((left, right)) = split_top_level_equals(expression) {
11738 Node::List(vec![
11739 parse_expression_shape(&left, true)?,
11740 Node::Leaf("=".to_string()),
11741 parse_expression_shape(&right, true)?,
11742 ])
11743 } else {
11744 parse_expression_shape(expression, true)?
11745 }
11746 } else {
11747 parse_expression_shape(expression, true)?
11748 };
11749 let lino = key_of(&ast);
11750 Ok((ast, lino))
11751}
11752
11753pub fn formalize_selected_interpretation(request: FormalizationRequest) -> Formalization {
11755 let kind = request.interpretation.kind.to_lowercase();
11756 let raw_expression = request
11757 .interpretation
11758 .expression
11759 .clone()
11760 .or_else(|| request.interpretation.lino.clone())
11761 .unwrap_or_else(|| normalize_question_expression(&request.text));
11762 let can_use_arithmetic = request.formal_system == "rml-arithmetic"
11763 || request.formal_system == "arithmetic"
11764 || kind.starts_with("arithmetic");
11765
11766 if can_use_arithmetic && !raw_expression.is_empty() {
11767 let value_kind =
11768 if kind.contains("equal") || split_top_level_equals(&raw_expression).is_some() {
11769 "truth-value"
11770 } else {
11771 "number"
11772 };
11773 match build_arithmetic_formalization(&raw_expression, value_kind) {
11774 Ok((ast, lino)) => Formalization {
11775 source_text: request.text,
11776 interpretation: request.interpretation,
11777 formal_system: request.formal_system,
11778 dependencies: request.dependencies,
11779 computable: true,
11780 formalization_level: 3,
11781 unknowns: vec![],
11782 value_kind: value_kind.to_string(),
11783 ast: Some(ast),
11784 lino: Some(lino),
11785 },
11786 Err(error) => partial_formalization(
11787 request,
11788 vec!["unsupported-arithmetic-shape".to_string(), error],
11789 1,
11790 ),
11791 }
11792 } else if request.interpretation.lino.is_some() && !raw_expression.is_empty() {
11793 match parse_expression_shape(&raw_expression, false) {
11794 Ok(ast) => {
11795 let lino = key_of(&ast);
11796 Formalization {
11797 source_text: request.text,
11798 interpretation: request.interpretation,
11799 formal_system: request.formal_system,
11800 dependencies: request.dependencies,
11801 computable: true,
11802 formalization_level: 3,
11803 unknowns: vec![],
11804 value_kind: if matches!(&ast, Node::List(children) if matches!(children.first(), Some(Node::Leaf(head)) if head == "?"))
11805 {
11806 "query".to_string()
11807 } else {
11808 "truth-value".to_string()
11809 },
11810 ast: Some(ast),
11811 lino: Some(lino),
11812 }
11813 }
11814 Err(error) => partial_formalization(
11815 request,
11816 vec!["unsupported-lino-shape".to_string(), error],
11817 1,
11818 ),
11819 }
11820 } else {
11821 let mut unknowns = vec![
11822 "selected-subject".to_string(),
11823 "selected-relation".to_string(),
11824 "evidence-source".to_string(),
11825 "formal-shape".to_string(),
11826 ];
11827 for dependency in &request.dependencies {
11828 if dependency.status == "missing"
11829 || dependency.status == "unknown"
11830 || dependency.status == "partial"
11831 {
11832 unknowns.push(format!("dependency:{}", dependency.id));
11833 }
11834 }
11835 partial_formalization(request, unknowns, 2)
11836 }
11837}
11838
11839pub fn evaluate_formalization(formalization: &Formalization) -> FormalizationEvaluation {
11841 let Some(ast) = formalization.ast.as_ref() else {
11842 return FormalizationEvaluation {
11843 computable: false,
11844 formalization_level: formalization.formalization_level,
11845 unknowns: formalization.unknowns.clone(),
11846 result: FormalizationResultValue::Partial("unknown".to_string()),
11847 };
11848 };
11849
11850 if !formalization.computable {
11851 return FormalizationEvaluation {
11852 computable: false,
11853 formalization_level: formalization.formalization_level,
11854 unknowns: formalization.unknowns.clone(),
11855 result: FormalizationResultValue::Partial("unknown".to_string()),
11856 };
11857 }
11858
11859 let mut env = Env::new(None);
11860 let evaluated = eval_node(ast, &mut env);
11861 let result = match formalization.value_kind.as_str() {
11862 "truth-value" => FormalizationResultValue::TruthValue(evaluated.as_f64()),
11863 "query" => match evaluated {
11864 EvalResult::TypeQuery(s) => FormalizationResultValue::Type(s),
11865 other => FormalizationResultValue::Number(other.as_f64()),
11866 },
11867 _ => FormalizationResultValue::Number(evaluated.as_f64()),
11868 };
11869
11870 FormalizationEvaluation {
11871 computable: true,
11872 formalization_level: formalization.formalization_level,
11873 unknowns: vec![],
11874 result,
11875 }
11876}
11877
11878#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11882pub enum ExtractTarget {
11883 JavaScript,
11884 Rust,
11885}
11886
11887impl ExtractTarget {
11888 pub fn from_name(name: &str) -> Option<Self> {
11889 match name {
11890 "js" | "javascript" => Some(Self::JavaScript),
11891 "rust" | "rs" => Some(Self::Rust),
11892 _ => None,
11893 }
11894 }
11895}
11896
11897#[derive(Debug, Clone)]
11898struct ExtractLambda {
11899 name: String,
11900 params: Vec<String>,
11901 body: Node,
11902}
11903
11904#[derive(Debug, Clone)]
11905struct ExtractTest {
11906 left: Node,
11907 right: Node,
11908}
11909
11910#[derive(Debug, Clone)]
11911struct ExtractProgram {
11912 lambdas: Vec<ExtractLambda>,
11913 tests: Vec<ExtractTest>,
11914}
11915
11916struct ExtractContext<'a> {
11917 target: ExtractTarget,
11918 name_map: &'a HashMap<String, String>,
11919 locals: &'a HashMap<String, String>,
11920}
11921
11922fn extract_compile_error(message: impl Into<String>) -> String {
11923 message.into()
11924}
11925
11926fn extract_is_probability_assignment(node: &Node) -> bool {
11927 if let Node::List(children) = node {
11928 if children.len() == 4 {
11929 if let (Node::Leaf(w1), Node::Leaf(w2)) = (&children[1], &children[2]) {
11930 return w1 == "has" && w2 == "probability";
11931 }
11932 }
11933 }
11934 false
11935}
11936
11937fn extract_is_query_form(node: &Node) -> bool {
11938 matches!(node, Node::List(children) if matches!(children.first(), Some(Node::Leaf(head)) if head == "?"))
11939}
11940
11941fn extract_is_lambda_definition(node: &Node) -> bool {
11942 if let Node::List(children) = node {
11943 if children.len() >= 3 {
11944 return matches!(&children[0], Node::Leaf(head) if head.ends_with(':'))
11945 && matches!(&children[1], Node::Leaf(head) if head == "lambda")
11946 && matches!(&children[2], Node::List(_));
11947 }
11948 }
11949 false
11950}
11951
11952fn extract_is_type_only_form(node: &Node) -> bool {
11953 let children = match node {
11954 Node::List(children) => children,
11955 Node::Leaf(_) => return true,
11956 };
11957 if children.is_empty() {
11958 return true;
11959 }
11960 if matches!(&children[0], Node::Leaf(head) if head == "Type" || head == "Prop" || head == "Pi")
11961 {
11962 return true;
11963 }
11964 let head = match &children[0] {
11965 Node::Leaf(head) if head.ends_with(':') => &head[..head.len() - 1],
11966 _ => return false,
11967 };
11968 let rhs = &children[1..];
11969 if rhs.len() == 2 {
11970 if let Node::Leaf(last) = &rhs[1] {
11971 if last == head {
11972 return true;
11973 }
11974 }
11975 }
11976 if rhs.len() == 3 {
11977 if let (Node::Leaf(r0), Node::Leaf(r1), Node::Leaf(r2)) = (&rhs[0], &rhs[1], &rhs[2]) {
11978 if r0 == head && r1 == "is" && r2 == head {
11979 return true;
11980 }
11981 }
11982 }
11983 rhs.len() == 1 && matches!(&rhs[0], Node::List(_))
11984}
11985
11986fn extract_logic_token(token: &str) -> bool {
11987 matches!(
11988 token,
11989 "and" | "or" | "not" | "both" | "neither" | "has" | "probability"
11990 )
11991}
11992
11993fn extract_contains_logic(node: &Node) -> bool {
11994 match node {
11995 Node::Leaf(s) => extract_logic_token(s),
11996 Node::List(children) => {
11997 extract_is_probability_assignment(node) || children.iter().any(extract_contains_logic)
11998 }
11999 }
12000}
12001
12002fn extract_special_form(head: &str) -> bool {
12003 matches!(
12004 head,
12005 "range"
12006 | "valence"
12007 | "mode"
12008 | "relation"
12009 | "world"
12010 | "total"
12011 | "coverage"
12012 | "terminating"
12013 | "coinductive"
12014 | "template"
12015 | "import"
12016 | "namespace"
12017 )
12018}
12019
12020fn extract_parse_forms(text: &str) -> Result<Vec<Node>, String> {
12021 let mut forms = Vec::new();
12022 for link in parse_lino(text) {
12023 let trimmed = link.trim();
12024 if trimmed.starts_with("(#") && trimmed.chars().nth(2).map_or(false, |c| c.is_whitespace())
12025 {
12026 continue;
12027 }
12028 let toks = tokenize_one(&link);
12029 let node = parse_one(&toks).map_err(extract_compile_error)?;
12030 forms.push(desugar_hoas(node));
12031 }
12032 Ok(forms)
12033}
12034
12035fn extract_lambda_declaration(form: &Node) -> Result<ExtractLambda, String> {
12036 let children = match form {
12037 Node::List(children) => children,
12038 _ => return Err(extract_compile_error("Malformed lambda definition")),
12039 };
12040 let name = match &children[0] {
12041 Node::Leaf(head) if head.ends_with(':') => head[..head.len() - 1].to_string(),
12042 _ => return Err(extract_compile_error("Malformed lambda definition head")),
12043 };
12044 if children.len() != 4 {
12045 return Err(extract_compile_error(format!(
12046 "Cannot extract \"{}\": lambda definitions must have one body",
12047 name
12048 )));
12049 }
12050 let bindings = parse_bindings(&children[2]).ok_or_else(|| {
12051 extract_compile_error(format!(
12052 "Cannot extract \"{}\": malformed lambda binding",
12053 name
12054 ))
12055 })?;
12056 Ok(ExtractLambda {
12057 name,
12058 params: bindings.into_iter().map(|(param, _)| param).collect(),
12059 body: children[3].clone(),
12060 })
12061}
12062
12063fn extract_parse_query(form: &Node) -> Result<ExtractTest, String> {
12064 let children = match form {
12065 Node::List(children) => children,
12066 _ => {
12067 return Err(extract_compile_error(format!(
12068 "Cannot extract query \"{}\"",
12069 key_of(form)
12070 )))
12071 }
12072 };
12073 let parts = strip_with_proof(&children[1..]);
12074 let target = if parts.len() == 1 {
12075 parts[0].clone()
12076 } else {
12077 Node::List(parts.to_vec())
12078 };
12079 if let Node::List(target_children) = target {
12080 if target_children.len() == 3 {
12081 if matches!(&target_children[1], Node::Leaf(op) if op == "=") {
12082 return Ok(ExtractTest {
12083 left: target_children[0].clone(),
12084 right: target_children[2].clone(),
12085 });
12086 }
12087 }
12088 }
12089 Err(extract_compile_error(format!(
12090 "Cannot extract query \"{}\"; expected (? (<left> = <right>))",
12091 key_of(form)
12092 )))
12093}
12094
12095fn extract_parse_program(text: &str) -> Result<ExtractProgram, String> {
12096 let forms = extract_parse_forms(text)?;
12097 let mut lambdas = Vec::new();
12098 let mut tests = Vec::new();
12099 for form in forms {
12100 if extract_is_probability_assignment(&form) {
12101 return Err(extract_compile_error(
12102 "Cannot extract probability assignments",
12103 ));
12104 }
12105 if extract_is_lambda_definition(&form) {
12106 let lambda = extract_lambda_declaration(&form)?;
12107 if extract_contains_logic(&lambda.body) {
12108 return Err(extract_compile_error(format!(
12109 "Cannot extract probabilistic or logical lambda \"{}\"",
12110 lambda.name
12111 )));
12112 }
12113 lambdas.push(lambda);
12114 continue;
12115 }
12116 if extract_is_query_form(&form) {
12117 tests.push(extract_parse_query(&form)?);
12118 continue;
12119 }
12120 if let Node::List(children) = &form {
12121 if let Some(Node::Leaf(raw_head)) = children.first() {
12122 let head = raw_head.strip_suffix(':').unwrap_or(raw_head.as_str());
12123 if extract_special_form(head)
12124 || matches!(head, "and" | "or" | "not" | "both" | "neither" | "=" | "!=")
12125 {
12126 return Err(extract_compile_error(format!(
12127 "Cannot extract unsupported form \"{}\"",
12128 key_of(&form)
12129 )));
12130 }
12131 }
12132 }
12133 if !extract_is_type_only_form(&form) {
12134 return Err(extract_compile_error(format!(
12135 "Cannot extract unsupported form \"{}\"",
12136 key_of(&form)
12137 )));
12138 }
12139 }
12140 if lambdas.is_empty() {
12141 return Err(extract_compile_error(
12142 "Cannot extract program: no lambda definitions found",
12143 ));
12144 }
12145 Ok(ExtractProgram { lambdas, tests })
12146}
12147
12148fn extract_identifier(name: &str, target: ExtractTarget, used: &mut HashSet<String>) -> String {
12149 let mut out: String = name
12150 .chars()
12151 .map(|c| {
12152 if c.is_ascii_alphanumeric() || c == '_' {
12153 c
12154 } else {
12155 '_'
12156 }
12157 })
12158 .collect();
12159 if out.is_empty() || out.chars().next().map_or(false, |c| c.is_ascii_digit()) {
12160 out.insert(0, '_');
12161 }
12162 let reserved = match target {
12163 ExtractTarget::JavaScript => matches!(
12164 out.as_str(),
12165 "await"
12166 | "break"
12167 | "case"
12168 | "catch"
12169 | "class"
12170 | "const"
12171 | "continue"
12172 | "debugger"
12173 | "default"
12174 | "delete"
12175 | "do"
12176 | "else"
12177 | "export"
12178 | "extends"
12179 | "finally"
12180 | "for"
12181 | "function"
12182 | "if"
12183 | "import"
12184 | "in"
12185 | "instanceof"
12186 | "let"
12187 | "new"
12188 | "return"
12189 | "super"
12190 | "switch"
12191 | "this"
12192 | "throw"
12193 | "try"
12194 | "typeof"
12195 | "var"
12196 | "void"
12197 | "while"
12198 | "with"
12199 | "yield"
12200 ),
12201 ExtractTarget::Rust => matches!(
12202 out.as_str(),
12203 "as" | "break"
12204 | "const"
12205 | "continue"
12206 | "crate"
12207 | "else"
12208 | "enum"
12209 | "extern"
12210 | "false"
12211 | "fn"
12212 | "for"
12213 | "if"
12214 | "impl"
12215 | "in"
12216 | "let"
12217 | "loop"
12218 | "match"
12219 | "mod"
12220 | "move"
12221 | "mut"
12222 | "pub"
12223 | "ref"
12224 | "return"
12225 | "self"
12226 | "Self"
12227 | "static"
12228 | "struct"
12229 | "super"
12230 | "trait"
12231 | "true"
12232 | "type"
12233 | "unsafe"
12234 | "use"
12235 | "where"
12236 | "while"
12237 | "async"
12238 | "await"
12239 | "dyn"
12240 ),
12241 };
12242 if reserved {
12243 out.push('_');
12244 }
12245 let base = out.clone();
12246 let mut i = 2usize;
12247 while used.contains(&out) {
12248 out = format!("{}_{}", base, i);
12249 i += 1;
12250 }
12251 used.insert(out.clone());
12252 out
12253}
12254
12255fn extract_name_map(names: &[String], target: ExtractTarget) -> HashMap<String, String> {
12256 let mut used = HashSet::new();
12257 let mut out = HashMap::new();
12258 for name in names {
12259 out.insert(name.clone(), extract_identifier(name, target, &mut used));
12260 }
12261 out
12262}
12263
12264fn extract_number_literal(token: &str, target: ExtractTarget) -> String {
12265 if target == ExtractTarget::Rust && token.parse::<i64>().is_ok() {
12266 format!("{}.0", token)
12267 } else {
12268 token.to_string()
12269 }
12270}
12271
12272fn extract_collect_apply_spine<'a>(node: &'a Node) -> (&'a Node, Vec<&'a Node>) {
12273 let mut args = Vec::new();
12274 let mut head = node;
12275 loop {
12276 match head {
12277 Node::List(children)
12278 if children.len() == 3
12279 && matches!(&children[0], Node::Leaf(apply) if apply == "apply") =>
12280 {
12281 args.push(&children[2]);
12282 head = &children[1];
12283 }
12284 _ => break,
12285 }
12286 }
12287 args.reverse();
12288 (head, args)
12289}
12290
12291fn extract_compile_expr(node: &Node, ctx: &ExtractContext<'_>) -> Result<String, String> {
12292 match node {
12293 Node::Leaf(s) => {
12294 if is_num(s) {
12295 return Ok(extract_number_literal(s, ctx.target));
12296 }
12297 if let Some(local) = ctx.locals.get(s) {
12298 return Ok(local.clone());
12299 }
12300 if let Some(name) = ctx.name_map.get(s) {
12301 return Ok(name.clone());
12302 }
12303 Err(extract_compile_error(format!(
12304 "Cannot extract unresolved symbol \"{}\"",
12305 s
12306 )))
12307 }
12308 Node::List(children) => {
12309 if children.is_empty() {
12310 return Err(extract_compile_error(
12311 "Cannot extract malformed expression \"()\"",
12312 ));
12313 }
12314 if extract_contains_logic(node) {
12315 return Err(extract_compile_error(format!(
12316 "Cannot extract probabilistic or logical expression \"{}\"",
12317 key_of(node)
12318 )));
12319 }
12320 if children.len() == 3 {
12321 if let Node::Leaf(op) = &children[1] {
12322 if matches!(op.as_str(), "+" | "-" | "*" | "/") {
12323 return Ok(format!(
12324 "({} {} {})",
12325 extract_compile_expr(&children[0], ctx)?,
12326 op,
12327 extract_compile_expr(&children[2], ctx)?
12328 ));
12329 }
12330 }
12331 }
12332 if children.len() == 3 && matches!(&children[0], Node::Leaf(head) if head == "apply") {
12333 let (head, args) = extract_collect_apply_spine(node);
12334 let fn_name = match head {
12335 Node::Leaf(_) => extract_compile_expr(head, ctx)?,
12336 _ => {
12337 return Err(extract_compile_error(format!(
12338 "Cannot extract higher-order application \"{}\"",
12339 key_of(node)
12340 )))
12341 }
12342 };
12343 let compiled_args: Result<Vec<String>, String> = args
12344 .iter()
12345 .map(|arg| extract_compile_expr(arg, ctx))
12346 .collect();
12347 return Ok(format!("{}({})", fn_name, compiled_args?.join(", ")));
12348 }
12349 if let Some(Node::Leaf(head)) = children.first() {
12350 if let Some(fn_name) = ctx.name_map.get(head) {
12351 let compiled_args: Result<Vec<String>, String> = children[1..]
12352 .iter()
12353 .map(|arg| extract_compile_expr(arg, ctx))
12354 .collect();
12355 return Ok(format!("{}({})", fn_name, compiled_args?.join(", ")));
12356 }
12357 }
12358 Err(extract_compile_error(format!(
12359 "Cannot extract expression \"{}\"",
12360 key_of(node)
12361 )))
12362 }
12363 }
12364}
12365
12366fn compile_javascript_program(program: &ExtractProgram) -> Result<String, String> {
12367 let names: Vec<String> = program.lambdas.iter().map(|l| l.name.clone()).collect();
12368 let name_map = extract_name_map(&names, ExtractTarget::JavaScript);
12369 let mut lines = vec![
12370 "// Generated by rml extract js. Do not edit by hand.".to_string(),
12371 "import { pathToFileURL } from 'node:url';".to_string(),
12372 String::new(),
12373 ];
12374 for lambda in &program.lambdas {
12375 let mut used: HashSet<String> = name_map.values().cloned().collect();
12376 let mut locals = HashMap::new();
12377 for param in &lambda.params {
12378 locals.insert(
12379 param.clone(),
12380 extract_identifier(param, ExtractTarget::JavaScript, &mut used),
12381 );
12382 }
12383 let ctx = ExtractContext {
12384 target: ExtractTarget::JavaScript,
12385 name_map: &name_map,
12386 locals: &locals,
12387 };
12388 let params = lambda
12389 .params
12390 .iter()
12391 .map(|param| locals.get(param).cloned().unwrap_or_else(|| param.clone()))
12392 .collect::<Vec<_>>()
12393 .join(", ");
12394 lines.push(format!(
12395 "export function {}({}) {{",
12396 name_map.get(&lambda.name).unwrap(),
12397 params
12398 ));
12399 lines.push(format!(
12400 " return {};",
12401 extract_compile_expr(&lambda.body, &ctx)?
12402 ));
12403 lines.push("}".to_string());
12404 lines.push(String::new());
12405 }
12406 lines.push("function __rmlApproxEq(left, right) {".to_string());
12407 lines.push(" return Object.is(left, right) || Math.abs(left - right) <= 1e-9;".to_string());
12408 lines.push("}".to_string());
12409 lines.push(String::new());
12410 lines.push("export function __runRmlExtractedTests() {".to_string());
12411 if program.tests.is_empty() {
12412 lines.push(" return true;".to_string());
12413 } else {
12414 for (idx, test) in program.tests.iter().enumerate() {
12415 let locals = HashMap::new();
12416 let ctx = ExtractContext {
12417 target: ExtractTarget::JavaScript,
12418 name_map: &name_map,
12419 locals: &locals,
12420 };
12421 lines.push(format!(
12422 " if (!__rmlApproxEq({}, {})) {{",
12423 extract_compile_expr(&test.left, &ctx)?,
12424 extract_compile_expr(&test.right, &ctx)?
12425 ));
12426 lines.push(format!(
12427 " throw new Error('RML extracted test {} failed');",
12428 idx + 1
12429 ));
12430 lines.push(" }".to_string());
12431 }
12432 lines.push(" return true;".to_string());
12433 }
12434 lines.push("}".to_string());
12435 lines.push(String::new());
12436 lines.push(
12437 "if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {"
12438 .to_string(),
12439 );
12440 lines.push(" __runRmlExtractedTests();".to_string());
12441 lines.push("}".to_string());
12442 lines.push(String::new());
12443 Ok(lines.join("\n"))
12444}
12445
12446fn compile_rust_program(program: &ExtractProgram) -> Result<String, String> {
12447 let names: Vec<String> = program.lambdas.iter().map(|l| l.name.clone()).collect();
12448 let name_map = extract_name_map(&names, ExtractTarget::Rust);
12449 let mut lines = vec![
12450 "// Generated by rml extract rust. Do not edit by hand.".to_string(),
12451 String::new(),
12452 ];
12453 for lambda in &program.lambdas {
12454 let mut used: HashSet<String> = name_map.values().cloned().collect();
12455 let mut locals = HashMap::new();
12456 for param in &lambda.params {
12457 locals.insert(
12458 param.clone(),
12459 extract_identifier(param, ExtractTarget::Rust, &mut used),
12460 );
12461 }
12462 let ctx = ExtractContext {
12463 target: ExtractTarget::Rust,
12464 name_map: &name_map,
12465 locals: &locals,
12466 };
12467 let params = lambda
12468 .params
12469 .iter()
12470 .map(|param| format!("{}: f64", locals.get(param).unwrap()))
12471 .collect::<Vec<_>>()
12472 .join(", ");
12473 lines.push(format!(
12474 "pub fn {}({}) -> f64 {{",
12475 name_map.get(&lambda.name).unwrap(),
12476 params
12477 ));
12478 lines.push(format!(" {}", extract_compile_expr(&lambda.body, &ctx)?));
12479 lines.push("}".to_string());
12480 lines.push(String::new());
12481 }
12482 if !program.tests.is_empty() {
12483 lines.push("#[cfg(test)]".to_string());
12484 lines.push("mod tests {".to_string());
12485 lines.push(" use super::*;".to_string());
12486 lines.push(String::new());
12487 lines.push(" fn rml_approx_eq(left: f64, right: f64) -> bool {".to_string());
12488 lines.push(" (left - right).abs() <= 1e-9".to_string());
12489 lines.push(" }".to_string());
12490 lines.push(String::new());
12491 for (idx, test) in program.tests.iter().enumerate() {
12492 let locals = HashMap::new();
12493 let ctx = ExtractContext {
12494 target: ExtractTarget::Rust,
12495 name_map: &name_map,
12496 locals: &locals,
12497 };
12498 lines.push(" #[test]".to_string());
12499 lines.push(format!(" fn rml_query_{}() {{", idx + 1));
12500 lines.push(format!(
12501 " assert!(rml_approx_eq({}, {}), \"RML query {} failed\");",
12502 extract_compile_expr(&test.left, &ctx)?,
12503 extract_compile_expr(&test.right, &ctx)?,
12504 idx + 1
12505 ));
12506 lines.push(" }".to_string());
12507 lines.push(String::new());
12508 }
12509 lines.push("}".to_string());
12510 lines.push(String::new());
12511 }
12512 Ok(lines.join("\n"))
12513}
12514
12515pub fn extract_program(text: &str, target: ExtractTarget) -> Result<String, String> {
12523 let parsed = extract_parse_program(text)?;
12524 match target {
12525 ExtractTarget::JavaScript => compile_javascript_program(&parsed),
12526 ExtractTarget::Rust => compile_rust_program(&parsed),
12527 }
12528}
12529
12530#[derive(Debug, Clone, PartialEq)]
12535pub enum RunResult {
12536 Num(f64),
12537 Type(String),
12538 Foundation(FoundationReport),
12539 Proof(ProofReport),
12540}
12541
12542pub fn evaluate(text: &str, file: Option<&str>, options: Option<EnvOptions>) -> EvaluateResult {
12550 evaluate_with_options(
12551 text,
12552 file,
12553 EvaluateOptions {
12554 env: options,
12555 ..EvaluateOptions::default()
12556 },
12557 )
12558}
12559
12560pub fn evaluate_with_options(
12566 text: &str,
12567 file: Option<&str>,
12568 options: EvaluateOptions,
12569) -> EvaluateResult {
12570 let mut env = Env::new(options.env.clone());
12571 env.trace_enabled = options.trace;
12572 env.default_span = Span::new(file.map(|s| s.to_string()), 1, 1, 0);
12573 let mut ctx = ImportContext::default();
12574 evaluate_inner(text, file, &mut env, &options, &mut ctx)
12575}
12576
12577pub fn evaluate_with_env(text: &str, file: Option<&str>, env: &mut Env) -> EvaluateResult {
12580 let options = EvaluateOptions::default();
12581 let mut ctx = ImportContext::default();
12582 evaluate_inner(text, file, env, &options, &mut ctx)
12583}
12584
12585pub fn evaluate_file(file_path: &str, options: EvaluateOptions) -> EvaluateResult {
12590 let resolved: PathBuf = match fs::canonicalize(file_path) {
12591 Ok(p) => p,
12592 Err(_) => Path::new(file_path).to_path_buf(),
12593 };
12594 let text = match fs::read_to_string(&resolved) {
12595 Ok(t) => t,
12596 Err(err) => {
12597 let diag = Diagnostic::new(
12598 "E007",
12599 format!("Failed to read \"{}\": {}", file_path, err),
12600 Span::new(Some(file_path.to_string()), 1, 1, 0),
12601 );
12602 return EvaluateResult {
12603 results: Vec::new(),
12604 diagnostics: vec![diag],
12605 trace: Vec::new(),
12606 proofs: Vec::new(),
12607 provenance: Vec::new(),
12608 };
12609 }
12610 };
12611 let mut env = Env::new(options.env.clone());
12612 env.trace_enabled = options.trace;
12613 let resolved_str = resolved.to_string_lossy().into_owned();
12614 env.default_span = Span::new(Some(resolved_str.clone()), 1, 1, 0);
12615 let mut ctx = ImportContext::default();
12616 ctx.stack.push(resolved.clone());
12617 ctx.loaded.insert(resolved.clone());
12618 evaluate_inner(&text, Some(&resolved_str), &mut env, &options, &mut ctx)
12619}
12620
12621#[derive(Default)]
12626struct ImportContext {
12627 stack: Vec<PathBuf>,
12628 loaded: HashSet<PathBuf>,
12629}
12630
12631fn unquote_path(s: &str) -> &str {
12635 let bytes = s.as_bytes();
12636 if bytes.len() >= 2
12637 && (bytes[0] == b'"' || bytes[0] == b'\'')
12638 && bytes[bytes.len() - 1] == bytes[0]
12639 {
12640 &s[1..s.len() - 1]
12641 } else {
12642 s
12643 }
12644}
12645
12646fn resolve_import_path(target: &str, importing_file: Option<&str>) -> PathBuf {
12650 let cleaned = unquote_path(target);
12651 let candidate = Path::new(cleaned);
12652 if candidate.is_absolute() {
12653 return candidate.to_path_buf();
12654 }
12655 let base_dir: PathBuf = if let Some(file) = importing_file {
12656 Path::new(file)
12657 .parent()
12658 .map(|p| p.to_path_buf())
12659 .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
12660 } else {
12661 std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
12662 };
12663 base_dir.join(candidate)
12664}
12665
12666fn canonicalize_import(p: &Path) -> PathBuf {
12670 fs::canonicalize(p).unwrap_or_else(|_| p.to_path_buf())
12671}
12672
12673fn handle_import(
12682 target_node: &Node,
12683 alias: Option<&str>,
12684 span: &Span,
12685 importing_file: Option<&str>,
12686 env: &mut Env,
12687 options: &EvaluateOptions,
12688 ctx: &mut ImportContext,
12689 diagnostics: &mut Vec<Diagnostic>,
12690) -> Option<Diagnostic> {
12691 let raw = match target_node {
12692 Node::Leaf(s) => s.clone(),
12693 _ => {
12694 return Some(Diagnostic::new(
12695 "E007",
12696 "Import target must be a string path",
12697 span.clone(),
12698 ));
12699 }
12700 };
12701 let cleaned = unquote_path(&raw);
12702 if cleaned.is_empty() {
12703 return Some(Diagnostic::new(
12704 "E007",
12705 "Import target must be a non-empty string path",
12706 span.clone(),
12707 ));
12708 }
12709
12710 if let Some(a) = alias {
12712 if env.aliases.contains_key(a) || env.namespace.as_deref() == Some(a) {
12713 return Some(Diagnostic::new(
12714 "E009",
12715 format!(
12716 "Import alias \"{}\" collides with an existing namespace or alias",
12717 a
12718 ),
12719 span.clone(),
12720 ));
12721 }
12722 }
12723
12724 let unresolved = resolve_import_path(&raw, importing_file);
12725 let resolved = canonicalize_import(&unresolved);
12726
12727 if ctx.stack.iter().any(|p| p == &resolved) {
12728 let mut chain: Vec<String> = ctx
12729 .stack
12730 .iter()
12731 .map(|p| p.to_string_lossy().into_owned())
12732 .collect();
12733 chain.push(resolved.to_string_lossy().into_owned());
12734 return Some(Diagnostic::new(
12735 "E007",
12736 format!("Import cycle detected: {}", chain.join(" -> ")),
12737 span.clone(),
12738 ));
12739 }
12740
12741 if ctx.loaded.contains(&resolved) {
12742 if let Some(a) = alias {
12745 let recorded_ns = env
12746 .file_namespaces
12747 .get(&resolved)
12748 .cloned()
12749 .unwrap_or_else(|| a.to_string());
12750 env.aliases.insert(a.to_string(), recorded_ns);
12751 }
12752 if options.trace {
12753 env.trace_events.push(TraceEvent::new(
12754 "import",
12755 format!("{} (cached)", resolved.to_string_lossy()),
12756 span.clone(),
12757 ));
12758 }
12759 return None;
12760 }
12761
12762 let text = match fs::read_to_string(&resolved) {
12763 Ok(t) => t,
12764 Err(err) => {
12765 return Some(Diagnostic::new(
12766 "E007",
12767 format!("Failed to read import \"{}\": {}", cleaned, err),
12768 span.clone(),
12769 ));
12770 }
12771 };
12772
12773 ctx.loaded.insert(resolved.clone());
12774 ctx.stack.push(resolved.clone());
12775 if options.trace {
12776 env.trace_events.push(TraceEvent::new(
12777 "import",
12778 resolved.to_string_lossy().into_owned(),
12779 span.clone(),
12780 ));
12781 }
12782
12783 let before_ops: HashSet<String> = env.ops.keys().cloned().collect();
12787 let before_syms: HashSet<String> = env.symbol_prob.keys().cloned().collect();
12788 let before_terms: HashSet<String> = env.terms.iter().cloned().collect();
12789 let before_lambdas: HashSet<String> = env.lambdas.keys().cloned().collect();
12790 let before_templates: HashSet<String> = env.templates.keys().cloned().collect();
12791 let before_namespace = env.namespace.clone();
12792
12793 let resolved_str = resolved.to_string_lossy().into_owned();
12794 let inner = evaluate_inner(&text, Some(&resolved_str), env, options, ctx);
12795 ctx.stack.pop();
12796
12797 let imported_namespace = env.namespace.clone();
12801 env.namespace = before_namespace;
12802 if let Some(ns) = &imported_namespace {
12803 env.file_namespaces.insert(resolved.clone(), ns.clone());
12804 }
12805
12806 for k in env.ops.keys() {
12809 if !before_ops.contains(k) {
12810 env.imported.insert(k.clone());
12811 }
12812 }
12813 for k in env.symbol_prob.keys() {
12814 if !before_syms.contains(k) {
12815 env.imported.insert(k.clone());
12816 }
12817 }
12818 for k in env.terms.iter() {
12819 if !before_terms.contains(k) {
12820 env.imported.insert(k.clone());
12821 }
12822 }
12823 for k in env.lambdas.keys() {
12824 if !before_lambdas.contains(k) {
12825 env.imported.insert(k.clone());
12826 }
12827 }
12828 for k in env.templates.keys() {
12829 if !before_templates.contains(k) {
12830 env.imported.insert(k.clone());
12831 }
12832 }
12833
12834 if let Some(a) = alias {
12838 let target_ns = imported_namespace.unwrap_or_else(|| a.to_string());
12839 env.aliases.insert(a.to_string(), target_ns);
12840 }
12841
12842 for diag in inner.diagnostics {
12843 diagnostics.push(diag);
12844 }
12845 if options.trace {
12848 env.trace_events.extend(inner.trace);
12849 }
12850 None
12851}
12852
12853fn eval_foundation_body_form(
12858 form: Node,
12859 span: &Span,
12860 env: &mut Env,
12861 diagnostics: &mut Vec<Diagnostic>,
12862 results: &mut Vec<RunResult>,
12863 proofs: &mut Option<Vec<Option<Node>>>,
12864 provenance: &mut Option<Vec<Option<String>>>,
12865 options: &EvaluateOptions,
12866) {
12867 let mut form = form;
12868 loop {
12869 match form {
12870 Node::List(ref children) if children.len() == 1 => {
12871 if let Node::List(_) = &children[0] {
12872 form = children[0].clone();
12873 } else {
12874 break;
12875 }
12876 }
12877 _ => break,
12878 }
12879 }
12880
12881 if let Node::List(children) = &form {
12882 if let Some(Node::Leaf(head)) = children.first() {
12883 if head == "with-foundation" {
12884 if children.len() < 2 {
12885 diagnostics.push(Diagnostic::new(
12886 "E062",
12887 "with-foundation form must be `(with-foundation <name> <body>...)`",
12888 span.clone(),
12889 ));
12890 return;
12891 }
12892 let fname = match &children[1] {
12893 Node::Leaf(s) if !s.is_empty() => s.clone(),
12894 _ => {
12895 diagnostics.push(Diagnostic::new(
12896 "E062",
12897 "with-foundation requires a foundation name",
12898 span.clone(),
12899 ));
12900 return;
12901 }
12902 };
12903 if let Err(message) = env.enter_foundation(&fname) {
12904 diagnostics.push(Diagnostic::new("E062", message, span.clone()));
12905 return;
12906 }
12907 if options.trace {
12908 env.trace_events.push(TraceEvent::new(
12909 "with-foundation/enter",
12910 fname.clone(),
12911 span.clone(),
12912 ));
12913 }
12914 let bodies: Vec<Node> = children[2..].to_vec();
12915 for body in bodies {
12916 eval_foundation_body_form(
12917 body,
12918 span,
12919 env,
12920 diagnostics,
12921 results,
12922 proofs,
12923 provenance,
12924 options,
12925 );
12926 }
12927 env.exit_foundation();
12928 if options.trace {
12929 env.trace_events.push(TraceEvent::new(
12930 "with-foundation/exit",
12931 fname,
12932 span.clone(),
12933 ));
12934 }
12935 return;
12936 }
12937 if head == "foundation" {
12938 match parse_foundation_form(&form) {
12939 Ok(foundation) => {
12940 let name = foundation.name.clone();
12941 if let Err(message) = env.register_foundation(foundation) {
12942 diagnostics.push(Diagnostic::new("E061", message, span.clone()));
12943 } else if options.trace {
12944 env.trace_events.push(TraceEvent::new(
12945 "foundation",
12946 name,
12947 span.clone(),
12948 ));
12949 }
12950 }
12951 Err(message) => {
12952 diagnostics.push(Diagnostic::new("E061", message, span.clone()));
12953 }
12954 }
12955 return;
12956 }
12957 if head == "root-construct" {
12958 match parse_root_construct_form(&form) {
12959 Ok(descriptor) => {
12960 let name = descriptor.name.clone();
12961 if let Err(message) = env.register_root_construct(descriptor) {
12962 diagnostics.push(Diagnostic::new("E060", message, span.clone()));
12963 } else if options.trace {
12964 env.trace_events.push(TraceEvent::new(
12965 "root-construct",
12966 name,
12967 span.clone(),
12968 ));
12969 }
12970 }
12971 Err(message) => {
12972 diagnostics.push(Diagnostic::new("E060", message, span.clone()));
12973 }
12974 }
12975 return;
12976 }
12977 if head == "foundation-report" || head == "foundation-report?" {
12978 let report = env.foundation_report();
12979 if options.trace {
12980 env.trace_events.push(TraceEvent::new(
12981 "foundation-report",
12982 report.active_foundation.clone(),
12983 span.clone(),
12984 ));
12985 }
12986 results.push(RunResult::Foundation(report));
12987 if let Some(p) = proofs.as_mut() {
12988 p.push(None);
12989 }
12990 if let Some(pv) = provenance.as_mut() {
12991 pv.push(None);
12992 }
12993 return;
12994 }
12995 if head == "rule" && is_proof_rule_shape(children) {
12996 match parse_rule_form(&form) {
12997 Ok(rule) => {
12998 let name = rule.name.clone();
12999 env.register_proof_rule(rule);
13000 if options.trace {
13001 env.trace_events
13002 .push(TraceEvent::new("rule", name, span.clone()));
13003 }
13004 }
13005 Err(message) => {
13006 diagnostics.push(Diagnostic::new("E064", message, span.clone()));
13007 }
13008 }
13009 return;
13010 }
13011 if head == "assumption" || head == "axiom" {
13012 match parse_proof_assumption_form(&form) {
13013 Ok(assumption) => {
13014 let kind = assumption.kind.clone();
13015 let name = assumption.name.clone();
13016 env.register_proof_assumption(assumption);
13017 if options.trace {
13018 env.trace_events
13019 .push(TraceEvent::new(&kind, name, span.clone()));
13020 }
13021 }
13022 Err(message) => {
13023 diagnostics.push(Diagnostic::new("E064", message, span.clone()));
13024 }
13025 }
13026 return;
13027 }
13028 if head == "proof-object" {
13029 match parse_proof_object_form(&form) {
13030 Ok(po) => {
13031 let name = po.name.clone();
13032 env.register_proof_object(po);
13033 if options.trace {
13034 env.trace_events.push(TraceEvent::new(
13035 "proof-object",
13036 name,
13037 span.clone(),
13038 ));
13039 }
13040 }
13041 Err(message) => {
13042 diagnostics.push(Diagnostic::new("E064", message, span.clone()));
13043 }
13044 }
13045 return;
13046 }
13047 if head == "check-proof" {
13048 if children.len() != 2 {
13049 diagnostics.push(Diagnostic::new(
13050 "E064",
13051 "(check-proof <name>) requires a proof-object name",
13052 span.clone(),
13053 ));
13054 return;
13055 }
13056 let target = match &children[1] {
13057 Node::Leaf(s) if !s.is_empty() => s.clone(),
13058 _ => {
13059 diagnostics.push(Diagnostic::new(
13060 "E064",
13061 "(check-proof <name>) requires a proof-object name",
13062 span.clone(),
13063 ));
13064 return;
13065 }
13066 };
13067 let verdict = check_proof_object(env, &target);
13068 let (value, error) = match verdict {
13069 CheckProofVerdict::Ok(_) => (1.0_f64, None),
13070 CheckProofVerdict::Err(msg) => (0.0_f64, Some(msg)),
13071 };
13072 results.push(RunResult::Num(value));
13073 if let Some(p) = proofs.as_mut() {
13074 p.push(None);
13075 }
13076 if let Some(pv) = provenance.as_mut() {
13077 pv.push(None);
13078 }
13079 if let Some(msg) = error {
13080 diagnostics.push(Diagnostic::new("E064", msg, span.clone()));
13081 }
13082 if options.trace {
13083 env.trace_events.push(TraceEvent::new(
13084 "check-proof",
13085 format!("{} → {}", target, if value == 1.0 { "ok" } else { "fail" }),
13086 span.clone(),
13087 ));
13088 }
13089 return;
13090 }
13091 if head == "proof-report" {
13092 if children.len() != 2 {
13093 diagnostics.push(Diagnostic::new(
13094 "E064",
13095 "(proof-report <name>) requires a proof-object name",
13096 span.clone(),
13097 ));
13098 return;
13099 }
13100 let target = match &children[1] {
13101 Node::Leaf(s) if !s.is_empty() => s.clone(),
13102 _ => {
13103 diagnostics.push(Diagnostic::new(
13104 "E064",
13105 "(proof-report <name>) requires a proof-object name",
13106 span.clone(),
13107 ));
13108 return;
13109 }
13110 };
13111 let report = env.proof_report(&target);
13112 results.push(RunResult::Proof(report));
13113 if let Some(p) = proofs.as_mut() {
13114 p.push(None);
13115 }
13116 if let Some(pv) = provenance.as_mut() {
13117 pv.push(None);
13118 }
13119 if options.trace {
13120 env.trace_events.push(TraceEvent::new(
13121 "proof-report",
13122 target,
13123 span.clone(),
13124 ));
13125 }
13126 return;
13127 }
13128 if head == "eval-nat" {
13129 if children.len() != 2 {
13130 diagnostics.push(Diagnostic::new(
13131 "E067",
13132 "(eval-nat <term>) requires exactly one term argument",
13133 span.clone(),
13134 ));
13135 return;
13136 }
13137 match eval_nat_term(env, &children[1]) {
13138 Ok(result) => {
13139 results.push(RunResult::Num(result.value));
13140 if let Some(p) = proofs.as_mut() {
13141 p.push(None);
13142 }
13143 if let Some(pv) = provenance.as_mut() {
13144 pv.push(None);
13145 }
13146 if options.trace {
13147 env.trace_events.push(TraceEvent::new(
13148 "eval-nat",
13149 format!(
13150 "{} -> normal-form {} -> {}; rules-used: {}; host-primitives-used: structural-matcher; renderer: nat-normal-form-to-host-number",
13151 key_of(&children[1]),
13152 key_of(&result.normal_form),
13153 format_trace_value(result.value),
13154 if result.steps.is_empty() {
13155 "<none>".to_string()
13156 } else {
13157 result.steps.join(", ")
13158 }
13159 ),
13160 span.clone(),
13161 ));
13162 }
13163 }
13164 Err(message) => {
13165 diagnostics.push(Diagnostic::new("E067", message, span.clone()));
13166 }
13167 }
13168 return;
13169 }
13170 if head == "strict-foundation" {
13171 match parse_strict_foundation_form(&form) {
13172 Ok(decl) => {
13173 env.strict_pure_links = true;
13174 if options.trace {
13175 env.trace_events.push(TraceEvent::new(
13176 "strict-foundation",
13177 decl.profile,
13178 span.clone(),
13179 ));
13180 }
13181 }
13182 Err(message) => {
13183 diagnostics.push(Diagnostic::new("E065", message, span.clone()));
13184 }
13185 }
13186 return;
13187 }
13188 if head == "allow-host-primitive" {
13189 match parse_allow_host_primitive_form(&form) {
13190 Ok(decl) => {
13191 for name in &decl.names {
13192 env.allowed_host_primitives.insert(name.clone());
13193 }
13194 if options.trace {
13195 env.trace_events.push(TraceEvent::new(
13196 "allow-host-primitive",
13197 decl.names.join(" "),
13198 span.clone(),
13199 ));
13200 }
13201 }
13202 Err(message) => {
13203 diagnostics.push(Diagnostic::new("E065", message, span.clone()));
13204 }
13205 }
13206 return;
13207 }
13208 }
13209 }
13210 if let Node::Leaf(head) = &form {
13211 if head == "foundation-report" || head == "foundation-report?" {
13212 let report = env.foundation_report();
13213 if options.trace {
13214 env.trace_events.push(TraceEvent::new(
13215 "foundation-report",
13216 report.active_foundation.clone(),
13217 span.clone(),
13218 ));
13219 }
13220 results.push(RunResult::Foundation(report));
13221 if let Some(p) = proofs.as_mut() {
13222 p.push(None);
13223 }
13224 if let Some(pv) = provenance.as_mut() {
13225 pv.push(None);
13226 }
13227 return;
13228 }
13229 }
13230
13231 let inner_result = catch_unwind(AssertUnwindSafe(|| {
13232 let mut stack = Vec::new();
13233 let expanded = expand_templates(&form, env, &mut stack);
13234 let eval_res = eval_node(&expanded, env);
13235 (expanded, eval_res)
13236 }));
13237 match inner_result {
13238 Ok((expanded, eval_res)) => {
13239 let was_query = matches!(eval_res, EvalResult::Query(_) | EvalResult::TypeQuery(_));
13240 let query_value = if let EvalResult::Query(v) = &eval_res {
13241 Some(*v)
13242 } else {
13243 None
13244 };
13245 match eval_res {
13246 EvalResult::Query(v) => results.push(RunResult::Num(v)),
13247 EvalResult::TypeQuery(s) => results.push(RunResult::Type(s)),
13248 _ => {}
13249 }
13250 if was_query {
13251 if let Some(p) = proofs.as_mut() {
13252 p.push(None);
13253 }
13254 let prov = equality_provenance_for_query(&expanded, env);
13255 record_provenance(
13256 provenance,
13257 results.len(),
13258 prov,
13259 env,
13260 &expanded,
13261 span,
13262 options,
13263 );
13264 if let Some(v) = query_value {
13270 if let Some(msg) = env.check_carrier_value(v) {
13271 diagnostics.push(Diagnostic::new(
13272 "E063",
13273 format!(
13274 "Query result {} violates active foundation carrier: {}",
13275 format_trace_value(v),
13276 msg
13277 ),
13278 span.clone(),
13279 ));
13280 }
13281 }
13282 if env.strict_pure_links {
13284 if let Node::List(form_children) = &expanded {
13285 if matches!(form_children.first(), Some(Node::Leaf(s)) if s == "?") {
13286 let parts = &form_children[1..];
13287 let inner = strip_with_proof(parts);
13288 let target: Node = if inner.len() == 1 {
13289 inner[0].clone()
13290 } else {
13291 Node::List(inner.to_vec())
13292 };
13293 let offenders = scan_pure_links_offenders(&target, env);
13294 if !offenders.is_empty() {
13295 diagnostics.push(Diagnostic::new(
13296 "E065",
13297 format!(
13298 "Query depends on host-primitive construct(s) under pure-links strict mode: {}",
13299 offenders.join(", ")
13300 ),
13301 span.clone(),
13302 ));
13303 }
13304 }
13305 }
13306 }
13307 }
13308 }
13309 Err(payload) => {
13310 let (code, message) = decode_panic_payload(&payload);
13311 diagnostics.push(Diagnostic::new(&code, message, span.clone()));
13312 }
13313 }
13314}
13315
13316fn record_provenance(
13321 provenance: &mut Option<Vec<Option<String>>>,
13322 results_len: usize,
13323 rule: Option<String>,
13324 env: &mut Env,
13325 _form: &Node,
13326 span: &Span,
13327 options: &EvaluateOptions,
13328) {
13329 if let Some(rule_name) = rule {
13330 if provenance.is_none() {
13331 let backfill = results_len.saturating_sub(1);
13332 *provenance = Some(vec![None; backfill]);
13333 }
13334 provenance.as_mut().unwrap().push(Some(rule_name.clone()));
13335 if options.trace {
13336 env.trace_events.push(TraceEvent::new(
13337 "equality-layer",
13338 rule_name,
13339 span.clone(),
13340 ));
13341 }
13342 } else if let Some(pv) = provenance.as_mut() {
13343 pv.push(None);
13344 }
13345}
13346
13347fn evaluate_inner(
13348 text: &str,
13349 file: Option<&str>,
13350 env: &mut Env,
13351 options: &EvaluateOptions,
13352 ctx: &mut ImportContext,
13353) -> EvaluateResult {
13354 let mut diagnostics: Vec<Diagnostic> = Vec::new();
13355 let extracted_literate = if is_literate_lino_path(file) {
13356 Some(extract_literate_lino(text))
13357 } else {
13358 None
13359 };
13360 let source_text = extracted_literate.as_deref().unwrap_or(text);
13361 let spans = compute_form_spans(source_text, file);
13362
13363 let (links, parse_errors) = parse_lino_with_errors(source_text);
13364 for parse_err in parse_errors {
13365 diagnostics.push(Diagnostic::new(
13366 "E006",
13367 format!("LiNo parse failure: {}", parse_err),
13368 Span::new(file.map(|s| s.to_string()), 1, 1, 0),
13369 ));
13370 }
13371 let forms: Vec<Node> = links
13372 .iter()
13373 .filter(|link_str| {
13374 let s = link_str.trim();
13375 !(s.starts_with("(#") && s.chars().nth(2).map_or(false, |c| c.is_whitespace()))
13376 })
13377 .filter_map(|link_str| {
13378 let toks = tokenize_one(link_str);
13384 let toks = if toks.len() == 1 && toks[0] != "(" && toks[0] != ")" {
13385 vec!["(".to_string(), toks[0].clone(), ")".to_string()]
13386 } else {
13387 toks
13388 };
13389 match parse_one(&toks) {
13390 Ok(node) => Some(desugar_hoas(node)),
13391 Err(msg) => {
13392 diagnostics.push(Diagnostic::new(
13393 "E002",
13394 msg,
13395 Span::new(file.map(|s| s.to_string()), 1, 1, 0),
13396 ));
13397 None
13398 }
13399 }
13400 })
13401 .collect();
13402
13403 let mut results: Vec<RunResult> = Vec::new();
13404
13405 let proofs_enabled = options.with_proofs;
13412 let mut proofs: Option<Vec<Option<Node>>> = if proofs_enabled {
13413 Some(Vec::new())
13414 } else {
13415 None
13416 };
13417
13418 let mut provenance: Option<Vec<Option<String>>> = None;
13425
13426 let prev_hook = std::panic::take_hook();
13429 std::panic::set_hook(Box::new(|_| {}));
13430
13431 for (idx, form) in forms.into_iter().enumerate() {
13432 let mut form = form;
13433 loop {
13434 match form {
13435 Node::List(ref children) if children.len() == 1 => {
13436 if let Node::List(_) = &children[0] {
13437 form = children[0].clone();
13438 } else {
13439 break;
13440 }
13441 }
13442 _ => break,
13443 }
13444 }
13445 let span = spans
13446 .get(idx)
13447 .cloned()
13448 .unwrap_or_else(|| Span::new(file.map(|s| s.to_string()), 1, 1, 0));
13449 env.current_span = Some(span.clone());
13450
13451 if let Node::List(children) = &form {
13455 if children.len() == 2 {
13456 if let (Node::Leaf(h), Node::Leaf(n)) = (&children[0], &children[1]) {
13457 if h == "namespace" {
13458 if n.is_empty() || n.contains('.') {
13459 diagnostics.push(Diagnostic::new(
13460 "E009",
13461 format!("Invalid namespace name \"{}\"", n),
13462 span.clone(),
13463 ));
13464 } else {
13465 env.namespace = Some(n.clone());
13466 if options.trace {
13467 env.trace_events.push(TraceEvent::new(
13468 "namespace",
13469 n.clone(),
13470 span.clone(),
13471 ));
13472 }
13473 }
13474 continue;
13475 }
13476 }
13477 }
13478 }
13479
13480 if let Node::List(children) = &form {
13485 if let Some(Node::Leaf(head)) = children.first() {
13486 if head == "import" {
13487 if children.len() == 2 {
13488 let target = children[1].clone();
13489 if let Some(diag) = handle_import(
13490 &target,
13491 None,
13492 &span,
13493 file,
13494 env,
13495 options,
13496 ctx,
13497 &mut diagnostics,
13498 ) {
13499 diagnostics.push(diag);
13500 }
13501 continue;
13502 }
13503 if children.len() == 4 {
13504 if let (Node::Leaf(as_kw), Node::Leaf(alias_name)) =
13505 (&children[2], &children[3])
13506 {
13507 if as_kw == "as" {
13508 let target = children[1].clone();
13509 if let Some(diag) = handle_import(
13510 &target,
13511 Some(alias_name),
13512 &span,
13513 file,
13514 env,
13515 options,
13516 ctx,
13517 &mut diagnostics,
13518 ) {
13519 diagnostics.push(diag);
13520 }
13521 continue;
13522 }
13523 }
13524 }
13525 }
13526 }
13527 }
13528
13529 if let Node::List(children) = &form {
13533 if let Some(Node::Leaf(head)) = children.first() {
13534 if head == "template" {
13535 match register_template_form(&form, env) {
13536 Ok(name) => {
13537 if options.trace {
13538 env.trace_events.push(TraceEvent::new(
13539 "template",
13540 name,
13541 span.clone(),
13542 ));
13543 }
13544 }
13545 Err(message) => {
13546 diagnostics.push(Diagnostic::new("E040", message, span.clone()));
13547 }
13548 }
13549 continue;
13550 }
13551 }
13552 }
13553
13554 if let Node::List(children) = &form {
13560 if let Some(Node::Leaf(head)) = children.first() {
13561 if head == "root-construct" {
13562 match parse_root_construct_form(&form) {
13563 Ok(descriptor) => {
13564 let name = descriptor.name.clone();
13565 if let Err(message) = env.register_root_construct(descriptor) {
13566 diagnostics.push(Diagnostic::new("E060", message, span.clone()));
13567 } else if options.trace {
13568 env.trace_events.push(TraceEvent::new(
13569 "root-construct",
13570 name,
13571 span.clone(),
13572 ));
13573 }
13574 }
13575 Err(message) => {
13576 diagnostics.push(Diagnostic::new("E060", message, span.clone()));
13577 }
13578 }
13579 continue;
13580 }
13581 if head == "foundation" {
13582 match parse_foundation_form(&form) {
13583 Ok(foundation) => {
13584 let name = foundation.name.clone();
13585 if let Err(message) = env.register_foundation(foundation) {
13586 diagnostics.push(Diagnostic::new("E061", message, span.clone()));
13587 } else if options.trace {
13588 env.trace_events.push(TraceEvent::new(
13589 "foundation",
13590 name,
13591 span.clone(),
13592 ));
13593 }
13594 }
13595 Err(message) => {
13596 diagnostics.push(Diagnostic::new("E061", message, span.clone()));
13597 }
13598 }
13599 continue;
13600 }
13601 if head == "with-foundation" {
13602 if children.len() < 2 {
13603 diagnostics.push(Diagnostic::new(
13604 "E062",
13605 "with-foundation form must be `(with-foundation <name> <body>...)`",
13606 span.clone(),
13607 ));
13608 continue;
13609 }
13610 let fname = match &children[1] {
13611 Node::Leaf(s) if !s.is_empty() => s.clone(),
13612 _ => {
13613 diagnostics.push(Diagnostic::new(
13614 "E062",
13615 "with-foundation requires a foundation name",
13616 span.clone(),
13617 ));
13618 continue;
13619 }
13620 };
13621 if let Err(message) = env.enter_foundation(&fname) {
13622 diagnostics.push(Diagnostic::new("E062", message, span.clone()));
13623 continue;
13624 }
13625 if options.trace {
13626 env.trace_events.push(TraceEvent::new(
13627 "with-foundation/enter",
13628 fname.clone(),
13629 span.clone(),
13630 ));
13631 }
13632 let bodies: Vec<Node> = children[2..].to_vec();
13633 for body in bodies {
13634 eval_foundation_body_form(
13635 body,
13636 &span,
13637 env,
13638 &mut diagnostics,
13639 &mut results,
13640 &mut proofs,
13641 &mut provenance,
13642 options,
13643 );
13644 }
13645 env.exit_foundation();
13646 if options.trace {
13647 env.trace_events.push(TraceEvent::new(
13648 "with-foundation/exit",
13649 fname,
13650 span.clone(),
13651 ));
13652 }
13653 continue;
13654 }
13655 if head == "foundation-report" || head == "foundation-report?" {
13656 let report = env.foundation_report();
13657 if options.trace {
13658 env.trace_events.push(TraceEvent::new(
13659 "foundation-report",
13660 report.active_foundation.clone(),
13661 span.clone(),
13662 ));
13663 }
13664 results.push(RunResult::Foundation(report));
13665 if let Some(p) = proofs.as_mut() {
13666 p.push(None);
13667 }
13668 if let Some(pv) = provenance.as_mut() {
13669 pv.push(None);
13670 }
13671 continue;
13672 }
13673 if head == "rule" && is_proof_rule_shape(children) {
13681 match parse_rule_form(&form) {
13682 Ok(rule) => {
13683 let name = rule.name.clone();
13684 env.register_proof_rule(rule);
13685 if options.trace {
13686 env.trace_events
13687 .push(TraceEvent::new("rule", name, span.clone()));
13688 }
13689 }
13690 Err(message) => {
13691 diagnostics.push(Diagnostic::new("E064", message, span.clone()));
13692 }
13693 }
13694 continue;
13695 }
13696 if head == "assumption" || head == "axiom" {
13697 match parse_proof_assumption_form(&form) {
13698 Ok(assumption) => {
13699 let kind = assumption.kind.clone();
13700 let name = assumption.name.clone();
13701 env.register_proof_assumption(assumption);
13702 if options.trace {
13703 env.trace_events
13704 .push(TraceEvent::new(&kind, name, span.clone()));
13705 }
13706 }
13707 Err(message) => {
13708 diagnostics.push(Diagnostic::new("E064", message, span.clone()));
13709 }
13710 }
13711 continue;
13712 }
13713 if head == "proof-object" {
13714 match parse_proof_object_form(&form) {
13715 Ok(po) => {
13716 let name = po.name.clone();
13717 env.register_proof_object(po);
13718 if options.trace {
13719 env.trace_events.push(TraceEvent::new(
13720 "proof-object",
13721 name,
13722 span.clone(),
13723 ));
13724 }
13725 }
13726 Err(message) => {
13727 diagnostics.push(Diagnostic::new("E064", message, span.clone()));
13728 }
13729 }
13730 continue;
13731 }
13732 if head == "check-proof" {
13733 if children.len() != 2 {
13734 diagnostics.push(Diagnostic::new(
13735 "E064",
13736 "(check-proof <name>) requires a proof-object name",
13737 span.clone(),
13738 ));
13739 continue;
13740 }
13741 let target = match &children[1] {
13742 Node::Leaf(s) if !s.is_empty() => s.clone(),
13743 _ => {
13744 diagnostics.push(Diagnostic::new(
13745 "E064",
13746 "(check-proof <name>) requires a proof-object name",
13747 span.clone(),
13748 ));
13749 continue;
13750 }
13751 };
13752 let verdict = check_proof_object(env, &target);
13753 let (value, error) = match verdict {
13754 CheckProofVerdict::Ok(_) => (1.0_f64, None),
13755 CheckProofVerdict::Err(msg) => (0.0_f64, Some(msg)),
13756 };
13757 results.push(RunResult::Num(value));
13758 if let Some(p) = proofs.as_mut() {
13759 p.push(None);
13760 }
13761 if let Some(pv) = provenance.as_mut() {
13762 pv.push(None);
13763 }
13764 if let Some(msg) = error {
13765 diagnostics.push(Diagnostic::new("E064", msg, span.clone()));
13766 }
13767 if options.trace {
13768 env.trace_events.push(TraceEvent::new(
13769 "check-proof",
13770 format!("{} → {}", target, if value == 1.0 { "ok" } else { "fail" }),
13771 span.clone(),
13772 ));
13773 }
13774 continue;
13775 }
13776 if head == "proof-report" {
13777 if children.len() != 2 {
13778 diagnostics.push(Diagnostic::new(
13779 "E064",
13780 "(proof-report <name>) requires a proof-object name",
13781 span.clone(),
13782 ));
13783 continue;
13784 }
13785 let target = match &children[1] {
13786 Node::Leaf(s) if !s.is_empty() => s.clone(),
13787 _ => {
13788 diagnostics.push(Diagnostic::new(
13789 "E064",
13790 "(proof-report <name>) requires a proof-object name",
13791 span.clone(),
13792 ));
13793 continue;
13794 }
13795 };
13796 let report = env.proof_report(&target);
13797 results.push(RunResult::Proof(report));
13798 if let Some(p) = proofs.as_mut() {
13799 p.push(None);
13800 }
13801 if let Some(pv) = provenance.as_mut() {
13802 pv.push(None);
13803 }
13804 if options.trace {
13805 env.trace_events.push(TraceEvent::new(
13806 "proof-report",
13807 target,
13808 span.clone(),
13809 ));
13810 }
13811 continue;
13812 }
13813 if head == "eval-nat" {
13814 if children.len() != 2 {
13815 diagnostics.push(Diagnostic::new(
13816 "E067",
13817 "(eval-nat <term>) requires exactly one term argument",
13818 span.clone(),
13819 ));
13820 continue;
13821 }
13822 match eval_nat_term(env, &children[1]) {
13823 Ok(result) => {
13824 results.push(RunResult::Num(result.value));
13825 if let Some(p) = proofs.as_mut() {
13826 p.push(None);
13827 }
13828 if let Some(pv) = provenance.as_mut() {
13829 pv.push(None);
13830 }
13831 if options.trace {
13832 env.trace_events.push(TraceEvent::new(
13833 "eval-nat",
13834 format!(
13835 "{} -> normal-form {} -> {}; rules-used: {}; host-primitives-used: structural-matcher; renderer: nat-normal-form-to-host-number",
13836 key_of(&children[1]),
13837 key_of(&result.normal_form),
13838 format_trace_value(result.value),
13839 if result.steps.is_empty() {
13840 "<none>".to_string()
13841 } else {
13842 result.steps.join(", ")
13843 }
13844 ),
13845 span.clone(),
13846 ));
13847 }
13848 }
13849 Err(message) => {
13850 diagnostics.push(Diagnostic::new("E067", message, span.clone()));
13851 }
13852 }
13853 continue;
13854 }
13855 if head == "strict-foundation" {
13857 match parse_strict_foundation_form(&form) {
13858 Ok(decl) => {
13859 env.strict_pure_links = true;
13860 if options.trace {
13861 env.trace_events.push(TraceEvent::new(
13862 "strict-foundation",
13863 decl.profile,
13864 span.clone(),
13865 ));
13866 }
13867 }
13868 Err(message) => {
13869 diagnostics.push(Diagnostic::new("E065", message, span.clone()));
13870 }
13871 }
13872 continue;
13873 }
13874 if head == "allow-host-primitive" {
13875 match parse_allow_host_primitive_form(&form) {
13876 Ok(decl) => {
13877 for name in &decl.names {
13878 env.allowed_host_primitives.insert(name.clone());
13879 }
13880 if options.trace {
13881 env.trace_events.push(TraceEvent::new(
13882 "allow-host-primitive",
13883 decl.names.join(" "),
13884 span.clone(),
13885 ));
13886 }
13887 }
13888 Err(message) => {
13889 diagnostics.push(Diagnostic::new("E065", message, span.clone()));
13890 }
13891 }
13892 continue;
13893 }
13894 }
13895 }
13896
13897 let result = catch_unwind(AssertUnwindSafe(|| {
13898 let mut stack = Vec::new();
13899 let expanded_form = expand_templates(&form, env, &mut stack);
13900 let eval_res = eval_node(&expanded_form, env);
13901 (expanded_form, eval_res)
13902 }));
13903 match result {
13904 Ok((expanded_form, eval_res)) => {
13905 if options.trace {
13906 let form_key = key_of(&expanded_form);
13907 let summary = match &eval_res {
13908 EvalResult::Query(v) => {
13909 format!("{} → query {}", form_key, format_trace_value(*v))
13910 }
13911 EvalResult::TypeQuery(s) => {
13912 format!("{} → type {}", form_key, s)
13913 }
13914 EvalResult::Value(v) => {
13915 format!("{} → {}", form_key, format_trace_value(*v))
13916 }
13917 EvalResult::Term(term) => {
13918 format!("{} → term {}", form_key, key_of(term))
13919 }
13920 };
13921 env.trace_events
13922 .push(TraceEvent::new("eval", summary, span.clone()));
13923 }
13924 let was_query = matches!(eval_res, EvalResult::Query(_) | EvalResult::TypeQuery(_));
13925 let query_value = if let EvalResult::Query(v) = &eval_res {
13926 Some(*v)
13927 } else {
13928 None
13929 };
13930 match eval_res {
13931 EvalResult::Query(v) => results.push(RunResult::Num(v)),
13932 EvalResult::TypeQuery(s) => results.push(RunResult::Type(s)),
13933 _ => {}
13934 }
13935 if was_query {
13936 let wants_proof = proofs_enabled || query_requests_proof(&expanded_form);
13937 if wants_proof {
13938 if proofs.is_none() {
13943 let backfill = results.len().saturating_sub(1);
13944 proofs = Some(vec![None; backfill]);
13945 }
13946 let proof_node = match &expanded_form {
13951 Node::List(form_children)
13952 if matches!(
13953 form_children.first(),
13954 Some(Node::Leaf(s)) if s == "?"
13955 ) =>
13956 {
13957 let parts = &form_children[1..];
13958 let inner = strip_with_proof(parts);
13959 let target: Node = if inner.len() == 1 {
13960 inner[0].clone()
13961 } else {
13962 Node::List(inner.to_vec())
13963 };
13964 build_proof(&target, env)
13965 }
13966 _ => build_proof(&expanded_form, env),
13967 };
13968 proofs.as_mut().unwrap().push(Some(proof_node));
13969 } else if let Some(p) = proofs.as_mut() {
13970 p.push(None);
13971 }
13972 let prov = equality_provenance_for_query(&expanded_form, env);
13973 record_provenance(
13974 &mut provenance,
13975 results.len(),
13976 prov,
13977 env,
13978 &expanded_form,
13979 &span,
13980 options,
13981 );
13982 if let Some(v) = query_value {
13987 if let Some(msg) = env.check_carrier_value(v) {
13988 diagnostics.push(Diagnostic::new(
13989 "E063",
13990 format!(
13991 "Query result {} violates active foundation carrier: {}",
13992 format_trace_value(v),
13993 msg
13994 ),
13995 span.clone(),
13996 ));
13997 }
13998 }
13999 if env.strict_pure_links {
14006 if let Node::List(form_children) = &expanded_form {
14007 if matches!(form_children.first(), Some(Node::Leaf(s)) if s == "?") {
14008 let parts = &form_children[1..];
14009 let inner = strip_with_proof(parts);
14010 let target: Node = if inner.len() == 1 {
14011 inner[0].clone()
14012 } else {
14013 Node::List(inner.to_vec())
14014 };
14015 let offenders = scan_pure_links_offenders(&target, env);
14016 if !offenders.is_empty() {
14017 diagnostics.push(Diagnostic::new(
14018 "E065",
14019 format!(
14020 "Query depends on host-primitive construct(s) under pure-links strict mode: {}",
14021 offenders.join(", ")
14022 ),
14023 span.clone(),
14024 ));
14025 }
14026 }
14027 }
14028 }
14029 }
14030 }
14031 Err(payload) => {
14032 let (code, message) = decode_panic_payload(&payload);
14033 diagnostics.push(Diagnostic::new(&code, message, span));
14034 }
14035 }
14036 }
14037
14038 env.current_span = None;
14039
14040 std::panic::set_hook(prev_hook);
14041
14042 if !env.shadow_diagnostics.is_empty() {
14046 let drained = std::mem::take(&mut env.shadow_diagnostics);
14047 for d in drained {
14048 diagnostics.push(d);
14049 }
14050 }
14051
14052 let trace = if options.trace {
14053 std::mem::take(&mut env.trace_events)
14054 } else {
14055 Vec::new()
14056 };
14057
14058 let provenance_vec = match provenance {
14059 Some(mut v) => {
14060 while v.len() < results.len() {
14061 v.push(None);
14062 }
14063 v
14064 }
14065 None => Vec::new(),
14066 };
14067
14068 EvaluateResult {
14069 results,
14070 diagnostics,
14071 trace,
14072 proofs: proofs.unwrap_or_default(),
14073 provenance: provenance_vec,
14074 }
14075}
14076
14077fn decode_panic_payload(payload: &Box<dyn std::any::Any + Send>) -> (String, String) {
14081 let raw_msg: String = if let Some(s) = payload.downcast_ref::<&'static str>() {
14082 (*s).to_string()
14083 } else if let Some(s) = payload.downcast_ref::<String>() {
14084 s.clone()
14085 } else {
14086 "evaluation panicked".to_string()
14087 };
14088 if raw_msg.starts_with("Unknown op:") {
14089 ("E001".to_string(), raw_msg)
14090 } else if raw_msg.starts_with("Unknown aggregator") {
14091 ("E004".to_string(), raw_msg)
14092 } else if raw_msg.starts_with("Freshness error:") {
14093 (
14094 "E010".to_string(),
14095 raw_msg.replacen("Freshness error: ", "", 1),
14096 )
14097 } else if raw_msg.starts_with("Mode declaration error:") {
14098 (
14099 "E030".to_string(),
14100 raw_msg.replacen("Mode declaration error: ", "", 1),
14101 )
14102 } else if raw_msg.starts_with("Mode mismatch:") {
14103 (
14104 "E031".to_string(),
14105 raw_msg.replacen("Mode mismatch: ", "", 1),
14106 )
14107 } else if raw_msg.starts_with("Relation declaration error:") {
14108 (
14109 "E032".to_string(),
14110 raw_msg.replacen("Relation declaration error: ", "", 1),
14111 )
14112 } else if raw_msg.starts_with("Totality check error:") {
14113 (
14114 "E032".to_string(),
14115 raw_msg.replacen("Totality check error: ", "", 1),
14116 )
14117 } else if raw_msg.starts_with("Coverage check error:") {
14118 (
14119 "E037".to_string(),
14120 raw_msg.replacen("Coverage check error: ", "", 1),
14121 )
14122 } else if raw_msg.starts_with("World declaration error:") {
14123 (
14124 "E034".to_string(),
14125 raw_msg.replacen("World declaration error: ", "", 1),
14126 )
14127 } else if raw_msg.starts_with("World violation:") {
14128 (
14129 "E034".to_string(),
14130 raw_msg.replacen("World violation: ", "", 1),
14131 )
14132 } else if raw_msg.starts_with("Inductive declaration error:") {
14133 (
14134 "E033".to_string(),
14135 raw_msg.replacen("Inductive declaration error: ", "", 1),
14136 )
14137 } else if raw_msg.starts_with("Termination check error:") {
14138 (
14139 "E035".to_string(),
14140 raw_msg.replacen("Termination check error: ", "", 1),
14141 )
14142 } else if raw_msg.starts_with("Coinductive declaration error:") {
14143 (
14144 "E036".to_string(),
14145 raw_msg.replacen("Coinductive declaration error: ", "", 1),
14146 )
14147 } else if raw_msg.starts_with("Normalization error:") {
14148 (
14149 "E038".to_string(),
14150 raw_msg.replacen("Normalization error: ", "", 1),
14151 )
14152 } else if raw_msg.starts_with("Template expansion error:") {
14153 (
14154 "E040".to_string(),
14155 raw_msg.replacen("Template expansion error: ", "", 1),
14156 )
14157 } else if raw_msg.starts_with("Domain plugin error:") {
14158 (
14159 "E041".to_string(),
14160 raw_msg.replacen("Domain plugin error: ", "", 1),
14161 )
14162 } else if raw_msg.starts_with("Carrier violation:") {
14163 (
14164 "E063".to_string(),
14165 raw_msg.replacen("Carrier violation: ", "", 1),
14166 )
14167 } else {
14168 ("E000".to_string(), raw_msg)
14169 }
14170}
14171
14172pub fn run_typed(text: &str, options: Option<EnvOptions>) -> Vec<RunResult> {
14174 evaluate(text, None, options).results
14175}
14176
14177pub fn run(text: &str, options: Option<EnvOptions>) -> Vec<f64> {
14179 run_typed(text, options)
14180 .into_iter()
14181 .filter_map(|result| match result {
14182 RunResult::Num(v) => Some(v),
14183 RunResult::Type(_) => None,
14184 RunResult::Foundation(_) => None,
14185 RunResult::Proof(_) => None,
14186 })
14187 .collect()
14188}
14189
14190pub mod repl;
14194pub mod check;
14195pub mod meta;
14196pub mod rocq;
14197
14198pub mod cst;
14200pub mod cst_rust;
14201pub mod cst_js;
14202pub mod cst_lean;
14203pub mod cst_rocq;
14204pub mod cst_convert;