1use std::collections::BTreeSet;
2
3use sim_kernel::{Error, Expr, Result, Symbol};
4use sim_value::build::entry;
5
6#[derive(Clone, Debug, PartialEq, Eq)]
8pub struct BridgeWeaveRow {
9 pub slot: String,
11 pub head: Symbol,
13 pub roles: Vec<(Symbol, Expr)>,
15}
16
17impl BridgeWeaveRow {
18 pub fn new(slot: impl Into<String>, head: Symbol, roles: Vec<(Symbol, Expr)>) -> Self {
20 Self {
21 slot: slot.into(),
22 head,
23 roles,
24 }
25 }
26
27 pub fn from_expr(expr: &Expr) -> Result<Self> {
29 let fields = map_fields(expr, "bridge/WeaveRow")?;
30 reject_unknown(fields, &["slot", "head", "roles"])?;
31 Ok(Self::new(
32 required_string(fields, "slot")?.to_owned(),
33 required_symbol(fields, "head")?.clone(),
34 required_roles(fields, "roles")?,
35 ))
36 }
37
38 pub fn to_expr(&self) -> Expr {
40 Expr::Map(vec![
41 entry("slot", Expr::String(self.slot.clone())),
42 entry("head", Expr::Symbol(self.head.clone())),
43 entry(
44 "roles",
45 Expr::Map(
46 self.roles
47 .iter()
48 .map(|(role, value)| (Expr::Symbol(role.clone()), value.clone()))
49 .collect(),
50 ),
51 ),
52 ])
53 }
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
58pub struct BridgeWeavePayload {
59 pub rows: Vec<BridgeWeaveRow>,
61 pub result_shape: Expr,
63}
64
65impl BridgeWeavePayload {
66 pub fn new(rows: Vec<BridgeWeaveRow>) -> Self {
68 let result_shape = derive_weave_result_shape(&rows);
69 Self { rows, result_shape }
70 }
71
72 pub fn from_expr(expr: &Expr) -> Result<Self> {
75 let fields = map_fields(expr, "bridge/Weave payload")?;
76 reject_unknown(fields, &["rows", "result-shape"])?;
77 let rows = required_vector(fields, "rows")?
78 .iter()
79 .map(BridgeWeaveRow::from_expr)
80 .collect::<Result<Vec<_>>>()?;
81 let result_shape = required_field(fields, "result-shape")?.clone();
82 let derived = derive_weave_result_shape(&rows);
83 if result_shape != derived {
84 return Err(Error::Eval(
85 "BRIDGE weave result-shape disagrees with derived row Shape".to_owned(),
86 ));
87 }
88 Ok(Self { rows, result_shape })
89 }
90
91 pub fn to_expr(&self) -> Expr {
93 Expr::Map(vec![
94 entry(
95 "rows",
96 Expr::Vector(self.rows.iter().map(BridgeWeaveRow::to_expr).collect()),
97 ),
98 entry("result-shape", derive_weave_result_shape(&self.rows)),
99 ])
100 }
101}
102
103pub type WeavePart = BridgeWeavePayload;
105
106pub fn derive_weave_result_shape(rows: &[BridgeWeaveRow]) -> Expr {
108 Expr::Map(vec![
109 entry(
110 "shape",
111 Expr::Symbol(Symbol::qualified("bridge", "WeaveResult")),
112 ),
113 entry(
114 "slots",
115 Expr::Vector(
116 rows.iter()
117 .map(|row| {
118 Expr::Map(vec![
119 entry("slot", Expr::String(row.slot.clone())),
120 entry("head", Expr::Symbol(row.head.clone())),
121 ])
122 })
123 .collect(),
124 ),
125 ),
126 ])
127}
128
129pub fn validate_weave_payload(payload: &Expr) -> Result<BridgeWeavePayload> {
131 let payload = BridgeWeavePayload::from_expr(payload)?;
132 if payload.rows.is_empty() {
133 return Err(Error::Eval(
134 "BRIDGE weave payload must contain at least one row".to_owned(),
135 ));
136 }
137 let mut slots = BTreeSet::new();
138 for row in &payload.rows {
139 if !is_token(&row.slot) {
140 return Err(Error::Eval(format!(
141 "BRIDGE weave slot {} must be a token",
142 row.slot
143 )));
144 }
145 if !slots.insert(row.slot.clone()) {
146 return Err(Error::Eval(format!(
147 "BRIDGE weave slot {} is duplicated",
148 row.slot
149 )));
150 }
151 }
152 Ok(payload)
153}
154
155fn map_fields<'a>(expr: &'a Expr, label: &str) -> Result<&'a [(Expr, Expr)]> {
156 match expr {
157 Expr::Map(fields) => Ok(fields),
158 _ => Err(Error::Eval(format!("{label} must be a map"))),
159 }
160}
161
162fn reject_unknown(fields: &[(Expr, Expr)], allowed: &[&str]) -> Result<()> {
163 for (key, _) in fields {
164 let Some(name) = field_name(key) else {
165 return Err(Error::Eval(
166 "BRIDGE weave field keys must be symbols".to_owned(),
167 ));
168 };
169 if !allowed.contains(&name.as_str()) {
170 return Err(Error::Eval(format!("unknown BRIDGE weave field {name}")));
171 }
172 }
173 Ok(())
174}
175
176fn required_field<'a>(fields: &'a [(Expr, Expr)], name: &str) -> Result<&'a Expr> {
177 fields
178 .iter()
179 .find_map(|(key, value)| (field_name(key).as_deref() == Some(name)).then_some(value))
180 .ok_or_else(|| Error::Eval(format!("BRIDGE weave record is missing {name}")))
181}
182
183fn required_symbol<'a>(fields: &'a [(Expr, Expr)], name: &str) -> Result<&'a Symbol> {
184 match required_field(fields, name)? {
185 Expr::Symbol(symbol) => Ok(symbol),
186 _ => Err(Error::TypeMismatch {
187 expected: "symbol",
188 found: "non-symbol",
189 }),
190 }
191}
192
193fn required_string<'a>(fields: &'a [(Expr, Expr)], name: &str) -> Result<&'a str> {
194 match required_field(fields, name)? {
195 Expr::String(value) => Ok(value),
196 _ => Err(Error::TypeMismatch {
197 expected: "string",
198 found: "non-string",
199 }),
200 }
201}
202
203fn required_vector<'a>(fields: &'a [(Expr, Expr)], name: &str) -> Result<&'a [Expr]> {
204 match required_field(fields, name)? {
205 Expr::Vector(items) | Expr::List(items) => Ok(items),
206 _ => Err(Error::Eval(format!(
207 "BRIDGE weave {name} field must be a vector"
208 ))),
209 }
210}
211
212fn required_roles(fields: &[(Expr, Expr)], name: &str) -> Result<Vec<(Symbol, Expr)>> {
213 let Expr::Map(entries) = required_field(fields, name)? else {
214 return Err(Error::Eval(format!(
215 "BRIDGE weave {name} field must be a map"
216 )));
217 };
218 entries
219 .iter()
220 .map(|(key, value)| match key {
221 Expr::Symbol(symbol) => Ok((symbol.clone(), value.clone())),
222 _ => Err(Error::Eval(
223 "BRIDGE weave role keys must be symbols".to_owned(),
224 )),
225 })
226 .collect()
227}
228
229fn field_name(expr: &Expr) -> Option<String> {
230 match expr {
231 Expr::Symbol(symbol) => Some(symbol.name.to_string()),
232 Expr::String(value) => Some(value.clone()),
233 _ => None,
234 }
235}
236
237fn is_token(text: &str) -> bool {
238 !text.is_empty()
239 && text
240 .chars()
241 .all(|ch| ch.is_ascii() && !ch.is_whitespace() && !matches!(ch, '{' | '}'))
242}