1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
//! Proc-macros for the phoxal framework.
//!
//! Three macro families make up the authoring surface:
//!
//! - [`phoxal_api_tree!`] - declares concrete API-revision modules
//! (`phoxal_api::v0_1`, …), their revision-local body types, the
//! `ContractBody`/`ApiVersion` impls, and the api-local topic builders.
//! - [`derive@Config`] - derives the config schema embedded in participant
//! metadata.
//! - [`macro@service`] / [`macro@driver`] / [`macro@simulator`] / [`macro@tool`] -
//! declare a unit marker's `Config`/`State`/`Api` types and identity
//! (`ParticipantSpec`).
//! - [`macro@step`] - records cadence on the ordinary `Participant::step`
//! override. Setup, reset, shutdown, and query handlers are plain Rust.
//!
//! The participant authoring macros (`macro@service` / `macro@driver` /
//! `macro@tool` / `macro@simulator` / `macro@step`) reference the framework
//! through `::phoxal::…`; the engine crate makes that path resolve to itself with
//! `extern crate self as phoxal;`. The
//! `phoxal_api_tree!` output instead targets the bus ABI floor directly as
//! `::phoxal_bus`, since it is invoked in the `phoxal-api` crate, which does not
//! depend on the engine.
use TokenStream;
/// Declare a versioned API tree of version-local wire bodies + topics.
///
/// One invocation owns one or more `version vM_N { … }` blocks and exactly one
/// final `latest vM_N;` declaration. A child may `extends` one earlier parent;
/// inherited definitions are fully materialized with the child's concrete
/// identity. Additions are direct and same-path changes require explicit
/// `replace` or `remove`. The generated tree references the bus ABI floor as
/// `::phoxal_bus`.
///
/// # Node grammar
///
/// A version body is a tree of **nodes**. A node is either static (`name { … }`)
/// or dynamic (`name(var) { … }`, binding exactly one variable), and may nest to
/// any depth. Inside a node block, in any order:
///
/// - `struct …` / `enum …` - a version-local wire body. Macro-declared structs
/// get public fields; every body gets the standard derive set (`Clone`,
/// `Debug`, `PartialEq`, `serde::Serialize`/`Deserialize`).
/// - `topic <leaf>: command <Body>;` - a pub/sub topic the owning service
/// subscribes (a control input).
/// - `topic <leaf>: state <Body>;` - a pub/sub topic the owning service publishes
/// (telemetry/output). Same wire shape as `command`, but the side-branded
/// builders give it the inverse brand (see *Generated topic builders* below).
/// - `topic <leaf>: query <Req> => <Resp>;` - a request/response topic.
/// - a child node (`name { … }` / `name(var) { … }`).
///
/// Doc-comments and attributes attach to the next `struct`/`enum`; `topic`
/// declarations and child nodes take none.
///
/// # What each topic derives from its node path
///
/// A topic carries no per-topic params; its identity is derived from the path of
/// nodes enclosing it:
///
/// - **`TOPIC`** (the wire key) - the version, then the `/`-joined node
/// segments plus the leaf, where a static node contributes `name` and a
/// dynamic node contributes `name/{var}` (e.g.
/// `v0.1/component/{instance}/motor/{capability}/command`). Folding the
/// version into the key (D1) makes differently-versioned contracts
/// physically distinct Zenoh keys.
/// - **body type path** - `phoxal_api::vM_N::<node>::…::<Body>`; variables never
/// appear in the module path.
///
/// A topic is dynamic when its node path contains at least one `(var)` node, and
/// static otherwise.
///
/// # Generated topic builders
///
/// Each version also gets an api-local `topic` module emitted with BOTH side trees
/// (L1, plan #00). `topic::client()` returns a `Root` for the PUBLIC **client** side;
/// `topic::owner()` returns a `Root` for the OWNER side. Both
/// have a method per node that walks the identical
/// tree (a dynamic node's method takes its variable as `impl Display`) and a leaf
/// method that returns a typed `bus::Topic<Kind>` with the key formatted from the
/// carried variables. The leaf brand is side-specific: on the client side a
/// `command` leaf is `Publish<Body>`, a `state` leaf is `Subscribe<Body>`, and a
/// `query` leaf is `AskQuery<Req, Resp>`; on the owner side those flip to
/// `Subscribe<Body>` / `Publish<Body>` / `ServeQuery<Req, Resp>`.
/// Attach a positive, finite frequency to the ordinary
/// [`Participant::step`](https://docs.rs/phoxal) override.
/// Derive a compile-time Draft 2020-12 JSON Schema from the same supported
/// `#[serde(...)]` attributes used by `Deserialize`: `rename`, `rename_all`,
/// `default`, and `deny_unknown_fields`. Unsupported Serde attributes are a
/// compile error rather than an approximate schema.
/// Declare a service marker's `Config`/`State`/`Api` types. Each omitted type
/// defaults to `()`; identity defaults from `CARGO_PKG_NAME`.
///
/// An explicit `id` is still required whenever a crate defines more than one
/// participant - they cannot all default to the one package name - and
/// remains available any time the package name isn't the id you want.
///
/// For user runtimes, `Config` is the user-authored `robot.yaml` surface. A
/// framework runtime may use this same slot for a CLI-synthesized launch
/// payload (for example a cross-robot staging product); ordinary framework
/// knobs belong in the robot model received through `ctx.robot()`.
/// The driver-shaped counterpart to [`service`].
/// The simulator-shaped counterpart to [`service`].
/// The tool-shaped counterpart to [`service`]. `Api` and `Config` default to
/// `()` - tools stay raw-bus only (decided 2026-07-09), and a configless tool
/// can launch without `PHOXAL_CONFIG`. An explicit `config = Type` remains
/// required at launch unless that type itself accepts `null` (for example,
/// `Option<T>`).