use std::sync::Arc;
use sim_kernel::{Cx, Diagnostic, Expr, Result, Value, shape_is_subshape_of};
use crate::{
algebra::{capture_symbol, number_expr, number_value},
base::{Bindings, MatchScore, Shape, ShapeDoc, ShapeMatch},
};
pub struct AndShape {
parts: Vec<Arc<dyn Shape>>,
}
impl AndShape {
pub fn new(parts: Vec<Arc<dyn Shape>>) -> Self {
Self { parts }
}
pub fn parts(&self) -> &[Arc<dyn Shape>] {
&self.parts
}
}
impl Shape for AndShape {
fn is_total(&self) -> bool {
self.parts.iter().all(|part| part.is_total())
}
fn is_effectful(&self) -> bool {
self.parts.iter().any(|part| part.is_effectful())
}
fn is_subshape_of(&self, cx: &mut Cx, parent: &dyn Shape) -> Result<Option<bool>> {
for part in &self.parts {
if shape_is_subshape_of(cx, part.as_ref(), parent)? {
return Ok(Some(true));
}
}
let Some(parent) = parent.as_any().downcast_ref::<Self>() else {
return Ok(None);
};
for parent_part in parent.parts() {
let mut covered = false;
for part in &self.parts {
if shape_is_subshape_of(cx, part.as_ref(), parent_part.as_ref())? {
covered = true;
break;
}
}
if !covered {
return Ok(None);
}
}
Ok(Some(true))
}
fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
if self.parts.is_empty() {
return Ok(ShapeMatch::accept(MatchScore::exact(0)));
}
let mut out = ShapeMatch::accept(MatchScore::exact(10));
for part in &self.parts {
let mut matched = part.check_value(cx, value.clone())?;
if !matched.accepted {
matched
.diagnostics
.insert(0, Diagnostic::error("shape-and: child rejected"));
return Ok(matched);
}
out.captures.extend(matched.captures);
out.score += matched.score;
}
Ok(out)
}
fn check_expr(&self, cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
if self.parts.is_empty() {
return Ok(ShapeMatch::accept(MatchScore::exact(0)));
}
let mut out = ShapeMatch::accept(MatchScore::exact(10));
for part in &self.parts {
let mut matched = part.check_expr(cx, expr)?;
if !matched.accepted {
matched
.diagnostics
.insert(0, Diagnostic::error("shape-and: child rejected"));
return Ok(matched);
}
out.captures.extend(matched.captures);
out.score += matched.score;
}
Ok(out)
}
fn describe(&self, cx: &mut Cx) -> Result<ShapeDoc> {
let mut doc = ShapeDoc::new("and shape");
for part in &self.parts {
doc = doc.with_detail(part.describe(cx)?.name);
}
Ok(doc)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OrStrategy {
FirstMatch,
BestScore,
}
pub struct OrShape {
choices: Vec<Arc<dyn Shape>>,
strategy: OrStrategy,
}
impl OrShape {
pub fn new(choices: Vec<Arc<dyn Shape>>) -> Self {
Self::with_strategy(choices, OrStrategy::FirstMatch)
}
pub fn with_strategy(choices: Vec<Arc<dyn Shape>>, strategy: OrStrategy) -> Self {
Self { choices, strategy }
}
pub fn choices(&self) -> &[Arc<dyn Shape>] {
&self.choices
}
pub fn strategy(&self) -> OrStrategy {
self.strategy
}
}
impl Shape for OrShape {
fn is_total(&self) -> bool {
self.choices.iter().any(|choice| choice.is_total())
}
fn is_effectful(&self) -> bool {
self.choices.iter().any(|choice| choice.is_effectful())
}
fn is_subshape_of(&self, cx: &mut Cx, parent: &dyn Shape) -> Result<Option<bool>> {
for choice in &self.choices {
if !shape_is_subshape_of(cx, choice.as_ref(), parent)? {
return Ok(None);
}
}
Ok(Some(true))
}
fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
match self.strategy {
OrStrategy::FirstMatch => check_value_first(cx, &self.choices, value),
OrStrategy::BestScore => check_value_best(cx, &self.choices, value),
}
}
fn check_expr(&self, cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
match self.strategy {
OrStrategy::FirstMatch => check_expr_first(cx, &self.choices, expr),
OrStrategy::BestScore => check_expr_best(cx, &self.choices, expr),
}
}
fn describe(&self, cx: &mut Cx) -> Result<ShapeDoc> {
let mut doc = ShapeDoc::new("or shape");
for choice in &self.choices {
doc = doc.with_detail(choice.describe(cx)?.name);
}
Ok(doc)
}
}
pub struct NotShape {
inner: Arc<dyn Shape>,
}
impl NotShape {
pub fn new(inner: Arc<dyn Shape>) -> Self {
Self { inner }
}
pub fn inner(&self) -> &Arc<dyn Shape> {
&self.inner
}
}
impl Shape for NotShape {
fn is_effectful(&self) -> bool {
self.inner.is_effectful()
}
fn is_subshape_of(&self, cx: &mut Cx, parent: &dyn Shape) -> Result<Option<bool>> {
let Some(parent) = parent.as_any().downcast_ref::<Self>() else {
return Ok(None);
};
shape_is_subshape_of(cx, parent.inner.as_ref(), self.inner.as_ref()).map(Some)
}
fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
let matched = self.inner.check_value(cx, value)?;
if matched.accepted {
return Ok(ShapeMatch::reject("shape-not: inner accepted"));
}
let mut out = ShapeMatch::accept(MatchScore::exact(10));
out.captures
.bind_value(capture_symbol("negated"), cx.factory().bool(true)?);
Ok(out)
}
fn check_expr(&self, cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
let matched = self.inner.check_expr(cx, expr)?;
if matched.accepted {
return Ok(ShapeMatch::reject("shape-not: inner accepted"));
}
let mut out = ShapeMatch::accept(MatchScore::exact(10));
out.captures
.bind_expr(capture_symbol("negated"), Expr::Bool(true));
Ok(out)
}
fn describe(&self, cx: &mut Cx) -> Result<ShapeDoc> {
Ok(ShapeDoc::new("not shape").with_detail(self.inner.describe(cx)?.name))
}
}
fn check_value_first(cx: &mut Cx, choices: &[Arc<dyn Shape>], value: Value) -> Result<ShapeMatch> {
let mut diagnostics = Vec::new();
for (index, choice) in choices.iter().enumerate() {
let mut matched = choice.check_value(cx, value.clone())?;
if matched.accepted {
matched
.captures
.bind_value(capture_symbol("branch-index"), number_value(cx, index)?);
return Ok(matched);
}
diagnostics.extend(matched.diagnostics);
}
reject_or(diagnostics)
}
fn check_value_best(cx: &mut Cx, choices: &[Arc<dyn Shape>], value: Value) -> Result<ShapeMatch> {
let mut diagnostics = Vec::new();
let mut best = None::<(usize, ShapeMatch)>;
for (index, choice) in choices.iter().enumerate() {
let matched = choice.check_value(cx, value.clone())?;
if matched.accepted {
let replace = best
.as_ref()
.map(|(_, current)| matched.score > current.score)
.unwrap_or(true);
if replace {
best = Some((index, matched));
}
} else {
diagnostics.extend(matched.diagnostics);
}
}
match best {
Some((index, mut matched)) => {
matched
.captures
.bind_value(capture_symbol("branch-index"), number_value(cx, index)?);
Ok(matched)
}
None => reject_or(diagnostics),
}
}
fn check_expr_first(cx: &mut Cx, choices: &[Arc<dyn Shape>], expr: &Expr) -> Result<ShapeMatch> {
let mut diagnostics = Vec::new();
for (index, choice) in choices.iter().enumerate() {
let mut matched = choice.check_expr(cx, expr)?;
if matched.accepted {
matched
.captures
.bind_expr(capture_symbol("branch-index"), number_expr(index));
return Ok(matched);
}
diagnostics.extend(matched.diagnostics);
}
reject_or(diagnostics)
}
fn check_expr_best(cx: &mut Cx, choices: &[Arc<dyn Shape>], expr: &Expr) -> Result<ShapeMatch> {
let mut diagnostics = Vec::new();
let mut best = None::<(usize, ShapeMatch)>;
for (index, choice) in choices.iter().enumerate() {
let matched = choice.check_expr(cx, expr)?;
if matched.accepted {
let replace = best
.as_ref()
.map(|(_, current)| matched.score > current.score)
.unwrap_or(true);
if replace {
best = Some((index, matched));
}
} else {
diagnostics.extend(matched.diagnostics);
}
}
match best {
Some((index, mut matched)) => {
matched
.captures
.bind_expr(capture_symbol("branch-index"), number_expr(index));
Ok(matched)
}
None => reject_or(diagnostics),
}
}
fn reject_or(mut diagnostics: Vec<Diagnostic>) -> Result<ShapeMatch> {
diagnostics.insert(0, Diagnostic::error("shape-or: no branch accepted"));
Ok(ShapeMatch {
accepted: false,
captures: Bindings::new(),
score: MatchScore::reject(),
diagnostics,
})
}