11A 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
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})
10591end
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
174151Declare 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
203180Declare 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
280260The ` @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