use std::sync::Arc;
use sim_kernel::{Cx, Expr, MatchScore, Result, Shape, ShapeDoc, ShapeMatch, Symbol, Value};
use crate::kinds::{INTENT_KINDS, INTENT_NAMESPACE, intent_kind, is_known_kind};
use crate::model::intent_kind_of;
pub struct IntentKindShape {
kind: Symbol,
symbol: Symbol,
}
impl IntentKindShape {
pub fn new(name: &str) -> Self {
Self {
kind: intent_kind(name),
symbol: Symbol::qualified(INTENT_NAMESPACE, pascal_case(name)),
}
}
}
impl Shape for IntentKindShape {
fn symbol(&self) -> Option<Symbol> {
Some(self.symbol.clone())
}
fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
let expr = value.object().as_expr(cx)?;
self.check_expr(cx, &expr)
}
fn check_expr(&self, _cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
match intent_kind_of(expr) {
Some(kind) if kind == self.kind => Ok(ShapeMatch::accept(MatchScore::exact(20))),
Some(kind) => Ok(ShapeMatch::reject(format!(
"Intent kind '{kind}' does not match '{}'",
self.kind
))),
None => Ok(ShapeMatch::reject("value is not an Intent")),
}
}
fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
Ok(ShapeDoc::new(self.symbol.name.to_string())
.with_detail(format!("matches Intents tagged '{}'", self.kind)))
}
}
pub struct IntentShape;
impl Shape for IntentShape {
fn symbol(&self) -> Option<Symbol> {
Some(intent_shape_symbol())
}
fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
let expr = value.object().as_expr(cx)?;
self.check_expr(cx, &expr)
}
fn check_expr(&self, _cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
match intent_kind_of(expr) {
Some(kind) if is_known_kind(&kind) => Ok(ShapeMatch::accept(MatchScore::exact(5))),
Some(kind) => Ok(ShapeMatch::reject(format!(
"unrecognized Intent kind '{kind}'"
))),
None => Ok(ShapeMatch::reject("value is not an Intent")),
}
}
fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
Ok(ShapeDoc::new("Intent").with_detail("any recognized Intent (a kind-tagged map)"))
}
}
pub fn intent_shape_symbol() -> Symbol {
Symbol::qualified(INTENT_NAMESPACE, "Intent")
}
pub fn intent_shape_specs() -> Vec<(Symbol, Arc<dyn Shape>)> {
let mut specs: Vec<(Symbol, Arc<dyn Shape>)> =
vec![(intent_shape_symbol(), Arc::new(IntentShape))];
for name in INTENT_KINDS {
let shape = IntentKindShape::new(name);
specs.push((shape.symbol.clone(), Arc::new(shape)));
}
specs
}
fn pascal_case(name: &str) -> String {
name.split('-')
.map(|word| {
let mut chars = word.chars();
match chars.next() {
Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
None => String::new(),
}
})
.collect()
}