use crate::ast;
use limen_core::prelude::payload::Payload;
use std::any::type_name;
use std::path::Path;
use std::path::PathBuf;
use syn::parse_str;
use syn::{Expr, Ident, Type, TypePath, Visibility};
use limen_core::edge::link::EdgeLink;
use limen_core::node::link::NodeLink;
use limen_core::policy::{AdmissionPolicy, EdgePolicy, OverBudgetAction};
#[derive(Debug, Clone, Copy)]
pub enum GraphVisibility {
Public,
Crate,
Super,
Private,
}
impl GraphVisibility {
fn as_syn_visibility(&self) -> Visibility {
match &self {
GraphVisibility::Public => parse_str::<Visibility>("pub").expect("parse pub"),
GraphVisibility::Crate => {
parse_str::<Visibility>("pub(crate)").expect("parse pub(crate)")
}
GraphVisibility::Super => {
parse_str::<Visibility>("pub(super)").expect("parse pub(super)")
}
GraphVisibility::Private => Visibility::Inherited,
}
}
}
#[derive(Default)]
pub struct GraphBuilder {
vis: Option<Visibility>,
name: Option<Ident>,
nodes: Vec<ast::NodeDef>,
edges: Vec<ast::EdgeDef>,
emit_concurrent: bool,
}
impl GraphBuilder {
pub fn new(name: &str, vis: GraphVisibility) -> Self {
let vis_syn = vis.as_syn_visibility();
let name_ident: Ident = parse_str(name).expect("invalid graph name");
Self {
vis: Some(vis_syn),
name: Some(name_ident),
nodes: Vec::new(),
edges: Vec::new(),
emit_concurrent: false,
}
}
pub fn node(mut self, n: Node) -> Self {
self.nodes.push(n.finish());
self
}
pub fn node_from_link<N, const IN: usize, const OUT: usize, InP, OutP>(
mut self,
link: NodeLink<N, IN, OUT, InP, OutP>,
ingress_policy: Option<EdgePolicy>,
) -> Self
where
InP: Payload + 'static,
OutP: Payload + 'static,
N: limen_core::node::Node<IN, OUT, InP, OutP> + 'static,
{
let idx = *link.id().as_usize();
let node_ty = type_of_val_to_syn_type::<N>();
let in_p_ty = type_of_val_to_syn_type::<InP>();
let out_p_ty = type_of_val_to_syn_type::<OutP>();
let name_opt = link.name().map(|nm| name_to_expr(Some(nm)));
let ingress_expr = match ingress_policy {
Some(p) => {
let s = edge_policy_value_to_string(&p);
Some(parse_str::<Expr>(&s).expect("failed to parse EdgePolicy expr"))
}
None => None,
};
self.nodes.push(ast::NodeDef {
idx,
ty: match node_ty {
Type::Path(tp) => tp, _ => panic!("node type must be a path"),
},
in_ports: IN,
out_ports: OUT,
in_payload: in_p_ty,
out_payload: out_p_ty,
name_opt,
ingress_policy_opt: ingress_expr,
});
self
}
pub fn edge(mut self, e: Edge) -> Self {
self.edges.push(e.finish());
self
}
pub fn edge_from_link<Q, P, M>(mut self, link: EdgeLink<Q>) -> Self
where
P: Payload + 'static,
Q: limen_core::edge::Edge + 'static,
M: 'static,
{
let id = *link.id().as_usize();
let q_ty = type_of_val_to_syn_type::<Q>();
let p_ty = type_of_val_to_syn_type::<P>();
let m_ty = type_of_val_to_syn_type::<M>();
let up = link.upstream_port();
let dn = link.downstream_port();
let from_node = *up.node().as_usize();
let from_port = *up.port().as_usize();
let to_node = *dn.node().as_usize();
let to_port = *dn.port().as_usize();
let policy_expr = {
let s = edge_policy_value_to_string(link.policy());
parse_str::<Expr>(&s).expect("failed to parse EdgePolicy")
};
let name_opt = link.name().map(|nm| name_to_expr(Some(nm)));
self.edges.push(ast::EdgeDef {
idx: id,
ty: match q_ty {
Type::Path(tp) => tp,
_ => panic!("edge queue type must be a path"),
},
payload: p_ty,
manager_ty: match m_ty {
Type::Path(tp) => tp,
_ => panic!("edge manager type must be a path"),
},
from_node,
from_port,
to_node,
to_port,
policy: policy_expr,
name_opt,
});
self
}
pub fn concurrent(mut self, emit_concurrent: bool) -> Self {
self.emit_concurrent = emit_concurrent;
self
}
pub fn to_graph_def(self) -> ast::GraphDef {
ast::GraphDef {
vis: self.vis.expect("visibility required"),
name: self.name.expect("name required"),
emit_concurrent: self.emit_concurrent,
nodes: self.nodes,
edges: self.edges,
}
}
pub fn finish(self) -> GraphWriter {
GraphWriter {
g: ast::GraphDef {
vis: self.vis.expect("visibility required"),
name: self.name.expect("name required"),
emit_concurrent: self.emit_concurrent,
nodes: self.nodes,
edges: self.edges,
},
}
}
}
pub struct GraphWriter {
g: ast::GraphDef,
}
impl GraphWriter {
pub fn write_to_path<P: AsRef<Path>>(self, dest: P) -> Result<PathBuf, crate::CodegenError> {
crate::expand_ast_to_file(self.g, dest)
}
pub fn write(self, filename: &str) -> Result<PathBuf, crate::CodegenError> {
let name_with_extension = {
let p = std::path::Path::new(filename);
if p.extension().is_none() {
format!("{}.rs", filename)
} else {
filename.to_string()
}
};
let out_dir = std::env::var("OUT_DIR").map_err(|e| {
crate::CodegenError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("OUT_DIR environment variable not set: {}", e),
))
})?;
let dest_dir = std::path::Path::new(&out_dir).join("generated");
std::fs::create_dir_all(&dest_dir).map_err(crate::CodegenError::Io)?;
let dest = dest_dir.join(name_with_extension);
crate::expand_ast_to_file(self.g, &dest)?;
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:warning=generated graph A -> {}", dest.display());
Ok(dest)
}
}
pub struct Node {
idx: usize,
ty: Option<TypePath>,
in_ports: Option<usize>,
out_ports: Option<usize>,
in_payload: Option<Type>,
out_payload: Option<Type>,
name_opt: Option<Expr>,
ingress_policy_opt: Option<Expr>,
}
impl Node {
pub fn new(idx: usize) -> Self {
Self {
idx,
ty: None,
in_ports: None,
out_ports: None,
in_payload: None,
out_payload: None,
name_opt: None,
ingress_policy_opt: None,
}
}
pub fn ty<T: 'static>(mut self) -> Self {
let t = type_of_val_to_syn_type::<T>();
match t {
Type::Path(tp) => {
self.ty = Some(TypePath {
qself: None,
path: tp.path,
});
}
_ => panic!("ty_val: expected a path type for node type"),
}
self
}
pub fn in_ports(mut self, n: usize) -> Self {
self.in_ports = Some(n);
self
}
pub fn out_ports(mut self, n: usize) -> Self {
self.out_ports = Some(n);
self
}
pub fn in_payload<T: 'static>(mut self) -> Self {
let t = type_of_val_to_syn_type::<T>();
self.in_payload = Some(t);
self
}
pub fn out_payload<T: 'static>(mut self) -> Self {
let t = type_of_val_to_syn_type::<T>();
self.out_payload = Some(t);
self
}
pub fn name(mut self, s: Option<&'static str>) -> Self {
self.name_opt = Some(name_to_expr(s));
self
}
pub fn ingress_policy(mut self, p: EdgePolicy) -> Self {
let s = edge_policy_value_to_string(&p);
self.ingress_policy_opt = Some(parse_str::<Expr>(&s).expect("parse EdgePolicy"));
self
}
fn finish(self) -> ast::NodeDef {
ast::NodeDef {
idx: self.idx,
ty: self.ty.expect("node.ty"),
in_ports: self.in_ports.expect("node.in_ports"),
out_ports: self.out_ports.expect("node.out_ports"),
in_payload: self.in_payload.expect("node.in_payload"),
out_payload: self.out_payload.expect("node.out_payload"),
name_opt: self.name_opt,
ingress_policy_opt: self.ingress_policy_opt,
}
}
}
pub struct Edge {
idx: usize,
ty: Option<TypePath>,
payload: Option<Type>,
manager_ty: Option<TypePath>,
from_node: Option<usize>,
from_port: Option<usize>,
to_node: Option<usize>,
to_port: Option<usize>,
policy: Option<Expr>,
name_opt: Option<Expr>,
}
impl Edge {
pub fn new(idx: usize) -> Self {
Self {
idx,
ty: None,
payload: None,
manager_ty: None,
from_node: None,
from_port: None,
to_node: None,
to_port: None,
policy: None,
name_opt: None,
}
}
pub fn ty<T: 'static>(mut self) -> Self {
let t = type_of_val_to_syn_type::<T>();
match t {
Type::Path(tp) => {
self.ty = Some(TypePath {
qself: None,
path: tp.path,
});
}
_ => panic!("ty_val: expected a path type for edge queue"),
}
self
}
pub fn payload<T: 'static>(mut self) -> Self {
let t = type_of_val_to_syn_type::<T>();
self.payload = Some(t);
self
}
pub fn manager_ty<M: 'static>(mut self) -> Self {
let t = type_of_val_to_syn_type::<M>();
match t {
Type::Path(tp) => {
self.manager_ty = Some(TypePath {
qself: None,
path: tp.path,
});
}
_ => panic!("manager_ty: expected a path type for memory manager"),
}
self
}
pub fn from(mut self, node: usize, port: usize) -> Self {
self.from_node = Some(node);
self.from_port = Some(port);
self
}
pub fn to(mut self, node: usize, port: usize) -> Self {
self.to_node = Some(node);
self.to_port = Some(port);
self
}
pub fn policy(mut self, p: EdgePolicy) -> Self {
let s = edge_policy_value_to_string(&p);
self.policy = Some(parse_str::<Expr>(&s).expect("parse EdgePolicy"));
self
}
pub fn name(mut self, s: Option<&'static str>) -> Self {
self.name_opt = Some(name_to_expr(s));
self
}
fn finish(self) -> ast::EdgeDef {
ast::EdgeDef {
idx: self.idx,
ty: self.ty.expect("edge.ty"),
payload: self.payload.expect("edge.payload"),
manager_ty: self.manager_ty.expect("edge.manager_ty"),
from_node: self.from_node.expect("edge.from.node"),
from_port: self.from_port.expect("edge.from.port"),
to_node: self.to_node.expect("edge.to.node"),
to_port: self.to_port.expect("edge.to.port"),
policy: self.policy.expect("edge.policy"),
name_opt: self.name_opt,
}
}
}
fn name_to_expr(n: Option<&'static str>) -> Expr {
match n {
Some(s) => parse_str::<Expr>(&format!("Some({:?})", s)).expect("parse name"),
None => parse_str::<Expr>("None").expect("parse None"),
}
}
fn type_of_val_to_syn_type<T: 'static>() -> Type {
let s = type_name::<T>();
parse_str::<Type>(s).unwrap_or_else(|e| panic!("failed to parse type `{}`: {}", s, e))
}
fn edge_policy_value_to_string(p: &EdgePolicy) -> String {
let caps = p.caps(); let max_items = caps.max_items();
let soft_items = caps.soft_items();
let max_bytes = match caps.max_bytes() {
Some(b) => format!("Some({})", b),
None => "None".to_string(),
};
let soft_bytes = match caps.soft_bytes() {
Some(b) => format!("Some({})", b),
None => "None".to_string(),
};
let admission_str = match p.admission() {
AdmissionPolicy::DropNewest => "limen_core::policy::AdmissionPolicy::DropNewest",
AdmissionPolicy::DropOldest => "limen_core::policy::AdmissionPolicy::DropOldest",
AdmissionPolicy::Block => "limen_core::policy::AdmissionPolicy::Block",
AdmissionPolicy::DeadlineAndQoSAware => {
"limen_core::policy::AdmissionPolicy::DeadlineAndQoSAware"
}
_ => panic!("Unsupported AdmissionPolicy {:?}", p.admission()),
};
let over_str = match p.over_budget() {
OverBudgetAction::Drop => "limen_core::policy::OverBudgetAction::Drop",
OverBudgetAction::SkipStage => "limen_core::policy::OverBudgetAction::SkipStage",
OverBudgetAction::Degrade => "limen_core::policy::OverBudgetAction::Degrade",
OverBudgetAction::DefaultOnTimeout => {
"limen_core::policy::OverBudgetAction::DefaultOnTimeout"
}
_ => panic!("Unsupported OverBudgetAction {:?}", p.over_budget()),
};
format!(
"limen_core::policy::EdgePolicy::new(limen_core::policy::QueueCaps::new({}, {}, {}, {}), {}, {})",
max_items, soft_items, max_bytes, soft_bytes, admission_str, over_str
)
}