use super::per_pol::PerPol;
use super::schema::{self, Fact, ParamDefault, ParamInfo};
use super::{Block, BuildContext};
use super::{analysis, ce_ofdm, channel, coding, cpm, dsp, fiber, optical, source, systems, wireless};
use crate::params::split_block;
use comnum_core::{Error, Result};
use serde_json::{Map, Value as Json};
use std::fmt::Write;
use std::sync::{Mutex, MutexGuard, OnceLock, PoisonError};
#[macro_export]
macro_rules! block_info {
(@info $starts:expr, $per_pol:expr, $name:expr, $summary:expr, $params:ty,
needs [$($n:ident),* $(,)?], gives [$($g:ident),* $(,)?] $(,)?) => {
$crate::blocks::BlockInfo {
name: $name,
summary: $summary,
params: <$params as $crate::blocks::Params>::params(),
needs: &[$($crate::blocks::Fact::$n),*],
gives: &[$($crate::blocks::Fact::$g),*],
starts: $starts,
per_polarization: $per_pol,
}
};
(start $($rest:tt)+) => { $crate::block_info!(@info true, false, $($rest)+) };
(per_polarization $($rest:tt)+) => { $crate::block_info!(@info false, true, $($rest)+) };
($($rest:tt)+) => { $crate::block_info!(@info false, false, $($rest)+) };
}
macro_rules! builtin {
($($flag:ident)? $name:literal, $summary:expr, $params:ty,
needs [$($n:ident),* $(,)?], gives [$($g:ident),* $(,)?],
|$q:pat_param, $label:pat_param, $ctx:pat_param| $body:expr $(,)?) => {
$crate::blocks::Extension {
info: block_info!($($flag)? $name, $summary, $params, needs [$($n),*], gives [$($g),*]),
build: |p, label, ctx| {
let build = |$q: $params,
$label: String,
$ctx: &$crate::blocks::BuildContext|
-> comnum_core::Result<Box<dyn $crate::blocks::Block>> { $body };
build(<$params as $crate::blocks::Params>::parse($name, p)?, label, ctx)
},
}
};
}
#[derive(Clone, Debug)]
pub struct BlockInfo {
pub name: &'static str,
pub summary: &'static str,
pub params: Vec<ParamInfo>,
pub needs: &'static [Fact],
pub gives: &'static [Fact],
pub starts: bool,
pub per_polarization: bool,
}
impl BlockInfo {
pub fn describe(&self) -> String {
let names = |f: &[Fact]| {
if f.is_empty() { "nothing".to_string() } else { f.iter().map(|f| f.name()).collect::<Vec<_>>().join(", ") }
};
let mut s = format!("{}\n {}\n", self.name, self.summary);
let start = if self.starts { "starts a link; " } else { "" };
let _ = writeln!(s, " [{start}needs: {}; gives: {}]", names(self.needs), names(self.gives));
for p in &self.params {
s.push_str(&p.describe(4));
}
s
}
pub fn to_json(&self) -> Json {
let params: Map<String, Json> = self.params.iter().map(|p| (p.name.to_string(), p.json_schema())).collect();
serde_json::json!({
"name": self.name,
"summary": self.summary,
"starts": self.starts,
"per_polarization": self.per_polarization,
"needs": self.needs.iter().map(|f| f.name()).collect::<Vec<_>>(),
"gives": self.gives.iter().map(|f| f.name()).collect::<Vec<_>>(),
"required": self.params.iter().filter(|p| matches!(p.default, ParamDefault::Required)).map(|p| p.name).collect::<Vec<_>>(),
"params": params,
})
}
}
type FamilyFn = fn() -> Vec<Extension>;
const FAMILIES: &[FamilyFn] = &[
source::blocks,
channel::blocks,
fiber::blocks,
dsp::blocks,
ce_ofdm::blocks,
cpm::blocks,
analysis::blocks,
optical::blocks,
wireless::blocks,
coding::blocks,
systems::blocks,
];
fn builtins() -> &'static [Extension] {
static BUILTINS: OnceLock<Vec<Extension>> = OnceLock::new();
BUILTINS.get_or_init(|| FAMILIES.iter().flat_map(|family| family()).collect())
}
#[derive(Clone)]
pub struct Extension {
pub info: BlockInfo,
pub build: fn(Json, String, &BuildContext) -> Result<Box<dyn Block>>,
}
static EXTENSIONS: Mutex<Vec<Extension>> = Mutex::new(Vec::new());
fn extensions() -> MutexGuard<'static, Vec<Extension>> {
EXTENSIONS.lock().unwrap_or_else(PoisonError::into_inner)
}
pub fn register(ext: Extension) -> Result<()> {
let name = ext.info.name;
let mut extensions = extensions();
if builtins().iter().chain(extensions.iter()).any(|e| e.info.name == name) {
return Err(Error::Config(format!("a block {name:?} is already registered")));
}
extensions.push(ext);
Ok(())
}
pub fn registry() -> Vec<BlockInfo> {
with_registry(|all| all.iter().map(|&b| b.clone()).collect())
}
pub(crate) fn with_registry<R>(f: impl FnOnce(&[&BlockInfo]) -> R) -> R {
let extensions = extensions();
let all: Vec<&BlockInfo> = builtins().iter().chain(extensions.iter()).map(|e| &e.info).collect();
f(&all)
}
pub fn build(v: &Json, ctx: &BuildContext) -> Result<Box<dyn Block>> {
let (kind, label, p) = split_block(v)?;
build_parts(kind, label, p, ctx)
}
#[cfg_attr(
not(debug_assertions),
allow(clippy::needless_pass_by_value, reason = "`kind` is consumed by the port check of the debug builds")
)]
pub(crate) fn build_parts(kind: String, label: Option<String>, p: Json, ctx: &BuildContext) -> Result<Box<dyn Block>> {
let label = label.unwrap_or_else(|| kind.clone());
let found = builtins()
.iter()
.find(|e| e.info.name == kind)
.map(|e| (e.build, e.info.gives, e.info.per_polarization))
.or_else(|| {
extensions().iter().find(|e| e.info.name == kind).map(|e| (e.build, e.info.gives, e.info.per_polarization))
});
let Some((builder, gives, per_polarization)) = found else {
let known: Vec<&str> = with_registry(|r| r.iter().map(|b| b.name).collect());
return Err(crate::unknown("block", &kind, &known));
};
let block = builder(p, label, ctx)?;
let block: Box<dyn Block> = if per_polarization { Box::new(PerPol(block)) } else { block };
#[cfg(debug_assertions)]
let block: Box<dyn Block> = Box::new(schema::Checked { inner: block, kind, gives });
#[cfg(not(debug_assertions))]
let _ = gives;
Ok(block)
}
pub fn check_link(kinds: &[&str]) -> Result<()> {
with_registry(|r| schema::check_link(kinds, r))
}
pub fn json_schema() -> Json {
with_registry(|r| schema::json_schema(r))
}