flow_expression_parser/
ast.rs

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
16/// Set the seed for the RNG.
17///
18/// The RNG is most commonly used for generating UUIDs for anonymous nodes.
19pub 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")]
27/// A node instance
28pub enum InstanceTarget {
29  /// A flow input node.
30  Input,
31  /// A flow output node.
32  Output,
33  /// A black hole for inputs.
34  Null(Option<String>),
35  /// A reserved namespace for built-in nodes.
36  Core,
37  /// An unspecified node.
38  Default,
39  #[doc(hidden)]
40  Link,
41  /// A named node instance.
42  Named(String),
43  /// An instance created inline.
44  Path {
45    /// The path to the operation, i.e. namespace::operation
46    path: String,
47    /// The optional ID to use for this instance.
48    id: TargetId,
49  },
50}
51
52impl InstanceTarget {
53  /// Returns [self] unless self is [InstanceTarget::Default], in which case it returns `other`.
54  #[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  /// Get the id of the instance target.
63  #[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  /// Create a new [InstanceTarget::Named] from a string.
78  pub fn named<T: Into<String>>(name: T) -> Self {
79    Self::Named(name.into())
80  }
81
82  /// Create a new [InstanceTarget::Path] from a path and id.
83  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  /// Create a new [InstanceTarget::Path] from a path without an id.
91  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  /// Create a new [InstanceTarget::Path] from a path without an id.
99  #[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/// [TargetId] differentiates between user-provided IDs, generated IDs, and no IDs.
140#[allow(clippy::exhaustive_enums)]
141#[serde(into = "Option<String>")]
142pub enum TargetId {
143  /// An automatically generated ID
144  Generated(String),
145  /// No ID provided
146  None,
147  /// A user-provided ID
148  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  /// Convert the [TargetId] to an Option<String>.
167  #[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  /// Turn the [TargetId] into something that can be appended as an inline ID.
177  #[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  /// Replace the [TargetId] with a [TargetId::Named] variant.
193  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/// A connection between two targets.
205#[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  /// Create a new [ConnectionExpression] from two [ConnectionTargetExpression]s.
220  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  /// Get the owned parts of the connection.
228  #[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  /// Get the from target.
235  #[must_use]
236  pub const fn from(&self) -> &ConnectionTargetExpression {
237    &self.from
238  }
239
240  /// Get the from target.
241  #[must_use]
242  pub fn from_mut(&mut self) -> &mut ConnectionTargetExpression {
243    &mut self.from
244  }
245
246  /// Get the to target.
247  #[must_use]
248  pub const fn to(&self) -> &ConnectionTargetExpression {
249    &self.to
250  }
251
252  /// Get the to target.
253  #[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/// A flow expression.
261#[allow(clippy::exhaustive_enums)]
262#[serde(untagged)]
263pub enum FlowExpression {
264  /// A [ConnectionExpression].
265  ConnectionExpression(Box<ConnectionExpression>),
266  /// A [BlockExpression].
267  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  /// Get the expression as a [ConnectionExpression].
281  #[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  /// Get the expression as a [BlockExpression].
290  #[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  /// Make a new [FlowExpression::ConnectionExpression] from a [ConnectionExpression].
300  pub fn connection(expr: ConnectionExpression) -> Self {
301    FlowExpression::ConnectionExpression(Box::new(expr))
302  }
303
304  #[must_use]
305  /// Make a new [FlowExpression::BlockExpression] from a [BlockExpression].
306  pub const fn block(expr: BlockExpression) -> Self {
307    FlowExpression::BlockExpression(expr)
308  }
309
310  /// Convenience method to replace the expression with a new one.
311  pub fn replace(&mut self, expr: Self) {
312    *self = expr;
313  }
314}
315
316#[derive(Debug, Clone, PartialEq, serde::Serialize)]
317/// A block expression.
318pub struct BlockExpression {
319  #[serde(skip_serializing_if = "Vec::is_empty")]
320  expressions: Vec<FlowExpression>,
321}
322
323impl BlockExpression {
324  /// Create a new [BlockExpression] from a vector of [FlowExpression]s.
325  #[must_use]
326  pub fn new(expressions: Vec<FlowExpression>) -> Self {
327    Self { expressions }
328  }
329
330  /// Get the owned parts of the block expression.
331  #[must_use]
332  #[allow(clippy::missing_const_for_fn)]
333  pub fn into_parts(self) -> Vec<FlowExpression> {
334    self.expressions
335  }
336
337  /// Get a list of the inner expressions.
338  #[must_use]
339  pub fn inner(&self) -> &[FlowExpression] {
340    &self.expressions
341  }
342
343  /// Get a mutable list of the inner expressions.
344  #[must_use]
345  pub fn inner_mut(&mut self) -> &mut Vec<FlowExpression> {
346    &mut self.expressions
347  }
348
349  /// Get the expressions in the block as a mutable iterator.
350  pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut FlowExpression> {
351    self.expressions.iter_mut()
352  }
353
354  /// Get the expressions in the block as an iterator.
355  pub fn iter(&self) -> impl Iterator<Item = &FlowExpression> {
356    self.expressions.iter()
357  }
358}
359
360#[derive(Debug, Clone, PartialEq, serde::Serialize)]
361/// A flow program.
362pub struct FlowProgram {
363  #[serde(skip_serializing_if = "Vec::is_empty")]
364  expressions: Vec<FlowExpression>,
365}
366
367impl FlowProgram {
368  /// Create a new [FlowProgram] from a list of [FlowExpression]s.
369  #[must_use]
370  pub fn new(expressions: Vec<FlowExpression>) -> Self {
371    Self { expressions }
372  }
373
374  /// Get the owned parts of the flow program.
375  #[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)]
385/// The port associated with an instance in a connection.
386pub enum InstancePort {
387  /// A simple, named port.
388  Named(String),
389  /// A named port with a path to an inner value.
390  Path(String, Vec<String>),
391  /// An unnamed port that must be inferred or it's an error.
392  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  /// Quickly create a [InstancePort::Named] variant.
436  #[must_use]
437  pub fn named<T: Into<String>>(name: T) -> Self {
438    Self::Named(name.into())
439  }
440
441  /// Quickly create a [InstancePort::Path] variant.
442  #[must_use]
443  pub fn path<T: Into<String>>(name: T, path: Vec<String>) -> Self {
444    Self::Path(name.into(), path)
445  }
446
447  /// Get the name of the port.
448  #[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  /// Convert the [InstancePort] to an Option<String> representing the (optional) parseable value.
458  #[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/// A connection target, specified by an instance and a port.
469#[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      // TODO: Implement data syntax. There's no way of stringifying this yet.
482      Err(std::fmt::Error)
483    } else {
484      write!(f, "{}.{}", self.instance, self.port)
485    }
486  }
487}
488
489impl ConnectionTargetExpression {
490  /// Create a new [ConnectionTargetExpression]
491  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  /// Create a new [ConnectionTargetExpression] with default data
500  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  /// Get the instance target.
513  pub const fn instance(&self) -> &InstanceTarget {
514    &self.instance
515  }
516
517  /// Get the instance target.
518  pub fn instance_mut(&mut self) -> &mut InstanceTarget {
519    &mut self.instance
520  }
521
522  /// Get the port.
523  #[must_use]
524  pub const fn port(&self) -> &InstancePort {
525    &self.port
526  }
527
528  /// Get the data.
529  #[must_use]
530  pub const fn data(&self) -> Option<&HashMap<String, LiquidJsonValue>> {
531    self.data.as_ref()
532  }
533
534  /// Get the owned parts of the connection target.
535  #[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}