Skip to content

Commit a72b04a

Browse files
fahchenclaude
andcommitted
docs(dsl): polish spec.md and drop macro-call parens in tests
- Restructure spec.md as a user-facing reference: removed the internal "File layout" section, tightened "Generated module surface" to document `cpnet/0` and the new `initial_markings/0` accessor as a pair, clarified what each generates and when. - Strip optional parens from DSL macro invocations in test fixtures (`name/1`, `place/2`, `input/{2,3}`, `output/{2,3}`, etc.) so the test sources mirror the canonical paren-less style established in `.formatter.exs#locals_without_parens`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 2577054 commit a72b04a

8 files changed

Lines changed: 245 additions & 286 deletions

File tree

lib/coloured_flow/dsl/spec.md

Lines changed: 80 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
A declarative Elixir DSL for defining Coloured Petri Nets.
22

3-
This is the high-level workflow-assembly layer. The low-level building
4-
blocks (`colset/1`, `var/1`, `val/1`) live in `ColouredFlow.Notation.*` and
5-
are reused here. The DSL composes them into a complete
6-
`ColouredFlow.Definition.ColouredPetriNet` and validates it at compile
7-
time.
3+
This is the high-level workflow-assembly layer on top of
4+
`ColouredFlow.Notation.*`. The DSL composes colour sets, variables,
5+
constants, places, transitions, arcs, functions, and termination criteria
6+
into a complete `ColouredFlow.Definition.ColouredPetriNet` and validates
7+
it at compile time. Any issue raises during `mix compile`, never at
8+
runtime.
89

910
## Synopsis
1011

@@ -14,21 +15,16 @@ time.
1415
name "My Workflow"
1516
version "1.0.0"
1617

17-
# types
1818
colset int() :: integer()
1919
colset bool() :: boolean()
2020

21-
# bindings
2221
var x :: int()
23-
var y :: int()
2422
val pi :: float() = 3.14
2523

26-
# functions
2724
function is_even(x) :: bool() do
2825
Integer.mod(x, 2) === 0
2926
end
3027

31-
# graph
3228
place :input, :int
3329
place :output, :int
3430

@@ -42,13 +38,8 @@ time.
4238
output :output do
4339
if is_even(x), do: {1, x}, else: {1, x + 1}
4440
end
45-
46-
action do
47-
log("fired with x=\#{x}")
48-
end
4941
end
5042

