mod extract;
mod model;
mod parse;
use proc_macro2::TokenStream;
use quote::quote;
use extract::{Components, Extracted};
use model::Model;
pub(crate) use parse::{Parsed, ParsedAttr, ParsedItem};
impl Parsed {
pub(crate) fn try_expand(self) -> Result<TokenStream, TokenStream> {
let Self {
name,
attrs,
vis,
stmts,
} = self;
let extracted: Extracted = stmts.as_slice().try_into()?;
let twine_chain = Model::new(&extracted).generate_twine_chain()?;
let Extracted {
input,
components,
connections,
} = extracted;
let connections_type = generate_connections_type(&components);
let Components {
concrete,
inputs,
outputs,
..
} = components;
let new_fn_doc = format!("Creates a new `{name}` from instantiated components.");
Ok(quote! {
#(#attrs)*
#vis struct #name {
component: Box<
dyn twine_core::Component<
Input = #input,
Output = #outputs,
Error = twine_core::TwineError,
>,
>,
}
impl #name {
#[allow(clippy::no_effect)]
fn __lsp_support(input: #input, output: #outputs) {
#connections_type
let _: #inputs = #connections;
}
#[doc = #new_fn_doc]
#vis fn new(components: #concrete) -> Self {
use twine_core::Component;
let component = Box::new({
#twine_chain
});
Self { component }
}
}
impl twine_core::Component for #name {
type Input = #input;
type Output = #outputs;
type Error = twine_core::TwineError;
fn call(&self, input: Self::Input) -> Result<Self::Output, Self::Error> {
self.component.call(input)
}
}
})
}
}
fn generate_connections_type(components: &Components) -> TokenStream {
let comp_inputs = &components.inputs;
let template_name = components.template_name();
let docstring = format!(
r"Defines input connections for components within `{template_name}`.
Each field of this struct corresponds to a component in `{template_name}`. The field's
value specifies how that component receives its input, defined using expressions.
# Usage
- Reference the top-level input using `input`.
- Reference outputs from other components using `output.component_name`."
);
quote! {
#[doc = #docstring]
type Connections = #comp_inputs;
}
}