1use std::sync::Arc;
10
11use sim_kernel::{Cx, Expr, MatchScore, Result, Shape, ShapeDoc, ShapeMatch, Symbol, Value};
12use sim_shape::{ExactExprShape, OrShape, TableExtraPolicy, TableFieldSpec, TableShape};
13
14use crate::kinds::{INTENT_KINDS, INTENT_NAMESPACE, KIND_KEY, intent_kind};
15
16struct RankedShape {
17 symbol: Symbol,
18 name: String,
19 detail: String,
20 score: MatchScore,
21 inner: Arc<dyn Shape>,
22}
23
24impl RankedShape {
25 fn ranked(&self, mut matched: ShapeMatch) -> ShapeMatch {
26 if matched.accepted {
27 matched.score = self.score;
28 }
29 matched
30 }
31}
32
33impl Shape for RankedShape {
34 fn symbol(&self) -> Option<Symbol> {
35 Some(self.symbol.clone())
36 }
37
38 fn is_effectful(&self) -> bool {
39 self.inner.is_effectful()
40 }
41
42 fn is_total(&self) -> bool {
43 self.inner.is_total()
44 }
45
46 fn check_value(&self, cx: &mut Cx, value: Value) -> Result<ShapeMatch> {
47 self.inner
48 .check_value(cx, value)
49 .map(|matched| self.ranked(matched))
50 }
51
52 fn check_expr(&self, cx: &mut Cx, expr: &Expr) -> Result<ShapeMatch> {
53 self.inner
54 .check_expr(cx, expr)
55 .map(|matched| self.ranked(matched))
56 }
57
58 fn describe(&self, _cx: &mut Cx) -> Result<ShapeDoc> {
59 Ok(ShapeDoc::new(self.name.clone()).with_detail(self.detail.clone()))
60 }
61}
62
63fn kind_field_shape(kind: Symbol) -> Arc<dyn Shape> {
64 Arc::new(TableShape::new(
65 vec![TableFieldSpec {
66 key: Symbol::new(KIND_KEY),
67 shape: Arc::new(ExactExprShape::new(Expr::Symbol(kind))),
68 required: true,
69 }],
70 TableExtraPolicy::Allow,
71 ))
72}
73
74fn ranked_shape(
75 symbol: Symbol,
76 name: impl Into<String>,
77 detail: impl Into<String>,
78 score: i32,
79 inner: Arc<dyn Shape>,
80) -> Arc<dyn Shape> {
81 Arc::new(RankedShape {
82 symbol,
83 name: name.into(),
84 detail: detail.into(),
85 score: MatchScore::exact(score),
86 inner,
87 })
88}
89
90pub fn intent_shape_symbol() -> Symbol {
92 Symbol::qualified(INTENT_NAMESPACE, "Intent")
93}
94
95pub(crate) fn intent_shape() -> Arc<dyn Shape> {
96 let choices = INTENT_KINDS
97 .iter()
98 .map(|name| kind_field_shape(intent_kind(name)))
99 .collect();
100 ranked_shape(
101 intent_shape_symbol(),
102 "Intent",
103 "any recognized Intent (a kind-tagged map)",
104 5,
105 Arc::new(OrShape::new(choices)),
106 )
107}
108
109fn intent_kind_shape(name: &str) -> (Symbol, Arc<dyn Shape>) {
110 let symbol = Symbol::qualified(INTENT_NAMESPACE, pascal_case(name));
111 let kind = intent_kind(name);
112 let shape = ranked_shape(
113 symbol.clone(),
114 symbol.name.to_string(),
115 format!("matches Intents tagged '{kind}'"),
116 20,
117 kind_field_shape(kind),
118 );
119 (symbol, shape)
120}
121
122pub fn intent_shape_specs() -> Vec<(Symbol, Arc<dyn Shape>)> {
125 let mut specs: Vec<(Symbol, Arc<dyn Shape>)> = vec![(intent_shape_symbol(), intent_shape())];
126 for name in INTENT_KINDS {
127 specs.push(intent_kind_shape(name));
128 }
129 specs
130}
131
132fn pascal_case(name: &str) -> String {
134 name.split('-')
135 .map(|word| {
136 let mut chars = word.chars();
137 match chars.next() {
138 Some(first) => first.to_ascii_uppercase().to_string() + chars.as_str(),
139 None => String::new(),
140 }
141 })
142 .collect()
143}