51-
# termination
5243
termination do
5344
on_markings do
5445
match?(
@@ -59,73 +50,58 @@ time.
5950
end
6051
end
6152

62-
MyWorkflow.cpnet() #=> %ColouredFlow.Definition.ColouredPetriNet{...}
63-
64-
## Module surface
65-
66-
`use ColouredFlow.DSL` injects the macros listed below and registers an
67-
`@before_compile` hook that assembles a
68-
`%ColouredFlow.Definition.ColouredPetriNet{}` from the accumulated
69-
declarations and runs `ColouredFlow.Validators.run/1` against it. Any
70-
validator failure raises at compile time so misconfigured workflows never
71-
reach runtime.
53+
## Generated module surface
7254

73-
Compiled modules expose two zero-arity helpers:
55+
A module that uses `ColouredFlow.DSL` exposes two zero-arity helpers:
7456

7557
MyWorkflow.cpnet() :: %ColouredFlow.Definition.ColouredPetriNet{}
7658
MyWorkflow.initial_markings() :: [%ColouredFlow.Enactment.Marking{}]
7759

78-
`cpnet/0` returns the static CPN definition. `initial_markings/0` returns
79-
the list of `%Marking{}` structs declared via `initial_marking/2` — Runner
80-
seed data, deliberately *not* part of `cpnet/0`.
60+
`cpnet/0` returns the static CPN — colour sets, variables, places,
61+
transitions, arcs, and termination criteria. It is the input the runner
62+
reuses across every enactment of the workflow.
8163

82-
Higher-level integration (running an enactment, persisting it, etc.) is
83-
intentionally left to the existing `ColouredFlow.Runner.*` API. The DSL
84-
produces only the static definition.
64+
`initial_markings/0` returns the list of `%Marking{}` declared via
65+
`initial_marking/2`. This is enactment-seed data, deliberately *not*
66+
folded into `cpnet/0`. Pass it to `Storage.insert_enactment/1` (or your
67+
own runner glue) when starting an enactment.
68+
69+
Higher-level integration — spawning enactments, persisting flows,
70+
visualisation — is left to the existing `ColouredFlow.Runner.*` API.
8571

8672
## Universal expression rule
8773

88-
Every macro that accepts an expression follows the same rule:
74+
Every macro that accepts an expression follows the same shape:
8975

90-
- **Single-line**: the expression is the last positional argument.
91-
- **Multi-line**: pass a `do ... end` block; the block body **is** the
92-
expression. There is no `expr do ... end` wrapper anywhere.
93-
- **Options** (e.g., `label:`): keyword arguments only, placed before the
94-
`do` block.
76+
- **Single-line**: the expression is the last positional argument.
77+
- **Multi-line**: pass a `do ... end` block; the block body **is** the
78+
expression. There is no `expr do ... end` wrapper.
79+
- **Options** (e.g., `label:`): keyword arguments only, before the `do`
80+
block.
9581

9682
```elixir
97-
guard x > 0 # single-line
98-
guard do x > 0 and y > 0 end # multi-line
83+
guard x > 0 # single-line
84+
guard do x > 0 and y > 0 end # multi-line
9985

100-
input :p, bind({1, x}) # single-line, no label
101-
input :p, bind({1, x}), label: "in" # single-line, with label
86+
input :p, bind({1, x}) # single-line, no label
87+
input :p, bind({1, x}), label: "in" # single-line, with label
10288

103-
input :p, label: "in" do # multi-line, with label
89+
input :p, label: "in" do # multi-line, with label
10490
if x > 0, do: bind({1, x}), else: bind({2, x})
10591
end
10692
```
10793

108-
## Reuse
109-
110-
`colset/1`, `var/1`, and `val/1` are re-exported from
111-
`ColouredFlow.Notation.*` so existing notation users have one mental model.
112-
Everything else (`name`, `version`, `place`, `initial_marking`,
113-
`transition`, `input`, `output`, `guard`, `action`, `function`,
114-
`termination`, `on_markings`) is new and lives under
115-
`ColouredFlow.DSL.*`.
116-
11794
## Top-level macros
11895

11996
### `name/1`
12097

121-
Set the human-readable workflow name. Compile-time only.
98+
Set the human-readable workflow name.
12299

123100
name "Traffic Light"
124101

125102
### `version/1`
126103

127-
Set the workflow version (any string the caller chooses; semver is
128-
conventional).
104+
Set the workflow version (free-form string; semver is conventional).
129105

130106
version "1.0.0"
131107

@@ -140,41 +116,42 @@ Re-exported from `ColouredFlow.Notation.Colset`. Declares a colour set.
140116

141117
### `var/1`
142118

143-
Re-exported from `ColouredFlow.Notation.Var`. Declares a variable bound to
144-
a colour set.
119+
Re-exported from `ColouredFlow.Notation.Var`. Declares a variable bound
120+
to a colour set.
145121

146122
var x :: int()
147123

148124
### `val/1`
149125

150-
Re-exported from `ColouredFlow.Notation.Val`. Declares a constant bound to
151-
a colour set.
126+
Re-exported from `ColouredFlow.Notation.Val`. Declares a constant bound
127+
to a colour set.
152128

153129
val pi :: float() = 3.14
154130

155131
### `place/2`
156132

157-
Declare a place. The first argument is the place name (atom; converted to a
158-
string for the underlying `%Place{}`); the second is the colour set name
159-
(atom).
133+
Declare a place. Place names are atoms; the underlying `%Place{}` stores
134+
them as strings. The colour set name is also an atom and must be a
135+
declared `colset`.
160136

161137
place :input, :int
162138
place :output, :int
163139

164140
### `initial_marking/2`
165141

166-
Declare the initial marking for a place. Multiple `initial_marking/2`
167-
calls may target different places; they are scattered freely between
168-
other declarations.
142+
Declare an initial marking for a place. Multiple `initial_marking/2`
143+
calls accumulate in declaration order and are exposed via
144+
`initial_markings/0` on the host module. The cpnet definition itself is
145+
not affected.
169146

170147
initial_marking :input, ~MS[1 2 3]
171148

172149
### `function/2` and `function/3`
173150

174151
Declare a user-defined function (CPN procedure) usable in arc, guard,
175-
action, and termination expressions. The arguments listed in the head
176-
must appear as free variables in the body. The return type after `::` is
177-
the result colour set.
152+
action, and termination expressions. Arguments listed in the head must
153+
appear as free variables in the body; the result type after `::` is the
154+
result colour set.
178155

179156
function is_even(x) :: bool(), do: Integer.mod(x, 2) === 0
180157

@@ -184,8 +161,8 @@ the result colour set.
184161

185162
### `transition/2`
186163

187-
Declare a transition. The block accepts `guard/1`, `action/1`, `input/2,3`
188-
and `output/2,3`.
164+
Declare a transition. The block accepts `guard/1`, `action/1`,
165+
`input/2,3`, and `output/2,3`.
189166

190167
transition :pass_through do
191168
guard x > 0
@@ -201,9 +178,9 @@ and `output/2,3`.
201178
### `termination/1`
202179

203180
Declare termination criteria. The block accepts criterion-specific
204-
sub-macros. Currently only `on_markings/1` is supported. Future criterion
205-
kinds (`on_time`, `on_workitem_count`, etc.) plug in here without
206-
changing call sites.
181+
sub-macros. Currently `on_markings/1` is the only kind. Future kinds
182+
(`on_time`, `on_workitem_count`, ) plug in here without changing call
183+
sites.
207184

208185
termination do
209186
on_markings do
@@ -218,8 +195,9 @@ changing call sites.
218195

219196
### `guard/1`
220197

221-
Boolean expression over bound variables. Optional. Returning a falsy
222-
value disables the transition for the current binding.
198+
A boolean expression over bound variables. Optional. A falsy result
199+
disables the transition for the current binding. Declaring `guard` more
200+
than once in a transition is a compile-time error.
223201

224202
guard x > 0
225203
guard do
@@ -228,9 +206,10 @@ value disables the transition for the current binding.
228206

229207
### `action/1`
230208

231-
Expression evaluated when the transition fires. Optional. Use for output
232-
bindings (when an outgoing arc references an unbound variable) and for
233-
side effects.
209+
Expression evaluated when the transition fires. Optional. Use it to
210+
provide bindings for outgoing arcs that reference variables not bound by
211+
any incoming arc. Declaring `action` more than once in a transition is a
212+
compile-time error.
234213

235214
action :ok
236215
action do
@@ -240,8 +219,8 @@ side effects.
240219

241220
### `input/2` and `input/3`
242221

243-
Declare an incoming arc (place → transition). The expression must use the
244-
`bind/1` keyword to consume tokens. Options: `:label`.
222+
Declare an incoming arc (place → transition). The expression must use
223+
`bind/1` to consume tokens. Options: `:label`.
245224

246225
input :input, bind({1, x})
247226
input :input, bind({1, x}), label: "in"
@@ -251,8 +230,8 @@ Declare an incoming arc (place → transition). The expression must use the
251230

252231
### `output/2` and `output/3`
253232

254-
Declare an outgoing arc (transition → place). The expression evaluates to
255-
the multiset of tokens produced. Options: `:label`.
233+
Declare an outgoing arc (transition → place). The expression evaluates
234+
to the multiset of tokens produced. Options: `:label`.
256235

257236
output :output, {1, x}
258237
output :output, {1, x}, label: "out"
@@ -264,9 +243,10 @@ the multiset of tokens produced. Options: `:label`.
264243

265244
### `on_markings/1`
266245

267-
Boolean expression over the special variable `markings` (a map of place
268-
name → token multiset). Returning a truthy value terminates the enactment
269-
with reason `:explicit`.
246+
Boolean expression over the special variable `markings` — a map of place
247+
name (string) to token multiset. A truthy result terminates the
248+
enactment with reason `:explicit`. Declaring `on_markings` more than
249+
once is a compile-time error.
270250

271251
on_markings do
272252
match?(
@@ -278,39 +258,18 @@ with reason `:explicit`.
278258
## Compile-time validation
279259

280260
The `@before_compile` hook builds a `%ColouredPetriNet{}` from the
281-
accumulated declarations and runs the existing
282-
`ColouredFlow.Validators.run/1` pipeline:
283-
284-
- colour-set declarations are well-formed,
285-
- place colour-set references resolve,
286-
- arc endpoints exist,
287-
- incoming arcs use `bind/1`,
288-
- free variables in expressions resolve to declared `var/1` (or function
289-
arguments),
290-
- structural sanity (no orphan places, no duplicate names, etc.).
291-
292-
Any failure raises a clear compile-time error pointing to the offending
293-
line.
294-
295-
## File layout
296-
297-
Macros are partitioned by concern; the entry module re-exports them via
298-
`__using__/1`.
299-
300-
- `ColouredFlow.DSL` — entry, `__using__/1`, top-level macros
301-
- `ColouredFlow.DSL.Builder``@before_compile` hook
302-
- `ColouredFlow.DSL.Place``place/2`, `initial_marking/2`
303-
- `ColouredFlow.DSL.Transition``transition/2`, `guard/1`, `action/1`
304-
- `ColouredFlow.DSL.Arc``input/{2,3}`, `output/{2,3}`
305-
- `ColouredFlow.DSL.Function``function/{2,3}`
306-
- `ColouredFlow.DSL.Termination``termination/1`, `on_markings/1`
307-
- `ColouredFlow.DSL.ExpressionHelper` — AST → `%Expression{}` conversion
308-
309-
## Out of scope
310-
311-
- Runner integration: spawning enactments, persisting flows. The DSL
312-
produces a definition; how it is run is the caller's choice.
313-
- Visualisation / diagram generation. Could be layered on top of
314-
`cpnet/0`.
315-
- Live editing / hot reload. Workflows are expected to be defined in
316-
source and recompiled.
261+
accumulated declarations, runs `ColouredFlow.Builder.build/1` (which
262+
populates `action.outputs` from arc/guard analysis), and runs
263+
`ColouredFlow.Validators.run/1`:
264+
265+
- colour set declarations are well-formed,
266+
- place / variable / constant colour-set references resolve,
267+
- arc endpoints exist,
268+
- incoming arcs use `bind/1`,
269+
- free variables in expressions resolve to `var/1` (or function args),
270+
- guard variables are bound by the same transition's incoming arcs,
271+
- function names are unique and result descrs are fully resolved,
272+
- termination criteria reference only `markings`.
273+
274+
Any failure raises a `CompileError` pointing to the offending macro
275+
call.

0 commit comments

Comments
 (0)