1use std::collections::HashMap;
2use std::str::FromStr;
3
4use liquid_json::LiquidJsonValue;
5use once_cell::sync::Lazy;
6use parking_lot::Mutex;
7
8use crate::{parse, Error};
9
10#[cfg(feature = "std")]
11pub(crate) static RNG: Lazy<Mutex<seeded_random::Random>> = Lazy::new(|| Mutex::new(seeded_random::Random::new()));
12#[cfg(not(feature = "std"))]
13pub(crate) static RNG: Lazy<Mutex<seeded_random::Random>> =
14 Lazy::new(|| Mutex::new(seeded_random::Random::from_seed(seeded_random::Seed::unsafe_new(0))));
15
16pub fn set_seed(seed: u64) {
20 *RNG.lock() = seeded_random::Random::from_seed(seeded_random::Seed::unsafe_new(seed));
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)]
24#[must_use]
25#[allow(clippy::exhaustive_enums)]
26#[serde(rename_all = "kebab-case")]
27pub enum InstanceTarget {
29 Input,
31 Output,
33 Null(Option<String>),
35 Core,
37 Default,
39 #[doc(hidden)]
40 Link,
41 Named(String),
43 Path {
45 path: String,
47 id: TargetId,
49 },
50}
51
52impl InstanceTarget {
53 #[allow(clippy::missing_const_for_fn)]
55 pub fn or(self, other: InstanceTarget) -> InstanceTarget {
56 match self {
57 InstanceTarget::Default => other,
58 _ => self,
59 }
60 }
61
62 #[must_use]
64 pub fn id(&self) -> Option<&str> {
65 match self {
66 InstanceTarget::Input => Some(parse::SCHEMATIC_INPUT),
67 InstanceTarget::Output => Some(parse::SCHEMATIC_OUTPUT),
68 InstanceTarget::Null(id) => id.as_deref(),
69 InstanceTarget::Core => Some(parse::CORE_ID),
70 InstanceTarget::Default => panic!("Cannot get id of default instance"),
71 InstanceTarget::Link => Some(parse::NS_LINK),
72 InstanceTarget::Named(name) => Some(name),
73 InstanceTarget::Path { id, .. } => id.to_opt_str(),
74 }
75 }
76
77 pub fn named<T: Into<String>>(name: T) -> Self {
79 Self::Named(name.into())
80 }
81
82 pub(crate) fn path<T: Into<String>, I: Into<String>>(path: T, id: I) -> Self {
84 Self::Path {
85 path: path.into(),
86 id: TargetId::Named(id.into()),
87 }
88 }
89
90 pub(crate) fn anonymous_path<T: Into<String>>(path: T) -> Self {
92 Self::Path {
93 path: path.into(),
94 id: TargetId::None,
95 }
96 }
97
98 #[cfg(test)]
100 pub(crate) fn generated_path<T: Into<String>>(path: T) -> Self {
101 let path = path.into();
102 Self::Path {
103 id: TargetId::new_generated(&path.replace("::", "_")),
104 path,
105 }
106 }
107
108 pub(crate) fn ensure_id(&mut self) {
109 if let InstanceTarget::Path { id, path } = self {
110 id.ensure_id(&path.replace("::", "_"));
111 }
112 }
113}
114
115impl FromStr for InstanceTarget {
116 type Err = Error;
117
118 fn from_str(s: &str) -> Result<Self, Self::Err> {
119 parse::v1::parse_instance(s)
120 }
121}
122
123impl std::fmt::Display for InstanceTarget {
124 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125 match self {
126 InstanceTarget::Input => f.write_str(parse::SCHEMATIC_INPUT),
127 InstanceTarget::Output => f.write_str(parse::SCHEMATIC_OUTPUT),
128 InstanceTarget::Null(id) => f.write_str(id.as_deref().unwrap_or(parse::SCHEMATIC_NULL)),
129 InstanceTarget::Core => f.write_str(parse::CORE_ID),
130 InstanceTarget::Default => f.write_str("<>"),
131 InstanceTarget::Link => f.write_str(parse::NS_LINK),
132 InstanceTarget::Named(name) => f.write_str(name),
133 InstanceTarget::Path { path, id } => write!(f, "{}{}", path, id.as_inline_id()),
134 }
135 }
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)]
139#[allow(clippy::exhaustive_enums)]
141#[serde(into = "Option<String>")]
142pub enum TargetId {
143 Generated(String),
145 None,
147 Named(String),
149}
150
151fn generate_id(prefix: &str) -> String {
152 format!(
153 "{}_{}",
154 prefix,
155 RNG.lock().uuid().to_string().split_once('-').unwrap().0
156 )
157}
158
159impl TargetId {
160 #[cfg(test)]
161 #[must_use]
162 pub fn new_generated(prefix: &str) -> Self {
163 TargetId::Generated(generate_id(prefix))
164 }
165
166 #[must_use]
168 pub fn to_opt_str(&self) -> Option<&str> {
169 match self {
170 TargetId::Generated(id) => Some(id),
171 TargetId::None => None,
172 TargetId::Named(name) => Some(name),
173 }
174 }
175
176 #[must_use]
178 pub fn as_inline_id(&self) -> String {
179 match self {
180 TargetId::Generated(id) => format!("[{}]", id),
181 TargetId::None => String::new(),
182 TargetId::Named(name) => format!("[{}]", name),
183 }
184 }
185
186 fn ensure_id(&mut self, prefix: &str) {
187 if *self == TargetId::None {
188 *self = TargetId::Generated(generate_id(prefix));
189 }
190 }
191
192 pub fn replace<T: Into<String>>(&mut self, value: T) {
194 *self = TargetId::Named(value.into());
195 }
196}
197
198impl From<TargetId> for Option<String> {
199 fn from(value: TargetId) -> Self {
200 value.to_opt_str().map(ToOwned::to_owned)
201 }
202}
203
204#[derive(Debug, Clone, PartialEq, serde::Serialize)]
206#[must_use]
207pub struct ConnectionExpression {
208 from: ConnectionTargetExpression,
209 to: ConnectionTargetExpression,
210}
211
212impl std::fmt::Display for ConnectionExpression {
213 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214 write!(f, "{} -> {}", self.from, self.to)
215 }
216}
217
218impl ConnectionExpression {
219 pub fn new(mut from: ConnectionTargetExpression, mut to: ConnectionTargetExpression) -> Self {
221 from.instance = from.instance.or(InstanceTarget::Input);
222 to.instance = to.instance.or(InstanceTarget::Output);
223
224 Self { from, to }
225 }
226
227 #[must_use]
229 #[allow(clippy::missing_const_for_fn)]
230 pub fn into_parts(self) -> (ConnectionTargetExpression, ConnectionTargetExpression) {
231 (self.from, self.to)
232 }
233
234 #[must_use]
236 pub const fn from(&self) -> &ConnectionTargetExpression {
237 &self.from
238 }
239
240 #[must_use]
242 pub fn from_mut(&mut self) -> &mut ConnectionTargetExpression {
243 &mut self.from
244 }
245
246 #[must_use]
248 pub const fn to(&self) -> &ConnectionTargetExpression {
249 &self.to
250 }
251
252 #[must_use]
254 pub fn to_mut(&mut self) -> &mut ConnectionTargetExpression {
255 &mut self.to
256 }
257}
258
259#[derive(Debug, Clone, PartialEq, serde::Serialize)]
260#[allow(clippy::exhaustive_enums)]
262#[serde(untagged)]
263pub enum FlowExpression {
264 ConnectionExpression(Box<ConnectionExpression>),
266 BlockExpression(BlockExpression),
268}
269
270impl FromStr for FlowExpression {
271 type Err = Error;
272
273 fn from_str(s: &str) -> Result<Self, Self::Err> {
274 let (_, v) = crate::parse::v1::flow_expression(s).map_err(|e| Error::FlowExpressionParse(e.to_string()))?;
275 Ok(v)
276 }
277}
278
279impl FlowExpression {
280 #[must_use]
282 pub fn as_connection(&self) -> Option<&ConnectionExpression> {
283 match self {
284 FlowExpression::ConnectionExpression(expr) => Some(expr),
285 _ => None,
286 }
287 }
288
289 #[must_use]
291 pub const fn as_block(&self) -> Option<&BlockExpression> {
292 match self {
293 FlowExpression::BlockExpression(expr) => Some(expr),
294 _ => None,
295 }
296 }
297
298 #[must_use]
299 pub fn connection(expr: ConnectionExpression) -> Self {
301 FlowExpression::ConnectionExpression(Box::new(expr))
302 }
303
304 #[must_use]
305 pub const fn block(expr: BlockExpression) -> Self {
307 FlowExpression::BlockExpression(expr)
308 }
309
310 pub fn replace(&mut self, expr: Self) {
312 *self = expr;
313 }
314}
315
316#[derive(Debug, Clone, PartialEq, serde::Serialize)]
317pub struct BlockExpression {
319 #[serde(skip_serializing_if = "Vec::is_empty")]
320 expressions: Vec<FlowExpression>,
321}
322
323impl BlockExpression {
324 #[must_use]
326 pub fn new(expressions: Vec<FlowExpression>) -> Self {
327 Self { expressions }
328 }
329
330 #[must_use]
332 #[allow(clippy::missing_const_for_fn)]
333 pub fn into_parts(self) -> Vec<FlowExpression> {
334 self.expressions
335 }
336
337 #[must_use]
339 pub fn inner(&self) -> &[FlowExpression] {
340 &self.expressions
341 }
342
343 #[must_use]
345 pub fn inner_mut(&mut self) -> &mut Vec<FlowExpression> {
346 &mut self.expressions
347 }
348
349 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut FlowExpression> {
351 self.expressions.iter_mut()
352 }
353
354 pub fn iter(&self) -> impl Iterator<Item = &FlowExpression> {
356 self.expressions.iter()
357 }
358}
359
360#[derive(Debug, Clone, PartialEq, serde::Serialize)]
361pub struct FlowProgram {
363 #[serde(skip_serializing_if = "Vec::is_empty")]
364 expressions: Vec<FlowExpression>,
365}
366
367impl FlowProgram {
368 #[must_use]
370 pub fn new(expressions: Vec<FlowExpression>) -> Self {
371 Self { expressions }
372 }
373
374 #[must_use]
376 #[allow(clippy::missing_const_for_fn)]
377 pub fn into_parts(self) -> Vec<FlowExpression> {
378 self.expressions
379 }
380}
381
382#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
383#[allow(clippy::exhaustive_enums)]
384#[serde(untagged)]
385pub enum InstancePort {
387 Named(String),
389 Path(String, Vec<String>),
391 None,
393}
394
395impl From<&str> for InstancePort {
396 fn from(s: &str) -> Self {
397 match s {
398 "" => Self::None,
399 _ => Self::Named(s.to_owned()),
400 }
401 }
402}
403
404impl From<&String> for InstancePort {
405 fn from(s: &String) -> Self {
406 s.as_str().into()
407 }
408}
409
410impl FromStr for InstancePort {
411 type Err = Error;
412
413 fn from_str(s: &str) -> Result<Self, Self::Err> {
414 let (_, v) = crate::parse::v1::instance_port(s).map_err(|_e| Error::PortSyntax(s.to_owned()))?;
415 Ok(v)
416 }
417}
418
419impl std::fmt::Display for InstancePort {
420 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
421 match self {
422 InstancePort::None => f.write_str("<none>"),
423 InstancePort::Named(name) => write!(f, "{}", name),
424 InstancePort::Path(name, path) => write!(
425 f,
426 "{}.{}",
427 name,
428 path.iter().map(|v| format!("\"{}\"", v)).collect::<Vec<_>>().join(".")
429 ),
430 }
431 }
432}
433
434impl InstancePort {
435 #[must_use]
437 pub fn named<T: Into<String>>(name: T) -> Self {
438 Self::Named(name.into())
439 }
440
441 #[must_use]
443 pub fn path<T: Into<String>>(name: T, path: Vec<String>) -> Self {
444 Self::Path(name.into(), path)
445 }
446
447 #[must_use]
449 pub fn name(&self) -> Option<&str> {
450 match self {
451 InstancePort::Named(name) => Some(name),
452 InstancePort::Path(name, _) => Some(name),
453 InstancePort::None => None,
454 }
455 }
456
457 #[must_use]
459 pub fn to_option_string(&self) -> Option<String> {
460 match self {
461 InstancePort::Named(_) => Some(self.to_string()),
462 InstancePort::Path(_, _) => Some(self.to_string()),
463 InstancePort::None => None,
464 }
465 }
466}
467
468#[derive(Debug, Clone, PartialEq, serde::Serialize)]
470pub struct ConnectionTargetExpression {
471 instance: InstanceTarget,
472 port: InstancePort,
473 #[serde(skip_serializing_if = "Option::is_none")]
474 data: Option<HashMap<String, LiquidJsonValue>>,
475}
476
477impl std::fmt::Display for ConnectionTargetExpression {
478 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
479 #[allow(clippy::option_if_let_else)]
480 if let Some(_data) = &self.data {
481 Err(std::fmt::Error)
483 } else {
484 write!(f, "{}.{}", self.instance, self.port)
485 }
486 }
487}
488
489impl ConnectionTargetExpression {
490 pub fn new(instance: InstanceTarget, port: impl Into<InstancePort>) -> Self {
492 Self {
493 instance,
494 port: port.into(),
495 data: None,
496 }
497 }
498
499 pub fn new_data(
501 instance: InstanceTarget,
502 port: impl Into<InstancePort>,
503 data: Option<HashMap<String, LiquidJsonValue>>,
504 ) -> Self {
505 Self {
506 instance,
507 port: port.into(),
508 data,
509 }
510 }
511
512 pub const fn instance(&self) -> &InstanceTarget {
514 &self.instance
515 }
516
517 pub fn instance_mut(&mut self) -> &mut InstanceTarget {
519 &mut self.instance
520 }
521
522 #[must_use]
524 pub const fn port(&self) -> &InstancePort {
525 &self.port
526 }
527
528 #[must_use]
530 pub const fn data(&self) -> Option<&HashMap<String, LiquidJsonValue>> {
531 self.data.as_ref()
532 }
533
534 #[allow(clippy::missing_const_for_fn)]
536 pub fn into_parts(self) -> (InstanceTarget, InstancePort, Option<HashMap<String, LiquidJsonValue>>) {
537 (self.instance, self.port, self.data)
538 }
539}