use crate::ast::{EdgeDef, GraphDef, NodeDef};
use proc_macro2::Span;
use syn::parse::{Parse, ParseStream};
use syn::{braced, parenthesized, Expr, Ident, Result, Token, Type, TypePath, Visibility};
impl Parse for GraphDef {
fn parse(input: ParseStream) -> Result<Self> {
let vis: Visibility = input.parse()?;
input.parse::<Token![struct]>()?;
let name: Ident = input.parse()?;
input.parse::<Token![;]>()?;
let nodes_kw: Ident = input.parse()?;
if nodes_kw != "nodes" {
return Err(syn::Error::new_spanned(nodes_kw, "expected `nodes` block"));
}
let nodes_content;
braced!(nodes_content in input);
let mut nodes = Vec::new();
while !nodes_content.is_empty() {
let idx = parse_usize_lit(nodes_content.parse()?)?;
nodes_content.parse::<Token![:]>()?;
let body;
braced!(body in nodes_content);
let (mut ty, mut in_ports, mut out_ports, mut in_payload, mut out_payload) =
(None, None, None, None, None);
let (mut name_opt, mut ingress_policy_opt) = (None, None);
while !body.is_empty() {
let key: Ident = body.parse()?;
body.parse::<Token![:]>()?;
match key.to_string().as_str() {
"ty" => ty = Some(body.parse()?),
"in_ports" => in_ports = Some(parse_usize_lit(body.parse()?)?),
"out_ports" => out_ports = Some(parse_usize_lit(body.parse()?)?),
"in_payload" => in_payload = Some(body.parse()?),
"out_payload" => out_payload = Some(body.parse()?),
"name" => name_opt = Some(body.parse()?),
"ingress_policy" => ingress_policy_opt = Some(body.parse()?),
other => {
return Err(syn::Error::new_spanned(
key,
format!("unknown node field `{other}`"),
))
}
}
if body.peek(Token![,]) {
body.parse::<Token![,]>()?;
}
}
nodes.push(NodeDef {
idx,
ty: ty.ok_or_else(|| err("node.ty is required"))?,
in_ports: in_ports.ok_or_else(|| err("node.in_ports is required"))?,
out_ports: out_ports.ok_or_else(|| err("node.out_ports is required"))?,
in_payload: in_payload.ok_or_else(|| err("node.in_payload is required"))?,
out_payload: out_payload.ok_or_else(|| err("node.out_payload is required"))?,
name_opt,
ingress_policy_opt,
});
if nodes_content.peek(Token![,]) {
nodes_content.parse::<Token![,]>()?;
}
}
let edges_kw: Ident = input.parse()?;
if edges_kw != "edges" {
return Err(syn::Error::new_spanned(edges_kw, "expected `edges` block"));
}
let edges_content;
braced!(edges_content in input);
let mut edges = Vec::new();
while !edges_content.is_empty() {
let idx = parse_usize_lit(edges_content.parse()?)?;
edges_content.parse::<Token![:]>()?;
let body;
braced!(body in edges_content);
let (mut ty, mut payload) = (None::<TypePath>, None::<Type>);
let (mut from_node, mut from_port, mut to_node, mut to_port) = (None, None, None, None);
let (mut policy, mut name_opt) = (None, None);
let mut manager_ty = None::<TypePath>;
while !body.is_empty() {
let key: Ident = body.parse()?;
body.parse::<Token![:]>()?;
match key.to_string().as_str() {
"ty" => ty = Some(body.parse()?),
"payload" => payload = Some(body.parse()?),
"manager" => manager_ty = Some(body.parse()?),
"from" => {
let paren;
parenthesized!(paren in body);
let a: Expr = paren.parse()?;
paren.parse::<Token![,]>()?;
let b: Expr = paren.parse()?;
from_node = Some(parse_usize_lit(a)?);
from_port = Some(parse_usize_lit(b)?);
}
"to" => {
let paren;
parenthesized!(paren in body);
let a: Expr = paren.parse()?;
paren.parse::<Token![,]>()?;
let b: Expr = paren.parse()?;
to_node = Some(parse_usize_lit(a)?);
to_port = Some(parse_usize_lit(b)?);
}
"policy" => policy = Some(body.parse()?),
"name" => name_opt = Some(body.parse()?),
other => {
return Err(syn::Error::new_spanned(
key,
format!("unknown edge field `{other}`"),
))
}
}
if body.peek(Token![,]) {
body.parse::<Token![,]>()?;
}
}
edges.push(EdgeDef {
idx,
ty: ty.ok_or_else(|| err("edge.ty is required"))?,
payload: payload.ok_or_else(|| err("edge.payload is required"))?,
manager_ty: manager_ty.ok_or_else(|| err("edge.manager is required"))?,
from_node: from_node.ok_or_else(|| err("edge.from is required"))?,
from_port: from_port.ok_or_else(|| err("edge.from is required"))?,
to_node: to_node.ok_or_else(|| err("edge.to is required"))?,
to_port: to_port.ok_or_else(|| err("edge.to is required"))?,
policy: policy.ok_or_else(|| err("edge.policy is required"))?,
name_opt,
});
if edges_content.peek(Token![,]) {
edges_content.parse::<Token![,]>()?;
}
}
let mut emit_concurrent = false;
if !input.is_empty() {
let concurrent_kw: Ident = input.parse()?;
if concurrent_kw != "concurrent" {
return Err(syn::Error::new_spanned(
concurrent_kw,
"expected `concurrent;`",
));
}
input.parse::<Token![;]>()?;
emit_concurrent = true;
}
Ok(GraphDef {
vis,
name,
emit_concurrent,
nodes,
edges,
})
}
}
fn err(msg: &str) -> syn::Error {
syn::Error::new(Span::call_site(), msg)
}
fn parse_usize_lit(e: syn::Expr) -> syn::Result<usize> {
match e {
syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Int(li),
..
}) => li.base10_parse::<usize>(),
_ => Err(syn::Error::new_spanned(e, "expected integer literal")),
}
}