use std::collections::HashMap;
use std::str::FromStr;
use liquid_json::LiquidJsonValue;
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use crate::{parse, Error};
#[cfg(feature = "std")]
pub(crate) static RNG: Lazy<Mutex<seeded_random::Random>> = Lazy::new(|| Mutex::new(seeded_random::Random::new()));
#[cfg(not(feature = "std"))]
pub(crate) static RNG: Lazy<Mutex<seeded_random::Random>> =
Lazy::new(|| Mutex::new(seeded_random::Random::from_seed(seeded_random::Seed::unsafe_new(0))));
pub fn set_seed(seed: u64) {
*RNG.lock() = seeded_random::Random::from_seed(seeded_random::Seed::unsafe_new(seed));
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)]
#[must_use]
#[allow(clippy::exhaustive_enums)]
#[serde(rename_all = "kebab-case")]
pub enum InstanceTarget {
Input,
Output,
Null(Option<String>),
Core,
Default,
#[doc(hidden)]
Link,
Named(String),
Path {
path: String,
id: TargetId,
},
}
impl InstanceTarget {
#[allow(clippy::missing_const_for_fn)]
pub fn or(self, other: InstanceTarget) -> InstanceTarget {
match self {
InstanceTarget::Default => other,
_ => self,
}
}
#[must_use]
pub fn id(&self) -> Option<&str> {
match self {
InstanceTarget::Input => Some(parse::SCHEMATIC_INPUT),
InstanceTarget::Output => Some(parse::SCHEMATIC_OUTPUT),
InstanceTarget::Null(id) => id.as_deref(),
InstanceTarget::Core => Some(parse::CORE_ID),
InstanceTarget::Default => panic!("Cannot get id of default instance"),
InstanceTarget::Link => Some(parse::NS_LINK),
InstanceTarget::Named(name) => Some(name),
InstanceTarget::Path { id, .. } => id.to_opt_str(),
}
}
pub fn named<T: Into<String>>(name: T) -> Self {
Self::Named(name.into())
}
pub(crate) fn path<T: Into<String>, I: Into<String>>(path: T, id: I) -> Self {
Self::Path {
path: path.into(),
id: TargetId::Named(id.into()),
}
}
pub(crate) fn anonymous_path<T: Into<String>>(path: T) -> Self {
Self::Path {
path: path.into(),
id: TargetId::None,
}
}
#[cfg(test)]
pub(crate) fn generated_path<T: Into<String>>(path: T) -> Self {
let path = path.into();
Self::Path {
id: TargetId::new_generated(&path.replace("::", "_")),
path,
}
}
pub(crate) fn ensure_id(&mut self) {
if let InstanceTarget::Path { id, path } = self {
id.ensure_id(&path.replace("::", "_"));
}
}
}
impl FromStr for InstanceTarget {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
parse::v1::parse_instance(s)
}
}
impl std::fmt::Display for InstanceTarget {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InstanceTarget::Input => f.write_str(parse::SCHEMATIC_INPUT),
InstanceTarget::Output => f.write_str(parse::SCHEMATIC_OUTPUT),
InstanceTarget::Null(id) => f.write_str(id.as_deref().unwrap_or(parse::SCHEMATIC_NULL)),
InstanceTarget::Core => f.write_str(parse::CORE_ID),
InstanceTarget::Default => f.write_str("<>"),
InstanceTarget::Link => f.write_str(parse::NS_LINK),
InstanceTarget::Named(name) => f.write_str(name),
InstanceTarget::Path { path, id } => write!(f, "{}{}", path, id.as_inline_id()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize)]
#[allow(clippy::exhaustive_enums)]
#[serde(into = "Option<String>")]
pub enum TargetId {
Generated(String),
None,
Named(String),
}
fn generate_id(prefix: &str) -> String {
format!(
"{}_{}",
prefix,
RNG.lock().uuid().to_string().split_once('-').unwrap().0
)
}
impl TargetId {
#[cfg(test)]
#[must_use]
pub fn new_generated(prefix: &str) -> Self {
TargetId::Generated(generate_id(prefix))
}
#[must_use]
pub fn to_opt_str(&self) -> Option<&str> {
match self {
TargetId::Generated(id) => Some(id),
TargetId::None => None,
TargetId::Named(name) => Some(name),
}
}
#[must_use]
pub fn as_inline_id(&self) -> String {
match self {
TargetId::Generated(id) => format!("[{}]", id),
TargetId::None => String::new(),
TargetId::Named(name) => format!("[{}]", name),
}
}
fn ensure_id(&mut self, prefix: &str) {
if *self == TargetId::None {
*self = TargetId::Generated(generate_id(prefix));
}
}
pub fn replace<T: Into<String>>(&mut self, value: T) {
*self = TargetId::Named(value.into());
}
}
impl From<TargetId> for Option<String> {
fn from(value: TargetId) -> Self {
value.to_opt_str().map(ToOwned::to_owned)
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[must_use]
pub struct ConnectionExpression {
from: ConnectionTargetExpression,
to: ConnectionTargetExpression,
}
impl std::fmt::Display for ConnectionExpression {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} -> {}", self.from, self.to)
}
}
impl ConnectionExpression {
pub fn new(mut from: ConnectionTargetExpression, mut to: ConnectionTargetExpression) -> Self {
from.instance = from.instance.or(InstanceTarget::Input);
to.instance = to.instance.or(InstanceTarget::Output);
Self { from, to }
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn into_parts(self) -> (ConnectionTargetExpression, ConnectionTargetExpression) {
(self.from, self.to)
}
#[must_use]
pub const fn from(&self) -> &ConnectionTargetExpression {
&self.from
}
#[must_use]
pub fn from_mut(&mut self) -> &mut ConnectionTargetExpression {
&mut self.from
}
#[must_use]
pub const fn to(&self) -> &ConnectionTargetExpression {
&self.to
}
#[must_use]
pub fn to_mut(&mut self) -> &mut ConnectionTargetExpression {
&mut self.to
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[allow(clippy::exhaustive_enums)]
#[serde(untagged)]
pub enum FlowExpression {
ConnectionExpression(Box<ConnectionExpression>),
BlockExpression(BlockExpression),
}
impl FromStr for FlowExpression {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (_, v) = crate::parse::v1::flow_expression(s).map_err(|e| Error::FlowExpressionParse(e.to_string()))?;
Ok(v)
}
}
impl FlowExpression {
#[must_use]
pub fn as_connection(&self) -> Option<&ConnectionExpression> {
match self {
FlowExpression::ConnectionExpression(expr) => Some(expr),
_ => None,
}
}
#[must_use]
pub const fn as_block(&self) -> Option<&BlockExpression> {
match self {
FlowExpression::BlockExpression(expr) => Some(expr),
_ => None,
}
}
#[must_use]
pub fn connection(expr: ConnectionExpression) -> Self {
FlowExpression::ConnectionExpression(Box::new(expr))
}
#[must_use]
pub const fn block(expr: BlockExpression) -> Self {
FlowExpression::BlockExpression(expr)
}
pub fn replace(&mut self, expr: Self) {
*self = expr;
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
pub struct BlockExpression {
#[serde(skip_serializing_if = "Vec::is_empty")]
expressions: Vec<FlowExpression>,
}
impl BlockExpression {
#[must_use]
pub fn new(expressions: Vec<FlowExpression>) -> Self {
Self { expressions }
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn into_parts(self) -> Vec<FlowExpression> {
self.expressions
}
#[must_use]
pub fn inner(&self) -> &[FlowExpression] {
&self.expressions
}
#[must_use]
pub fn inner_mut(&mut self) -> &mut Vec<FlowExpression> {
&mut self.expressions
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut FlowExpression> {
self.expressions.iter_mut()
}
pub fn iter(&self) -> impl Iterator<Item = &FlowExpression> {
self.expressions.iter()
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
pub struct FlowProgram {
#[serde(skip_serializing_if = "Vec::is_empty")]
expressions: Vec<FlowExpression>,
}
impl FlowProgram {
#[must_use]
pub fn new(expressions: Vec<FlowExpression>) -> Self {
Self { expressions }
}
#[must_use]
#[allow(clippy::missing_const_for_fn)]
pub fn into_parts(self) -> Vec<FlowExpression> {
self.expressions
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
#[allow(clippy::exhaustive_enums)]
#[serde(untagged)]
pub enum InstancePort {
Named(String),
Path(String, Vec<String>),
None,
}
impl From<&str> for InstancePort {
fn from(s: &str) -> Self {
match s {
"" => Self::None,
_ => Self::Named(s.to_owned()),
}
}
}
impl From<&String> for InstancePort {
fn from(s: &String) -> Self {
s.as_str().into()
}
}
impl FromStr for InstancePort {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (_, v) = crate::parse::v1::instance_port(s).map_err(|_e| Error::PortSyntax(s.to_owned()))?;
Ok(v)
}
}
impl std::fmt::Display for InstancePort {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
InstancePort::None => f.write_str("<none>"),
InstancePort::Named(name) => write!(f, "{}", name),
InstancePort::Path(name, path) => write!(
f,
"{}.{}",
name,
path.iter().map(|v| format!("\"{}\"", v)).collect::<Vec<_>>().join(".")
),
}
}
}
impl InstancePort {
#[must_use]
pub fn named<T: Into<String>>(name: T) -> Self {
Self::Named(name.into())
}
#[must_use]
pub fn path<T: Into<String>>(name: T, path: Vec<String>) -> Self {
Self::Path(name.into(), path)
}
#[must_use]
pub fn name(&self) -> Option<&str> {
match self {
InstancePort::Named(name) => Some(name),
InstancePort::Path(name, _) => Some(name),
InstancePort::None => None,
}
}
#[must_use]
pub fn to_option_string(&self) -> Option<String> {
match self {
InstancePort::Named(_) => Some(self.to_string()),
InstancePort::Path(_, _) => Some(self.to_string()),
InstancePort::None => None,
}
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
pub struct ConnectionTargetExpression {
instance: InstanceTarget,
port: InstancePort,
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<HashMap<String, LiquidJsonValue>>,
}
impl std::fmt::Display for ConnectionTargetExpression {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
#[allow(clippy::option_if_let_else)]
if let Some(_data) = &self.data {
Err(std::fmt::Error)
} else {
write!(f, "{}.{}", self.instance, self.port)
}
}
}
impl ConnectionTargetExpression {
pub fn new(instance: InstanceTarget, port: impl Into<InstancePort>) -> Self {
Self {
instance,
port: port.into(),
data: None,
}
}
pub fn new_data(
instance: InstanceTarget,
port: impl Into<InstancePort>,
data: Option<HashMap<String, LiquidJsonValue>>,
) -> Self {
Self {
instance,
port: port.into(),
data,
}
}
pub const fn instance(&self) -> &InstanceTarget {
&self.instance
}
pub fn instance_mut(&mut self) -> &mut InstanceTarget {
&mut self.instance
}
#[must_use]
pub const fn port(&self) -> &InstancePort {
&self.port
}
#[must_use]
pub const fn data(&self) -> Option<&HashMap<String, LiquidJsonValue>> {
self.data.as_ref()
}
#[allow(clippy::missing_const_for_fn)]
pub fn into_parts(self) -> (InstanceTarget, InstancePort, Option<HashMap<String, LiquidJsonValue>>) {
(self.instance, self.port, self.data)
}
}