1#![doc = include_str!("lib.md")]
2pub mod api;
3pub mod ast;
4#[cfg(feature = "bin")]
5mod cli;
6mod command_macro;
7pub mod constraint;
8mod core;
9mod exec_state;
10pub mod extract;
11pub mod prelude;
12mod proofs;
13
14pub mod scheduler;
15mod serialize;
16pub mod sort;
17mod termdag;
18mod typechecking;
19pub mod util;
20pub use command_macro::{CommandMacro, CommandMacroRegistry};
21
22extern crate self as egglog;
25pub use ast::{ResolvedExpr, ResolvedFact, ResolvedVar};
26#[cfg(feature = "bin")]
27pub use cli::*;
28use constraint::{Constraint, Problem, SimpleTypeConstraint, TypeConstraint};
29use core::CoreActionContext;
30use core::ResolvedAtomTerm;
31pub use core::{Atom, AtomTerm};
32pub use core::{ResolvedCall, SpecializedPrimitive};
33pub use core_relations::{BaseValue, ContainerValue, Value};
34use core_relations::{ExecutionState, ExternalFunctionId, make_external_func};
35use csv::Writer;
36pub use egglog_add_primitive::add_literal_prim;
37pub use egglog_add_primitive::add_primitive;
38pub use egglog_add_primitive::add_primitive_with_validator;
39use egglog_ast::generic_ast::{Change, GenericExpr, Literal};
40use egglog_ast::span::Span;
41use egglog_ast::util::ListDisplay;
42use egglog_bridge::{ColumnTy, QueryEntry};
43use egglog_core_relations as core_relations;
44use egglog_numeric_id as numeric_id;
45use egglog_reports::{ReportLevel, RunReport};
46pub use exec_state::{
47 Context, Core, Enode, FullState, FunctionEntry, PureState, Read, ReadState, Write, WriteState,
48};
49use extract::{DefaultCost, Extractor, TreeAdditiveCostModel};
50use indexmap::map::Entry;
51use log::{Level, log_enabled};
52use numeric_id::DenseIdMap;
53use prelude::*;
54pub use proofs::proof_encoding_helpers::{file_supports_proofs, program_supports_proofs};
55
56pub mod proof {
58 pub use crate::proofs::proof_format::{Justification, Proof, ProofId, ProofStore, Proposition};
59}
60use scheduler::{SchedulerId, SchedulerRecord};
61pub use serialize::{SerializeConfig, SerializeOutput, SerializedNode};
62use sort::*;
63use std::any::{Any, TypeId};
64use std::fmt::{Debug, Display, Formatter};
65use std::fs::File;
66use std::hash::Hash;
67use std::io::{Read as _, Write as _};
68use std::iter::once;
69use std::ops::Deref;
70use std::path::PathBuf;
71use std::sync::Arc;
72pub use termdag::{OrdTerm, Term, TermDag, TermId};
73use thiserror::Error;
74pub use typechecking::PrimitiveValidator;
75pub use typechecking::TypeError;
76pub use typechecking::TypeInfo;
77use util::*;
78
79use crate::ast::desugar::desugar_command;
80use crate::ast::*;
81use crate::core::{GenericActionsExt, ResolvedRuleExt};
82use crate::proofs::proof_encoding::{EncodingState, ProofInstrumentor};
83use crate::proofs::proof_encoding_helpers::{
84 ProofEncodingUnsupportedReason, command_supports_proof_encoding,
85};
86use crate::proofs::proof_extraction::ProveExistsError;
87use crate::proofs::proof_format::{ProofId, ProofStore};
88use crate::proofs::proof_normal_form::proof_form;
89
90pub const GLOBAL_NAME_PREFIX: &str = "$";
91
92pub type ArcSort = Arc<dyn Sort>;
93
94pub trait Primitive: Send + Sync + 'static {
100 fn name(&self) -> &str;
102
103 fn get_type_constraints(&self, span: &Span) -> Box<dyn TypeConstraint>;
105}
106
107pub trait PurePrim: Primitive {
110 fn apply<'a, 'db>(&self, state: PureState<'a, 'db>, args: &[Value]) -> Option<Value>;
111}
112
113pub trait WritePrim: Primitive {
116 fn apply<'a, 'db>(&self, state: WriteState<'a, 'db>, args: &[Value]) -> Option<Value>;
117}
118
119pub trait ReadPrim: Primitive {
122 fn apply<'a, 'db>(&self, state: ReadState<'a, 'db>, args: &[Value]) -> Option<Value>;
123}
124
125pub trait FullPrim: Primitive {
128 fn apply<'a, 'db>(&self, state: FullState<'a, 'db>, args: &[Value]) -> Option<Value>;
129}
130
131pub trait UserDefinedCommandOutput: Debug + std::fmt::Display + Send + Sync {}
133impl<T> UserDefinedCommandOutput for T where T: Debug + std::fmt::Display + Send + Sync {}
134
135#[derive(Clone, Debug)]
137#[allow(clippy::large_enum_variant)]
138pub enum CommandOutput {
139 PrintFunctionSize(usize),
141 PrintAllFunctionsSize(Vec<(String, usize)>),
143 ExtractBest(TermDag, DefaultCost, TermId),
145 ExtractVariants(TermDag, Vec<TermId>),
147 ProveExists {
149 proof_store: ProofStore,
150 proof_id: ProofId,
151 },
152 OverallStatistics(RunReport),
154 PrintFunction(Function, TermDag, Vec<(TermId, TermId)>, PrintFunctionMode),
156 RunSchedule(RunReport),
158 UserDefined(Arc<dyn UserDefinedCommandOutput>),
160}
161
162impl CommandOutput {
163 pub fn snapshot_stable_under_proof_encoding(outputs: &[CommandOutput]) -> String {
168 outputs
169 .iter()
170 .filter_map(|output| match output {
171 CommandOutput::OverallStatistics(_) => None,
172 CommandOutput::PrintFunction(..) => None,
173 CommandOutput::ExtractBest(_, cost, _) => {
174 Some(format!("(extraction-costs {cost})\n"))
175 }
176 CommandOutput::ExtractVariants(..) => None,
177 other => Some(other.to_string()),
178 })
179 .collect::<Vec<_>>()
180 .join("")
181 }
182}
183
184impl std::fmt::Display for CommandOutput {
185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187 match self {
188 CommandOutput::PrintFunctionSize(size) => writeln!(f, "{size}"),
189 CommandOutput::PrintAllFunctionsSize(names_and_sizes) => {
190 write!(f, "(")?;
191 for (i, (name, size)) in names_and_sizes.iter().enumerate() {
192 if i > 0 {
194 write!(f, " ")?;
195 }
196 write!(f, "({name} {size})")?;
198 if i < names_and_sizes.len() - 1 {
200 writeln!(f)?;
201 }
202 }
203 writeln!(f, ")")
204 }
205 CommandOutput::ExtractBest(termdag, _cost, term) => {
206 writeln!(f, "{}", termdag.to_string(*term))
207 }
208 CommandOutput::ExtractVariants(termdag, terms) => {
209 writeln!(f, "(")?;
210 for expr in terms {
211 writeln!(f, " {}", termdag.to_string(*expr))?;
212 }
213 writeln!(f, ")")
214 }
215 CommandOutput::ProveExists {
216 proof_store,
217 proof_id,
218 } => writeln!(f, "{}", proof_store.proof_to_string(*proof_id)),
219 CommandOutput::OverallStatistics(run_report) => {
220 write!(f, "Overall statistics:\n{run_report}")
221 }
222 CommandOutput::PrintFunction(function, termdag, terms_and_outputs, mode) => {
223 let out_is_unit = function.schema.output.name() == UnitSort.name();
224 if *mode == PrintFunctionMode::CSV {
225 let mut wtr = Writer::from_writer(vec![]);
226 for (term_id, output) in terms_and_outputs {
227 let term = termdag.get(*term_id);
228 match term {
229 Term::App(name, children) => {
230 let mut values = vec![name.clone()];
231 for child_id in children {
232 values.push(termdag.to_string(*child_id));
233 }
234
235 if !out_is_unit {
236 values.push(termdag.to_string(*output));
237 }
238 wtr.write_record(&values).map_err(|_| std::fmt::Error)?;
239 }
240 _ => panic!("Expect function_to_dag to return a list of apps."),
241 }
242 }
243 let csv_bytes = wtr.into_inner().map_err(|_| std::fmt::Error)?;
244 f.write_str(&String::from_utf8(csv_bytes).map_err(|_| std::fmt::Error)?)
245 } else {
246 writeln!(f, "(")?;
247 for (term, output) in terms_and_outputs.iter() {
248 write!(f, " {}", termdag.to_string(*term))?;
249 if !out_is_unit {
250 write!(f, " -> {}", termdag.to_string(*output))?;
251 }
252 writeln!(f)?;
253 }
254 writeln!(f, ")")
255 }
256 }
257 CommandOutput::RunSchedule(_report) => Ok(()),
258 CommandOutput::UserDefined(output) => {
259 write!(f, "{}", *output)
260 }
261 }
262 }
263}
264
265trait ExtensionStateValue: Any + dyn_clone::DynClone + Send + Sync {}
279
280impl<T> ExtensionStateValue for T where T: Any + Clone + Send + Sync {}
281
282dyn_clone::clone_trait_object!(ExtensionStateValue);
283
284#[derive(Clone)]
285pub struct EGraph {
286 backend: egglog_bridge::EGraph,
287 pub parser: Parser,
288 names: check_shadowing::Names,
289 pushed_egraph: Option<Box<Self>>,
292 functions: IndexMap<String, Function>,
293 rulesets: IndexMap<String, Ruleset>,
294 pub fact_directory: Option<PathBuf>,
295 pub seminaive: bool,
296 pub no_decomp: bool,
297 type_info: TypeInfo,
298 overall_run_report: RunReport,
300 schedulers: DenseIdMap<SchedulerId, SchedulerRecord>,
301 commands: IndexMap<String, Arc<dyn UserDefinedCommand>>,
302 extension_state: HashMap<TypeId, Box<dyn ExtensionStateValue>>,
303 strict_mode: bool,
304 warned_about_global_prefix: bool,
305 command_macros: CommandMacroRegistry,
307 proof_state: EncodingState,
308 proof_check_program: Vec<ResolvedNCommand>,
310}
311
312pub trait UserDefinedCommand: Send + Sync {
318 fn update(&self, egraph: &mut EGraph, args: &[Expr]) -> Result<Vec<CommandOutput>, Error>;
320}
321
322#[derive(Clone)]
327pub struct Function {
328 decl: ResolvedFunctionDecl,
329 schema: ResolvedSchema,
330 can_subsume: bool,
331 backend_id: egglog_bridge::FunctionId,
332}
333
334impl Function {
335 pub fn name(&self) -> &str {
337 &self.decl.name
338 }
339
340 pub fn schema(&self) -> &ResolvedSchema {
342 &self.schema
343 }
344
345 pub fn can_subsume(&self) -> bool {
347 self.can_subsume
348 }
349
350 pub fn is_let_binding(&self) -> bool {
352 self.decl.internal_let
353 }
354
355 pub fn is_hidden(&self) -> bool {
358 self.decl.internal_hidden
359 }
360
361 pub fn term_constructor(&self) -> Option<&str> {
365 self.decl.term_constructor.as_deref()
366 }
367}
368
369#[derive(Clone, Debug)]
370pub struct ResolvedSchema {
371 pub input: Vec<ArcSort>,
372 pub output: ArcSort,
373}
374
375impl ResolvedSchema {
376 pub fn get_by_pos(&self, index: usize) -> Option<&ArcSort> {
378 if self.input.len() == index {
379 Some(&self.output)
380 } else {
381 self.input.get(index)
382 }
383 }
384}
385
386impl Debug for Function {
387 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
388 f.debug_struct("Function")
389 .field("decl", &self.decl)
390 .field("schema", &self.schema)
391 .finish()
392 }
393}
394
395impl Default for EGraph {
396 fn default() -> Self {
397 let mut parser = Parser::default();
398 let proof_state = EncodingState::new(&mut parser.symbol_gen);
399 let mut eg = Self {
400 backend: Default::default(),
401 parser,
402 names: Default::default(),
403 pushed_egraph: Default::default(),
404 functions: Default::default(),
405 rulesets: Default::default(),
406 fact_directory: None,
407 seminaive: true,
408 no_decomp: false,
409 overall_run_report: Default::default(),
410 type_info: Default::default(),
411 schedulers: Default::default(),
412 commands: Default::default(),
413 extension_state: Default::default(),
414 strict_mode: false,
415 warned_about_global_prefix: false,
416 command_macros: Default::default(),
417 proof_state,
418 proof_check_program: vec![],
419 };
420 add_base_sort(&mut eg, UnitSort, span!()).unwrap();
421 add_base_sort(&mut eg, StringSort, span!()).unwrap();
422 add_base_sort(&mut eg, BoolSort, span!()).unwrap();
423 add_base_sort(&mut eg, I64Sort, span!()).unwrap();
424 add_base_sort(&mut eg, F64Sort, span!()).unwrap();
425 add_base_sort(&mut eg, BigIntSort, span!()).unwrap();
426 add_base_sort(&mut eg, BigRatSort, span!()).unwrap();
427 eg.type_info.add_presort::<MapSort>(span!()).unwrap();
428 eg.type_info.add_presort::<SetSort>(span!()).unwrap();
429 eg.type_info.add_presort::<VecSort>(span!()).unwrap();
430 eg.type_info.add_presort::<FunctionSort>(span!()).unwrap();
431 eg.type_info.add_presort::<MultiSetSort>(span!()).unwrap();
432 eg.type_info.add_presort::<PairSort>(span!()).unwrap();
433
434 let neq_validator = |termdag: &mut TermDag, args: &[TermId]| -> Option<TermId> {
436 if args.len() == 2 && args[0] != args[1] {
437 Some(termdag.lit(Literal::Unit))
439 } else {
440 None
441 }
442 };
443 add_primitive_with_validator!(
444 &mut eg,
445 "!=" = |a: #, b: #| -?> () {
446 (a != b).then_some(())
447 },
448 neq_validator
449 );
450
451 add_primitive_with_validator!(
452 &mut eg,
453 "bool-!=" = |a: #, b: #| -> bool {
454 (a != b)
455 },
456 |termdag: &mut TermDag, args: &[TermId]| -> Option<TermId> {
457 if args.len() == 2 {
458 Some(termdag.lit(Literal::Bool(args[0] != args[1])))
459 } else {
460 None
461 }
462 }
463 );
464
465 add_primitive!(&mut eg, "value-eq" = |a: #, b: #| -?> () {
466 (a == b).then_some(())
467 });
468 add_primitive!(&mut eg, "ordering-min" = |a: #, b: #| -> # {
469 if a < b { a } else { b }
470 });
471 add_primitive!(&mut eg, "ordering-max" = |a: #, b: #| -> # {
472 if a > b { a } else { b }
473 });
474
475 eg.rulesets
476 .insert("".into(), Ruleset::Rules(Default::default()));
477
478 eg
479 }
480}
481
482struct ResolvedNCommands {
483 desugared: Vec<ResolvedNCommand>,
484 desugared_before_proofs: Vec<ResolvedNCommand>,
486}
487
488struct ResolvedNCommandsWithOutput {
489 outputs: Vec<CommandOutput>,
490 resolved: Vec<ResolvedNCommand>,
491 resolved_before_proofs: Vec<ResolvedNCommand>,
493}
494
495#[derive(Debug, Error)]
496#[error("Not found: {0}")]
497pub struct NotFoundError(String);
498
499impl EGraph {
500 pub fn new(num_threads: usize) -> Self {
505 EGraph::default().with_num_threads(num_threads)
506 }
507
508 pub fn new_with_term_encoding() -> Self {
517 let mut egraph = EGraph::default();
518 egraph.proof_state.original_typechecking = Some(Box::new(egraph.clone()));
519 egraph
520 }
521
522 pub fn new_with_proofs() -> Self {
524 let mut egraph = EGraph::new_with_term_encoding();
525 egraph.proof_state.proofs_enabled = true;
526 egraph
527 }
528
529 #[cfg(feature = "bin")]
533 pub(crate) fn with_term_encoding_enabled(mut self) -> Self {
534 self.proof_state.original_typechecking = Some(Box::new(self.clone()));
535 self
536 }
537
538 #[cfg(feature = "bin")]
542 pub(crate) fn with_proofs_enabled(mut self) -> Self {
543 self = self.with_term_encoding_enabled();
544 self.proof_state.proofs_enabled = true;
545 self
546 }
547
548 pub fn with_proof_testing(mut self) -> Self {
550 self.proof_state.proof_testing = true;
551 self
552 }
553
554 pub fn with_num_threads(mut self, num_threads: usize) -> Self {
556 self.set_num_threads(num_threads);
557 self
558 }
559
560 pub fn set_num_threads(&mut self, num_threads: usize) {
565 self.backend.set_num_threads(num_threads);
566 if let Some(original) = &mut self.proof_state.original_typechecking {
567 original.set_num_threads(num_threads);
568 }
569 }
570
571 pub fn num_threads(&self) -> usize {
573 self.backend.num_threads()
574 }
575
576 pub fn extension_state<T>(&self) -> Option<&T>
582 where
583 T: Send + Sync + 'static,
584 {
585 let value = self.extension_state.get(&TypeId::of::<T>())?;
586 (value.as_ref() as &dyn Any).downcast_ref()
587 }
588
589 pub fn extension_state_or_default<T>(&mut self) -> &mut T
591 where
592 T: Default + Clone + Send + Sync + 'static,
593 {
594 let value = self
595 .extension_state
596 .entry(TypeId::of::<T>())
597 .or_insert_with(|| Box::new(T::default()));
598 (value.as_mut() as &mut dyn Any)
599 .downcast_mut()
600 .expect("extension state entry must have the requested type")
601 }
602
603 pub fn type_info(&mut self) -> &mut TypeInfo {
606 &mut self.type_info
607 }
608
609 pub fn command_macros(&self) -> &CommandMacroRegistry {
611 &self.command_macros
612 }
613
614 pub fn command_macros_mut(&mut self) -> &mut CommandMacroRegistry {
616 &mut self.command_macros
617 }
618
619 pub fn add_command(
620 &mut self,
621 name: String,
622 command: Arc<dyn UserDefinedCommand>,
623 ) -> Result<(), Error> {
624 if self.commands.contains_key(&name)
625 || self.functions.contains_key(&name)
626 || self.type_info.get_prims(&name).is_some()
627 {
628 return Err(Error::CommandAlreadyExists(name, span!()));
629 }
630 self.commands.insert(name.clone(), command);
631 self.parser.add_user_defined(name)?;
632 Ok(())
633 }
634
635 pub fn set_strict_mode(&mut self, strict_mode: bool) {
637 self.strict_mode = strict_mode;
638 }
639
640 pub fn strict_mode(&self) -> bool {
642 self.strict_mode
643 }
644
645 #[doc(hidden)]
649 pub fn ensure_no_reserved_symbols(&mut self, should_ensure: bool) {
650 self.parser.ensure_no_reserved_symbols = should_ensure;
651 }
652
653 fn ensure_global_name_prefix(&mut self, span: &Span, name: &str) -> Result<(), TypeError> {
654 if name.starts_with(GLOBAL_NAME_PREFIX) {
655 return Ok(());
656 }
657 if self.strict_mode {
658 Err(TypeError::GlobalMissingPrefix {
659 name: name.to_owned(),
660 span: span.clone(),
661 })
662 } else {
663 self.warn_missing_global_prefix(span, name)?;
664 Ok(())
665 }
666 }
667
668 fn warn_missing_global_prefix(
669 &mut self,
670 span: &Span,
671 canonical_name: &str,
672 ) -> Result<(), TypeError> {
673 if self.strict_mode {
674 return Err(TypeError::GlobalMissingPrefix {
675 name: format!("{GLOBAL_NAME_PREFIX}{canonical_name}"),
676 span: span.clone(),
677 });
678 }
679 if self.warned_about_global_prefix {
680 return Ok(());
681 }
682 self.warned_about_global_prefix = true;
683 log::warn!(
684 "{span}\nGlobal `{canonical_name}` should start with `{GLOBAL_NAME_PREFIX}`. Enable `--strict-mode` to turn this warning into an error. Suppressing additional warnings of this type."
685 );
686 Ok(())
687 }
688
689 fn warn_prefixed_non_globals(
690 &mut self,
691 span: &Span,
692 canonical_name: &str,
693 ) -> Result<(), TypeError> {
694 if self.strict_mode {
695 return Err(TypeError::NonGlobalPrefixed {
696 name: canonical_name.to_string(),
697 span: span.clone(),
698 });
699 }
700 if self.warned_about_global_prefix {
701 return Ok(());
702 }
703 self.warned_about_global_prefix = true;
704 log::warn!(
705 "{span}\nNon-global `{canonical_name}` should not start with `{GLOBAL_NAME_PREFIX}`. Enable `--strict-mode` to turn this warning into an error. Suppressing additional warnings of this type."
706 );
707 Ok(())
708 }
709
710 pub fn push(&mut self) {
714 let prev_prev: Option<Box<Self>> = self.pushed_egraph.take();
715 let mut prev = self.clone();
716 prev.pushed_egraph = prev_prev;
717 self.pushed_egraph = Some(Box::new(prev));
718 }
719
720 pub fn pop(&mut self) -> Result<(), Error> {
725 match self.pushed_egraph.take() {
726 Some(mut e) => {
727 std::mem::swap(&mut self.overall_run_report, &mut e.overall_run_report);
729 std::mem::swap(&mut self.parser.symbol_gen, &mut e.parser.symbol_gen);
732 *self = *e;
733 Ok(())
734 }
735 None => Err(Error::Pop(span!())),
736 }
737 }
738
739 fn translate_expr_to_mergefn(
740 &self,
741 expr: &ResolvedExpr,
742 ) -> Result<egglog_bridge::MergeFn, Error> {
743 match expr {
744 GenericExpr::Lit(_, literal) => {
745 let val = literal_to_value(&self.backend, literal);
746 Ok(egglog_bridge::MergeFn::Const(val))
747 }
748 GenericExpr::Var(span, resolved_var) => match resolved_var.name.as_str() {
749 "old" => Ok(egglog_bridge::MergeFn::Old),
750 "new" => Ok(egglog_bridge::MergeFn::New),
751 _ => Err(TypeError::Unbound(resolved_var.name.clone(), span.clone()).into()),
753 },
754 GenericExpr::Call(_, ResolvedCall::Func(f), args) => {
755 let translated_args = args
756 .iter()
757 .map(|arg| self.translate_expr_to_mergefn(arg))
758 .collect::<Result<Vec<_>, _>>()?;
759 Ok(egglog_bridge::MergeFn::Function(
760 self.functions[&f.name].backend_id,
761 translated_args,
762 ))
763 }
764 GenericExpr::Call(_, ResolvedCall::Primitive(p), args) => {
765 let mut translated_args = args
766 .iter()
767 .map(|arg| self.translate_expr_to_mergefn(arg))
768 .collect::<Result<Vec<_>, _>>()?;
769 if p.name() == "unstable-fn" {
770 let Some(GenericExpr::Lit(_, Literal::String(name))) = args.first() else {
771 return Err(Error::BackendError(
772 "expected string literal after `unstable-fn`".into(),
773 ));
774 };
775 let resolved = resolve_function_container_target_with_context(
776 &self.backend,
777 &self.functions,
778 &self.type_info,
779 name,
780 p,
781 self.backend
782 .action_registry()
783 .read()
784 .unwrap()
785 .default_panic_id(),
786 )?;
787 translated_args[0] =
788 egglog_bridge::MergeFn::Const(self.backend.base_values().get(resolved));
789 }
790 Ok(egglog_bridge::MergeFn::Primitive(
791 p.external_id(crate::Context::Write),
792 translated_args,
793 ))
794 }
795 }
796 }
797
798 fn declare_function(&mut self, decl: &ResolvedFunctionDecl) -> Result<(), Error> {
799 let get_sort = |name: &String| match self.type_info.get_sort_by_name(name) {
800 Some(sort) => Ok(sort.clone()),
801 None => Err(Error::TypeError(TypeError::UndefinedSort(
802 name.to_owned(),
803 decl.span.clone(),
804 ))),
805 };
806
807 let input = decl
808 .schema
809 .input
810 .iter()
811 .map(get_sort)
812 .collect::<Result<Vec<_>, _>>()?;
813 let output = get_sort(&decl.schema.output)?;
814
815 let can_subsume = match decl.subtype {
816 FunctionSubtype::Constructor => true,
817 FunctionSubtype::Custom => decl.term_constructor.is_some(),
819 };
820
821 use egglog_bridge::{DefaultVal, MergeFn};
822 let backend_id = self.backend.add_table(egglog_bridge::FunctionConfig {
823 schema: input
824 .iter()
825 .chain([&output])
826 .map(|sort| sort.column_ty(&self.backend))
827 .collect(),
828 default: match decl.subtype {
829 FunctionSubtype::Constructor => DefaultVal::FreshId,
830 FunctionSubtype::Custom => DefaultVal::Fail,
831 },
832 merge: match decl.subtype {
833 FunctionSubtype::Constructor => MergeFn::UnionId,
834 FunctionSubtype::Custom => match &decl.merge {
835 None => MergeFn::AssertEq,
836 Some(expr) => self.translate_expr_to_mergefn(expr)?,
837 },
838 },
839 name: decl.name.to_string(),
840 can_subsume,
841 });
842
843 let function = Function {
844 decl: decl.clone(),
845 schema: ResolvedSchema { input, output },
846 can_subsume,
847 backend_id,
848 };
849
850 let old = self.functions.insert(decl.name.clone(), function);
851 if old.is_some() {
852 panic!(
853 "Typechecking should have caught function already bound: {}",
854 decl.name
855 );
856 }
857
858 Ok(())
859 }
860
861 pub fn print_function(
867 &mut self,
868 sym: &str,
869 n: Option<usize>,
870 file: Option<File>,
871 mode: PrintFunctionMode,
872 ) -> Result<Option<CommandOutput>, Error> {
873 let n = match n {
874 Some(n) => {
875 log::info!("Printing up to {n} tuples of function {sym} as {mode}");
876 n
877 }
878 None => {
879 log::info!("Printing all tuples of function {sym} as {mode}");
880 usize::MAX
881 }
882 };
883
884 let (terms, outputs, termdag) = self.function_to_dag(sym, n, true)?;
885 let f = self
886 .functions
887 .get(sym)
888 .unwrap();
890 let terms_and_outputs: Vec<_> = terms.into_iter().zip(outputs.unwrap()).collect();
891 let output = CommandOutput::PrintFunction(f.clone(), termdag, terms_and_outputs, mode);
892 match file {
893 Some(mut file) => {
894 log::info!("Writing output to file");
895 file.write_all(output.to_string().as_bytes())
896 .expect("Error writing to file");
897 Ok(None)
898 }
899 None => Ok(Some(output)),
900 }
901 }
902
903 #[doc(hidden)]
908 pub fn set_proof_checking_program(
909 &mut self,
910 prog: Vec<Command>,
911 proof_testing: bool,
912 ) -> Result<(), Error> {
913 let mut proof_check_eg = EGraph::new_with_proofs();
915 if proof_testing {
916 proof_check_eg = proof_check_eg.with_proof_testing();
917 }
918 let resolved = proof_check_eg.process_program_internal(prog, false)?;
919
920 self.proof_check_program = resolved.resolved_before_proofs;
921 Ok(())
922 }
923
924 pub fn print_size(&self, sym: Option<&str>) -> Result<CommandOutput, Error> {
928 if let Some(sym) = sym {
929 let f = self
933 .functions
934 .values()
935 .find(|f| f.decl.term_constructor.as_deref() == Some(sym))
936 .or_else(|| self.functions.get(sym))
937 .ok_or(TypeError::UnboundFunction(sym.to_owned(), span!()))?;
938 if f.decl.internal_hidden || f.decl.internal_let {
940 return Err(TypeError::UnboundFunction(sym.to_owned(), span!()).into());
941 }
942 let size = self.backend.table_size(f.backend_id);
943 log::info!("Function {sym} has size {size}");
944 Ok(CommandOutput::PrintFunctionSize(size))
945 } else {
946 let mut lens = self
949 .functions
950 .iter()
951 .filter(|(_, f)| !f.decl.internal_hidden && !f.decl.internal_let)
952 .map(|(sym, f)| {
953 let name = f
954 .decl
955 .term_constructor
956 .clone()
957 .unwrap_or_else(|| sym.clone());
958 (name, self.backend.table_size(f.backend_id))
959 })
960 .collect::<Vec<_>>();
961
962 lens.sort_by_key(|(name, _)| name.clone());
964 if log_enabled!(Level::Info) {
965 for (sym, len) in &lens {
966 log::info!("Function {sym} has size {len}");
967 }
968 }
969 Ok(CommandOutput::PrintAllFunctionsSize(lens))
970 }
971 }
972
973 fn run_schedule(&mut self, sched: &ResolvedSchedule) -> Result<RunReport, Error> {
975 match sched {
976 ResolvedSchedule::Run(span, config) => self.run_rules(span, config),
977 ResolvedSchedule::Repeat(_span, limit, sched) => {
978 let mut report = RunReport::default();
979 for _i in 0..*limit {
980 let rec = self.run_schedule(sched)?;
981 let can_stop = rec.can_stop;
982 report.union(rec);
983 if can_stop {
984 break;
985 }
986 }
987 Ok(report)
988 }
989 ResolvedSchedule::Saturate(_span, sched) => {
990 let mut report = RunReport::default();
991 let mut i = 0usize;
992 loop {
993 i += 1;
994 log::debug!(
995 "Saturate iteration {i} start: {}",
996 Self::schedule_for_log(sched)
997 );
998 let rec = self.run_schedule(sched)?;
999 let updated = rec.updated;
1000 log::debug!(
1001 "Saturate iteration {i} end: {}",
1002 Self::run_report_debug_summary(&rec)
1003 );
1004 report.union(rec);
1005 if !updated {
1006 log::debug!("Saturate reached fixpoint after {i} iteration(s)");
1007 break;
1008 }
1009 }
1010 Ok(report)
1011 }
1012 ResolvedSchedule::Sequence(_span, scheds) => {
1013 let mut report = RunReport::default();
1014 for sched in scheds {
1015 report.union(self.run_schedule(sched)?);
1016 }
1017 Ok(report)
1018 }
1019 }
1020 }
1021
1022 fn run_rules(&mut self, span: &Span, config: &ResolvedRunConfig) -> Result<RunReport, Error> {
1023 log::debug!("Running ruleset: {}", config.ruleset);
1024 let mut report: RunReport = Default::default();
1025
1026 let GenericRunConfig { ruleset, until } = config;
1027
1028 if !self.rulesets.contains_key(ruleset) {
1029 return Err(Error::NoSuchRuleset(ruleset.clone(), span.clone()));
1030 }
1031
1032 if let Some(facts) = until
1033 && self.check_facts(span, facts).is_ok()
1034 {
1035 log::info!(
1036 "Breaking early because of facts:\n {}!",
1037 ListDisplay(facts, "\n")
1038 );
1039 return Ok(report);
1040 }
1041
1042 let subreport = self.step_rules(ruleset)?;
1043 report.union(subreport);
1044
1045 if log_enabled!(Level::Debug) {
1046 log::debug!(
1047 "Finished ruleset {ruleset}: database size {}, {}",
1048 self.num_tuples(),
1049 Self::run_report_debug_summary(&report)
1050 );
1051 }
1052
1053 Ok(report)
1054 }
1055
1056 fn run_report_debug_summary(report: &RunReport) -> String {
1057 let mut rules = report
1058 .num_matches_per_rule
1059 .iter()
1060 .filter(|(_, matches)| **matches > 0)
1061 .collect::<Vec<_>>();
1062 rules.sort_by(|(_, left), (_, right)| right.cmp(left));
1063
1064 let top_rules = rules
1065 .into_iter()
1066 .take(5)
1067 .map(|(rule, matches)| {
1068 format!("{}={matches}", Self::truncate_for_log(rule.as_ref(), 80))
1069 })
1070 .collect::<Vec<_>>()
1071 .join(", ");
1072
1073 format!(
1074 "updated={}, can_stop={}, iterations={}, top_matches=[{}]",
1075 report.updated,
1076 report.can_stop,
1077 report.iterations.len(),
1078 top_rules
1079 )
1080 }
1081
1082 fn schedule_for_log(sched: &ResolvedSchedule) -> String {
1083 Self::truncate_for_log(&sched.to_string(), 160)
1084 }
1085
1086 fn truncate_for_log(s: &str, limit: usize) -> String {
1087 let mut s = s.replace('\n', " ");
1088 if s.len() > limit {
1089 s.truncate(limit);
1090 s.push_str("...");
1091 }
1092 s
1093 }
1094
1095 pub fn step_rules(&mut self, ruleset: &str) -> Result<RunReport, Error> {
1102 fn collect_rule_ids(
1103 ruleset: &str,
1104 rulesets: &IndexMap<String, Ruleset>,
1105 ids: &mut Vec<egglog_bridge::RuleId>,
1106 ) {
1107 match &rulesets[ruleset] {
1108 Ruleset::Rules(rules) => {
1109 for (_, id) in rules.values() {
1110 ids.push(*id);
1111 }
1112 }
1113 Ruleset::Combined(sub_rulesets) => {
1114 for sub_ruleset in sub_rulesets {
1115 collect_rule_ids(sub_ruleset, rulesets, ids);
1116 }
1117 }
1118 }
1119 }
1120
1121 let mut rule_ids = Vec::new();
1122 collect_rule_ids(ruleset, &self.rulesets, &mut rule_ids);
1123
1124 let iteration_report = self
1125 .backend
1126 .run_rules(&rule_ids)
1127 .map_err(|e| Error::BackendError(e.to_string()))?;
1128
1129 Ok(RunReport::singleton(ruleset, iteration_report))
1130 }
1131
1132 fn add_rule(&mut self, rule: ast::ResolvedRule) -> Result<String, Error> {
1133 let core_rule = rule.to_canonicalized_core_rule(
1136 &self.type_info,
1137 &mut self.parser.symbol_gen,
1138 self.proof_state.original_typechecking.is_none(),
1139 )?;
1140 let (query, actions) = (&core_rule.body, &core_rule.head);
1141
1142 let seminaive = self.seminaive && !rule.eval_mode.is_naive();
1147 let no_decomp = self.no_decomp || rule.no_decomp;
1151 let requires_read_context = !seminaive
1152 || matches!(
1153 rule.eval_mode,
1154 RuleEvalMode::Naive | RuleEvalMode::UnsafeSeminaive
1155 );
1156
1157 let rule_id = {
1158 let mut rb = self.backend.new_rule(&rule.name, seminaive);
1159 rb.set_no_decomp(no_decomp);
1160 let mut translator =
1161 BackendRule::new(rb, &self.functions, &self.type_info, requires_read_context);
1162 translator.query(query, rule.include_subsumed);
1163 translator.actions(actions)?;
1164 translator.build()
1165 };
1166
1167 if let Some(rules) = self.rulesets.get_mut(&rule.ruleset) {
1168 match rules {
1169 Ruleset::Rules(rules) => {
1170 match rules.entry(rule.name.clone()) {
1171 indexmap::map::Entry::Occupied(_) => {
1172 let name = rule.name;
1173 panic!("Rule '{name}' was already present")
1174 }
1175 indexmap::map::Entry::Vacant(e) => e.insert((core_rule, rule_id)),
1176 };
1177 Ok(rule.name)
1178 }
1179 Ruleset::Combined(_) => Err(Error::CombinedRulesetError(rule.ruleset, rule.span)),
1180 }
1181 } else {
1182 Err(Error::NoSuchRuleset(rule.ruleset, rule.span))
1183 }
1184 }
1185
1186 fn eval_actions(&mut self, actions: &ResolvedActions) -> Result<(), Error> {
1187 let mut binding = IndexSet::default();
1188 let mut ctx = CoreActionContext::new(
1189 &self.type_info,
1190 &mut binding,
1191 &mut self.parser.symbol_gen,
1192 self.proof_state.original_typechecking.is_none(),
1193 );
1194 let (actions, _) = actions.to_core_actions(&mut ctx)?;
1195
1196 let mut translator = BackendRule::new(
1197 self.backend.new_rule("eval_actions", false),
1198 &self.functions,
1199 &self.type_info,
1200 true, );
1202 translator.actions(&actions)?;
1203 let id = translator.build();
1204 let result = self.backend.run_rules(&[id]);
1205 self.backend.free_rule(id);
1206
1207 match result {
1208 Ok(_) => Ok(()),
1209 Err(e) => Err(Error::BackendError(e.to_string())),
1210 }
1211 }
1212
1213 pub fn get_function_names(&self) -> Vec<String> {
1215 self.functions.keys().cloned().collect()
1216 }
1217
1218 pub fn functions_iter(&self) -> impl Iterator<Item = (&String, &Function)> {
1221 self.functions.iter()
1222 }
1223
1224 pub fn read<R>(&self, f: impl FnOnce(ReadState<'_, '_>) -> R) -> R {
1229 let registry = self.backend.action_registry().clone();
1230 let guard = registry.read().unwrap();
1231 self.backend
1232 .with_execution_state_tracked(|es| f(ReadState::wrap(es, &guard, Context::Read)))
1233 .0
1234 }
1235
1236 pub fn function_entries(
1240 &self,
1241 name: &str,
1242 f: impl FnMut(FunctionEntry<'_>),
1243 ) -> Result<(), Error> {
1244 self.read(|rs| rs.function_entries(name, f))
1245 }
1246
1247 pub fn function_entries_while(
1249 &self,
1250 name: &str,
1251 f: impl FnMut(FunctionEntry<'_>) -> bool,
1252 ) -> Result<(), Error> {
1253 self.read(|rs| rs.function_entries_while(name, f))
1254 }
1255
1256 pub fn constructor_enodes(&self, name: &str, f: impl FnMut(Enode<'_>)) -> Result<(), Error> {
1260 self.read(|rs| rs.constructor_enodes(name, f))
1261 }
1262
1263 pub fn constructor_enodes_while(
1265 &self,
1266 name: &str,
1267 f: impl FnMut(Enode<'_>) -> bool,
1268 ) -> Result<(), Error> {
1269 self.read(|rs| rs.constructor_enodes_while(name, f))
1270 }
1271
1272 pub fn clear_function(&mut self, func_name: &str) -> Result<(), Error> {
1287 let backend_id = self
1288 .functions
1289 .get(func_name)
1290 .ok_or_else(|| TypeError::UnboundFunction(func_name.to_string(), span!()))?
1291 .backend_id;
1292 self.backend.clear_table(backend_id);
1293 Ok(())
1294 }
1295
1296 pub fn eval_expr(&mut self, expr: &Expr) -> Result<(ArcSort, Value), Error> {
1298 let span = expr.span();
1299 let command = Command::Action(Action::Expr(span.clone(), expr.clone()));
1300 let resolved = self.resolve_command(command)?;
1301 if self.are_proofs_enabled() {
1302 self.proof_check_program
1303 .extend(resolved.desugared_before_proofs);
1304 }
1305 let resolved_commands = resolved.desugared;
1306
1307 assert_eq!(resolved_commands.len(), 1);
1308 let resolved_command = resolved_commands.into_iter().next().unwrap();
1309 let resolved_expr = match resolved_command {
1310 ResolvedNCommand::CoreAction(ResolvedAction::Expr(_, resolved_expr)) => resolved_expr,
1311 _ => unreachable!(),
1312 };
1313 let sort = resolved_expr.output_type();
1314 let value = self.eval_resolved_expr(span, &resolved_expr)?;
1315 Ok((sort, value))
1316 }
1317
1318 pub fn typecheck_expr_with_bindings_and_output(
1329 &mut self,
1330 expr: &Expr,
1331 bindings: &[(String, Span, ArcSort)],
1332 output_sort: ArcSort,
1333 context: Context,
1334 ) -> Result<ResolvedExpr, TypeError> {
1335 let mut binding_map = IndexMap::default();
1336 binding_map.reserve(bindings.len());
1337 for (name, span, sort) in bindings {
1338 if binding_map
1339 .insert(name.as_str(), (span.clone(), sort.clone()))
1340 .is_some()
1341 {
1342 return Err(TypeError::AlreadyDefined(name.clone(), span.clone()));
1343 }
1344 }
1345 let resolved = self.type_info.typecheck_expr_with_output(
1346 &mut self.parser.symbol_gen,
1347 expr,
1348 &binding_map,
1349 output_sort,
1350 context,
1351 )?;
1352 Ok(remove_globals::remove_globals_expr(resolved))
1353 }
1354
1355 pub fn prepare_unstable_fn_targets_for_eval(
1367 &mut self,
1368 expr: &ResolvedExpr,
1369 ) -> Result<(ResolvedExpr, Vec<(String, Value)>), Error> {
1370 let mut bindings = Vec::new();
1371 let expr = self.prepare_unstable_fn_targets_for_eval_inner(expr, &mut bindings)?;
1372 Ok((expr, bindings))
1373 }
1374
1375 fn prepare_unstable_fn_targets_for_eval_inner(
1376 &mut self,
1377 expr: &ResolvedExpr,
1378 bindings: &mut Vec<(String, Value)>,
1379 ) -> Result<ResolvedExpr, Error> {
1380 match expr {
1381 ResolvedExpr::Lit(..) | ResolvedExpr::Var(..) => Ok(expr.clone()),
1382 ResolvedExpr::Call(span, resolved_call, children) => {
1383 if let ResolvedCall::Primitive(prim) = resolved_call
1384 && prim.name() == "unstable-fn"
1385 {
1386 let Some(ResolvedExpr::Lit(target_span, Literal::String(name))) =
1387 children.first()
1388 else {
1389 return Err(Error::BackendError(format!(
1390 "{}\nunstable-fn requires a literal string function name",
1391 children
1392 .first()
1393 .map(ResolvedExpr::span)
1394 .unwrap_or_else(|| Span::Panic)
1395 )));
1396 };
1397 let panic_id = self.backend.new_panic(format!(
1398 "unstable-fn over `{name}` was applied in a context where its wrapped \
1399 function is not valid for this call site, if in a rule, add :naive."
1400 ));
1401 let resolved_function = resolve_function_container_target_with_context(
1402 &self.backend,
1403 &self.functions,
1404 &self.type_info,
1405 name,
1406 prim,
1407 panic_id,
1408 )?;
1409 let fn_value = self.backend.base_values().get(resolved_function);
1410 let binding_name = self.parser.symbol_gen.fresh("unstable_fn_target");
1411 bindings.push((binding_name.clone(), fn_value));
1412 let mut prepared_children = Vec::with_capacity(children.len());
1413 prepared_children.push(ResolvedExpr::Var(
1414 target_span.clone(),
1415 ResolvedVar {
1416 name: binding_name,
1417 sort: children[0].output_type(),
1418 is_global_ref: false,
1419 },
1420 ));
1421 for child in &children[1..] {
1422 prepared_children.push(
1423 self.prepare_unstable_fn_targets_for_eval_inner(child, bindings)?,
1424 );
1425 }
1426 return Ok(ResolvedExpr::Call(
1427 span.clone(),
1428 resolved_call.clone(),
1429 prepared_children,
1430 ));
1431 }
1432
1433 let prepared_children = children
1434 .iter()
1435 .map(|child| self.prepare_unstable_fn_targets_for_eval_inner(child, bindings))
1436 .collect::<Result<Vec<_>, _>>()?;
1437 Ok(ResolvedExpr::Call(
1438 span.clone(),
1439 resolved_call.clone(),
1440 prepared_children,
1441 ))
1442 }
1443 }
1444 }
1445
1446 fn eval_resolved_expr(&mut self, span: Span, expr: &ResolvedExpr) -> Result<Value, Error> {
1447 let unit_id = self.backend.base_values().get_ty::<()>();
1448 let unit_val = self.backend.base_values().get(());
1449
1450 let result: egglog_bridge::SideChannel<Value> = Default::default();
1451 let result_ref = result.clone();
1452 let ext_id = self
1453 .backend
1454 .register_external_func(Box::new(make_external_func(move |_es, vals| {
1455 debug_assert!(vals.len() == 1);
1456 *result_ref.lock().unwrap() = Some(vals[0]);
1457 Some(unit_val)
1458 })));
1459
1460 let mut translator = BackendRule::new(
1461 self.backend.new_rule("eval_resolved_expr", false),
1462 &self.functions,
1463 &self.type_info,
1464 true, );
1466
1467 let result_var = ResolvedVar {
1468 name: self.parser.symbol_gen.fresh("eval_resolved_expr"),
1469 sort: expr.output_type(),
1470 is_global_ref: false,
1471 };
1472 let actions = ResolvedActions::singleton(ResolvedAction::Let(
1473 span.clone(),
1474 result_var.clone(),
1475 expr.clone(),
1476 ));
1477 let mut binding = IndexSet::default();
1478 let mut ctx = CoreActionContext::new(
1479 &self.type_info,
1480 &mut binding,
1481 &mut self.parser.symbol_gen,
1482 self.proof_state.original_typechecking.is_none(),
1483 );
1484 let actions = actions.to_core_actions(&mut ctx)?.0;
1485 translator.actions(&actions)?;
1486
1487 let arg = translator.entry(&ResolvedAtomTerm::Var(span.clone(), result_var));
1488 translator.rb.call_external_func(
1489 ext_id,
1490 &[arg],
1491 egglog_bridge::ColumnTy::Base(unit_id),
1492 || "this function will never panic".to_string(),
1493 );
1494
1495 let id = translator.build();
1496 let rule_result = self.backend.run_rules(&[id]);
1497 self.backend.free_rule(id);
1498 self.backend.free_external_func(ext_id);
1499 let _ = rule_result.map_err(|e| {
1500 Error::BackendError(format!("Failed to evaluate expression '{expr}': {e}"))
1501 })?;
1502
1503 let result = result.lock().unwrap().unwrap();
1504 Ok(result)
1505 }
1506
1507 fn add_combined_ruleset(&mut self, name: String, rulesets: Vec<String>) {
1508 match self.rulesets.entry(name.clone()) {
1509 Entry::Occupied(_) => panic!("Ruleset '{name}' was already present"),
1510 Entry::Vacant(e) => e.insert(Ruleset::Combined(rulesets)),
1511 };
1512 }
1513
1514 fn add_ruleset(&mut self, name: String) {
1515 match self.rulesets.entry(name.clone()) {
1516 Entry::Occupied(_) => panic!("Ruleset '{name}' was already present"),
1517 Entry::Vacant(e) => e.insert(Ruleset::Rules(Default::default())),
1518 };
1519 }
1520
1521 fn check_facts(&mut self, span: &Span, facts: &[ResolvedFact]) -> Result<(), Error> {
1522 let fresh_name = self.parser.symbol_gen.fresh("check_facts");
1523 let fresh_ruleset = self.parser.symbol_gen.fresh("check_facts_ruleset");
1524 let rule = ast::ResolvedRule {
1525 span: span.clone(),
1526 head: ResolvedActions::default(),
1527 body: facts.to_vec(),
1528 name: fresh_name.clone(),
1529 ruleset: fresh_ruleset.clone(),
1530 eval_mode: RuleEvalMode::default(),
1531 no_decomp: false,
1532 include_subsumed: false,
1533 };
1534 let core_rule = rule.to_canonicalized_core_rule(
1535 &self.type_info,
1536 &mut self.parser.symbol_gen,
1537 self.proof_state.original_typechecking.is_none(),
1538 )?;
1539 let query = core_rule.body;
1540
1541 let ext_sc = egglog_bridge::SideChannel::default();
1542 let ext_sc_ref = ext_sc.clone();
1543 let ext_id = self
1544 .backend
1545 .register_external_func(Box::new(make_external_func(move |_, _| {
1546 *ext_sc_ref.lock().unwrap() = Some(());
1547 Some(Value::new_const(0))
1548 })));
1549
1550 let mut translator = BackendRule::new(
1551 self.backend.new_rule("check_facts", false),
1552 &self.functions,
1553 &self.type_info,
1554 true, );
1556 translator.query(&query, true);
1557 translator
1558 .rb
1559 .call_external_func(ext_id, &[], egglog_bridge::ColumnTy::Id, || {
1560 "this function will never panic".to_string()
1561 });
1562 let id = translator.build();
1563 let run_result = self.backend.run_rules(&[id]);
1564 self.backend.free_rule(id);
1565 self.backend.free_external_func(ext_id);
1566 run_result.map_err(|e| Error::BackendError(e.to_string()))?;
1567
1568 let ext_sc_val = ext_sc.lock().unwrap().take();
1569 let matched = matches!(ext_sc_val, Some(()));
1570
1571 if !matched {
1572 Err(Error::CheckError(
1573 facts.iter().map(|f| f.clone().make_unresolved()).collect(),
1574 span.clone(),
1575 ))
1576 } else {
1577 Ok(())
1578 }
1579 }
1580
1581 fn run_command(&mut self, command: ResolvedNCommand) -> Result<Vec<CommandOutput>, Error> {
1582 match command {
1583 ResolvedNCommand::Sort {
1585 name,
1586 uf,
1587 proof_func,
1588 proof_constructors,
1589 ..
1590 } => {
1591 if let Some((uf_ctor, uf_index)) = uf {
1595 self.proof_state.uf_parent.insert(name.clone(), uf_ctor);
1596 if let Some(uf_index) = uf_index {
1597 self.proof_state.uf_function.insert(name.clone(), uf_index);
1598 }
1599 }
1600 if let Some(proof_func_name) = proof_func {
1603 self.proof_state
1604 .proof_func_parent
1605 .insert(name.clone(), proof_func_name);
1606 }
1607 if let Some(pc) = proof_constructors {
1610 let names = &mut self.proof_state.proof_names;
1611 names.proof_datatype = name.clone();
1612 names.congr_constructor = pc.congr;
1613 names.eq_trans_constructor = pc.trans;
1614 names.eq_sym_constructor = pc.sym;
1615 names.container_normalize_constructor = pc.normalize;
1616 }
1617 log::info!("Declared sort {name}.")
1618 }
1619 ResolvedNCommand::Function(fdecl) => {
1620 self.declare_function(&fdecl)?;
1621 log::info!("Declared {} {}.", fdecl.subtype, fdecl.name)
1622 }
1623 ResolvedNCommand::AddRuleset(_span, name) => {
1624 self.add_ruleset(name.clone());
1625 log::info!("Declared ruleset {name}.");
1626 }
1627 ResolvedNCommand::UnstableCombinedRuleset(_span, name, others) => {
1628 self.add_combined_ruleset(name.clone(), others);
1629 log::info!("Declared ruleset {name}.");
1630 }
1631 ResolvedNCommand::NormRule { rule } => {
1632 let name = rule.name.clone();
1633 self.add_rule(rule)?;
1634 log::info!("Declared rule {name}.")
1635 }
1636 ResolvedNCommand::RunSchedule(sched) => {
1637 let report = self.run_schedule(&sched)?;
1638 log::info!("Ran schedule {sched}.");
1639 log::info!("Report: {report}");
1640 self.overall_run_report.union(report.clone());
1641 return Ok(vec![CommandOutput::RunSchedule(report)]);
1642 }
1643 ResolvedNCommand::PrintOverallStatistics(span, file) => match file {
1644 None => {
1645 log::info!("Printed overall statistics");
1646 return Ok(vec![CommandOutput::OverallStatistics(
1647 self.overall_run_report.clone(),
1648 )]);
1649 }
1650 Some(path) => {
1651 let mut file = std::fs::File::create(&path)
1652 .map_err(|e| Error::IoError(path.clone().into(), e, span.clone()))?;
1653 log::info!("Printed overall statistics to json file {path}");
1654
1655 serde_json::to_writer(&mut file, &self.overall_run_report)
1656 .expect("error serializing to json");
1657 }
1658 },
1659 ResolvedNCommand::Check(span, facts) => {
1660 self.check_facts(&span, &facts)?;
1661 log::info!("Checked fact {facts:?}.");
1662 }
1663 ResolvedNCommand::CoreAction(action) => match &action {
1664 ResolvedAction::Let(_, name, contents) => {
1665 panic!("Globals should have been desugared away: {name} = {contents}")
1666 }
1667 _ => {
1668 self.eval_actions(&ResolvedActions::new(vec![action.clone()]))?;
1669 }
1670 },
1671 ResolvedNCommand::Extract(span, expr, variants) => {
1672 let sort = expr.output_type();
1673
1674 let x = self.eval_resolved_expr(span.clone(), &expr)?;
1675 let n = self.eval_resolved_expr(span, &variants)?;
1676 let n: i64 = self.backend.base_values().unwrap(n);
1677
1678 let mut termdag = TermDag::default();
1679
1680 let extractor = Extractor::compute_costs_from_rootsorts(
1681 Some(vec![sort]),
1682 self,
1683 TreeAdditiveCostModel::default(),
1684 );
1685 return if n == 0 {
1686 if let Some((cost, term)) = extractor.extract_best(self, &mut termdag, x) {
1687 if log_enabled!(Level::Info) {
1689 log::info!("extracted with cost {cost}: {}", termdag.to_string(term));
1690 }
1691 Ok(vec![CommandOutput::ExtractBest(termdag, cost, term)])
1692 } else {
1693 Err(Error::ExtractError(
1694 "Unable to find any valid extraction (likely due to subsume or delete)"
1695 .to_string(),
1696 ))
1697 }
1698 } else {
1699 if n < 0 {
1700 panic!("Cannot extract negative number of variants");
1701 }
1702 let terms: Vec<TermId> = extractor
1703 .extract_variants(self, &mut termdag, x, n as usize)
1704 .iter()
1705 .map(|e| e.1)
1706 .collect();
1707 if log_enabled!(Level::Info) {
1708 let expr_str = expr.to_string();
1709 log::info!("extracted {} variants for {expr_str}", terms.len());
1710 }
1711 Ok(vec![CommandOutput::ExtractVariants(termdag, terms)])
1712 };
1713 }
1714 ResolvedNCommand::Push(n) => {
1715 (0..n).for_each(|_| self.push());
1716 log::info!("Pushed {n} levels.")
1717 }
1718 ResolvedNCommand::Pop(span, n) => {
1719 for _ in 0..n {
1720 self.pop().map_err(|err| {
1721 if let Error::Pop(_) = err {
1722 Error::Pop(span.clone())
1723 } else {
1724 err
1725 }
1726 })?;
1727 }
1728 log::info!("Popped {n} levels.")
1729 }
1730 ResolvedNCommand::PrintFunction(span, f, n, file, mode) => {
1731 let file = file
1732 .map(|file| {
1733 std::fs::File::create(&file)
1734 .map_err(|e| Error::IoError(file.into(), e, span.clone()))
1735 })
1736 .transpose()?;
1737 return self
1738 .print_function(&f, n, file, mode)
1739 .map_err(|e| match e {
1740 Error::TypeError(TypeError::UnboundFunction(f, _)) => {
1741 Error::TypeError(TypeError::UnboundFunction(f, span.clone()))
1742 }
1743 _ => e,
1745 })
1746 .map(|opt| opt.into_iter().collect());
1747 }
1748 ResolvedNCommand::PrintSize(span, f) => {
1749 let res = self.print_size(f.as_deref()).map_err(|e| match e {
1750 Error::TypeError(TypeError::UnboundFunction(f, _)) => {
1751 Error::TypeError(TypeError::UnboundFunction(f, span.clone()))
1752 }
1753 _ => e,
1755 })?;
1756 return Ok(vec![res]);
1757 }
1758 ResolvedNCommand::Fail(span, c) => {
1759 let result = self.run_command(*c);
1760 if let Err(e) = result {
1761 log::info!("Command failed as expected: {e}");
1762 } else {
1763 return Err(Error::ExpectFail(span));
1764 }
1765 }
1766 ResolvedNCommand::Input {
1767 span: _,
1768 name,
1769 file,
1770 } => {
1771 self.input_file(&name, file)?;
1772 }
1773 ResolvedNCommand::Output { span, file, exprs } => {
1774 let mut filename = self.fact_directory.clone().unwrap_or_default();
1775 filename.push(file.as_str());
1776 let mut f = File::options()
1778 .append(true)
1779 .create(true)
1780 .open(&filename)
1781 .map_err(|e| Error::IoError(filename.clone(), e, span.clone()))?;
1782
1783 let extractor = Extractor::compute_costs_from_rootsorts(
1784 None,
1785 self,
1786 TreeAdditiveCostModel::default(),
1787 );
1788 let mut termdag: TermDag = Default::default();
1789
1790 use std::io::Write;
1791 for expr in exprs {
1792 let value = self.eval_resolved_expr(span.clone(), &expr)?;
1793 let expr_type = expr.output_type();
1794
1795 let term = extractor
1796 .extract_best_with_sort(self, &mut termdag, value, expr_type)
1797 .unwrap()
1798 .1;
1799 writeln!(f, "{}", termdag.to_string(term))
1800 .map_err(|e| Error::IoError(filename.clone(), e, span.clone()))?;
1801 }
1802
1803 log::info!("Output to '{filename:?}'.")
1804 }
1805 ResolvedNCommand::UserDefined(_span, name, exprs) => {
1806 let command = self
1807 .commands
1808 .get(&name)
1809 .ok_or_else(|| {
1810 NotFoundError(format!("Unrecognized user-defined command: {name}"))
1811 })?
1812 .clone();
1813 return command.update(self, &exprs);
1814 }
1815
1816 ResolvedNCommand::ProveExists(span, resolved_call) => {
1817 let mut instrument = ProofInstrumentor { egraph: self };
1818 let (proof_store, proof_id) =
1819 instrument
1820 .prove_exists(&resolved_call)
1821 .map_err(|error| Error::ProofError {
1822 span: span.clone(),
1823 error,
1824 })?;
1825 return Ok(vec![CommandOutput::ProveExists {
1826 proof_store,
1827 proof_id,
1828 }]);
1829 }
1830 };
1831
1832 Ok(vec![])
1833 }
1834
1835 fn input_file(&mut self, func_name: &str, file: String) -> Result<(), Error> {
1836 let function_type = self
1837 .type_info
1838 .get_func_type(func_name)
1839 .unwrap_or_else(|| panic!("Unrecognized function name {func_name}"));
1840 let func = self.functions.get_mut(func_name).unwrap();
1841
1842 let mut filename = self.fact_directory.clone().unwrap_or_default();
1843 filename.push(file.as_str());
1844
1845 for t in &func.schema.input {
1848 match t.name() {
1849 "i64" | "f64" | "String" => {}
1850 s => panic!("Unsupported type {s} for input"),
1851 }
1852 }
1853
1854 if function_type.subtype != FunctionSubtype::Constructor {
1855 match func.schema.output.name() {
1856 "i64" | "String" | "Unit" => {}
1857 s => panic!("Unsupported type {s} for input"),
1858 }
1859 }
1860
1861 log::info!("Opening file '{filename:?}'...");
1862 let mut f = File::open(filename).unwrap();
1863 let mut contents = String::new();
1864 f.read_to_string(&mut contents).unwrap();
1865
1866 let mut parsed_contents: Vec<Vec<Value>> = Vec::with_capacity(contents.lines().count());
1868
1869 let mut row_schema = func.schema.input.clone();
1870 if function_type.subtype == FunctionSubtype::Custom {
1871 row_schema.push(func.schema.output.clone());
1872 }
1873
1874 log::debug!("{row_schema:?}");
1875
1876 let unit_val = self.backend.base_values().get(());
1877
1878 for line in contents.lines() {
1879 let mut it = line.split('\t').map(|s| s.trim());
1880
1881 let mut row: Vec<Value> = Vec::with_capacity(row_schema.len());
1882
1883 for sort in row_schema.iter() {
1884 if let Some(raw) = it.next() {
1885 let val = match sort.name() {
1886 "i64" => {
1887 if let Ok(i) = raw.parse::<i64>() {
1888 self.backend.base_values().get(i)
1889 } else {
1890 return Err(Error::InputFileFormatError(file));
1891 }
1892 }
1893 "f64" => {
1894 if let Ok(f) = raw.parse::<f64>() {
1895 self.backend
1896 .base_values()
1897 .get::<F>(core_relations::Boxed::new(f.into()))
1898 } else {
1899 return Err(Error::InputFileFormatError(file));
1900 }
1901 }
1902 "String" => self.backend.base_values().get::<S>(raw.to_string().into()),
1903 "Unit" => unit_val,
1904 _ => panic!("Unreachable"),
1905 };
1906 row.push(val);
1907 } else {
1908 break;
1909 }
1910 }
1911
1912 if row.is_empty() {
1913 continue;
1914 }
1915
1916 if row.len() != row_schema.len() || it.next().is_some() {
1917 return Err(Error::InputFileFormatError(file));
1918 }
1919
1920 parsed_contents.push(row);
1921 }
1922
1923 log::debug!("Successfully loaded file.");
1924
1925 let num_facts = parsed_contents.len();
1926
1927 let table_action = egglog_bridge::TableAction::new(&self.backend, func.backend_id);
1928
1929 if function_type.subtype != FunctionSubtype::Constructor {
1930 self.backend.with_execution_state(|es| {
1931 for row in parsed_contents.iter() {
1932 table_action.insert(es, row.iter().copied());
1933 }
1934 Some(unit_val)
1935 });
1936 } else {
1937 self.backend.with_execution_state(|es| {
1938 for row in parsed_contents.iter() {
1939 table_action.lookup_or_insert(es, row);
1942 }
1943 Some(unit_val)
1944 });
1945 }
1946
1947 self.backend.flush_updates();
1948
1949 log::info!("Read {num_facts} facts into {func_name} from '{file}'.");
1950 Ok(())
1951 }
1952
1953 pub fn are_proofs_enabled(&self) -> bool {
1955 self.proof_state.proofs_enabled
1956 }
1957
1958 fn resolve_command_before_proofs(
1959 &mut self,
1960 command: Command,
1961 ) -> Result<Vec<ResolvedNCommand>, Error> {
1962 let desugared = desugar_command(command, &mut self.parser, self.proof_state.proof_testing)?;
1963 if let Some(original_typechecking) = self.proof_state.original_typechecking.as_mut() {
1964 let typechecked = original_typechecking.typecheck_program(&desugared)?;
1967
1968 for command in &typechecked {
1969 if let Err(reason) = command_supports_proof_encoding(
1970 &command.to_command(),
1971 &original_typechecking.type_info,
1972 ) {
1973 let command_text = format!("{}", command.to_command());
1974 return Err(Error::UnsupportedProofCommand {
1975 command: command_text,
1976 reason,
1977 });
1978 }
1979 }
1980
1981 Ok(proof_form(typechecked, &mut self.parser.symbol_gen))
1982 } else {
1983 let mut typechecked = self.typecheck_program(&desugared)?;
1984
1985 typechecked = remove_globals::remove_globals(typechecked, &mut self.parser.symbol_gen);
1986 for command in &typechecked {
1987 self.names.check_shadowing(command)?;
1988 }
1989 Ok(typechecked)
1990 }
1991 }
1992
1993 fn resolve_command(&mut self, command: Command) -> Result<ResolvedNCommands, Error> {
1997 let resolved_before_proofs = self.resolve_command_before_proofs(command)?;
1998
1999 if self.proof_state.original_typechecking.is_none() {
2001 Ok(ResolvedNCommands {
2002 desugared: resolved_before_proofs,
2003 desugared_before_proofs: vec![],
2004 })
2005 } else {
2006 let typechecked_no_globals = proof_global_remover::remove_globals(
2008 resolved_before_proofs.clone(),
2009 &mut self.parser.symbol_gen,
2010 );
2011 for command in &typechecked_no_globals {
2012 self.names.check_shadowing(command)?;
2013 }
2014
2015 let term_encoding_added =
2016 ProofInstrumentor::add_term_encoding(self, typechecked_no_globals);
2017 let mut new_typechecked = vec![];
2018 for new_cmd in term_encoding_added {
2019 let desugared =
2020 desugar_command(new_cmd, &mut self.parser, self.proof_state.proof_testing)?;
2021 for cmd in &desugared {
2022 log::trace!("Desugared term encoding: {}", cmd.to_command());
2023 }
2024
2025 let desugared_typechecked = self.typecheck_program(&desugared)?;
2027 let desugared_typechecked = remove_globals::remove_globals(
2029 desugared_typechecked,
2030 &mut self.parser.symbol_gen,
2031 );
2032
2033 new_typechecked.extend(desugared_typechecked);
2034 }
2035 Ok(ResolvedNCommands {
2036 desugared: new_typechecked,
2037 desugared_before_proofs: resolved_before_proofs,
2038 })
2039 }
2040 }
2041
2042 fn process_program_internal(
2045 &mut self,
2046 program: Vec<Command>,
2047 run_commands: bool,
2048 ) -> Result<ResolvedNCommandsWithOutput, Error> {
2049 let mut outputs = Vec::new();
2050 let mut desugared_before_proofs = Vec::new();
2051 let mut desugared = Vec::new();
2052
2053 for before_expanded_command in program {
2054 let macro_type_info = self
2057 .proof_state
2058 .original_typechecking
2059 .as_ref()
2060 .map(|egraph| &egraph.type_info)
2061 .unwrap_or(&self.type_info);
2062 let macro_expanded = self.command_macros.apply(
2063 before_expanded_command,
2064 &mut self.parser.symbol_gen,
2065 macro_type_info,
2066 )?;
2067
2068 for command in macro_expanded {
2069 if let Command::Include(span, file) = &command {
2071 let s = std::fs::read_to_string(file)
2072 .map_err(|e| Error::IoError(file.clone().into(), e, span.clone()))?;
2073 let included_program = self
2074 .parser
2075 .get_program_from_string(Some(file.clone()), &s)?;
2076 let resolved = self.process_program_internal(included_program, run_commands)?;
2078 outputs.extend(resolved.outputs);
2079 desugared.extend(resolved.resolved);
2080 desugared_before_proofs.extend(resolved.resolved_before_proofs);
2081 } else {
2082 let resolved = self.resolve_command(command)?;
2083 if run_commands && self.are_proofs_enabled() {
2084 self.proof_check_program
2085 .extend(resolved.desugared_before_proofs.clone());
2086 }
2087
2088 desugared_before_proofs.extend(resolved.desugared_before_proofs);
2089 desugared.extend(resolved.desugared.clone());
2090
2091 for processed in resolved.desugared {
2092 if run_commands
2094 || matches!(
2095 processed,
2096 ResolvedNCommand::Push(_) | ResolvedNCommand::Pop(_, _)
2097 )
2098 {
2099 let result = self.run_command(processed)?;
2100 outputs.extend(result);
2101 }
2102 }
2103 }
2104 }
2105 }
2106
2107 Ok(ResolvedNCommandsWithOutput {
2108 outputs,
2109 resolved_before_proofs: desugared_before_proofs,
2110 resolved: desugared,
2111 })
2112 }
2113
2114 pub fn run_program(&mut self, program: Vec<Command>) -> Result<Vec<CommandOutput>, Error> {
2117 let res = self.process_program_internal(program, true)?;
2118 Ok(res.outputs)
2119 }
2120
2121 pub fn resolve_program(
2125 &mut self,
2126 filename: Option<String>,
2127 input: &str,
2128 ) -> Result<Vec<ResolvedCommand>, Error> {
2129 let parsed = self.parser.get_program_from_string(filename, input)?;
2130 let res = self.process_program_internal(parsed, false)?;
2131 Ok(res.resolved.into_iter().map(|c| c.to_command()).collect())
2132 }
2133
2134 pub fn parse_program(
2136 &mut self,
2137 filename: Option<String>,
2138 input: &str,
2139 ) -> Result<Vec<Command>, Error> {
2140 let parsed = self.parser.get_program_from_string(filename, input)?;
2141 Ok(parsed)
2142 }
2143
2144 pub fn parse_and_run_program(
2150 &mut self,
2151 filename: Option<String>,
2152 input: &str,
2153 ) -> Result<Vec<CommandOutput>, Error> {
2154 let parsed = self.parser.get_program_from_string(filename, input)?;
2155 self.run_program(parsed)
2156 }
2157
2158 pub fn num_tuples(&self) -> usize {
2161 self.functions
2162 .values()
2163 .map(|f| self.backend.table_size(f.backend_id))
2164 .sum()
2165 }
2166
2167 pub fn get_sort<S: Sort>(&self) -> Arc<S> {
2169 self.type_info.get_sort()
2170 }
2171
2172 pub fn get_sort_by<S: Sort>(&self, f: impl Fn(&Arc<S>) -> bool) -> Arc<S> {
2174 self.type_info.get_sort_by(f)
2175 }
2176
2177 pub fn get_sorts<S: Sort>(&self) -> Vec<Arc<S>> {
2179 self.type_info.get_sorts()
2180 }
2181
2182 pub fn get_sorts_by<S: Sort>(&self, f: impl Fn(&Arc<S>) -> bool) -> Vec<Arc<S>> {
2184 self.type_info.get_sorts_by(f)
2185 }
2186
2187 pub fn get_arcsort_by(&self, f: impl Fn(&ArcSort) -> bool) -> ArcSort {
2189 self.type_info.get_arcsort_by(f)
2190 }
2191
2192 pub fn get_arcsort_for_value_type<T: 'static>(&self) -> ArcSort {
2194 self.type_info.get_arcsort_for_value_type::<T>()
2195 }
2196
2197 pub fn get_arcsorts_by(&self, f: impl Fn(&ArcSort) -> bool) -> Vec<ArcSort> {
2199 self.type_info.get_arcsorts_by(f)
2200 }
2201
2202 pub fn get_sort_by_name(&self, sym: &str) -> Option<&ArcSort> {
2204 self.type_info.get_sort_by_name(sym)
2205 }
2206
2207 pub fn get_overall_run_report(&self) -> &RunReport {
2209 &self.overall_run_report
2210 }
2211
2212 pub fn value_to_base<T: BaseValue>(&self, x: Value) -> T {
2215 self.backend.base_values().unwrap::<T>(x)
2216 }
2217
2218 pub fn base_to_value<T: BaseValue>(&self, x: T) -> Value {
2220 self.backend.base_values().get::<T>(x)
2221 }
2222
2223 pub fn value_to_container<T: ContainerValue>(
2230 &self,
2231 x: Value,
2232 ) -> Option<impl Deref<Target = T>> {
2233 self.backend.container_values().get_val::<T>(x)
2234 }
2235
2236 pub fn container_to_value<T: ContainerValue>(&mut self, x: T) -> Value {
2238 self.backend.with_execution_state(|state| {
2239 self.backend.container_values().register_val::<T>(x, state)
2240 })
2241 }
2242
2243 pub fn get_size(&self, func: &str) -> usize {
2247 let function_id = self.functions.get(func).unwrap().backend_id;
2248 self.backend.table_size(function_id)
2249 }
2250
2251 pub fn get_function(&self, name: &str) -> Option<&Function> {
2255 self.functions.get(name)
2256 }
2257
2258 pub fn has_command(&self, name: &str) -> bool {
2261 self.commands.contains_key(name)
2262 }
2263
2264 pub fn run_user_defined_command(
2271 &mut self,
2272 name: &str,
2273 args: &[Expr],
2274 ) -> Result<Vec<CommandOutput>, Error> {
2275 self.run_command(ResolvedNCommand::UserDefined(
2276 span!(),
2277 name.to_string(),
2278 args.to_vec(),
2279 ))
2280 }
2281
2282 pub fn set_report_level(&mut self, level: ReportLevel) {
2284 self.backend.set_report_level(level);
2285 }
2286
2287 pub fn dump_debug_info(&self) {
2291 self.backend.dump_debug_info();
2292 }
2293
2294 pub fn update<R>(
2328 &mut self,
2329 f: impl FnOnce(FullState<'_, '_>) -> Result<R, Error>,
2330 ) -> Result<R, Error> {
2331 if self.are_proofs_enabled() {
2332 return Err(Error::ProofsIncompatibleApi {
2333 api: "EGraph::update",
2334 reason: "writes inside the closure bypass the proof-encoding pipeline,\n\
2335 so any rule derivations resting on them would be unverifiable.",
2336 });
2337 }
2338 self.update_unchecked(f)
2339 }
2340
2341 pub(crate) fn update_unchecked<R>(
2345 &mut self,
2346 f: impl FnOnce(FullState<'_, '_>) -> Result<R, Error>,
2347 ) -> Result<R, Error> {
2348 let registry = self.backend.action_registry().clone();
2349 let guard = registry.read().unwrap();
2350 let (result, changed) = self
2351 .backend
2352 .with_execution_state_tracked(|es| f(FullState::wrap(es, &guard, Context::Full)));
2353 drop(guard);
2354 if changed {
2359 self.backend.flush_updates();
2360 }
2361 result
2362 }
2363
2364 pub fn query(
2371 &mut self,
2372 vars: &[(&str, ArcSort)],
2373 facts: ast::Facts<String, String>,
2374 ) -> Result<Vec<HashMap<String, Value>>, Error> {
2375 if self.are_proofs_enabled() {
2379 return Err(Error::ProofsIncompatibleApi {
2380 api: "EGraph::query",
2381 reason: "the underlying rust_rule callback has no proof-encoding validator,\n\
2382 so query matches cannot be verified.",
2383 });
2384 }
2385 use std::sync::{Arc, Mutex};
2386 let names: Arc<[String]> = vars.iter().map(|(n, _)| (*n).to_owned()).collect();
2387 let results: Arc<Mutex<Vec<HashMap<String, Value>>>> = Arc::new(Mutex::new(Vec::new()));
2388 let results_weak = Arc::downgrade(&results);
2389 let names_for_cb = names.clone();
2390
2391 let ruleset = self.parser.symbol_gen.fresh("query_ruleset");
2392 prelude::add_ruleset(self, &ruleset)?;
2393 let outcome = (|| -> Result<_, Error> {
2397 prelude::rust_rule(self, "query", &ruleset, vars, facts, move |_, values| {
2398 let arc = results_weak.upgrade().unwrap();
2399 let mut results = arc.lock().unwrap();
2400 let map: HashMap<String, Value> = names_for_cb
2401 .iter()
2402 .zip(values.iter().copied())
2403 .map(|(n, v)| (n.clone(), v))
2404 .collect();
2405 results.push(map);
2406 Some(())
2407 })?;
2408 prelude::run_ruleset(self, &ruleset)?;
2409 Ok(())
2410 })();
2411
2412 if let Some(Ruleset::Rules(rules)) = self.rulesets.swap_remove(&ruleset) {
2415 for (_, rule) in rules {
2416 self.backend.free_rule(rule.1);
2417 }
2418 }
2419 outcome?;
2420
2421 let Some(mutex) = Arc::into_inner(results) else {
2422 panic!("`results_weak` outlived the callback");
2423 };
2424 Ok(mutex.into_inner().unwrap())
2425 }
2426}
2427
2428pub use crate::api::{ApiError, FromValue, FromValues, IntoValue, IntoValues, RawValues};
2429
2430fn resolve_function_container_target_with_context(
2437 backend: &egglog_bridge::EGraph,
2438 functions: &IndexMap<String, Function>,
2439 type_info: &TypeInfo,
2440 name: &str,
2441 primitive: &core::SpecializedPrimitive,
2442 panic_id: ExternalFunctionId,
2443) -> Result<ResolvedFunction, Error> {
2444 let target_function = type_info
2445 .get_sorts::<FunctionSort>()
2446 .into_iter()
2447 .find(|function| function.name() == primitive.output().name())
2448 .ok_or_else(|| {
2449 Error::BackendError(format!(
2450 "`unstable-fn` output sort `{}` is not a function sort",
2451 primitive.output().name()
2452 ))
2453 })?;
2454
2455 let partial_arcsorts: Vec<_> = primitive.input().iter().skip(1).cloned().collect();
2456 let remaining_inputs = target_function.inputs();
2457 let output = target_function.output();
2458
2459 let id = if let Some(func) = functions.get(name) {
2460 let func_type = type_info
2461 .get_func_type(name)
2462 .ok_or_else(|| Error::BackendError(format!("No resolution for {name:?}")))?;
2463 let expected_inputs = partial_arcsorts
2464 .iter()
2465 .chain(remaining_inputs)
2466 .collect::<Vec<_>>();
2467 let inputs_match = func_type.input.len() == expected_inputs.len()
2468 && func_type
2469 .input
2470 .iter()
2471 .zip(&expected_inputs)
2472 .all(|(actual, expected)| actual.name() == expected.name());
2473 if !inputs_match || func_type.output.name() != output.name() {
2474 let expected_input_names = expected_inputs
2475 .iter()
2476 .map(|sort| sort.name())
2477 .collect::<Vec<_>>()
2478 .join(", ");
2479 let actual_input_names = func_type
2480 .input
2481 .iter()
2482 .map(|sort| sort.name())
2483 .collect::<Vec<_>>()
2484 .join(", ");
2485 return Err(Error::BackendError(format!(
2486 "function container lookup for `{name}` expected ({}) -> {}, found ({}) -> {}",
2487 expected_input_names,
2488 output.name(),
2489 actual_input_names,
2490 func_type.output.name(),
2491 )));
2492 }
2493
2494 let action = egglog_bridge::TableAction::new(backend, func.backend_id);
2495 match func_type.subtype {
2496 ast::FunctionSubtype::Constructor => ResolvedFunctionId::Constructor(action),
2497 ast::FunctionSubtype::Custom => ResolvedFunctionId::Function(action),
2498 }
2499 } else if let Some(primitives) = type_info.get_prims(name) {
2500 let signature: Vec<_> = partial_arcsorts
2501 .iter()
2502 .chain(remaining_inputs)
2503 .chain(once(&output))
2504 .cloned()
2505 .collect();
2506 let candidates: Vec<_> = primitives
2507 .iter()
2508 .filter(|primitive| primitive.accept(&signature, type_info))
2509 .collect();
2510 let mut context_ids = enum_map::EnumMap::from_fn(|_| None);
2511 for runtime_ctx in Context::ALL {
2512 let mut ids = candidates
2513 .iter()
2514 .filter_map(|primitive| primitive.context_ids[runtime_ctx]);
2515 match (ids.next(), ids.next()) {
2518 (None, _) => {}
2519 (Some(id), None) => context_ids[runtime_ctx] = Some(id),
2520 (Some(_), Some(_)) => {
2521 return Err(Error::BackendError(format!(
2522 "Ambiguous primitive resolution for {name:?} in unstable-fn context {runtime_ctx:?}"
2523 )));
2524 }
2525 }
2526 }
2527 if !context_ids.iter().any(|(_, id)| id.is_some()) {
2528 let (output_sort, input_sorts) = signature
2529 .split_last()
2530 .expect("primitive signature should include an output sort");
2531 let input_names = input_sorts
2532 .iter()
2533 .map(|sort| sort.name())
2534 .collect::<Vec<_>>()
2535 .join(", ");
2536 return Err(Error::BackendError(format!(
2537 "no primitive overload matched expected signature for {name:?}: ({}) -> {}; \
2538 context ids: {context_ids:?}",
2539 input_names,
2540 output_sort.name(),
2541 )));
2542 }
2543 ResolvedFunctionId::Primitive { context_ids }
2544 } else {
2545 return Err(Error::BackendError(format!("No resolution for {name:?}")));
2546 };
2547
2548 Ok(ResolvedFunction {
2549 id,
2550 partial_arcsorts,
2551 name: name.to_owned(),
2552 panic_id,
2553 })
2554}
2555
2556struct BackendRule<'a> {
2557 rb: egglog_bridge::RuleBuilder<'a>,
2558 entries: HashMap<core::ResolvedAtomTerm, QueryEntry>,
2559 functions: &'a IndexMap<String, Function>,
2560 type_info: &'a TypeInfo,
2561 requires_read_context: bool,
2566}
2567
2568impl<'a> BackendRule<'a> {
2569 fn new(
2570 rb: egglog_bridge::RuleBuilder<'a>,
2571 functions: &'a IndexMap<String, Function>,
2572 type_info: &'a TypeInfo,
2573 requires_read_context: bool,
2574 ) -> BackendRule<'a> {
2575 BackendRule {
2576 rb,
2577 functions,
2578 type_info,
2579 requires_read_context,
2580 entries: Default::default(),
2581 }
2582 }
2583
2584 fn query_context(&self) -> crate::Context {
2591 if self.requires_read_context {
2592 crate::Context::Read
2593 } else {
2594 crate::Context::Pure
2595 }
2596 }
2597
2598 fn action_context(&self) -> crate::Context {
2604 if self.requires_read_context {
2605 crate::Context::Full
2606 } else {
2607 crate::Context::Write
2608 }
2609 }
2610
2611 fn entry(&mut self, x: &core::ResolvedAtomTerm) -> QueryEntry {
2612 self.entries
2613 .entry(x.clone())
2614 .or_insert_with(|| match x {
2615 core::GenericAtomTerm::Var(_, v) => self
2616 .rb
2617 .new_var_named(v.sort.column_ty(self.rb.egraph()), &v.name),
2618 core::GenericAtomTerm::Literal(_, l) => literal_to_entry(self.rb.egraph(), l),
2619 core::GenericAtomTerm::Global(..) => {
2620 panic!("Globals should have been desugared")
2621 }
2622 })
2623 .clone()
2624 }
2625
2626 fn func(&self, f: &typechecking::FuncType) -> egglog_bridge::FunctionId {
2627 self.functions[&f.name].backend_id
2628 }
2629
2630 fn prim(
2631 &mut self,
2632 prim: &core::SpecializedPrimitive,
2633 args: &[core::ResolvedAtomTerm],
2634 ctx: crate::Context,
2635 ) -> (ExternalFunctionId, Vec<QueryEntry>, ColumnTy) {
2636 let resolved_id = prim.external_id(ctx);
2640
2641 let mut qe_args = self.args(args);
2642
2643 if prim.name() == "unstable-fn" {
2644 let core::ResolvedAtomTerm::Literal(_, Literal::String(ref name)) = args[0] else {
2645 panic!("expected string literal after `unstable-fn`")
2646 };
2647 let panic_id = self.rb.new_panic(format!(
2653 "unstable-fn over `{name}` was applied in a context where its wrapped \
2654 function is not valid for this call site, if in a rule, add :naive."
2655 ));
2656 let resolved = resolve_function_container_target_with_context(
2657 self.rb.egraph(),
2658 self.functions,
2659 self.type_info,
2660 name,
2661 prim,
2662 panic_id,
2663 )
2664 .unwrap_or_else(|err| panic!("{err}"));
2665
2666 qe_args[0] = self.rb.egraph().base_value_constant(resolved);
2667 }
2668
2669 (
2670 resolved_id,
2671 qe_args,
2672 prim.output().column_ty(self.rb.egraph()),
2673 )
2674 }
2675
2676 fn args<'b>(
2677 &mut self,
2678 args: impl IntoIterator<Item = &'b core::ResolvedAtomTerm>,
2679 ) -> Vec<QueryEntry> {
2680 args.into_iter().map(|x| self.entry(x)).collect()
2681 }
2682
2683 fn query(&mut self, query: &core::Query<ResolvedCall, ResolvedVar>, include_subsumed: bool) {
2684 for atom in &query.atoms {
2685 match &atom.head {
2686 ResolvedCall::Func(f) => {
2687 let f = self.func(f);
2688 let args = self.args(&atom.args);
2689 let is_subsumed = match include_subsumed {
2690 true => None,
2691 false => Some(false),
2692 };
2693 self.rb.query_table(f, &args, is_subsumed).unwrap();
2694 }
2695 ResolvedCall::Primitive(p) => {
2696 let ctx = self.query_context();
2697 let (p, args, ty) = self.prim(p, &atom.args, ctx);
2698 self.rb.query_prim(p, &args, ty).unwrap()
2699 }
2700 }
2701 }
2702 }
2703
2704 fn actions(&mut self, actions: &core::ResolvedCoreActions) -> Result<(), Error> {
2705 for action in &actions.0 {
2706 match action {
2707 core::GenericCoreAction::Let(span, v, f, args) => {
2708 let v = core::GenericAtomTerm::Var(span.clone(), v.clone());
2709 let y = match f {
2710 ResolvedCall::Func(f) => {
2711 let name = f.name.clone();
2712 let f = self.func(f);
2713 let args = self.args(args);
2714 let span = span.clone();
2715 self.rb.lookup(f, &args, move || {
2716 format!("{span}: lookup of function {name} failed")
2717 })
2718 }
2719 ResolvedCall::Primitive(p) => {
2720 let name = p.name().to_owned();
2721 let ctx = self.action_context();
2722 let (p, args, ty) = self.prim(p, args, ctx);
2723 let span = span.clone();
2724 self.rb.call_external_func(p, &args, ty, move || {
2725 format!("{span}: call of primitive {name} failed")
2726 })
2727 }
2728 };
2729 self.entries.insert(v, y.into());
2730 }
2731 core::GenericCoreAction::LetAtomTerm(span, v, x) => {
2732 let v = core::GenericAtomTerm::Var(span.clone(), v.clone());
2733 let x = self.entry(x);
2734 self.entries.insert(v, x);
2735 }
2736 core::GenericCoreAction::Set(_, f, xs, y) => match f {
2737 ResolvedCall::Primitive(..) => panic!("runtime primitive set!"),
2738 ResolvedCall::Func(f) => {
2739 let f = self.func(f);
2740 let args = self.args(xs.iter().chain([y]));
2741 self.rb.set(f, &args)
2742 }
2743 },
2744 core::GenericCoreAction::Change(span, change, f, args) => match f {
2745 ResolvedCall::Primitive(..) => panic!("runtime primitive change!"),
2746 ResolvedCall::Func(f) => {
2747 let name = f.name.clone();
2748 let can_subsume = self.functions[&f.name].can_subsume;
2749 let f = self.func(f);
2750 let args = self.args(args);
2751 match change {
2752 Change::Delete => self.rb.remove(f, &args),
2753 Change::Subsume if can_subsume => self.rb.subsume(f, &args),
2754 Change::Subsume => {
2755 return Err(Error::SubsumeMergeError(name, span.clone()));
2756 }
2757 }
2758 }
2759 },
2760 core::GenericCoreAction::Union(_, x, y) => {
2761 let x = self.entry(x);
2762 let y = self.entry(y);
2763 self.rb.union(x, y)
2764 }
2765 core::GenericCoreAction::Panic(_, message) => self.rb.panic(message.clone()),
2766 }
2767 }
2768 Ok(())
2769 }
2770
2771 fn build(self) -> egglog_bridge::RuleId {
2772 self.rb.build()
2773 }
2774}
2775
2776fn literal_to_entry(egraph: &egglog_bridge::EGraph, l: &Literal) -> QueryEntry {
2777 match l {
2778 Literal::Int(x) => egraph.base_value_constant::<i64>(*x),
2779 Literal::Float(x) => egraph.base_value_constant::<sort::F>(x.into()),
2780 Literal::String(x) => egraph.base_value_constant::<sort::S>(sort::S::new(x.clone())),
2781 Literal::Bool(x) => egraph.base_value_constant::<bool>(*x),
2782 Literal::Unit => egraph.base_value_constant::<()>(()),
2783 }
2784}
2785
2786fn literal_to_value(egraph: &egglog_bridge::EGraph, l: &Literal) -> Value {
2787 match l {
2788 Literal::Int(x) => egraph.base_values().get::<i64>(*x),
2789 Literal::Float(x) => egraph.base_values().get::<sort::F>(x.into()),
2790 Literal::String(x) => egraph.base_values().get::<sort::S>(sort::S::new(x.clone())),
2791 Literal::Bool(x) => egraph.base_values().get::<bool>(*x),
2792 Literal::Unit => egraph.base_values().get::<()>(()),
2793 }
2794}
2795
2796#[derive(Debug, Error)]
2797pub enum Error {
2798 #[error(transparent)]
2799 ParseError(#[from] ParseError),
2800 #[error(transparent)]
2801 NotFoundError(#[from] NotFoundError),
2802 #[error(transparent)]
2803 TypeError(#[from] TypeError),
2804 #[error(transparent)]
2805 ApiError(#[from] crate::api::ApiError),
2806 #[error("Errors:\n{}", ListDisplay(.0, "\n"))]
2807 TypeErrors(Vec<TypeError>),
2808 #[error("{}\nCheck failed: \n{}", .1, ListDisplay(.0, "\n"))]
2809 CheckError(Vec<Fact>, Span),
2810 #[error("{1}\nNo such ruleset: {0}")]
2811 NoSuchRuleset(String, Span),
2812 #[error(
2813 "{1}\nAttempted to add a rule to combined ruleset {0}. Combined rulesets may only depend on other rulesets."
2814 )]
2815 CombinedRulesetError(String, Span),
2816 #[error("{0}")]
2817 BackendError(String),
2818 #[error("{0}\nTried to pop too much")]
2819 Pop(Span),
2820 #[error("{0}\nCommand should have failed.")]
2821 ExpectFail(Span),
2822 #[error("{2}\nIO error: {0}: {1}")]
2823 IoError(PathBuf, std::io::Error, Span),
2824 #[error("{1}\nCannot subsume function with merge: {0}")]
2825 SubsumeMergeError(String, Span),
2826 #[error("extraction failure: {:?}", .0)]
2827 ExtractError(String),
2828 #[error("{span}\n{error}")]
2829 ProofError {
2830 span: Span,
2831 #[source]
2832 error: ProveExistsError,
2833 },
2834 #[error("{1}\n{2}\nShadowing is not allowed, but found {0}")]
2835 Shadowing(String, Span, Span),
2836 #[error("{1}\nCommand already exists: {0}")]
2837 CommandAlreadyExists(String, Span),
2838 #[error("Incorrect format in file '{0}'.")]
2839 InputFileFormatError(String),
2840 #[error(
2841 "Command is not supported by the current proof term encoding implementation.\n\
2842 Reason: {reason}\n\
2843 This typically means the command uses constructs that cannot yet be represented as proof terms.\n\
2844 Consider disabling proof term encoding for this run or rewriting the command to avoid unsupported features.\n\
2845 Offending command: {command}"
2846 )]
2847 UnsupportedProofCommand {
2848 command: String,
2849 reason: ProofEncodingUnsupportedReason,
2850 },
2851 #[error(
2852 "`{api}` is incompatible with proof mode: {reason} \
2853 Disable proofs or make the operation a command in the syntax of the egglog language and use `EGraph::parse_and_run`."
2854 )]
2855 ProofsIncompatibleApi {
2856 api: &'static str,
2857 reason: &'static str,
2858 },
2859}
2860
2861#[cfg(test)]
2862mod tests {
2863 use crate::constraint::SimpleTypeConstraint;
2864 use crate::*;
2865
2866 use crate::PureState;
2867
2868 #[derive(Clone)]
2869 struct InnerProduct {
2870 vec: ArcSort,
2871 }
2872
2873 impl Primitive for InnerProduct {
2878 fn name(&self) -> &str {
2879 "inner-product"
2880 }
2881
2882 fn get_type_constraints(&self, span: &Span) -> Box<dyn crate::constraint::TypeConstraint> {
2883 SimpleTypeConstraint::new(
2884 self.name(),
2885 vec![self.vec.clone(), self.vec.clone(), I64Sort.to_arcsort()],
2886 span.clone(),
2887 )
2888 .into_box()
2889 }
2890 }
2891
2892 impl PurePrim for InnerProduct {
2893 fn apply<'a, 'db>(&self, state: PureState<'a, 'db>, args: &[Value]) -> Option<Value> {
2894 let mut sum = 0;
2895 let vec1 = state
2896 .container_values()
2897 .get_val::<VecContainer>(args[0])
2898 .unwrap();
2899 let vec2 = state
2900 .container_values()
2901 .get_val::<VecContainer>(args[1])
2902 .unwrap();
2903 assert_eq!(vec1.data.len(), vec2.data.len());
2904 for (a, b) in vec1.data.iter().zip(vec2.data.iter()) {
2905 let a = state.base_values().unwrap::<i64>(*a);
2906 let b = state.base_values().unwrap::<i64>(*b);
2907 sum += a * b;
2908 }
2909 Some(state.base_values().get::<i64>(sum))
2910 }
2911 }
2912
2913 #[derive(Clone)]
2914 struct FullOnly;
2915
2916 impl Primitive for FullOnly {
2917 fn name(&self) -> &str {
2918 "full-only"
2919 }
2920
2921 fn get_type_constraints(&self, span: &Span) -> Box<dyn crate::constraint::TypeConstraint> {
2922 SimpleTypeConstraint::new(self.name(), vec![I64Sort.to_arcsort()], span.clone())
2923 .into_box()
2924 }
2925 }
2926
2927 impl FullPrim for FullOnly {
2928 fn apply<'a, 'db>(&self, state: FullState<'a, 'db>, _args: &[Value]) -> Option<Value> {
2929 Some(state.base_values().get::<i64>(1))
2930 }
2931 }
2932
2933 #[test]
2934 fn test_user_defined_primitive() {
2935 let mut egraph = EGraph::default();
2936 egraph
2937 .parse_and_run_program(None, "(sort IntVec (Vec i64))")
2938 .unwrap();
2939
2940 let int_vec_sort = egraph.get_arcsort_by(|s| {
2941 s.value_type() == Some(std::any::TypeId::of::<VecContainer>())
2942 && s.inner_sorts()[0].name() == I64Sort.name()
2943 });
2944
2945 egraph.add_pure_primitive(InnerProduct { vec: int_vec_sort }, None);
2946
2947 egraph
2948 .parse_and_run_program(
2949 None,
2950 "
2951 (let a (vec-of 1 2 3 4 5 6))
2952 (let b (vec-of 6 5 4 3 2 1))
2953 (check (= (inner-product a b) 56))
2954 ",
2955 )
2956 .unwrap();
2957 }
2958
2959 #[test]
2960 fn proof_support_accepts_container_sort_declarations() {
2961 let mut egraph = EGraph::default();
2962 let resolved = egraph
2963 .resolve_program(None, "(datatype X (x))\n(sort XPair (Pair X i64))")
2964 .unwrap();
2965 assert!(program_supports_proofs(&resolved, &egraph.type_info));
2966
2967 let mut egraph = EGraph::default();
2968 let resolved = egraph
2969 .resolve_program(None, "(datatype X (x))\n(sort XFn (UnstableFn (X) X))")
2970 .unwrap();
2971 assert!(program_supports_proofs(&resolved, &egraph.type_info));
2972 }
2973
2974 #[test]
2975 fn proof_support_rejects_unstable_fn_primitives_without_validators() {
2976 let mut egraph = EGraph::default();
2977 let resolved = egraph
2978 .resolve_program(
2979 None,
2980 r#"
2981 (datatype X (x))
2982 (sort XFn (UnstableFn (X) X))
2983 (function id (X) X :merge old)
2984 (let f (unstable-fn "id"))
2985 "#,
2986 )
2987 .unwrap();
2988 assert!(!program_supports_proofs(&resolved, &egraph.type_info));
2989 }
2990
2991 #[test]
2992 fn proof_support_accepts_set_primitive_validators() {
2993 let mut egraph = EGraph::default();
2994 let resolved = egraph
2995 .resolve_program(
2996 None,
2997 r#"
2998 (sort ISet (Set i64))
2999 (function Shared () ISet :merge (set-intersect old new))
3000
3001 (check (= (set-insert (set-empty) 1) (set-of 1)))
3002 (check (= (set-remove (set-of 1 2) 2) (set-of 1)))
3003 (check (= (set-length (set-of 1 2)) 2))
3004 (check (set-contains (set-of 1 2) 1))
3005 (check (set-not-contains (set-of 1 2) 3))
3006 (check (= (set-union (set-of 1) (set-of 2)) (set-of 1 2)))
3007 (check (= (set-diff (set-of 1 2) (set-of 2)) (set-of 1)))
3008 (check (= (set-intersect (set-of 1 2) (set-of 2 3)) (set-of 2)))
3009 "#,
3010 )
3011 .unwrap();
3012
3013 assert!(program_supports_proofs(&resolved, &egraph.type_info));
3014 }
3015
3016 #[test]
3019 fn proof_support_rejects_set_get() {
3020 let mut egraph = EGraph::default();
3021 let resolved = egraph
3022 .resolve_program(
3023 None,
3024 r#"
3025 (sort ISet (Set i64))
3026 (check (= (set-get (set-of 1 2) 0) 1))
3027 "#,
3028 )
3029 .unwrap();
3030
3031 assert!(!program_supports_proofs(&resolved, &egraph.type_info));
3032 }
3033
3034 #[test]
3035 fn test_typecheck_expr_with_bindings_and_output_rejects_mismatch() {
3036 let mut egraph = EGraph::default();
3037 let mut parser = crate::ast::Parser::default();
3038 let expr = parser.get_expr_from_string(None, "(+ 1 2)").unwrap();
3039
3040 let resolved = egraph
3041 .typecheck_expr_with_bindings_and_output(
3042 &expr,
3043 &[],
3044 I64Sort.to_arcsort(),
3045 Context::Pure,
3046 )
3047 .unwrap();
3048 assert_eq!(resolved.output_type().name(), I64Sort.name());
3049
3050 let err = egraph
3051 .typecheck_expr_with_bindings_and_output(
3052 &expr,
3053 &[],
3054 BoolSort.to_arcsort(),
3055 Context::Pure,
3056 )
3057 .unwrap_err();
3058 match err {
3059 TypeError::Mismatch {
3060 expected, actual, ..
3061 } => {
3062 assert_eq!(expected.name(), BoolSort.name());
3063 assert_eq!(actual.name(), I64Sort.name());
3064 }
3065 other => panic!("expected mismatch, got {other:?}"),
3066 }
3067
3068 let literal = parser.get_expr_from_string(None, "1").unwrap();
3069 let err = egraph
3070 .typecheck_expr_with_bindings_and_output(
3071 &literal,
3072 &[],
3073 BoolSort.to_arcsort(),
3074 Context::Pure,
3075 )
3076 .unwrap_err();
3077 match err {
3078 TypeError::Mismatch {
3079 expected, actual, ..
3080 } => {
3081 assert_eq!(expected.name(), BoolSort.name());
3082 assert_eq!(actual.name(), I64Sort.name());
3083 }
3084 other => panic!("expected literal mismatch, got {other:?}"),
3085 }
3086 }
3087
3088 #[test]
3089 fn test_typecheck_expr_with_bindings_and_output_uses_explicit_bindings() {
3090 let mut egraph = EGraph::default();
3091 let mut parser = crate::ast::Parser::default();
3092 let expr = parser.get_expr_from_string(None, "(+ x 2)").unwrap();
3093 let bindings = vec![("x".to_string(), span!(), I64Sort.to_arcsort())];
3094
3095 let resolved = egraph
3096 .typecheck_expr_with_bindings_and_output(
3097 &expr,
3098 &bindings,
3099 I64Sort.to_arcsort(),
3100 Context::Pure,
3101 )
3102 .unwrap();
3103
3104 assert_eq!(resolved.output_type().name(), I64Sort.name());
3105 }
3106
3107 #[test]
3108 fn test_typecheck_expr_with_bindings_and_output_uses_context() {
3109 let mut egraph = EGraph::default();
3110 egraph.add_full_primitive(FullOnly, None);
3111 let mut parser = crate::ast::Parser::default();
3112 let expr = parser.get_expr_from_string(None, "(full-only)").unwrap();
3113
3114 let resolved = egraph
3115 .typecheck_expr_with_bindings_and_output(
3116 &expr,
3117 &[],
3118 I64Sort.to_arcsort(),
3119 Context::Full,
3120 )
3121 .unwrap();
3122 assert_eq!(resolved.output_type().name(), I64Sort.name());
3123
3124 let err = egraph
3125 .typecheck_expr_with_bindings_and_output(
3126 &expr,
3127 &[],
3128 I64Sort.to_arcsort(),
3129 Context::Pure,
3130 )
3131 .unwrap_err();
3132 match err {
3133 TypeError::UnboundFunction(name, _) => assert_eq!(name, "full-only"),
3134 other => panic!("expected unbound function, got {other:?}"),
3135 }
3136 }
3137
3138 #[test]
3139 fn test_typecheck_expr_with_bindings_and_output_rejects_duplicate_bindings() {
3140 let mut egraph = EGraph::default();
3141 let mut parser = crate::ast::Parser::default();
3142 let expr = parser.get_expr_from_string(None, "x").unwrap();
3143 let bindings = vec![
3144 ("x".to_string(), span!(), I64Sort.to_arcsort()),
3145 ("x".to_string(), span!(), BoolSort.to_arcsort()),
3146 ];
3147
3148 let err = egraph
3149 .typecheck_expr_with_bindings_and_output(
3150 &expr,
3151 &bindings,
3152 I64Sort.to_arcsort(),
3153 Context::Pure,
3154 )
3155 .unwrap_err();
3156
3157 match err {
3158 TypeError::AlreadyDefined(name, _) => assert_eq!(name, "x"),
3159 other => panic!("expected duplicate binding, got {other:?}"),
3160 }
3161 }
3162
3163 #[test]
3164 fn test_typecheck_expr_with_bindings_and_output_rewrites_globals() {
3165 let mut egraph = EGraph::default();
3166 egraph.parse_and_run_program(None, "(let $x 1)").unwrap();
3167 let mut parser = crate::ast::Parser::default();
3168 let expr = parser.get_expr_from_string(None, "$x").unwrap();
3169
3170 let resolved = egraph
3171 .typecheck_expr_with_bindings_and_output(
3172 &expr,
3173 &[],
3174 I64Sort.to_arcsort(),
3175 Context::Read,
3176 )
3177 .unwrap();
3178
3179 match resolved {
3180 ResolvedExpr::Call(_, ResolvedCall::Func(func), children) => {
3181 assert_eq!(func.name, "$x");
3182 assert!(children.is_empty());
3183 assert_eq!(func.output.name(), I64Sort.name());
3184 }
3185 other => panic!("expected global function call rewrite, got {other:?}"),
3186 }
3187 }
3188
3189 #[test]
3191 fn test_egraph_send_sync() {
3192 fn is_send<T: Send>(_t: &T) -> bool {
3193 true
3194 }
3195 fn is_sync<T: Sync>(_t: &T) -> bool {
3196 true
3197 }
3198 let egraph = EGraph::default();
3199 assert!(is_send(&egraph) && is_sync(&egraph));
3200 }
3201
3202 #[test]
3203 fn test_extension_state_clones_and_restores_with_egraph() {
3204 let mut egraph = EGraph::default();
3205 assert_eq!(egraph.extension_state::<usize>(), None);
3206 assert_eq!(egraph.clone().extension_state::<usize>(), None);
3207
3208 *egraph.extension_state_or_default::<usize>() = 1;
3209
3210 let mut cloned = egraph.clone();
3211 assert_eq!(cloned.extension_state::<usize>(), Some(&1));
3212 *cloned.extension_state_or_default::<usize>() = 2;
3213 assert_eq!(egraph.extension_state::<usize>(), Some(&1));
3214
3215 egraph.push();
3216 *egraph.extension_state_or_default::<usize>() = 3;
3217 egraph.pop().unwrap();
3218
3219 assert_eq!(egraph.extension_state::<usize>(), Some(&1));
3220 }
3221
3222 fn get_function(egraph: &EGraph, name: &str) -> Function {
3223 egraph.functions.get(name).unwrap().clone()
3224 }
3225
3226 fn get_value(egraph: &EGraph, name: &str) -> Value {
3227 let mut out = None;
3228 let id = get_function(egraph, name).backend_id;
3229 egraph.backend.for_each(id, |row| out = Some(row.vals[0]));
3230 out.unwrap()
3231 }
3232
3233 #[test]
3234 fn test_subsumed_unextractable_rebuild_arg() {
3235 let mut egraph = EGraph::default();
3237
3238 egraph
3239 .parse_and_run_program(
3240 None,
3241 r#"
3242 (datatype Math)
3243 (constructor container (Math) Math)
3244 (constructor expensive () Math :cost 100)
3245 (constructor cheap () Math)
3246 (constructor cheap-1 () Math)
3247 ; we make the container cheap so that it will be extracted if possible, but then we mark it as subsumed
3248 ; so the (expensive) expr should be extracted instead
3249 (let res (container (cheap)))
3250 (union res (expensive))
3251 (cheap)
3252 (cheap-1)
3253 (subsume (container (cheap)))
3254 "#,
3255 ).unwrap();
3256 let orig_cheap_value = get_value(&egraph, "cheap");
3258 let orig_cheap_1_value = get_value(&egraph, "cheap-1");
3259 assert_ne!(orig_cheap_value, orig_cheap_1_value);
3260 egraph
3262 .parse_and_run_program(
3263 None,
3264 r#"
3265 (union (cheap-1) (cheap))
3266 "#,
3267 )
3268 .unwrap();
3269 let new_cheap_value = get_value(&egraph, "cheap");
3271 let new_cheap_1_value = get_value(&egraph, "cheap-1");
3272 assert_eq!(new_cheap_value, new_cheap_1_value);
3273 assert!(new_cheap_value != orig_cheap_value || new_cheap_1_value != orig_cheap_1_value);
3274 let outputs = egraph
3276 .parse_and_run_program(
3277 None,
3278 r#"
3279 (extract res)
3280 "#,
3281 )
3282 .unwrap();
3283 assert_eq!(outputs[0].to_string(), "(expensive)\n");
3284 }
3285
3286 #[test]
3287 fn test_subsumed_unextractable_rebuild_self() {
3288 let mut egraph = EGraph::default();
3290
3291 egraph
3292 .parse_and_run_program(
3293 None,
3294 r#"
3295 (datatype Math)
3296 (constructor container (Math) Math)
3297 (constructor expensive () Math :cost 100)
3298 (constructor cheap () Math)
3299 (expensive)
3300 (let x (cheap))
3301 (subsume (cheap))
3302 "#,
3303 )
3304 .unwrap();
3305
3306 let orig_cheap_value = get_value(&egraph, "cheap");
3307 egraph
3309 .parse_and_run_program(
3310 None,
3311 r#"
3312 (union (expensive) x)
3313 "#,
3314 )
3315 .unwrap();
3316 let new_cheap_value = get_value(&egraph, "cheap");
3318 assert_ne!(new_cheap_value, orig_cheap_value);
3319
3320 let res = egraph
3322 .parse_and_run_program(
3323 None,
3324 r#"
3325 (extract x)
3326 "#,
3327 )
3328 .unwrap();
3329 assert_eq!(res[0].to_string(), "(expensive)\n");
3330 }
3331
3332 #[test]
3333 fn test_run_undefined_ruleset_errors() {
3334 let mut egraph = EGraph::default();
3335 let err = egraph
3336 .parse_and_run_program(None, "(ruleset test)\n(run test2 1)")
3337 .unwrap_err();
3338 assert!(matches!(err, Error::NoSuchRuleset(name, _) if name == "test2"));
3339 }
3340}