use super::functions::*;
use crate::{Environment, Expr, Level, Name};
#[allow(dead_code)]
pub struct ProjectionRewriteSet {
rules: Vec<ProjectionRewrite>,
}
#[allow(dead_code)]
impl ProjectionRewriteSet {
pub fn new() -> Self {
ProjectionRewriteSet { rules: Vec::new() }
}
pub fn add(&mut self, rule: ProjectionRewrite) {
self.rules.push(rule);
}
pub fn find_by_projector(&self, projector: &str) -> Option<&ProjectionRewrite> {
self.rules.iter().find(|r| r.projector_name == projector)
}
pub fn rules_for_ctor(&self, ctor: &str) -> Vec<&ProjectionRewrite> {
self.rules.iter().filter(|r| r.ctor_name == ctor).collect()
}
pub fn len(&self) -> usize {
self.rules.len()
}
pub fn is_empty(&self) -> bool {
self.rules.is_empty()
}
pub fn projector_names(&self) -> Vec<&str> {
self.rules
.iter()
.map(|r| r.projector_name.as_str())
.collect()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct EtaExpanded {
pub ctor: String,
pub expr_id: u64,
pub field_ids: Vec<u64>,
}
#[allow(dead_code)]
impl EtaExpanded {
pub fn new(ctor: impl Into<String>, expr_id: u64, field_ids: Vec<u64>) -> Self {
EtaExpanded {
ctor: ctor.into(),
expr_id,
field_ids,
}
}
pub fn arity(&self) -> usize {
self.field_ids.len()
}
}
#[allow(dead_code)]
pub struct EtaRedexCollector {
redexes: Vec<EtaRedex>,
max_depth: usize,
}
#[allow(dead_code)]
impl EtaRedexCollector {
pub fn new() -> Self {
EtaRedexCollector {
redexes: Vec::new(),
max_depth: usize::MAX,
}
}
pub fn with_max_depth(max_depth: usize) -> Self {
EtaRedexCollector {
redexes: Vec::new(),
max_depth,
}
}
pub fn add(&mut self, redex: EtaRedex) {
if redex.depth() <= self.max_depth {
self.redexes.push(redex);
}
}
pub fn redexes(&self) -> &[EtaRedex] {
&self.redexes
}
pub fn count(&self) -> usize {
self.redexes.len()
}
pub fn has_top_level(&self) -> bool {
self.redexes.iter().any(|r| r.is_top_level())
}
pub fn sorted_by_depth(&self) -> Vec<&EtaRedex> {
let mut v: Vec<&EtaRedex> = self.redexes.iter().collect();
v.sort_by_key(|r| r.depth());
v
}
}
pub struct SingletonKReducer<'a> {
env: &'a Environment,
}
impl<'a> SingletonKReducer<'a> {
pub fn new(env: &'a Environment) -> Self {
SingletonKReducer { env }
}
pub fn is_singleton_type(&self, ty: &Expr) -> bool {
let name = match head_const(ty) {
Some(n) => n,
None => return false,
};
let iv = match self.env.get_inductive_val(name) {
Some(v) => v,
None => return false,
};
if iv.ctors.len() != 1 {
return false;
}
let ctor_name = &iv.ctors[0];
match self.env.get_constructor_val(ctor_name) {
Some(cv) => cv.num_fields == 0,
None => false,
}
}
pub fn get_unique_ctor(&self, type_name: &Name) -> Option<Name> {
let iv = self.env.get_inductive_val(type_name)?;
if iv.ctors.len() != 1 {
return None;
}
let ctor_name = iv.ctors[0].clone();
let cv = self.env.get_constructor_val(&ctor_name)?;
if cv.num_fields == 0 {
Some(ctor_name)
} else {
None
}
}
pub fn k_reduce(&self, _expr: &Expr, ty: &Expr) -> Option<Expr> {
if !self.is_singleton_type(ty) {
return None;
}
let type_name = head_const(ty)?;
let ctor_name = self.get_unique_ctor(type_name)?;
Some(Expr::Const(ctor_name, vec![]))
}
pub fn apply_k_reduction(motive: &Expr, proof: &Expr) -> Option<Expr> {
let simplified = Expr::App(Box::new(motive.clone()), Box::new(proof.clone()));
Some(simplified)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct StructureDef {
pub name: String,
pub ctor_name: String,
pub fields: Vec<FieldDescriptor>,
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct EtaStats {
pub expansions: u64,
pub reductions: u64,
pub failed_expansions: u64,
pub failed_reductions: u64,
}
#[allow(dead_code)]
impl EtaStats {
pub fn new() -> Self {
EtaStats::default()
}
pub fn record_expansion(&mut self) {
self.expansions += 1;
}
pub fn record_reduction(&mut self) {
self.reductions += 1;
}
pub fn record_failed_expansion(&mut self) {
self.failed_expansions += 1;
}
pub fn record_failed_reduction(&mut self) {
self.failed_reductions += 1;
}
pub fn expansion_rate(&self) -> f64 {
let total = self.expansions + self.failed_expansions;
if total == 0 {
1.0
} else {
self.expansions as f64 / total as f64
}
}
pub fn reduction_rate(&self) -> f64 {
let total = self.reductions + self.failed_reductions;
if total == 0 {
1.0
} else {
self.reductions as f64 / total as f64
}
}
pub fn summary(&self) -> String {
format!(
"expansions={} (fail={}) reductions={} (fail={})",
self.expansions, self.failed_expansions, self.reductions, self.failed_reductions
)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq)]
enum EtaState {
Idle,
Expanding,
Done,
Failed,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct EtaUnificationHint {
pub lhs_id: u64,
pub rhs_id: u64,
pub suggested_struct: String,
}
#[allow(dead_code)]
impl EtaUnificationHint {
pub fn new(lhs_id: u64, rhs_id: u64, suggested_struct: impl Into<String>) -> Self {
EtaUnificationHint {
lhs_id,
rhs_id,
suggested_struct: suggested_struct.into(),
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct EtaReduction {
pub inner_id: u64,
pub ctor: String,
pub is_valid: bool,
}
#[allow(dead_code)]
impl EtaReduction {
pub fn valid(inner_id: u64, ctor: impl Into<String>) -> Self {
EtaReduction {
inner_id,
ctor: ctor.into(),
is_valid: true,
}
}
pub fn invalid(ctor: impl Into<String>) -> Self {
EtaReduction {
inner_id: 0,
ctor: ctor.into(),
is_valid: false,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct EtaNormRunSummary {
pub total_expressions: u64,
pub eta_expansions: u64,
pub eta_reductions: u64,
pub k_reductions: u64,
pub proj_rewrites: u64,
pub unchanged: u64,
}
#[allow(dead_code)]
impl EtaNormRunSummary {
pub fn new() -> Self {
EtaNormRunSummary::default()
}
pub fn change_rate(&self) -> f64 {
if self.total_expressions == 0 {
return 0.0;
}
self.unchanged as f64 / self.total_expressions as f64
}
pub fn total_changes(&self) -> u64 {
self.eta_expansions + self.eta_reductions + self.k_reductions + self.proj_rewrites
}
pub fn format(&self) -> String {
format!(
"total={} expansions={} reductions={} k_red={} proj_rew={} unchanged={}",
self.total_expressions,
self.eta_expansions,
self.eta_reductions,
self.k_reductions,
self.proj_rewrites,
self.unchanged
)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct EtaPassConfig {
pub do_expand: bool,
pub do_reduce: bool,
pub do_k_reduce: bool,
pub do_proj_rewrite: bool,
pub max_passes: u32,
pub verbose: bool,
}
#[allow(dead_code)]
impl EtaPassConfig {
pub fn default_config() -> Self {
EtaPassConfig {
do_expand: true,
do_reduce: true,
do_k_reduce: true,
do_proj_rewrite: true,
max_passes: 10,
verbose: false,
}
}
pub fn proj_only() -> Self {
EtaPassConfig {
do_expand: false,
do_reduce: false,
do_k_reduce: false,
do_proj_rewrite: true,
max_passes: 1,
verbose: false,
}
}
pub fn any_enabled(&self) -> bool {
self.do_expand || self.do_reduce || self.do_k_reduce || self.do_proj_rewrite
}
}
#[allow(dead_code)]
pub struct EtaNormalFormChecker {
registry: StructureRegistry,
}
#[allow(dead_code)]
impl EtaNormalFormChecker {
pub fn new(registry: StructureRegistry) -> Self {
EtaNormalFormChecker { registry }
}
pub fn knows_structure(&self, name: &str) -> bool {
self.registry.find(name).is_some()
}
pub fn expected_arity(&self, structure_name: &str) -> Option<usize> {
self.registry.find(structure_name).map(|s| s.fields.len())
}
pub fn check_expansion(&self, exp: &EtaExpanded) -> bool {
match self.expected_arity(&exp.ctor) {
Some(arity) => exp.field_ids.len() == arity,
None => false,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EtaChangeKind {
Expanded,
Reduced,
KReduced,
ProjRewritten,
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CoherenceResult {
Coherent,
Incoherent { reason: String },
Unknown,
}
#[allow(dead_code)]
impl CoherenceResult {
pub fn ok() -> Self {
CoherenceResult::Coherent
}
pub fn fail(reason: impl Into<String>) -> Self {
CoherenceResult::Incoherent {
reason: reason.into(),
}
}
pub fn is_coherent(&self) -> bool {
matches!(self, CoherenceResult::Coherent)
}
}
#[allow(dead_code)]
pub struct EtaNormalizationPass {
pub stats: EtaStats,
worklist: Vec<u64>,
}
#[allow(dead_code)]
impl EtaNormalizationPass {
pub fn new() -> Self {
EtaNormalizationPass {
stats: EtaStats::new(),
worklist: Vec::new(),
}
}
pub fn schedule(&mut self, id: u64) {
if !self.worklist.contains(&id) {
self.worklist.push(id);
}
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Option<u64> {
if self.worklist.is_empty() {
None
} else {
Some(self.worklist.remove(0))
}
}
pub fn is_done(&self) -> bool {
self.worklist.is_empty()
}
pub fn pending(&self) -> usize {
self.worklist.len()
}
}
#[allow(dead_code)]
pub struct KReductionTable {
entries: Vec<(String, bool)>,
}
#[allow(dead_code)]
impl KReductionTable {
pub fn new() -> Self {
KReductionTable {
entries: Vec::new(),
}
}
pub fn set(&mut self, name: &str, reducible: bool) {
if let Some(e) = self.entries.iter_mut().find(|(n, _)| n == name) {
e.1 = reducible;
} else {
self.entries.push((name.to_string(), reducible));
}
}
pub fn is_k_reducible(&self, name: &str) -> bool {
self.entries
.iter()
.find(|(n, _)| n == name)
.is_some_and(|(_, r)| *r)
}
pub fn k_reducible_names(&self) -> Vec<&str> {
self.entries
.iter()
.filter(|(_, r)| *r)
.map(|(n, _)| n.as_str())
.collect()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
#[allow(dead_code)]
pub struct ShapeEquivalence {
registry: StructureRegistry,
}
#[allow(dead_code)]
impl ShapeEquivalence {
pub fn new(registry: StructureRegistry) -> Self {
ShapeEquivalence { registry }
}
pub fn may_be_equal(&self, a: &StructShape, b: &StructShape) -> bool {
match (a, b) {
(
StructShape::Ctor {
name: n1,
arity: ar1,
},
StructShape::Ctor {
name: n2,
arity: ar2,
},
) => n1 == n2 && ar1 == ar2,
(StructShape::Proj { field_index: i }, StructShape::Proj { field_index: j }) => i == j,
(StructShape::Other, StructShape::Other) => true,
_ => false,
}
}
pub fn is_expandable(&self, shape: &StructShape) -> bool {
match shape {
StructShape::Ctor { name, .. } => self.registry.find(name).is_some(),
_ => false,
}
}
}
#[allow(dead_code)]
pub struct RecordUpdateBatch {
updates: Vec<RecordUpdate>,
}
#[allow(dead_code)]
impl RecordUpdateBatch {
pub fn new() -> Self {
RecordUpdateBatch {
updates: Vec::new(),
}
}
pub fn add(&mut self, update: RecordUpdate) {
self.updates.push(update);
}
pub fn updates(&self) -> &[RecordUpdate] {
&self.updates
}
pub fn len(&self) -> usize {
self.updates.len()
}
pub fn is_empty(&self) -> bool {
self.updates.is_empty()
}
pub fn clear(&mut self) {
self.updates.clear();
}
pub fn updates_for_expr(&self, expr_id: u64) -> Vec<&RecordUpdate> {
self.updates
.iter()
.filter(|u| u.expr_id == expr_id)
.collect()
}
}
#[allow(dead_code)]
pub struct EtaEqualityOracle {
canon_map: EtaCanonMap,
}
#[allow(dead_code)]
impl EtaEqualityOracle {
pub fn new(canon_map: EtaCanonMap) -> Self {
EtaEqualityOracle { canon_map }
}
pub fn are_eta_equal(&self, a: u64, b: u64) -> bool {
self.canon_map.canonical(a) == self.canon_map.canonical(b)
}
pub fn canonical(&self, id: u64) -> u64 {
self.canon_map.canonical(id)
}
pub fn class_count(&self) -> usize {
let mut canons: Vec<u64> = self.canon_map.map.iter().map(|(_, c)| *c).collect();
canons.sort_unstable();
canons.dedup();
canons.len()
}
}
#[allow(dead_code)]
pub struct FieldBoundsChecker;
#[allow(dead_code)]
impl FieldBoundsChecker {
pub fn check(arity: u32, field_index: u32) -> Result<(), String> {
if field_index < arity {
Ok(())
} else {
Err(format!(
"field index {} out of bounds for arity {}",
field_index, arity
))
}
}
pub fn validate_set(set: &ProjectionRewriteSet, reg: &StructureRegistry) -> Vec<String> {
let mut errors = Vec::new();
for rule in &set.rules {
let arity = reg.field_count(&rule.ctor_name) as u32;
if arity == 0 {
errors.push(format!("unknown structure: {}", rule.ctor_name));
} else if let Err(e) = Self::check(arity, rule.field_index) {
errors.push(format!("rule '{}': {}", rule.projector_name, e));
}
}
errors
}
}
#[allow(dead_code)]
pub struct EtaLongChecker {
results: Vec<(u64, EtaLongStatus)>,
}
#[allow(dead_code)]
impl EtaLongChecker {
pub fn new() -> Self {
EtaLongChecker {
results: Vec::new(),
}
}
pub fn record(&mut self, id: u64, status: EtaLongStatus) {
self.results.push((id, status));
}
pub fn status(&self, id: u64) -> Option<EtaLongStatus> {
self.results.iter().find(|(i, _)| *i == id).map(|(_, s)| *s)
}
pub fn summary(&self) -> (usize, usize, usize) {
let eta_long = self
.results
.iter()
.filter(|(_, s)| *s == EtaLongStatus::EtaLong)
.count();
let not_eta_long = self
.results
.iter()
.filter(|(_, s)| *s == EtaLongStatus::NotEtaLong)
.count();
let unknown = self
.results
.iter()
.filter(|(_, s)| *s == EtaLongStatus::Unknown)
.count();
(eta_long, not_eta_long, unknown)
}
pub fn len(&self) -> usize {
self.results.len()
}
pub fn is_empty(&self) -> bool {
self.results.is_empty()
}
}
#[allow(dead_code)]
pub struct EtaChangeLog {
entries: Vec<EtaChangeEntry>,
}
#[allow(dead_code)]
impl EtaChangeLog {
pub fn new() -> Self {
EtaChangeLog {
entries: Vec::new(),
}
}
pub fn record(&mut self, expr_id: u64, kind: EtaChangeKind, pass_num: u32) {
self.entries.push(EtaChangeEntry {
expr_id,
kind,
pass_num,
});
}
pub fn changes_of_kind(&self, kind: EtaChangeKind) -> Vec<&EtaChangeEntry> {
self.entries.iter().filter(|e| e.kind == kind).collect()
}
pub fn changes_for_expr(&self, id: u64) -> Vec<&EtaChangeEntry> {
self.entries.iter().filter(|e| e.expr_id == id).collect()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn changes_in_pass(&self, pass: u32) -> Vec<&EtaChangeEntry> {
self.entries.iter().filter(|e| e.pass_num == pass).collect()
}
}
#[allow(dead_code)]
pub struct StructureRegistry {
structures: Vec<StructureDef>,
}
#[allow(dead_code)]
impl StructureRegistry {
pub fn new() -> Self {
StructureRegistry {
structures: Vec::new(),
}
}
pub fn register(
&mut self,
name: impl Into<String>,
ctor_name: impl Into<String>,
fields: Vec<FieldDescriptor>,
) {
self.structures.push(StructureDef {
name: name.into(),
ctor_name: ctor_name.into(),
fields,
});
}
pub fn find(&self, name: &str) -> Option<&StructureDef> {
self.structures.iter().find(|s| s.name == name)
}
pub fn len(&self) -> usize {
self.structures.len()
}
pub fn is_empty(&self) -> bool {
self.structures.is_empty()
}
pub fn names(&self) -> Vec<&str> {
self.structures.iter().map(|s| s.name.as_str()).collect()
}
pub fn field_count(&self, name: &str) -> usize {
self.find(name).map_or(0, |s| s.fields.len())
}
pub fn has_prop_fields(&self, name: &str) -> bool {
self.find(name)
.is_some_and(|s| s.fields.iter().any(|f| f.is_prop))
}
pub fn projectors(&self, name: &str) -> Vec<String> {
self.find(name).map_or(Vec::new(), |s| {
s.fields
.iter()
.map(|f| format!("{}.{}", s.name, f.name))
.collect()
})
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EtaCategory {
Record,
Function,
Inductive,
Proposition,
Primitive,
}
#[allow(dead_code)]
impl EtaCategory {
pub fn needs_eta(&self) -> bool {
matches!(self, EtaCategory::Record | EtaCategory::Function)
}
pub fn label(&self) -> &'static str {
match self {
EtaCategory::Record => "record",
EtaCategory::Function => "function",
EtaCategory::Inductive => "inductive",
EtaCategory::Proposition => "proposition",
EtaCategory::Primitive => "primitive",
}
}
}
pub struct StructureEta<'a> {
env: &'a Environment,
}
impl<'a> StructureEta<'a> {
pub fn new(env: &'a Environment) -> Self {
StructureEta { env }
}
pub fn is_structure_type(&self, ty: &Expr) -> bool {
let name = match ty {
Expr::Const(n, _) => n,
_ => {
if let Some(n) = head_const(ty) {
return self.env.is_structure_like(n);
}
return false;
}
};
self.env.is_structure_like(name)
}
pub fn collect_field_types(&self, struct_name: &Name) -> Vec<(Name, Expr)> {
let iv = match self.env.get_inductive_val(struct_name) {
Some(v) => v,
None => return vec![],
};
let ctor_name = match iv.ctors.first() {
Some(n) => n.clone(),
None => return vec![],
};
let cv = match self.env.get_constructor_val(&ctor_name) {
Some(v) => v,
None => return vec![],
};
let ctor_ty = match self.env.get_type(&ctor_name) {
Some(t) => t.clone(),
None => return vec![],
};
let num_params = cv.num_params as usize;
let num_fields = cv.num_fields as usize;
let mut current = &ctor_ty;
let mut skipped = 0usize;
let mut fields = Vec::with_capacity(num_fields);
while let Expr::Pi(_, name, domain, codomain) = current {
if skipped < num_params {
skipped += 1;
current = codomain;
} else if fields.len() < num_fields {
fields.push((name.clone(), *domain.clone()));
current = codomain;
} else {
break;
}
}
fields
}
pub fn make_proj_chain(&self, expr: &Expr, struct_name: &Name, num_fields: usize) -> Vec<Expr> {
(0..num_fields)
.map(|i| Expr::Proj(struct_name.clone(), i as u32, Box::new(expr.clone())))
.collect()
}
pub fn eta_expand_struct(&self, expr: &Expr, ty: &Expr) -> Option<Expr> {
let struct_name = head_const(ty)?.clone();
if !self.env.is_structure_like(&struct_name) {
return None;
}
let iv = self.env.get_inductive_val(&struct_name)?;
let ctor_name = iv.ctors.first()?.clone();
let cv = self.env.get_constructor_val(&ctor_name)?;
let num_fields = cv.num_fields as usize;
let ctor_levels: Vec<Level> = vec![];
let ctor_const = Expr::Const(ctor_name, ctor_levels);
let projections = self.make_proj_chain(expr, &struct_name, num_fields);
if projections.is_empty() {
return Some(ctor_const);
}
let result = projections.into_iter().fold(ctor_const, |acc, proj| {
Expr::App(Box::new(acc), Box::new(proj))
});
Some(result)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StructShape {
Ctor { name: String, arity: u32 },
Proj { field_index: u32 },
Other,
}
#[allow(dead_code)]
impl StructShape {
pub fn ctor(name: impl Into<String>, arity: u32) -> Self {
StructShape::Ctor {
name: name.into(),
arity,
}
}
pub fn proj(field_index: u32) -> Self {
StructShape::Proj { field_index }
}
pub fn is_ctor(&self) -> bool {
matches!(self, StructShape::Ctor { .. })
}
pub fn is_proj(&self) -> bool {
matches!(self, StructShape::Proj { .. })
}
pub fn arity(&self) -> Option<u32> {
match self {
StructShape::Ctor { arity, .. } => Some(*arity),
_ => None,
}
}
}
#[allow(dead_code)]
pub struct EtaRewriteEngine {
proj_rules: ProjectionRewriteSet,
max_steps: u32,
steps_taken: u32,
}
#[allow(dead_code)]
impl EtaRewriteEngine {
pub fn new(proj_rules: ProjectionRewriteSet, max_steps: u32) -> Self {
EtaRewriteEngine {
proj_rules,
max_steps,
steps_taken: 0,
}
}
pub fn steps_taken(&self) -> u32 {
self.steps_taken
}
pub fn is_exhausted(&self) -> bool {
self.steps_taken >= self.max_steps
}
pub fn apply_proj(&mut self, projector: &str) -> Option<u32> {
if self.is_exhausted() {
return None;
}
if let Some(rule) = self.proj_rules.find_by_projector(projector) {
let idx = rule.field_index;
self.steps_taken += 1;
Some(idx)
} else {
None
}
}
pub fn reset(&mut self) {
self.steps_taken = 0;
}
pub fn rule_count(&self) -> usize {
self.proj_rules.len()
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ProjectionDescriptor {
pub structure_name: String,
pub projector_name: String,
pub field_index: u32,
}
#[allow(dead_code)]
impl ProjectionDescriptor {
pub fn new(
structure_name: impl Into<String>,
projector_name: impl Into<String>,
field_index: u32,
) -> Self {
ProjectionDescriptor {
structure_name: structure_name.into(),
projector_name: projector_name.into(),
field_index,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ProjectionRewrite {
pub ctor_name: String,
pub projector_name: String,
pub field_index: u32,
}
#[allow(dead_code)]
impl ProjectionRewrite {
pub fn new(
ctor_name: impl Into<String>,
projector_name: impl Into<String>,
field_index: u32,
) -> Self {
ProjectionRewrite {
ctor_name: ctor_name.into(),
projector_name: projector_name.into(),
field_index,
}
}
pub fn as_rule(&self) -> String {
format!(
"{} ({}.mk f0 ... f{} ...) → f{}",
self.projector_name, self.ctor_name, self.field_index, self.field_index
)
}
}
#[allow(dead_code)]
pub struct EtaCategorizer {
entries: Vec<(u64, EtaCategory)>,
}
#[allow(dead_code)]
impl EtaCategorizer {
pub fn new() -> Self {
EtaCategorizer {
entries: Vec::new(),
}
}
pub fn assign(&mut self, id: u64, cat: EtaCategory) {
self.entries.push((id, cat));
}
pub fn get(&self, id: u64) -> Option<EtaCategory> {
self.entries.iter().find(|(i, _)| *i == id).map(|(_, c)| *c)
}
pub fn needs_eta_ids(&self) -> Vec<u64> {
self.entries
.iter()
.filter(|(_, c)| c.needs_eta())
.map(|(i, _)| *i)
.collect()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn count_by_category(&self) -> [(EtaCategory, usize); 5] {
let cats = [
EtaCategory::Record,
EtaCategory::Function,
EtaCategory::Inductive,
EtaCategory::Proposition,
EtaCategory::Primitive,
];
cats.map(|c| {
let count = self.entries.iter().filter(|(_, cat)| *cat == c).count();
(c, count)
})
}
}
#[allow(dead_code)]
pub struct EtaCanonMap {
map: Vec<(u64, u64)>,
}
#[allow(dead_code)]
impl EtaCanonMap {
pub fn new() -> Self {
EtaCanonMap { map: Vec::new() }
}
pub fn insert(&mut self, original: u64, canon: u64) {
if let Some(e) = self.map.iter_mut().find(|(o, _)| *o == original) {
e.1 = canon;
} else {
self.map.push((original, canon));
}
}
pub fn canonical(&self, original: u64) -> u64 {
self.map
.iter()
.find(|(o, _)| *o == original)
.map_or(original, |(_, c)| *c)
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
pub fn originals_of(&self, canon: u64) -> Vec<u64> {
self.map
.iter()
.filter(|(_, c)| *c == canon)
.map(|(o, _)| *o)
.collect()
}
}
#[allow(dead_code)]
pub struct EtaGraph {
edges: Vec<(u64, u64)>,
}
#[allow(dead_code)]
impl EtaGraph {
pub fn new() -> Self {
EtaGraph { edges: Vec::new() }
}
pub fn add_edge(&mut self, from: u64, to: u64) {
if !self.has_edge(from, to) {
self.edges.push((from, to));
}
}
pub fn has_edge(&self, from: u64, to: u64) -> bool {
self.edges.iter().any(|&(f, t)| f == from && t == to)
}
pub fn dependents_of(&self, id: u64) -> Vec<u64> {
self.edges
.iter()
.filter(|(_, t)| *t == id)
.map(|(f, _)| *f)
.collect()
}
pub fn dependencies_of(&self, id: u64) -> Vec<u64> {
self.edges
.iter()
.filter(|(f, _)| *f == id)
.map(|(_, t)| *t)
.collect()
}
pub fn edge_count(&self) -> usize {
self.edges.len()
}
pub fn is_empty(&self) -> bool {
self.edges.is_empty()
}
pub fn remove_node(&mut self, id: u64) {
self.edges.retain(|(f, t)| *f != id && *t != id);
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct EtaRedex {
pub path: Vec<u32>,
pub struct_name: String,
pub inner_id: u64,
}
#[allow(dead_code)]
impl EtaRedex {
pub fn new(path: Vec<u32>, struct_name: impl Into<String>, inner_id: u64) -> Self {
EtaRedex {
path,
struct_name: struct_name.into(),
inner_id,
}
}
pub fn is_top_level(&self) -> bool {
self.path.is_empty()
}
pub fn depth(&self) -> usize {
self.path.len()
}
}
#[allow(dead_code)]
pub struct EtaDepthTracker {
stack: Vec<String>,
}
#[allow(dead_code)]
impl EtaDepthTracker {
pub fn new() -> Self {
EtaDepthTracker { stack: Vec::new() }
}
pub fn push(&mut self, struct_name: &str) {
self.stack.push(struct_name.to_string());
}
pub fn pop(&mut self) -> Option<String> {
self.stack.pop()
}
pub fn depth(&self) -> usize {
self.stack.len()
}
pub fn is_nested(&self) -> bool {
!self.stack.is_empty()
}
pub fn current(&self) -> Option<&str> {
self.stack.last().map(|s| s.as_str())
}
pub fn path(&self) -> String {
self.stack.join(".")
}
pub fn contains(&self, name: &str) -> bool {
self.stack.iter().any(|s| s == name)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct RecordUpdate {
pub expr_id: u64,
pub struct_name: String,
pub field_index: u32,
pub new_value_id: u64,
}
#[allow(dead_code)]
impl RecordUpdate {
pub fn new(
expr_id: u64,
struct_name: impl Into<String>,
field_index: u32,
new_value_id: u64,
) -> Self {
RecordUpdate {
expr_id,
struct_name: struct_name.into(),
field_index,
new_value_id,
}
}
pub fn describe(&self) -> String {
format!(
"update {}.{} of expr #{} with expr #{}",
self.struct_name, self.field_index, self.expr_id, self.new_value_id
)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct EtaChangeEntry {
pub expr_id: u64,
pub kind: EtaChangeKind,
pub pass_num: u32,
}
#[allow(dead_code)]
pub struct EtaStateMachine {
state: EtaState,
structure_name: Option<String>,
field_count: u32,
processed_fields: u32,
}
#[allow(dead_code)]
impl EtaStateMachine {
pub fn new() -> Self {
EtaStateMachine {
state: EtaState::Idle,
structure_name: None,
field_count: 0,
processed_fields: 0,
}
}
pub fn start(&mut self, name: &str, field_count: u32) {
self.state = EtaState::Expanding;
self.structure_name = Some(name.to_string());
self.field_count = field_count;
self.processed_fields = 0;
}
pub fn process_field(&mut self) -> bool {
if self.state != EtaState::Expanding {
return false;
}
self.processed_fields += 1;
if self.processed_fields >= self.field_count {
self.state = EtaState::Done;
}
true
}
pub fn fail(&mut self) {
self.state = EtaState::Failed;
}
pub fn is_done(&self) -> bool {
self.state == EtaState::Done
}
pub fn is_failed(&self) -> bool {
self.state == EtaState::Failed
}
pub fn is_expanding(&self) -> bool {
self.state == EtaState::Expanding
}
pub fn remaining(&self) -> u32 {
self.field_count.saturating_sub(self.processed_fields)
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EtaLongStatus {
EtaLong,
NotEtaLong,
Unknown,
}
#[allow(dead_code)]
impl EtaLongStatus {
pub fn is_eta_long(&self) -> bool {
*self == EtaLongStatus::EtaLong
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct EtaPassResult {
pub summary: EtaNormRunSummary,
pub changed_ids: Vec<u64>,
}
#[allow(dead_code)]
impl EtaPassResult {
pub fn new() -> Self {
EtaPassResult {
summary: EtaNormRunSummary::new(),
changed_ids: Vec::new(),
}
}
pub fn any_changes(&self) -> bool {
!self.changed_ids.is_empty()
}
}
#[allow(dead_code)]
pub struct StructFlatteningPass {
pub processed: u64,
pub flattened: u64,
}
#[allow(dead_code)]
impl StructFlatteningPass {
pub fn new() -> Self {
StructFlatteningPass {
processed: 0,
flattened: 0,
}
}
pub fn record_processed(&mut self) {
self.processed += 1;
}
pub fn record_flattened(&mut self) {
self.flattened += 1;
}
pub fn flatten_rate(&self) -> f64 {
if self.processed == 0 {
0.0
} else {
self.flattened as f64 / self.processed as f64
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct FieldDescriptor {
pub name: String,
pub index: u32,
pub is_prop: bool,
}
#[allow(dead_code)]
impl FieldDescriptor {
pub fn new(name: impl Into<String>, index: u32, is_prop: bool) -> Self {
FieldDescriptor {
name: name.into(),
index,
is_prop,
}
}
pub fn is_data(&self) -> bool {
!self.is_prop
}
}
#[allow(dead_code)]
pub struct InjectivityChecker {
injective: Vec<String>,
}
#[allow(dead_code)]
impl InjectivityChecker {
pub fn new() -> Self {
InjectivityChecker {
injective: Vec::new(),
}
}
pub fn mark_injective(&mut self, ctor: &str) {
if !self.injective.contains(&ctor.to_string()) {
self.injective.push(ctor.to_string());
}
}
pub fn is_injective(&self, ctor: &str) -> bool {
self.injective.iter().any(|s| s == ctor)
}
pub fn injective_ctors(&self) -> &[String] {
&self.injective
}
pub fn count(&self) -> usize {
self.injective.len()
}
}