1use std::sync::Arc;
10
11use sim_kernel::{Cx, Expr, MatchScore, Result, Shape, ShapeDoc, ShapeMatch, Symbol, Value};
12
13use crate::kinds::{INTENT_KINDS, INTENT_NAMESPACE, intent_kind, is_known_kind};
14use crate::model::intent_kind_of;
15
16pub struct IntentKindShape {
18 kind: Symbol,
19 symbol: Symbol,
20}
21
22impl IntentKindShape {
23 pub fn new(name: &str) -> Self {
25 Self {
26 kind: intent_kind(name),
27 symbol: Symbol::qualified(INTENT_NAMESPACE, pascal_case(name)),
28 }
29 }
30}
31
32impl Shape for IntentKindShape {
33 fn symbol(&self) -> Option<Symbol> {
34 Some(self.symbol.clone())
35 }
36
37 fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
38 let expr = value.object().as_expr(cx)?;
39 self.check_expr(cx, &expr)
40 }
41
42 fn check_expr(&self, _cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
43 match intent_kind_of(expr) {
44 Some(kind) if kind == self.kind => Ok(ShapeMatch::accept(MatchScore::exact(20))),
45 Some(kind) => Ok(ShapeMatch::reject(format!(
46 "Intent kind '{kind}' does not match '{}'",
47 self.kind
48 ))),
49 None => Ok(ShapeMatch::reject("value is not an Intent")),
50 }
51 }
52
53 fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
54 Ok(ShapeDoc::new(self.symbol.name.to_string())
55 .with_detail(format!("matches Intents tagged '{}'", self.kind)))
56 }
57}
58
59pub struct IntentShape;
61
62impl Shape for IntentShape {
63 fn symbol(&self) -> Option<Symbol> {
64 Some(intent_shape_symbol())
65 }
66
67 fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
68 let expr = value.object().as_expr(cx)?;
69 self.check_expr(cx, &expr)
70 }
71
72 fn check_expr(&self, _cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
73 match intent_kind_of(expr) {
74 Some(kind) if is_known_kind(&kind) => Ok(ShapeMatch::accept(MatchScore::exact(5))),
75 Some(kind) => Ok(ShapeMatch::reject(format!(
76 "unrecognized Intent kind '{kind}'"
77 ))),
78 None => Ok(ShapeMatch::reject("value is not an Intent")),
79 }
80 }
81
82 fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
83 Ok(ShapeDoc::new("Intent").with_detail("any recognized Intent (a kind-tagged map)"))
84 }
85}
86
87pub fn intent_shape_symbol() -> Symbol {
89 Symbol::qualified(INTENT_NAMESPACE, "Intent")
90}
91
92pub fn intent_shape_specs() -> Vec<(Symbol, Arc<dyn Shape>)> {
95 let mut specs: Vec<(Symbol, Arc<dyn Shape>)> =
96 vec![(intent_shape_symbol(), Arc::new(IntentShape))];
97 for name in INTENT_KINDS {
98 let shape = IntentKindShape::new(name);
99 specs.push((shape.symbol.clone(), Arc::new(shape)));
100 }
101 specs
102}
103
104fn pascal_case(name: &str) -> String {
106 name.split('-')
107 .map(|word| {
108 let mut chars = word.chars();
109 match chars.next() {
110 Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
111 None => String::new(),
112 }
113 })
114 .collect()
115}