diff --git a/Cargo.toml b/Cargo.toml index 7e17e0e4..7c0b9701 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,18 +11,19 @@ repository = "https://github.com/egraphs-good/egg" version = "0.11.0" [dependencies] -env_logger = {version = "0.9.0", default-features = false} -hashbrown = "0.15.2" -indexmap = "2.7.0" -log = "0.4.17" -num-bigint = "0.4" -num-traits = "0.2" -quanta = "0.12" -rustc-hash = "2.0.0" +env_logger = {version = "0.9.0", default-features = false, optional = true} +hashbrown = {version = "0.15.2", default-features = false} +indexmap = {version = "2.7.0", default-features = false} +log = {version = "0.4.17", default-features = false} +num-bigint = {version = "0.4", default-features = false} +num-traits = {version = "0.2", default-features = false} +quanta = {version = "0.12", optional = true} +rustc-hash = {version = "2.0.0", default-features = false} smallvec = {version = "1.8.0", features = ["union", "const_generics"]} -symbol_table = {version = "0.4.0", features = ["global"]} -symbolic_expressions = "5.0.3" -thiserror = "1.0.31" +spin = {version = "0.9", default-features = false, features = ["spin_mutex", "lazy"]} +symbol_table = {git = "https://github.com/mwillsey/symbol_table", rev = "a716c39a4aa4757c910c93fc10c5c7fa8f467133", default-features = false, features = ["global"]} +symbolic_expressions = {version = "5.0.3", optional = true} +thiserror = "2" # for the lp feature good_lp = { version = "1", optional = true } @@ -38,9 +39,22 @@ serde_json = {version = "1.0.81", optional = true} ordered-float = "3.0.0" [features] +default = ["std"] +std = [ + "dep:env_logger", + "dep:quanta", + "dep:symbolic_expressions", + "hashbrown/default", + "indexmap/std", + "num-bigint/std", + "num-traits/std", + "log/std", + "rustc-hash/std", + "symbol_table/std", +] # forces the use of indexmaps over hashmaps deterministic = [] -lp = ["good_lp"] +lp = ["std", "good_lp"] reports = ["serde-1", "serde_json"] serde-1 = [ "serde", @@ -49,7 +63,7 @@ serde-1 = [ "symbol_table/serde", "vectorize", ] -wasm-bindgen = [] +wasm-bindgen = ["std"] # private features for testing test-explanations = [] diff --git a/Makefile b/Makefile index 229977bf..7d31f113 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,9 @@ test: cargo test --release --features=lp # don't run examples in proof-production mode cargo test --release --features "test-explanations" - + # verify no_std build + cargo test --no-default-features + .PHONY: nits nits: diff --git a/src/dot.rs b/src/dot.rs index 508e7293..0f3e8a02 100644 --- a/src/dot.rs +++ b/src/dot.rs @@ -8,7 +8,7 @@ Use the [`Dot`] struct to visualize an [`EGraph`] use std::ffi::OsStr; use std::fmt::{self, Debug, Display, Formatter}; -use std::io::{Error, ErrorKind, Result, Write}; +use std::io::{Error, Result, Write}; use std::path::Path; use crate::{Analysis, Language, egraph::EGraph}; @@ -140,14 +140,11 @@ where write!(stdin, "{}", self)?; match child.wait()?.code() { Some(0) => Ok(()), - Some(e) => Err(Error::new( - ErrorKind::Other, - format!("dot program returned error code {}", e), - )), - None => Err(Error::new( - ErrorKind::Other, - "dot program was killed by a signal", - )), + Some(e) => Err(Error::other(format!( + "dot program returned error code {}", + e + ))), + None => Err(Error::other("dot program was killed by a signal")), } } diff --git a/src/eclass.rs b/src/eclass.rs index 48ea500a..89ecc8d9 100644 --- a/src/eclass.rs +++ b/src/eclass.rs @@ -1,5 +1,6 @@ -use std::fmt::Debug; -use std::iter::ExactSizeIterator; +use crate::no_std_prelude::*; +use core::fmt::Debug; +use core::iter::ExactSizeIterator; use crate::*; diff --git a/src/egraph.rs b/src/egraph.rs index 04772621..084fd3fb 100644 --- a/src/egraph.rs +++ b/src/egraph.rs @@ -1,5 +1,6 @@ +use crate::no_std_prelude::*; use crate::*; -use std::{ +use core::{ borrow::BorrowMut, fmt::{self, Debug, Display}, marker::PhantomData, @@ -577,6 +578,7 @@ impl> EGraph { } /// Creates a [`Dot`] to visualize this egraph. See [`Dot`]. + #[cfg(feature = "std")] pub fn dot(&self) -> Dot<'_, L, N> { Dot { egraph: self, @@ -792,7 +794,7 @@ where } /// Given an `Id` using the `egraph[id]` syntax, retrieve the e-class. -impl> std::ops::Index for EGraph { +impl> core::ops::Index for EGraph { type Output = EClass; fn index(&self, id: Id) -> &Self::Output { let id = self.find(id); @@ -804,7 +806,7 @@ impl> std::ops::Index for EGraph { /// Given an `Id` using the `&mut egraph[id]` syntax, retrieve a mutable /// reference to the e-class. -impl> std::ops::IndexMut for EGraph { +impl> core::ops::IndexMut for EGraph { fn index_mut(&mut self, id: Id) -> &mut Self::Output { let id = self.find_mut(id); self.classes @@ -1144,7 +1146,7 @@ impl> EGraph { #[track_caller] pub fn union(&mut self, id1: Id, id2: Id) -> bool { if self.explain.is_some() { - let caller = std::panic::Location::caller(); + let caller = core::panic::Location::caller(); self.union_trusted(id1, id2, caller.to_string()) } else { self.perform_union(id1, id2, None) @@ -1158,10 +1160,10 @@ impl> EGraph { let mut id1 = self.find_mut(enode_id1); let mut id2 = self.find_mut(enode_id2); if id1 == id2 { - if let Some(Justification::Rule(_)) = rule { - if let Some(explain) = &mut self.explain { - explain.alternate_rewrite(enode_id1, enode_id2, rule.unwrap()); - } + if let Some(Justification::Rule(_)) = rule + && let Some(explain) = &mut self.explain + { + explain.alternate_rewrite(enode_id1, enode_id2, rule.unwrap()); } return false; } @@ -1169,7 +1171,7 @@ impl> EGraph { let class1_parents = self.classes[&id1].parents.len(); let class2_parents = self.classes[&id2].parents.len(); if class1_parents < class2_parents { - std::mem::swap(&mut id1, &mut id2); + core::mem::swap(&mut id1, &mut id2); } if let Some(explain) = &mut self.explain { @@ -1180,6 +1182,7 @@ impl> EGraph { self.unionfind.union(id1, id2); assert_ne!(id1, id2); + #[allow(deprecated)] let class2 = self.classes.remove(&id2).unwrap(); let class1 = self.classes.get_mut(&id1).unwrap(); assert_eq!(id1, class1.id); @@ -1232,10 +1235,10 @@ impl> EGraph { /// Useful for testing. pub fn check_goals(&self, id: Id, goals: &[Pattern]) { let (cost, best) = Extractor::new(self, AstSize).find_best(id); - println!("End ({}): {}", cost, best.pretty(80)); + log::info!("End ({}): {}", cost, best.pretty(80)); for (i, goal) in goals.iter().enumerate() { - println!("Trying to prove goal {}: {}", i, goal.pretty(40)); + log::info!("Trying to prove goal {}: {}", i, goal.pretty(40)); let matches = goal.search_eclass(self, id); if matches.is_none() { let best = Extractor::new(self, AstSize).find_best(id).1; @@ -1257,7 +1260,7 @@ impl> EGraph { impl> EGraph { #[inline(never)] fn rebuild_classes(&mut self) -> usize { - let mut classes_by_op = std::mem::take(&mut self.classes_by_op); + let mut classes_by_op = core::mem::take(&mut self.classes_by_op); classes_by_op.values_mut().for_each(|ids| ids.clear()); let mut trimmed = 0; @@ -1398,7 +1401,6 @@ impl> EGraph { /// let y = egraph.add(S::leaf("y")); /// let ax = egraph.add_expr(&"(+ a x)".parse().unwrap()); /// let ay = egraph.add_expr(&"(+ a y)".parse().unwrap()); - /// // Union x and y /// egraph.union(x, y); /// // Classes: [x y] [ax] [ay] [a] @@ -1467,7 +1469,7 @@ impl<'a, L: Language, N: Analysis> Debug for EGraphDump<'a, L, N> { } } -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests { use super::*; diff --git a/src/explain.rs b/src/explain.rs index 64614dd6..e155600c 100644 --- a/src/explain.rs +++ b/src/explain.rs @@ -1,19 +1,17 @@ #![allow(clippy::only_used_in_recursion)] use crate::Symbol; +use crate::no_std_prelude::*; use crate::{ Analysis, EClass, ENodeOrVar, FromOp, HashMap, HashSet, Id, Language, PatternAst, RecExpr, Rewrite, UnionFind, Var, util::pretty_print, }; +use core::cmp::Ordering; +use core::fmt::{self, Debug, Display, Formatter}; +use core::ops::{Deref, DerefMut}; -use std::cmp::Ordering; -use std::collections::{BinaryHeap, VecDeque}; -use std::fmt::{self, Debug, Display, Formatter}; -use std::ops::{Deref, DerefMut}; -use std::rc::Rc; - +use crate::sexp::Sexp; use num_bigint::BigUint; use num_traits::identities::{One, Zero}; -use symbolic_expressions::Sexp; type ProofCost = BigUint; @@ -359,12 +357,10 @@ impl Explanation { /// Construct the flat representation of the explanation and return it. pub fn make_flat_explanation(&mut self) -> &FlatExplanation { - if self.flat_explanation.is_some() { - return self.flat_explanation.as_ref().unwrap(); - } else { + if self.flat_explanation.is_none() { self.flat_explanation = Some(TreeTerm::flatten_proof(&self.explanation_trees)); - self.flat_explanation.as_ref().unwrap() } + self.flat_explanation.as_ref().expect("just set") } /// Check the validity of the explanation with respect to the given rules. @@ -404,16 +400,14 @@ impl Explanation { table: &HashMap>, is_forward: bool, ) -> bool { - if is_forward && next.forward_rule.is_some() { - let rule_name = next.forward_rule.as_ref().unwrap(); + if is_forward && let Some(rule_name) = next.forward_rule.as_ref() { if let Some(rule) = table.get(rule_name) { Explanation::check_rewrite(current, next, rule) } else { // give up when the rule is not provided true } - } else if !is_forward && next.backward_rule.is_some() { - let rule_name = next.backward_rule.as_ref().unwrap(); + } else if !is_forward && let Some(rule_name) = next.backward_rule.as_ref() { if let Some(rule) = table.get(rule_name) { Explanation::check_rewrite(next, current, rule) } else { @@ -435,12 +429,12 @@ impl Explanation { next: &'a FlatTerm, rewrite: &Rewrite, ) -> bool { - if let Some(lhs) = rewrite.searcher.get_pattern_ast() { - if let Some(rhs) = rewrite.applier.get_pattern_ast() { - let rewritten = current.rewrite(lhs, rhs); - if &rewritten != next { - return false; - } + if let Some(lhs) = rewrite.searcher.get_pattern_ast() + && let Some(rhs) = rewrite.applier.get_pattern_ast() + { + let rewritten = current.rewrite(lhs, rhs); + if &rewritten != next { + return false; } } true @@ -944,10 +938,10 @@ impl Explain { if node1 == node2 { return; } - if let Some((cost, _)) = self.shortest_explanation_memo.get(&(node1, node2)) { - if cost.is_zero() || cost.is_one() { - return; - } + if let Some((cost, _)) = self.shortest_explanation_memo.get(&(node1, node2)) + && (cost.is_zero() || cost.is_one()) + { + return; } let lconnection = Connection { @@ -1015,10 +1009,10 @@ impl Explain { let mut equalities = vec![]; for node in &self.explainfind { for neighbor in &node.neighbors { - if neighbor.is_rewrite_forward { - if let Justification::Rule(r) = neighbor.justification { - equalities.push((neighbor.current, neighbor.next, r)); - } + if neighbor.is_rewrite_forward + && let Justification::Rule(r) = neighbor.justification + { + equalities.push((neighbor.current, neighbor.next, r)); } } } @@ -1090,18 +1084,13 @@ impl<'x, L: Language> ExplainNodes<'x, L> { self.node_to_flat_explanation(explain_node.parent_connection.next); if let Justification::Rule(rule_name) = &explain_node.parent_connection.justification + && let Some(rule) = rule_table.get(rule_name) { - if let Some(rule) = rule_table.get(rule_name) { - if !explain_node.parent_connection.is_rewrite_forward { - std::mem::swap(&mut current_explanation, &mut next_explanation); - } - if !Explanation::check_rewrite( - ¤t_explanation, - &next_explanation, - rule, - ) { - return false; - } + if !explain_node.parent_connection.is_rewrite_forward { + core::mem::swap(&mut current_explanation, &mut next_explanation); + } + if !Explanation::check_rewrite(¤t_explanation, &next_explanation, rule) { + return false; } } } @@ -1177,10 +1166,10 @@ impl<'x, L: Language> ExplainNodes<'x, L> { fn get_neighbor(&self, current: Id, next: Id) -> Connection { for neighbor in &self.explainfind[usize::from(current)].neighbors { - if neighbor.next == next { - if let Justification::Rule(_) = neighbor.justification { - return neighbor.clone(); - } + if neighbor.next == next + && let Justification::Rule(_) = neighbor.justification + { + return neighbor.clone(); } } Connection { @@ -1233,7 +1222,7 @@ impl<'x, L: Language> ExplainNodes<'x, L> { let mut connection = connection.clone(); if i >= left_connections.len() { connection.is_rewrite_forward = !connection.is_rewrite_forward; - std::mem::swap(&mut connection.next, &mut connection.current); + core::mem::swap(&mut connection.next, &mut connection.current); } proof.push(self.explain_adjacent( @@ -1689,7 +1678,7 @@ impl<'x, L: Language> ExplainNodes<'x, L> { let mut next = connection.next; let mut current = connection.current; if i >= left_connections.len() { - std::mem::swap(&mut next, &mut current); + core::mem::swap(&mut next, &mut current); } if let Justification::Congruence = connection.justification { let current_node = self.node(current).clone(); @@ -1841,7 +1830,7 @@ impl<'x, L: Language> ExplainNodes<'x, L> { } } -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests { use super::super::*; @@ -1949,6 +1938,7 @@ mod tests { } } +#[cfg(feature = "std")] #[test] fn simple_explain_union_trusted() { use crate::{EGraph, SymbolLang}; diff --git a/src/extract.rs b/src/extract.rs index bcabd286..9d4baf47 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -1,5 +1,5 @@ -use std::cmp::Ordering; -use std::fmt::Debug; +use core::cmp::Ordering; +use core::fmt::Debug; use crate::util::{HashMap, hashmap_with_capacity}; use crate::{Analysis, EClass, EGraph, Id, Language, RecExpr}; diff --git a/src/language.rs b/src/language.rs index 052d1bb3..e76d6bbb 100644 --- a/src/language.rs +++ b/src/language.rs @@ -1,17 +1,19 @@ -use std::borrow::{Borrow, BorrowMut}; -use std::iter::FromIterator; -use std::ops::{BitOr, Deref, DerefMut, Index, IndexMut}; -use std::{cmp::Ordering, convert::TryFrom}; -use std::{ +use core::borrow::{Borrow, BorrowMut}; +use core::iter::FromIterator; +use core::ops::{BitOr, Deref, DerefMut, Index, IndexMut}; +use core::{cmp::Ordering, convert::TryFrom}; +use core::{ convert::Infallible, fmt::{self, Debug, Display}, }; -use std::{hash::Hash, str::FromStr}; +use core::{hash::Hash, str::FromStr}; + +use crate::no_std_prelude::*; use crate::*; +use crate::sexp::{Sexp, SexpError}; use fmt::Formatter; -use symbolic_expressions::{Sexp, SexpError}; use thiserror::Error; /// Trait that defines a Language whose terms will be in the [`EGraph`]. @@ -176,7 +178,7 @@ pub trait Language: Debug + Clone + Eq + Ord + Hash { where F: FnMut(Id) -> Self, { - self.try_build_recexpr::<_, std::convert::Infallible>(|id| Ok(get_node(id))) + self.try_build_recexpr::<_, core::convert::Infallible>(|id| Ok(get_node(id))) .unwrap() } @@ -362,8 +364,8 @@ impl LanguageChildren for Id { fn len(&self) -> usize { 1 } fn can_be_length(n: usize) -> bool { n == 1 } fn from_vec(v: Vec) -> Self { v[0] } - fn as_slice(&self) -> &[Id] { std::slice::from_ref(self) } - fn as_mut_slice(&mut self) -> &mut [Id] { std::slice::from_mut(self) } + fn as_slice(&self) -> &[Id] { core::slice::from_ref(self) } + fn as_mut_slice(&mut self) -> &mut [Id] { core::slice::from_mut(self) } } /// A recursive expression from a user-defined [`Language`]. @@ -530,7 +532,7 @@ impl IndexMut for RecExpr { impl IntoIterator for RecExpr { type Item = L; - type IntoIter = std::vec::IntoIter; + type IntoIter = alloc::vec::IntoIter; fn into_iter(self) -> Self::IntoIter { self.nodes.into_iter() @@ -539,7 +541,7 @@ impl IntoIterator for RecExpr { impl<'a, L> IntoIterator for &'a RecExpr { type Item = &'a L; - type IntoIter = std::slice::Iter<'a, L>; + type IntoIter = core::slice::Iter<'a, L>; fn into_iter(self) -> Self::IntoIter { self.iter() @@ -548,7 +550,7 @@ impl<'a, L> IntoIterator for &'a RecExpr { impl<'a, L> IntoIterator for &'a mut RecExpr { type Item = &'a mut L; - type IntoIter = std::slice::IterMut<'a, L>; + type IntoIter = core::slice::IterMut<'a, L>; fn into_iter(self) -> Self::IntoIter { self.iter_mut() @@ -619,6 +621,16 @@ impl RecExpr { } } +#[cfg(feature = "std")] +fn parse_sexp(s: &str) -> Result { + symbolic_expressions::parser::parse_str(s) +} + +#[cfg(not(feature = "std"))] +fn parse_sexp(s: &str) -> Result { + s.parse() +} + /// An error type for failures when attempting to parse an s-expression as a /// [`RecExpr`]. #[derive(Debug, Error)] @@ -676,7 +688,7 @@ impl FromStr for RecExpr { } let mut expr = RecExpr::default(); - let sexp = symbolic_expressions::parser::parse_str(s.trim()).map_err(BadSexp)?; + let sexp = parse_sexp(s.trim()).map_err(BadSexp)?; parse_sexp_into(&sexp, &mut expr)?; Ok(expr) } diff --git a/src/lib.rs b/src/lib.rs index 3eeb9d8f..453d79b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ #![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] +#![allow(clippy::test_attr_in_doctest)] /*! `egg` (**e**-**g**raphs **g**ood) is a e-graph library optimized for equality saturation. @@ -30,13 +32,42 @@ for less or more logging. #![doc = include_str!("../tests/simple.rs")] #![doc = "\n```"] +extern crate alloc; + +/// Crate-internal prelude that re-exports `alloc` / `core` items normally +/// provided by `std`. Every module imports `use crate::no_std_prelude::*;` +/// instead of scattering `#[allow(unused_imports)] use alloc::{…}` blocks. +pub(crate) mod no_std_prelude { + pub use alloc::{ + borrow::{Cow, ToOwned}, + boxed::Box, + collections::{BinaryHeap, VecDeque}, + format, + rc::Rc, + string::{String, ToString}, + sync::Arc, + vec, + vec::Vec, + }; +} + +// Hidden re-exports used by the `define_language!` macro so downstream crates +// don't need `extern crate alloc`. +#[doc(hidden)] +pub mod __private { + pub use alloc::{format, string::ToString, vec, vec::Vec}; + pub use core::result::Result; +} + mod macros; +#[cfg(feature = "std")] #[doc(hidden)] pub mod test; pub mod tutorials; +#[cfg(feature = "std")] mod dot; mod eclass; mod egraph; @@ -50,6 +81,7 @@ mod multipattern; mod pattern; mod rewrite; mod run; +mod sexp; mod subst; mod unionfind; mod util; @@ -73,14 +105,14 @@ impl From for usize { } } -impl std::fmt::Debug for Id { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Debug for Id { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", self.0) } } -impl std::fmt::Display for Id { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl core::fmt::Display for Id { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{}", self.0) } } @@ -88,7 +120,6 @@ impl std::fmt::Display for Id { pub(crate) use {explain::Explain, unionfind::UnionFind}; pub use { - dot::Dot, eclass::EClass, egraph::{EGraph, LanguageMapper, SimpleLanguageMapper}, explain::{ @@ -105,10 +136,13 @@ pub use { util::*, }; +#[cfg(feature = "std")] +pub use dot::Dot; + #[cfg(feature = "lp")] pub use lp_extract::*; -#[cfg(test)] +#[cfg(all(test, feature = "std"))] fn init_logger() { let _ = env_logger::builder().is_test(true).try_init(); } diff --git a/src/lp_extract.rs b/src/lp_extract.rs index 2eebf113..16a6b5f8 100644 --- a/src/lp_extract.rs +++ b/src/lp_extract.rs @@ -203,7 +203,7 @@ where for class in egraph.classes() { for (i, &node_var) in vars[&class.id].nodes.iter().enumerate() { let c = self.costs[&class.id][i]; - objective = objective + c * node_var; + objective += c * node_var; } } @@ -411,7 +411,7 @@ where } } -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests { use crate::{SymbolLang as S, *}; diff --git a/src/machine.rs b/src/machine.rs index d65f33a6..b9854df9 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -1,5 +1,6 @@ +use crate::no_std_prelude::*; use crate::*; -use std::result; +use core::result; type Result = result::Result<(), ()>; @@ -182,11 +183,11 @@ impl Compiler { (n_free == 0, n_free, -size) }; - self.todo_nodes - .keys() - .max_by_key(key) - .copied() - .map(|k| (k, self.todo_nodes.remove(&k).unwrap())) + self.todo_nodes.keys().max_by_key(key).copied().map(|k| { + #[allow(deprecated)] + let v = self.todo_nodes.remove(&k).unwrap(); + (k, v) + }) } /// check to see if this e-node corresponds to a term that is grounded by @@ -318,12 +319,11 @@ impl Program { &self.instructions, &self.subst, &mut |machine, subst| { - if !egraph.analysis.allow_ematching_cycles() { - if let Some((first, rest)) = machine.reg.split_first() { - if rest.contains(first) { - return Ok(()); - } - } + if !egraph.analysis.allow_ematching_cycles() + && let Some((first, rest)) = machine.reg.split_first() + && rest.contains(first) + { + return Ok(()); } let subst_vec = subst diff --git a/src/macros.rs b/src/macros.rs index 09c59f10..fb94fa6a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -121,16 +121,16 @@ macro_rules! __define_language { $vis enum $name <$($gen),*> $decl impl<$($gen),*> $crate::Language for $name <$($gen),*> where $($where)* { - type Discriminant = std::mem::Discriminant; + type Discriminant = ::core::mem::Discriminant; #[inline(always)] fn discriminant(&self) -> Self::Discriminant { - std::mem::discriminant(self) + ::core::mem::discriminant(self) } #[inline(always)] fn matches(&self, other: &Self) -> bool { - ::std::mem::discriminant(self) == ::std::mem::discriminant(other) && + ::core::mem::discriminant(self) == ::core::mem::discriminant(other) && match (self, other) { $($matches)* _ => false } } @@ -138,8 +138,8 @@ macro_rules! __define_language { fn children_mut(&mut self) -> &mut [$crate::Id] { match self $children_mut } } - impl<$($gen),*> ::std::fmt::Display for $name <$($gen),*> where $($where)* { - fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + impl<$($gen),*> ::core::fmt::Display for $name <$($gen),*> where $($where)* { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { // We need to pass `f` to the match expression for hygiene // reasons. match (self, f) $display @@ -149,7 +149,7 @@ macro_rules! __define_language { impl<$($gen),*> $crate::FromOp for $name <$($gen),*> where $($where)* { type Error = $crate::FromOpError; - fn from_op(op: &str, children: ::std::vec::Vec<$crate::Id>) -> ::std::result::Result { + fn from_op(op: &str, children: $crate::__private::Vec<$crate::Id>) -> $crate::__private::Result { match (op, children) { $($from_op)* (op, children) => Err($crate::FromOpError::new(op, children)), @@ -229,7 +229,7 @@ macro_rules! __define_language { { $($matches)* ($name::$variant(data1), $name::$variant(data2)) => data1 == data2, } { $($children)* $name::$variant(_data) => &[], } { $($children_mut)* $name::$variant(_data) => &mut [], } - { $($display)* ($name::$variant(data), f) => ::std::fmt::Display::fmt(data, f), } + { $($display)* ($name::$variant(data), f) => ::core::fmt::Display::fmt(data, f), } { $($from_op)* (op, children) if op.parse::<$data>().is_ok() && children.is_empty() => Ok($name::$variant(op.parse().unwrap())), } ); }; @@ -253,7 +253,7 @@ macro_rules! __define_language { { $($matches)* ($name::$variant(d1, l), $name::$variant(d2, r)) => d1 == d2 && $crate::LanguageChildren::len(l) == $crate::LanguageChildren::len(r), } { $($children)* $name::$variant(_, ids) => $crate::LanguageChildren::as_slice(ids), } { $($children_mut)* $name::$variant(_, ids) => $crate::LanguageChildren::as_mut_slice(ids), } - { $($display)* ($name::$variant(data, _), f) => ::std::fmt::Display::fmt(data, f), } + { $($display)* ($name::$variant(data, _), f) => ::core::fmt::Display::fmt(data, f), } { $($from_op)* (op, children) if op.parse::<$data>().is_ok() && <$ids as $crate::LanguageChildren>::can_be_length(children.len()) => { let data = op.parse::<$data>().unwrap(); let children = <$ids as $crate::LanguageChildren>::from_vec(children); @@ -353,6 +353,7 @@ macro_rules! rewrite { $lhs:tt => $rhs:tt $(if $cond:expr)* ) => {{ + use $crate::__private::ToString as _; let searcher = $crate::__rewrite!(@parse Pattern $lhs); let core_applier = $crate::__rewrite!(@parse Pattern $rhs); let applier = $crate::__rewrite!(@applier core_applier; $($cond,)*); @@ -363,8 +364,9 @@ macro_rules! rewrite { $lhs:tt <=> $rhs:tt $(if $cond:expr)* ) => {{ + use $crate::__private::{ToString as _, vec}; let name = $name; - let name2 = String::from(name.clone()) + "-rev"; + let name2 = name.clone().to_string() + "-rev"; vec![ $crate::rewrite!(name; $lhs => $rhs $(if $cond)*), $crate::rewrite!(name2; $rhs => $lhs $(if $cond)*) @@ -386,6 +388,7 @@ macro_rules! multi_rewrite { $name:expr; $lhs:tt => $rhs:tt ) => {{ + use $crate::__private::ToString as _; let searcher = $crate::__rewrite!(@parse MultiPattern $lhs); let applier = $crate::__rewrite!(@parse MultiPattern $rhs); $crate::Rewrite::new($name.to_string(), searcher, applier).unwrap() @@ -411,6 +414,7 @@ macro_rules! __rewrite { #[cfg(test)] mod tests { + use crate::no_std_prelude::*; use crate::*; define_language! { diff --git a/src/multipattern.rs b/src/multipattern.rs index d754f81c..fd801ea8 100644 --- a/src/multipattern.rs +++ b/src/multipattern.rs @@ -1,4 +1,5 @@ -use std::str::FromStr; +use crate::no_std_prelude::*; +use core::str::FromStr; use thiserror::Error; use crate::*; @@ -206,7 +207,7 @@ impl> Applier for MultiPattern { } } -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests { use crate::{SymbolLang as S, *}; diff --git a/src/pattern.rs b/src/pattern.rs index 92dad84a..5707eb2c 100644 --- a/src/pattern.rs +++ b/src/pattern.rs @@ -1,9 +1,9 @@ +use crate::no_std_prelude::*; +use core::convert::TryInto; +use core::fmt::{self, Display}; +use core::{convert::TryFrom, str::FromStr}; use fmt::Formatter; use log::*; -use std::borrow::Cow; -use std::convert::TryInto; -use std::fmt::{self, Display}; -use std::{convert::TryFrom, str::FromStr}; use thiserror::Error; @@ -113,10 +113,10 @@ impl Pattern { pub fn vars(&self) -> Vec { let mut vars = vec![]; for n in &self.ast { - if let ENodeOrVar::Var(v) = n { - if !vars.contains(v) { - vars.push(*v) - } + if let ENodeOrVar::Var(v) = n + && !vars.contains(v) + { + vars.push(*v) } } vars @@ -216,7 +216,7 @@ impl FromOp for ENodeOrVar { } } -impl std::str::FromStr for Pattern { +impl core::str::FromStr for Pattern { type Err = RecExprParseError>; fn from_str(s: &str) -> Result { @@ -430,7 +430,7 @@ pub(crate) fn apply_pat>( *ids.last().unwrap() } -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests { use crate::{SymbolLang as S, *}; diff --git a/src/rewrite.rs b/src/rewrite.rs index 135c7d6e..808ae514 100644 --- a/src/rewrite.rs +++ b/src/rewrite.rs @@ -1,6 +1,6 @@ +use crate::no_std_prelude::*; +use core::fmt::{self, Debug, Display}; use pattern::apply_pat; -use std::fmt::{self, Debug, Display}; -use std::sync::Arc; use crate::*; @@ -104,7 +104,7 @@ impl> Rewrite { /// This `run` is for testing use only. You should use things /// from the `egg::run` module - #[cfg(test)] + #[cfg(all(test, feature = "std"))] pub(crate) fn run(&self, egraph: &mut EGraph) -> Vec { let start = crate::util::Instant::now(); @@ -542,11 +542,11 @@ where } } -#[cfg(test)] +#[cfg(all(test, feature = "std"))] mod tests { use crate::{SymbolLang as S, *}; - use std::str::FromStr; + use core::str::FromStr; type EGraph = crate::EGraph; diff --git a/src/run.rs b/src/run.rs index fbd37b30..77c053e0 100644 --- a/src/run.rs +++ b/src/run.rs @@ -1,5 +1,6 @@ -use std::fmt::{self, Debug, Formatter}; +use core::fmt::{self, Debug, Formatter}; +use crate::no_std_prelude::*; use log::*; use crate::*; @@ -270,7 +271,7 @@ pub struct Report { pub rebuild_time: f64, } -impl std::fmt::Display for Report { +impl core::fmt::Display for Report { #[rustfmt::skip] fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { writeln!(f, "Runner report")?; @@ -327,7 +328,7 @@ pub struct Iteration { } /// Type alias for the result of a [`Runner`]. -pub type RunnerResult = std::result::Result; +pub type RunnerResult = core::result::Result; impl Runner where @@ -500,6 +501,7 @@ where } /// Prints some information about a runners run. + #[cfg(feature = "std")] pub fn print_report(&self) { println!("{}", self.report()) } @@ -532,7 +534,7 @@ where let egraph_classes = self.egraph.number_of_classes(); let hook_time = Instant::now(); - let mut hooks = std::mem::take(&mut self.hooks); + let mut hooks = core::mem::take(&mut self.hooks); result = result.and_then(|_| { hooks .iter_mut() @@ -650,10 +652,12 @@ fn check_rules(rules: &[&Rewrite]) { name_counts.retain(|_, count: &mut usize| *count > 1); if !name_counts.is_empty() { + #[cfg(feature = "std")] eprintln!("WARNING: Duplicated rule names may affect rule reporting and scheduling."); log::warn!("Duplicated rule names may affect rule reporting and scheduling."); for (name, &count) in name_counts.iter() { assert!(count > 1); + #[cfg(feature = "std")] eprintln!("Rule '{}' appears {} times", name, count); log::warn!("Rule '{}' appears {} times", name, count); } diff --git a/src/sexp.rs b/src/sexp.rs new file mode 100644 index 00000000..cef529bb --- /dev/null +++ b/src/sexp.rs @@ -0,0 +1,141 @@ +//! Minimal s-expression type. +//! +//! When the `std` feature is enabled, this re-exports from `symbolic_expressions`. +//! In `no_std` mode, a minimal compatible implementation is provided. + +#[cfg(feature = "std")] +pub(crate) use symbolic_expressions::{Sexp, SexpError}; + +#[cfg(not(feature = "std"))] +pub(crate) use self::minimal::*; + +#[cfg(not(feature = "std"))] +mod minimal { + use alloc::fmt; + use alloc::string::String; + use alloc::vec::Vec; + + /// A minimal s-expression type compatible with `symbolic_expressions::Sexp`. + #[derive(Debug, Clone, PartialEq, Eq)] + pub enum Sexp { + /// A string atom. + String(String), + /// A list of sub-expressions. + List(Vec), + /// An empty s-expression. + Empty, + } + + impl fmt::Display for Sexp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Sexp::String(s) => { + if s.contains(' ') || s.contains('(') || s.contains(')') || s.is_empty() { + write!(f, "\"{}\"", s) + } else { + write!(f, "{}", s) + } + } + Sexp::List(items) => { + write!(f, "(")?; + for (i, item) in items.iter().enumerate() { + if i > 0 { + write!(f, " ")?; + } + write!(f, "{}", item)?; + } + write!(f, ")") + } + Sexp::Empty => write!(f, "()"), + } + } + } + + /// Error type for s-expression parsing. + #[derive(Debug, Clone)] + pub struct SexpError { + /// Description of the parse error. + pub message: String, + } + + impl fmt::Display for SexpError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "S-expression parse error: {}", self.message) + } + } + + impl core::error::Error for SexpError {} + + impl core::str::FromStr for Sexp { + type Err = SexpError; + + fn from_str(s: &str) -> Result { + parse(s) + } + } + + fn parse(input: &str) -> Result { + let input = input.trim(); + if input.is_empty() { + return Err(SexpError { + message: String::from("empty input"), + }); + } + + let (sexp, rest) = parse_one(input)?; + let rest = rest.trim(); + if rest.is_empty() { + Ok(sexp) + } else { + Err(SexpError { + message: String::from("trailing input"), + }) + } + } + + fn parse_one(input: &str) -> Result<(Sexp, &str), SexpError> { + let input = input.trim_start(); + if input.is_empty() { + return Err(SexpError { + message: String::from("unexpected end of input"), + }); + } + + if input.starts_with('(') { + let mut rest = &input[1..]; + let mut items = Vec::new(); + loop { + rest = rest.trim_start(); + if rest.is_empty() { + return Err(SexpError { + message: String::from("unclosed parenthesis"), + }); + } + if rest.starts_with(')') { + rest = &rest[1..]; + break; + } + let (item, new_rest) = parse_one(rest)?; + items.push(item); + rest = new_rest; + } + if items.is_empty() { + Ok((Sexp::Empty, rest)) + } else { + Ok((Sexp::List(items), rest)) + } + } else if input.starts_with('"') { + let end = input[1..].find('"').ok_or_else(|| SexpError { + message: String::from("unclosed string"), + })? + 1; + let s = &input[1..end]; + Ok((Sexp::String(String::from(s)), &input[end + 1..])) + } else { + let end = input + .find(|c: char| c.is_whitespace() || c == '(' || c == ')') + .unwrap_or(input.len()); + let atom = &input[..end]; + Ok((Sexp::String(String::from(atom)), &input[end..])) + } + } +} diff --git a/src/subst.rs b/src/subst.rs index a3c95880..2dd18ab6 100644 --- a/src/subst.rs +++ b/src/subst.rs @@ -1,5 +1,7 @@ -use std::fmt; -use std::str::FromStr; +use core::fmt; +use core::str::FromStr; + +use crate::no_std_prelude::*; use crate::*; use fmt::{Debug, Display, Formatter}; @@ -119,7 +121,7 @@ impl Subst { pub fn insert(&mut self, var: Var, id: Id) -> Option { for pair in &mut self.vec { if pair.0 == var { - return Some(std::mem::replace(&mut pair.1, id)); + return Some(core::mem::replace(&mut pair.1, id)); } } self.vec.push((var, id)); @@ -135,7 +137,7 @@ impl Subst { } } -impl std::ops::Index for Subst { +impl core::ops::Index for Subst { type Output = Id; fn index(&self, var: Var) -> &Self::Output { diff --git a/src/tutorials/_01_background.rs b/src/tutorials/_01_background.rs index 8968f954..b5e01e32 100644 --- a/src/tutorials/_01_background.rs +++ b/src/tutorials/_01_background.rs @@ -145,26 +145,26 @@ Let's put it all together with an example referring to the four e-graphs in the 1. The initial e-graph represents the term _(a × 2) / 2_. Since each e-class only has one e-node, - the e-graph is basically an abstract syntax tree - with sharing (the 2 is not duplicated). + the e-graph is basically an abstract syntax tree + with sharing (the 2 is not duplicated). 2. Applying the rewrite _x × 2 → x << 1_ has recorded the fact that _a × 2 = a << 1_ - without forgetting about _a × 2_. + without forgetting about _a × 2_. Note how the newly added _a << 1_ refers to the existing "_a_" e-node, and the "<<" e-node has been unioned into the same e-class as the equivalent "×" e-node where the pattern _x × 2_ matched. 3. Applying rewrite _(x × y) / z → x × (y / z)_ realizes that division - associates with multiplication. + associates with multiplication. This rewrite is critical to discovering the cancellation of 2s that we are looking for, - and it still works despite the fact that we applied the "wrong" rewrite previously. + and it still works despite the fact that we applied the "wrong" rewrite previously. 4. Applying rewrites _x / x → 1_ and _x × 1 → x_ doesn't add any new e-nodes, - since all the e-nodes were already present in the e-graph. + since all the e-nodes were already present in the e-graph. The result only unions e-classes, - meaning that e-graph actually got _smaller_ from applying these rewrites, - even though it now represents more terms. + meaning that e-graph actually got _smaller_ from applying these rewrites, + even though it now represents more terms. In fact, observe that the top-right "×" e-node's left child is _itself_; - this cycle means the e-class represents the _infinite_ (!) set of terms - _a_, _a × 1_, _a × 1 × 1_, and so on. + this cycle means the e-class represents the _infinite_ (!) set of terms + _a_, _a × 1_, _a × 1 × 1_, and so on. ## Invariants and Rebuilding @@ -178,7 +178,7 @@ These operations maintains two key (related) invariants: An e-graph maintains not just an [equivalence relation] over expressions, but a [congruence relation]. Congruence basically states that if _x_ is equivalent to _y_, - _f(x)_ must be equivalent to _f(y)_. + _f(x)_ must be equivalent to _f(y)_. So as the user calls [`union`], many e-classes other than the given two may need to merge to maintain congruence. @@ -258,10 +258,10 @@ Most of this was covered above, but we need to define two new terms: - _Saturation_ occurs when an e-graph detects that rewrites no longer add new information. Consider the commutative rewrite _x + y → y + x_. After applying it once, the second time adds no new information - since the e-graph didn't forget about the initial _x + y_ terms. + since the e-graph didn't forget about the initial _x + y_ terms. If all the rewrites are in this state, we say the e-graph is _saturated_, - meaning that the e-graph encodes all possible equivalences derivable from - the given rewrites. + meaning that the e-graph encodes all possible equivalences derivable from + the given rewrites. - _Extraction_ is a procedure for picking a single represented term from an e-class that is optimal according to some cost function. `egg`'s [`Extractor`]s provide this functionality. diff --git a/src/unionfind.rs b/src/unionfind.rs index 39e9bc58..06ca28aa 100644 --- a/src/unionfind.rs +++ b/src/unionfind.rs @@ -1,5 +1,6 @@ use crate::Id; -use std::fmt::Debug; +use crate::no_std_prelude::*; +use core::fmt::Debug; #[derive(Debug, Clone, Default)] #[cfg_attr(feature = "serde-1", derive(serde::Serialize, serde::Deserialize))] diff --git a/src/util.rs b/src/util.rs index 6c05c60d..a1f88f7f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,14 +1,16 @@ -use std::{fmt, iter::FromIterator}; -use symbolic_expressions::Sexp; +use crate::no_std_prelude::*; +use core::fmt::{self, Debug, Display, Formatter}; +use core::iter::FromIterator; -use fmt::{Debug, Display, Formatter}; +use crate::sexp::Sexp; #[cfg(feature = "serde-1")] use serde::{Deserialize, Serialize}; -#[allow(unused_imports)] use crate::*; +// --- Symbol --- + /// An interned string. /// /// This is provided by the [`symbol_table`](https://crates.io/crates/symbol_table) crate. @@ -42,10 +44,9 @@ use crate::*; /// pub use symbol_table::GlobalSymbol as Symbol; -pub(crate) type BuildHasher = rustc_hash::FxBuildHasher; +// --- Hashing --- -// pub(crate) type HashMap = hashbrown::HashMap; -// pub(crate) type HashSet = hashbrown::HashSet; +pub(crate) type BuildHasher = rustc_hash::FxBuildHasher; pub(crate) use hashmap::*; @@ -68,12 +69,42 @@ pub(crate) fn hashmap_with_capacity(cap: usize) -> hashmap::HashMap pub(crate) type IndexMap = indexmap::IndexMap; pub(crate) type IndexSet = indexmap::IndexSet; +// --- Timing --- + +#[cfg(feature = "std")] pub(crate) type Instant = quanta::Instant; -pub(crate) type Duration = std::time::Duration; + +#[cfg(not(feature = "std"))] +pub(crate) type Instant = no_std_instant::Instant; + +pub(crate) type Duration = core::time::Duration; + +#[cfg(not(feature = "std"))] +mod no_std_instant { + /// A no-op instant for `no_std` environments. + /// + /// Time limits are effectively disabled; iteration and node limits still work. + #[derive(Clone, Copy, Debug)] + pub struct Instant; + + impl Instant { + /// Returns a no-op instant. + pub fn now() -> Self { + Instant + } + + /// Always returns `Duration::ZERO`. + pub fn elapsed(&self) -> core::time::Duration { + core::time::Duration::ZERO + } + } +} + +// --- Utilities --- pub(crate) fn concat_vecs(to: &mut Vec, mut from: Vec) { if to.len() < from.len() { - std::mem::swap(to, &mut from) + core::mem::swap(to, &mut from) } to.extend(from); } @@ -83,8 +114,8 @@ pub(crate) fn pretty_print( sexp: &Sexp, width: usize, level: usize, -) -> std::fmt::Result { - use std::fmt::Write; +) -> fmt::Result { + use fmt::Write; if let Sexp::List(list) = sexp { let indent = sexp.to_string().len() > width; write!(buf, "(")?; @@ -127,27 +158,27 @@ Notably, insert/pop operations have O(1) expected amortized runtime complexity. #[cfg_attr(feature = "serde-1", derive(Serialize, Deserialize))] pub(crate) struct UniqueQueue where - T: Eq + std::hash::Hash + Clone, + T: Eq + core::hash::Hash + Clone, { - set: hashbrown::HashSet, - queue: std::collections::VecDeque, + set: hashbrown::HashSet, + queue: alloc::collections::VecDeque, } impl Default for UniqueQueue where - T: Eq + std::hash::Hash + Clone, + T: Eq + core::hash::Hash + Clone, { fn default() -> Self { UniqueQueue { - set: hashbrown::HashSet::default(), - queue: std::collections::VecDeque::new(), + set: hashbrown::HashSet::with_hasher(BuildHasher::default()), + queue: alloc::collections::VecDeque::new(), } } } impl UniqueQueue where - T: Eq + std::hash::Hash + Clone, + T: Eq + core::hash::Hash + Clone, { pub fn insert(&mut self, t: T) { if self.set.insert(t.clone()) { @@ -179,11 +210,11 @@ where impl IntoIterator for UniqueQueue where - T: Eq + std::hash::Hash + Clone, + T: Eq + core::hash::Hash + Clone, { type Item = T; - type IntoIter = as IntoIterator>::IntoIter; + type IntoIter = as IntoIterator>::IntoIter; fn into_iter(self) -> Self::IntoIter { self.queue.into_iter() @@ -192,7 +223,7 @@ where impl FromIterator for UniqueQueue where - A: Eq + std::hash::Hash + Clone, + A: Eq + core::hash::Hash + Clone, { fn from_iter>(iter: T) -> Self { let mut queue = UniqueQueue::default(); diff --git a/tests/lambda.rs b/tests/lambda.rs index c0304258..63a067d0 100644 --- a/tests/lambda.rs +++ b/tests/lambda.rs @@ -1,7 +1,9 @@ +#![cfg(feature = "std")] use egg::{rewrite as rw, *}; use rustc_hash::FxHashSet as HashSet; define_language! { + #[allow(clippy::enum_variant_names)] enum Lambda { Bool(bool), Num(i32), diff --git a/tests/math.rs b/tests/math.rs index f9ae6fd0..2215ff91 100644 --- a/tests/math.rs +++ b/tests/math.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "std")] use egg::{rewrite as rw, *}; use ordered_float::NotNan; diff --git a/tests/prop.rs b/tests/prop.rs index fe05eda5..bca5ebfa 100644 --- a/tests/prop.rs +++ b/tests/prop.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "std")] use egg::*; define_language! {