Expand description
§limen-codegen — reusable generator for Limen graphs
This crate turns a compact, declarative graph DSL into fully-typed Rust
implementations that conform to the limen-core graph, node, edge, and
policy traits. The emitted code always includes a single concrete graph
structure, with an optional std-only scoped execution API.
It is designed to be used in two ways:
-
Proc-macro mode (recommended for quick iteration):
- Add
limen-build(proc-macro crate) andlimen-coreto yourCargo.toml. - Write the DSL inline in your code using the
define_graph! { ... }macro. - The macro forwards its token stream to
limen-codegen::expand_tokens(..).
ⓘuse limen_build::define_graph; define_graph! { pub struct MyGraph; nodes { 0: { ty: my_crate::nodes::MySourceNode, in_ports: 0, out_ports: 1, in_payload: (), out_payload: u32, name: Some("src"), // See "Ingress edges" below for rules: // Only valid for source nodes (in_ports == 0, out_ports > 0). ingress_policy: my_crate::policies::Q32_POLICY }, 1: { ty: my_crate::nodes::MyMapNode, in_ports: 1, out_ports: 1, in_payload: u32, out_payload: u32, name: Some("map") }, 2: { ty: my_crate::nodes::MySinkNode, in_ports: 1, out_ports: 0, in_payload: u32, out_payload: (), name: Some("sink") }, } edges { 0: { ty: limen_core::edge::bench::TestSpscRingBuf<8>, payload: u32, manager: limen_core::memory::static_manager::StaticMemoryManager<u32, 8>, from: (0, 0), to: (1, 0), policy: my_crate::policies::EDGE_POLICY, name: Some("src->map") }, 1: { ty: limen_core::edge::bench::TestSpscRingBuf<8>, payload: u32, manager: limen_core::memory::static_manager::StaticMemoryManager<u32, 8>, from: (1, 0), to: (2, 0), policy: my_crate::policies::EDGE_POLICY, name: Some("map->sink") }, } concurrent; } - Add
The trailing concurrent; keyword does not generate a separate graph type.
It adds the std-only ScopedGraphApi implementation for the same graph.
-
Build-script mode (recommended when proc-macros slow down the language server or you want to inspect/generated source):
- Add
limen-codegen(this crate) andlimen-coreto yourCargo.toml. - Put your DSL in a file (for example,
src/my_graph.limen). - In
build.rs, callexpand_str_to_file(..)to emit pretty-printed Rust intoOUT_DIR, theninclude!()it from your library or binary.
ⓘ// build.rs fn main() { let spec = std::fs::read_to_string("src/my_graph.limen").unwrap(); let out = std::env::var("OUT_DIR").unwrap(); let dest = std::path::Path::new(&out).join("my_graph.rs"); limen_codegen::expand_str_to_file(&spec, &dest).unwrap(); println!("cargo:rerun-if-changed=src/my_graph.limen"); }ⓘ// lib.rs or main.rs include!(concat!(env!("OUT_DIR"), "/my_graph.rs"));You can also build the graph programmatically in
build.rsusingbuilder::GraphBuilderinstead of writing the DSL as a string:ⓘuse limen_codegen::builder::{Edge, GraphBuilder, GraphVisibility, Node}; use limen_core::policy::{AdmissionPolicy, EdgePolicy, OverBudgetAction, QueueCaps}; fn main() { let edge_policy = EdgePolicy::new( QueueCaps::new(8, 6, None, None), AdmissionPolicy::DropNewest, OverBudgetAction::Drop, ); GraphBuilder::new("MyGraph", GraphVisibility::Public) .node( Node::new(0) .ty::<my_crate::nodes::MySource>() .in_ports(0) .out_ports(1) .in_payload::<()>() .out_payload::<u32>() .name(Some("src")) .ingress_policy(edge_policy), ) .node( Node::new(1) .ty::<my_crate::nodes::MyMap>() .in_ports(1) .out_ports(1) .in_payload::<u32>() .out_payload::<u32>() .name(Some("map")), ) .node( Node::new(2) .ty::<my_crate::nodes::MySink>() .in_ports(1) .out_ports(0) .in_payload::<u32>() .out_payload::<()>() .name(Some("sink")), ) .edge( Edge::new(0) .ty::<my_crate::queues::MyQueue<u32, 8>>() .payload::<u32>() .manager_ty::<my_crate::memory::MyMemoryManager<u32>>() .from(0, 0) .to(1, 0) .policy(edge_policy) .name(Some("src->map")), ) .edge( Edge::new(1) .ty::<my_crate::queues::MyQueue<u32, 8>>() .payload::<u32>() .manager_ty::<my_crate::memory::MyMemoryManager<u32>>() .from(1, 0) .to(2, 0) .policy(edge_policy) .name(Some("map->sink")), ) .concurrent(false) .finish() .write("my_graph") .unwrap(); }Set
.concurrent(true)to additionally emit the std-onlyScopedGraphApiimplementation for the same graph type. - Add
§What gets generated
Each invocation emits a single concrete graph type.
-
The generated graph struct stores:
nodes: a tuple ofNodeLink<..>(one per node),edges: a tuple ofEdgeLink<..>(one per real edge; see ingress below),managers: a tuple of memory manager instances (one per real edge).
-
It also implements
GraphApifor the concrete type, plus the per-index helper traits (GraphNodeAccess,GraphEdgeAccess,GraphNodeTypes,GraphNodeContextBuilder) that wire the graph into the Limen runtime APIs. -
When
concurrent = false(default), codegen emits the graph structure and the coreGraphApi/ node-access / context-builder impls only. -
When
concurrent = true, codegen additionally emits a std-onlyScopedGraphApiimplementation for that same graph type, behind#[cfg(feature = "std")]in the downstream crate.
§Feature flag note
The std-only scoped execution code is emitted behind #[cfg(feature = "std")]
in the generated file.
This crate (limen-codegen) does not define or forward a std feature; you control it in
the crate that compiles the generated code.
§DSL: shape and types
The DSL defines one graph per block:
- A visibility and a struct name:
pub struct MyGraph; - A
nodes { ... }section: numbered nodes, each with type and I/O shape. - An
edges { ... }section: numbered edges, each with its queue type, payload type, endpoints, and policy.
Node fields (all required unless marked optional):
ty: <TypePath>— Concrete node implementation type.in_ports: <usize>— Number of input ports (constant).out_ports: <usize>— Number of output ports (constant).in_payload: <Type>— Type received on each input port.out_payload: <Type>— Type emitted on each output port.name: <Option<Expr>>— Optional human-friendly identifier (for descriptors).ingress_policy: <Expr>— Optional policy that creates a synthetic ingress edge for this node. See Ingress edges below.
Edge fields (all required unless marked optional):
ty: <TypePath>— Queue implementation type for this edge (for example,TestSpscRingBuf<8>).payload: <Type>— Payload carried on this edge (must match nodeout_payload/in_payload).manager: <TypePath>— Memory manager implementation for this edge (for example,StaticMemoryManager<P, DEPTH>forno_stdorConcurrentMemoryManager<P>for concurrent graphs).from: (<usize>, <usize>)—(node_index, out_port_index).to: (<usize>, <usize>)—(node_index, in_port_index).policy: <Expr>— Policy value used to compute occupancy and admission.name: <Option<Expr>>— Optional human-friendly identifier (for descriptors).
§Important rules and assumptions
-
Two edge classes
- Ingress edges are synthetic and created only for source nodes that
specify
ingress_policy. They occupy the lowest global edge indices. - Real edges are those declared in
edges { ... }and are stored in the graph.
- Ingress edges are synthetic and created only for source nodes that
specify
-
Contiguous indices
- Node indices must be contiguous
0..nodes.len()with no gaps. - Edge indices must be contiguous
0..edges.len()with no gaps.
- Node indices must be contiguous
-
Port bounds
- For every edge,
from_port < from_node.out_portsandto_port < to_node.in_ports.
- For every edge,
-
Payload compatibility
- For every edge,
edge.payload == from_node.out_payload == to_node.in_payload(token-level equality).
- For every edge,
-
Queue uniformity per node
- All inbound edges to the same node must have an identical queue type.
- All outbound edges from the same node must have an identical queue type.
- This allows the generator to infer a single
InQandOutQtype per node.
-
Manager uniformity per node
- All inbound edges to the same node must have an identical manager type.
- All outbound edges from the same node must have an identical manager type.
- This allows the generator to infer a single
InMandOutMtype per node.
-
Ingress edges (synthetic)
- If a node specifies
ingress_policy, a synthetic ingress edge is created for that node. - Ingress edges do not live in the real
edgestuple and do not carry data; they exist to expose external ingress occupancy via the node’s source interface. - Assumption:
ingress_policymay only be specified for source nodes (in_ports == 0andout_ports > 0) that implement the source interface inlimen-core. These ingress edges occupy the lowest global edge indices[0..ingress_count).
- If a node specifies
-
Dependency on
limen-core- Generated code references the
limen_corecrate (note the underscore), which must be available to the downstream crate. Ensure your Cargo manifest includes a dependency onlimen-core(the hyphenated package name maps to thelimen_corecrate identifier).
- Generated code references the
§Programmatic entry points (when not using the proc macro)
All of the following:
-
parse the DSL (from tokens or string),
-
validate its structure and typing,
-
and emit the graph plus any optional scoped API selected by the input AST.
-
expand_tokens: parse+validate+emit from a token stream (used by the proc macro). -
expand_str_to_tokens: parse+validate+emit from a&strDSL (for build scripts or tests). -
expand_str_to_string: same as above, but pretty-prints to a Rust source string. -
expand_str_to_file: same as above, writes to a path (creating parent directories if needed). -
expand_ast_to_tokens,expand_ast_to_file: like the above, but take a typed AST (for use with thebuildermodule so you can write graphs as normal Rust).
Each entry point emits the single graph type, plus the optional std-only
scoped execution API determined by the emit_concurrent flag on the input AST.
All entry points perform validation before emitting code. Errors are returned as
CodegenError, with precise messages for parsing, validation, pretty-print, or I/O failures.
Modules§
- builder
- Optional: typed, LS-friendly graph builder (no proc-macro, no big strings). Strongly typed, Language Server–friendly builder for Limen graphs (no proc-macro input and no stringly-typed DSL).
Enums§
- Codegen
Error - Errors that can occur while expanding the graph DSL into Rust code.
Functions§
- expand_
ast_ to_ file - Validate, emit, pretty-print, and write a typed
ast::GraphDeftodest. - expand_
ast_ to_ tokens - Validate and emit Rust code from a typed
ast::GraphDef. - expand_
str_ to_ file - Parse, validate, emit, pretty-print, and write the Rust code for a DSL
string to
dest. Parent directories are created if needed, and writes are performed atomically. - expand_
str_ to_ string - Parse, validate, emit, and pretty-print the Rust code for a DSL string.
- expand_
str_ to_ tokens - Parse, validate, and emit Rust code from a DSL string (build script helper).
- expand_
tokens - Parse, validate, and emit Rust code from a proc-macro input token stream.
- write_
tokens_ pretty_ or_ raw - Write tokens to a file, preferring pretty-printing with fallback to raw.