1use core::sync::atomic::{AtomicUsize, Ordering};
9
10use std::collections::BTreeMap;
11use std::string::String;
12use std::vec::Vec;
13
14use crate::foundation::{
15 port_domain, port_slot, ports_of, KnotKind, NumericPath, PortDir, PortDomain, PortSlot,
16};
17
18use crate::authoring::pattern::{expand, Pattern};
19use crate::{BuildError, KnotDef, PortRefDef, ThreadDef, ValidationError, Weave, WeaveDef};
20
21static NEXT_OWNER: AtomicUsize = AtomicUsize::new(1);
22
23#[derive(Copy, Clone, Debug, PartialEq, Eq)]
25pub struct KnotHandle {
26 owner: usize,
27 index: u16,
28}
29
30#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct InputPort {
33 owner: usize,
34 knot: u16,
35 name: String,
36}
37
38#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct OutputPort {
41 owner: usize,
42 knot: u16,
43 name: String,
44}
45
46#[derive(Clone, Debug)]
48pub struct PatternInstance {
49 id: String,
50 inputs: BTreeMap<String, InputPort>,
51 outputs: BTreeMap<String, OutputPort>,
52}
53
54impl PatternInstance {
55 pub fn id(&self) -> &str {
57 &self.id
58 }
59
60 pub fn input(&self, export: &str) -> Result<InputPort, BuildError> {
62 self.inputs
63 .get(export)
64 .cloned()
65 .ok_or_else(|| BuildError::UnknownExport {
66 instance_id: self.id.clone(),
67 export: String::from(export),
68 direction: PortDir::In,
69 })
70 }
71
72 pub fn output(&self, export: &str) -> Result<OutputPort, BuildError> {
74 self.outputs
75 .get(export)
76 .cloned()
77 .ok_or_else(|| BuildError::UnknownExport {
78 instance_id: self.id.clone(),
79 export: String::from(export),
80 direction: PortDir::Out,
81 })
82 }
83}
84
85pub struct WeaveBuilder {
87 owner: usize,
88 id: String,
89 knots: Vec<KnotDef>,
90 threads: Vec<ThreadDef>,
91 numeric: NumericPath,
92 names: BTreeMap<String, u16>,
93}
94
95impl WeaveBuilder {
96 pub fn new(id: impl Into<String>) -> Result<Self, BuildError> {
98 let id = id.into();
99 if id.is_empty() {
100 return Err(BuildError::InvalidId {
101 id,
102 reason: "weave ids must be non-empty",
103 });
104 }
105 let owner = NEXT_OWNER.fetch_add(1, Ordering::Relaxed);
106 validate_owner(owner)?;
107 Ok(Self {
108 owner,
109 id,
110 knots: Vec::new(),
111 threads: Vec::new(),
112 numeric: NumericPath::compiled(),
113 names: BTreeMap::new(),
114 })
115 }
116
117 pub fn set_numeric(&mut self, path: NumericPath) -> Result<&mut Self, BuildError> {
119 self.numeric = path;
120 Ok(self)
121 }
122
123 pub fn knot(
125 &mut self,
126 id: impl Into<String>,
127 kind: KnotKind,
128 ) -> Result<KnotHandle, BuildError> {
129 let id = id.into();
130 if id.is_empty() {
131 return Err(BuildError::InvalidId {
132 id,
133 reason: "knot ids must be non-empty",
134 });
135 }
136 if self.names.contains_key(&id) {
137 return Err(BuildError::DuplicateKnotId { knot_id: id });
138 }
139 let index =
140 u16::try_from(self.knots.len()).map_err(|_| BuildError::RepresentationOverflow {
141 what: "knot",
142 actual: self.knots.len(),
143 limit: u16::MAX as usize,
144 })?;
145 self.names.insert(id.clone(), index);
146 self.knots.push(KnotDef { id, kind });
147 Ok(KnotHandle {
148 owner: self.owner,
149 index,
150 })
151 }
152
153 pub fn input(&self, knot: &KnotHandle, name: &str) -> Result<InputPort, BuildError> {
155 self.endpoint(knot, name, PortDir::In)
156 .map(|name| InputPort {
157 owner: self.owner,
158 knot: knot.index,
159 name,
160 })
161 }
162
163 pub fn output(&self, knot: &KnotHandle, name: &str) -> Result<OutputPort, BuildError> {
165 self.endpoint(knot, name, PortDir::Out)
166 .map(|name| OutputPort {
167 owner: self.owner,
168 knot: knot.index,
169 name,
170 })
171 }
172
173 pub fn connect(&mut self, from: OutputPort, to: InputPort) -> Result<&mut Self, BuildError> {
175 if from.owner != self.owner || to.owner != self.owner {
176 return Err(BuildError::ForeignHandle);
177 }
178 let from_knot = self
179 .knots
180 .get(from.knot as usize)
181 .ok_or(BuildError::ForeignHandle)?;
182 let to_knot = self
183 .knots
184 .get(to.knot as usize)
185 .ok_or(BuildError::ForeignHandle)?;
186 if let (Some(from_domain), Some(to_domain)) = (
187 fixed_port_domain(&from_knot.kind, &from.name),
188 fixed_port_domain(&to_knot.kind, &to.name),
189 ) {
190 if from_domain != to_domain {
191 return Err(BuildError::SignalDomainMismatch {
192 from_knot: from_knot.id.clone(),
193 from_port: from.name,
194 from_domain,
195 to_knot: to_knot.id.clone(),
196 to_port: to.name,
197 to_domain,
198 });
199 }
200 }
201 self.threads.push(ThreadDef {
202 from: PortRefDef::new(from_knot.id.clone(), from.name),
203 to: PortRefDef::new(to_knot.id.clone(), to.name),
204 });
205 Ok(self)
206 }
207
208 pub fn include(
210 &mut self,
211 instance_id: impl Into<String>,
212 pattern: &Pattern,
213 ) -> Result<PatternInstance, BuildError> {
214 let instance_id = instance_id.into();
215 if pattern.inner().numeric != self.numeric {
216 return Err(BuildError::NumericMismatch {
217 expected: self.numeric,
218 actual: pattern.inner().numeric,
219 });
220 }
221 let expanded = expand(&instance_id, pattern)?;
222 for knot in &expanded.knots {
223 if self.names.contains_key(&knot.id) {
224 return Err(BuildError::DuplicateKnotId {
225 knot_id: knot.id.clone(),
226 });
227 }
228 }
229 for knot in expanded.knots {
230 let index = u16::try_from(self.knots.len()).map_err(|_| {
231 BuildError::RepresentationOverflow {
232 what: "knot",
233 actual: self.knots.len(),
234 limit: u16::MAX as usize,
235 }
236 })?;
237 self.names.insert(knot.id.clone(), index);
238 self.knots.push(knot);
239 }
240 self.threads.extend(expanded.threads);
241 let inputs = expanded
242 .inputs
243 .into_iter()
244 .map(|(name, port)| {
245 let knot = self.names[port.knot.as_str()];
246 (
247 name,
248 InputPort {
249 owner: self.owner,
250 knot,
251 name: port.port,
252 },
253 )
254 })
255 .collect();
256 let outputs = expanded
257 .outputs
258 .into_iter()
259 .map(|(name, port)| {
260 let knot = self.names[port.knot.as_str()];
261 (
262 name,
263 OutputPort {
264 owner: self.owner,
265 knot,
266 name: port.port,
267 },
268 )
269 })
270 .collect();
271 Ok(PatternInstance {
272 id: instance_id,
273 inputs,
274 outputs,
275 })
276 }
277
278 pub fn build(self) -> Result<Weave, ValidationError> {
280 Weave::try_from(WeaveDef {
281 id: self.id,
282 numeric: self.numeric,
283 knots: self.knots,
284 threads: self.threads,
285 })
286 }
287
288 fn endpoint(
289 &self,
290 knot: &KnotHandle,
291 name: &str,
292 expected: PortDir,
293 ) -> Result<String, BuildError> {
294 if knot.owner != self.owner {
295 return Err(BuildError::ForeignHandle);
296 }
297 let knot_def = self
298 .knots
299 .get(knot.index as usize)
300 .ok_or(BuildError::ForeignHandle)?;
301 let ports = ports_of(&knot_def.kind);
302 let info =
303 ports
304 .iter()
305 .find(|port| port.name == name)
306 .ok_or_else(|| BuildError::UnknownPort {
307 knot_id: knot_def.id.clone(),
308 port: String::from(name),
309 expected: ports.iter().map(|port| String::from(port.name)).collect(),
310 })?;
311 if info.dir != expected {
312 return Err(BuildError::WrongPortDirection {
313 knot_id: knot_def.id.clone(),
314 port: String::from(name),
315 expected,
316 actual: info.dir,
317 });
318 }
319 Ok(String::from(name))
320 }
321}
322
323fn validate_owner(owner: usize) -> Result<(), BuildError> {
324 if owner == usize::MAX {
325 return Err(BuildError::RepresentationOverflow {
326 what: "builder owner token",
327 actual: owner,
328 limit: usize::MAX - 1,
329 });
330 }
331 Ok(())
332}
333
334fn fixed_port_domain(kind: &KnotKind, name: &str) -> Option<crate::foundation::SignalDomain> {
335 let slot = port_slot(kind, name)?;
336 match port_domain(kind, slot)? {
337 PortDomain::Fixed(domain) => Some(domain),
338 PortDomain::Variable(_) | PortDomain::Any => None,
339 }
340}
341
342pub fn slot_of(kind: &KnotKind, name: &str) -> Result<PortSlot, BuildError> {
344 port_slot(kind, name).ok_or_else(|| BuildError::UnknownPort {
345 knot_id: String::from("<kind>"),
346 port: String::from(name),
347 expected: ports_of(kind)
348 .iter()
349 .map(|port| String::from(port.name))
350 .collect(),
351 })
352}
353
354#[cfg(test)]
355mod tests {
356 use super::validate_owner;
357 use crate::BuildError;
358
359 #[test]
360 fn owner_tokens_reserve_the_overflow_sentinel() {
361 assert!(matches!(
362 validate_owner(usize::MAX),
363 Err(BuildError::RepresentationOverflow {
364 what: "builder owner token",
365 actual: usize::MAX,
366 limit,
367 }) if limit == usize::MAX - 1
368 ));
369 assert!(validate_owner(1).is_ok());
370 }
371}