openprocess 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cpnpy/__init__.py +46 -0
- openprocess/__init__.py +57 -0
- openprocess/analysis/__init__.py +0 -0
- openprocess/analysis/state_space.py +521 -0
- openprocess/analysis/state_space_process.py +251 -0
- openprocess/cli.py +742 -0
- openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/answer.pnml +31 -0
- openprocess/exercises/1 Petri nets/Exercise 1.1 Order handling/question.md +38 -0
- openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/answer.pnml +27 -0
- openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/net.pnml +29 -0
- openprocess/exercises/2 Soundness/Exercise 2.1 Spot the flaw/question.md +65 -0
- openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/log.txt +1 -0
- openprocess/exercises/3 Discovery/Exercise 3.1 The alpha-algorithm/question.md +67 -0
- openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/question.md +75 -0
- openprocess/exercises/4 Regions/Exercise 4.1 Regions of a transition system/ts.txt +5 -0
- openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/net.pnml +27 -0
- openprocess/exercises/5 Markings/Exercise 5.1 Markings and matrices/question.md +65 -0
- openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/log.txt +1 -0
- openprocess/exercises/6 Inductive Miner/Exercise 6.1 Cuts and trees/question.md +62 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/log.txt +1 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m1.pnml +36 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m2.pnml +28 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/m3.pnml +30 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/net.pnml +36 -0
- openprocess/exercises/7 Conformance/Exercise 7.1 Replay, alignments and workflows/question.md +69 -0
- openprocess/exercises/pack.md +14 -0
- openprocess/flow/__init__.py +50 -0
- openprocess/flow/box.py +466 -0
- openprocess/flow/boxes/__init__.py +7 -0
- openprocess/flow/boxes/check.py +119 -0
- openprocess/flow/boxes/compare.py +16 -0
- openprocess/flow/boxes/cpn.py +53 -0
- openprocess/flow/boxes/discover.py +124 -0
- openprocess/flow/boxes/filter.py +80 -0
- openprocess/flow/boxes/input.py +124 -0
- openprocess/flow/boxes/output.py +52 -0
- openprocess/flow/boxes/predict.py +186 -0
- openprocess/flow/boxes/science.py +159 -0
- openprocess/flow/boxes/sweeps.py +18 -0
- openprocess/flow/convert.py +187 -0
- openprocess/flow/datasets.py +198 -0
- openprocess/flow/explain.py +115 -0
- openprocess/flow/library.py +222 -0
- openprocess/flow/record.py +385 -0
- openprocess/flow/runner.py +357 -0
- openprocess/flow/sweep.py +92 -0
- openprocess/flow/types.py +290 -0
- openprocess/flow/workflow.py +628 -0
- openprocess/gui/__init__.py +0 -0
- openprocess/gui/app.py +90 -0
- openprocess/gui/arc_editing.py +295 -0
- openprocess/gui/canvas.py +1414 -0
- openprocess/gui/flow/__init__.py +8 -0
- openprocess/gui/flow/canvas.py +854 -0
- openprocess/gui/flow/page.py +972 -0
- openprocess/gui/flow/templates.py +131 -0
- openprocess/gui/flow/viewers.py +665 -0
- openprocess/gui/items.py +1275 -0
- openprocess/gui/learn/answer_boxes.py +978 -0
- openprocess/gui/learn/concealment.py +91 -0
- openprocess/gui/learn/mode.py +1181 -0
- openprocess/gui/panning.py +241 -0
- openprocess/gui/resources/openprocess-icon.png +0 -0
- openprocess/gui/studio/__init__.py +1 -0
- openprocess/gui/studio/__main__.py +3 -0
- openprocess/gui/studio/app.py +4031 -0
- openprocess/gui/studio/charts.py +115 -0
- openprocess/gui/studio/compare_page.py +487 -0
- openprocess/gui/studio/cpn_page.py +1858 -0
- openprocess/gui/studio/definition_view.py +284 -0
- openprocess/gui/studio/derivation_view.py +421 -0
- openprocess/gui/studio/documents.py +152 -0
- openprocess/gui/studio/dotted_chart.py +1401 -0
- openprocess/gui/studio/file_dialogs.py +143 -0
- openprocess/gui/studio/filter_dialog.py +247 -0
- openprocess/gui/studio/graph_builders.py +176 -0
- openprocess/gui/studio/graph_view.py +682 -0
- openprocess/gui/studio/instances.py +413 -0
- openprocess/gui/studio/log_editor.py +675 -0
- openprocess/gui/studio/log_page.py +800 -0
- openprocess/gui/studio/markdown_view.py +127 -0
- openprocess/gui/studio/mathtext.py +260 -0
- openprocess/gui/studio/ml_highlighter.py +75 -0
- openprocess/gui/studio/model_page.py +760 -0
- openprocess/gui/studio/net_comparison.py +124 -0
- openprocess/gui/studio/notes_overlay.py +275 -0
- openprocess/gui/studio/petri_page.py +844 -0
- openprocess/gui/studio/regions_view.py +502 -0
- openprocess/gui/studio/sidebar.py +149 -0
- openprocess/gui/studio/style.py +503 -0
- openprocess/gui/studio/tool_icons.py +134 -0
- openprocess/gui/studio/updates.py +439 -0
- openprocess/gui/studio/widgets.py +899 -0
- openprocess/gui/studio/workers.py +60 -0
- openprocess/gui/studio/workspace.py +447 -0
- openprocess/gui/theme.py +394 -0
- openprocess/gui/tidy.py +86 -0
- openprocess/io/__init__.py +0 -0
- openprocess/io/cpn_reader.py +389 -0
- openprocess/io/cpn_writer.py +357 -0
- openprocess/learn/__init__.py +23 -0
- openprocess/learn/answers.py +188 -0
- openprocess/learn/checks.py +953 -0
- openprocess/learn/computed.py +1180 -0
- openprocess/learn/context.py +145 -0
- openprocess/learn/exam.py +169 -0
- openprocess/learn/exercise-packs.md +325 -0
- openprocess/learn/importer.py +216 -0
- openprocess/learn/notation.py +474 -0
- openprocess/learn/pack.py +511 -0
- openprocess/learn/sheet.py +296 -0
- openprocess/mining/__init__.py +73 -0
- openprocess/mining/analysis.py +689 -0
- openprocess/mining/columns.py +282 -0
- openprocess/mining/compare_nets.py +246 -0
- openprocess/mining/conformance/__init__.py +0 -0
- openprocess/mining/conformance/alignments.py +263 -0
- openprocess/mining/conformance/quality.py +145 -0
- openprocess/mining/conformance/token_replay.py +252 -0
- openprocess/mining/csv_import.py +222 -0
- openprocess/mining/definitions.py +584 -0
- openprocess/mining/dfg.py +187 -0
- openprocess/mining/discovery/__init__.py +0 -0
- openprocess/mining/discovery/alpha.py +168 -0
- openprocess/mining/discovery/heuristics.py +332 -0
- openprocess/mining/discovery/inductive.py +477 -0
- openprocess/mining/discovery/state_regions.py +62 -0
- openprocess/mining/filtering.py +237 -0
- openprocess/mining/footprint.py +183 -0
- openprocess/mining/invariants.py +191 -0
- openprocess/mining/layout.py +279 -0
- openprocess/mining/log.py +364 -0
- openprocess/mining/petrinet.py +354 -0
- openprocess/mining/playout.py +75 -0
- openprocess/mining/pm4py_bridge.py +82 -0
- openprocess/mining/pnml.py +223 -0
- openprocess/mining/processtree.py +216 -0
- openprocess/mining/regions.py +476 -0
- openprocess/mining/stats.py +160 -0
- openprocess/mining/structure.py +374 -0
- openprocess/mining/transition_system.py +409 -0
- openprocess/mining/xes.py +399 -0
- openprocess/ml/__init__.py +0 -0
- openprocess/ml/ast_nodes.py +332 -0
- openprocess/ml/builtins.py +364 -0
- openprocess/ml/colorsets.py +522 -0
- openprocess/ml/errors.py +60 -0
- openprocess/ml/evaluator.py +754 -0
- openprocess/ml/lexer.py +277 -0
- openprocess/ml/multiset.py +417 -0
- openprocess/ml/parser.py +737 -0
- openprocess/ml/values.py +319 -0
- openprocess/model/__init__.py +0 -0
- openprocess/model/declarations.py +617 -0
- openprocess/model/examples.py +98 -0
- openprocess/model/net.py +701 -0
- openprocess/model/plain.py +192 -0
- openprocess/references.py +280 -0
- openprocess/sim/__init__.py +0 -0
- openprocess/sim/binding.py +620 -0
- openprocess/sim/export.py +66 -0
- openprocess/sim/simulator.py +315 -0
- openprocess/teaching/__init__.py +4 -0
- openprocess/teaching/answers.py +4 -0
- openprocess/teaching/checks.py +5 -0
- openprocess/teaching/pack.py +4 -0
- openprocess/teaching/sheet.py +4 -0
- openprocess-0.7.0.dist-info/METADATA +927 -0
- openprocess-0.7.0.dist-info/RECORD +173 -0
- openprocess-0.7.0.dist-info/WHEEL +5 -0
- openprocess-0.7.0.dist-info/entry_points.txt +6 -0
- openprocess-0.7.0.dist-info/licenses/LICENSE +21 -0
- openprocess-0.7.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
"""The colour set system: CPN's type system.
|
|
2
|
+
|
|
3
|
+
A *colour set* in CPN terminology is a type. Every place is annotated with
|
|
4
|
+
one, and a token in that place must carry a value belonging to it. CPN Tools
|
|
5
|
+
offers a fixed catalogue of colour set forms, and this module implements them
|
|
6
|
+
one class each.
|
|
7
|
+
|
|
8
|
+
The catalogue, with the CPN Tools declaration syntax we parse:
|
|
9
|
+
|
|
10
|
+
================================== ===========================
|
|
11
|
+
Declaration Class
|
|
12
|
+
================================== ===========================
|
|
13
|
+
``colset U = unit;`` :class:`UnitColourSet`
|
|
14
|
+
``colset B = bool;`` :class:`BoolColourSet`
|
|
15
|
+
``colset I = int;`` :class:`IntColourSet`
|
|
16
|
+
``colset I = int with 1..10;`` :class:`IntColourSet` (ranged)
|
|
17
|
+
``colset R = real;`` :class:`RealColourSet`
|
|
18
|
+
``colset S = string;`` :class:`StringColourSet`
|
|
19
|
+
``colset E = with a | b | c;`` :class:`EnumColourSet`
|
|
20
|
+
``colset X = index i with 1..5;`` :class:`IndexColourSet`
|
|
21
|
+
``colset P = product A * B;`` :class:`ProductColourSet`
|
|
22
|
+
``colset Rc = record f:A * g:B;`` :class:`RecordColourSet`
|
|
23
|
+
``colset L = list A;`` :class:`ListColourSet`
|
|
24
|
+
``colset Un = union C1:A + C2:B;`` :class:`UnionColourSet`
|
|
25
|
+
``colset Sub = subset A by pred;`` :class:`SubsetColourSet`
|
|
26
|
+
``colset Al = A;`` :class:`AliasColourSet`
|
|
27
|
+
================================== ===========================
|
|
28
|
+
|
|
29
|
+
Any of these may carry the ``timed`` keyword, which does not change the set of
|
|
30
|
+
values -- it changes how the *place* stores them (see
|
|
31
|
+
:class:`~openprocess.ml.multiset.TimedMultiset`).
|
|
32
|
+
|
|
33
|
+
Two operations matter for the rest of the system:
|
|
34
|
+
|
|
35
|
+
``contains(value)``
|
|
36
|
+
Type checking. Called when a place's initial marking is evaluated and
|
|
37
|
+
whenever a binding assigns a value to a typed variable.
|
|
38
|
+
|
|
39
|
+
``members()``
|
|
40
|
+
Enumeration. The binder falls back to enumerating a variable's colour set
|
|
41
|
+
when the variable cannot be determined from the input tokens (for example
|
|
42
|
+
a variable that appears only on an output arc). Infinite colour sets raise
|
|
43
|
+
:class:`InfiniteColourSetError`, which the binder turns into a clear
|
|
44
|
+
"cannot enumerate INT, constrain this variable" message rather than hanging.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
from __future__ import annotations
|
|
48
|
+
|
|
49
|
+
import itertools
|
|
50
|
+
from typing import Any, Callable, Iterator, Sequence
|
|
51
|
+
|
|
52
|
+
from .errors import CPNMLError
|
|
53
|
+
from .values import UNIT, Constructor, MLList, Record, Unit, format_value
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class InfiniteColourSetError(CPNMLError):
|
|
57
|
+
"""Raised by :meth:`ColourSet.members` on a colour set that cannot be listed."""
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ColourSet:
|
|
61
|
+
"""Abstract base class for all colour sets."""
|
|
62
|
+
|
|
63
|
+
#: Whether values of this colour set carry time stamps in a place.
|
|
64
|
+
timed: bool
|
|
65
|
+
|
|
66
|
+
def __init__(self, name: str, timed: bool = False) -> None:
|
|
67
|
+
self.name = name
|
|
68
|
+
self.timed = timed
|
|
69
|
+
|
|
70
|
+
# -- to be provided by subclasses ---------------------------------------
|
|
71
|
+
def contains(self, value: Any) -> bool:
|
|
72
|
+
"""Is ``value`` a legal member of this colour set?"""
|
|
73
|
+
raise NotImplementedError
|
|
74
|
+
|
|
75
|
+
def members(self) -> Iterator[Any]:
|
|
76
|
+
"""Yield every member. Raises for infinite colour sets."""
|
|
77
|
+
raise InfiniteColourSetError(
|
|
78
|
+
f"colour set '{self.name}' has infinitely many members and cannot be enumerated"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
def is_finite(self) -> bool:
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
# -- shared helpers ------------------------------------------------------
|
|
85
|
+
def check(self, value: Any) -> Any:
|
|
86
|
+
"""Return ``value`` if it belongs here, else raise a typing error."""
|
|
87
|
+
if not self.contains(value):
|
|
88
|
+
raise CPNMLError(
|
|
89
|
+
f"value {format_value(value)} is not a member of colour set '{self.name}'"
|
|
90
|
+
)
|
|
91
|
+
return value
|
|
92
|
+
|
|
93
|
+
def size(self) -> int:
|
|
94
|
+
"""Number of members; only meaningful when :meth:`is_finite`."""
|
|
95
|
+
return sum(1 for _ in self.members())
|
|
96
|
+
|
|
97
|
+
def __repr__(self) -> str:
|
|
98
|
+
return f"<{type(self).__name__} {self.name}{' timed' if self.timed else ''}>"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
# Simple (atomic) colour sets
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
class UnitColourSet(ColourSet):
|
|
105
|
+
"""``colset U = unit;`` -- exactly one value, ``()``.
|
|
106
|
+
|
|
107
|
+
``unit with e`` renames that single value to the identifier ``e``; we keep
|
|
108
|
+
the alias for printing but the value is still :data:`~openprocess.ml.values.UNIT`.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
def __init__(self, name: str, timed: bool = False, alias: str | None = None) -> None:
|
|
112
|
+
super().__init__(name, timed)
|
|
113
|
+
self.alias = alias
|
|
114
|
+
|
|
115
|
+
def contains(self, value: Any) -> bool:
|
|
116
|
+
return isinstance(value, Unit)
|
|
117
|
+
|
|
118
|
+
def members(self) -> Iterator[Any]:
|
|
119
|
+
yield UNIT
|
|
120
|
+
|
|
121
|
+
def is_finite(self) -> bool:
|
|
122
|
+
return True
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class BoolColourSet(ColourSet):
|
|
126
|
+
"""``colset B = bool;`` -- ``{false, true}``."""
|
|
127
|
+
|
|
128
|
+
def __init__(self, name: str, timed: bool = False,
|
|
129
|
+
false_alias: str | None = None, true_alias: str | None = None) -> None:
|
|
130
|
+
super().__init__(name, timed)
|
|
131
|
+
self.false_alias = false_alias
|
|
132
|
+
self.true_alias = true_alias
|
|
133
|
+
|
|
134
|
+
def contains(self, value: Any) -> bool:
|
|
135
|
+
return isinstance(value, bool)
|
|
136
|
+
|
|
137
|
+
def members(self) -> Iterator[Any]:
|
|
138
|
+
yield False
|
|
139
|
+
yield True
|
|
140
|
+
|
|
141
|
+
def is_finite(self) -> bool:
|
|
142
|
+
return True
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class IntColourSet(ColourSet):
|
|
146
|
+
"""``colset I = int;`` or ``colset I = int with 1..10;``.
|
|
147
|
+
|
|
148
|
+
Without a range this is infinite (and so not enumerable). With a range it
|
|
149
|
+
behaves like a finite interval, which is the usual way modellers make a
|
|
150
|
+
variable enumerable.
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
def __init__(self, name: str, timed: bool = False,
|
|
154
|
+
low: int | None = None, high: int | None = None) -> None:
|
|
155
|
+
super().__init__(name, timed)
|
|
156
|
+
self.low = low
|
|
157
|
+
self.high = high
|
|
158
|
+
|
|
159
|
+
def contains(self, value: Any) -> bool:
|
|
160
|
+
# ``bool`` is a subclass of ``int`` in Python but is a different colour
|
|
161
|
+
# set in CPN, so exclude it explicitly.
|
|
162
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
163
|
+
return False
|
|
164
|
+
if self.low is not None and value < self.low:
|
|
165
|
+
return False
|
|
166
|
+
if self.high is not None and value > self.high:
|
|
167
|
+
return False
|
|
168
|
+
return True
|
|
169
|
+
|
|
170
|
+
def is_finite(self) -> bool:
|
|
171
|
+
return self.low is not None and self.high is not None
|
|
172
|
+
|
|
173
|
+
def members(self) -> Iterator[Any]:
|
|
174
|
+
if not self.is_finite():
|
|
175
|
+
return super().members()
|
|
176
|
+
return iter(range(self.low, self.high + 1)) # type: ignore[arg-type]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class IntInfColourSet(IntColourSet):
|
|
180
|
+
"""``colset II = intinf;`` -- arbitrary precision integers.
|
|
181
|
+
|
|
182
|
+
Python integers are already arbitrary precision, so this differs from
|
|
183
|
+
:class:`IntColourSet` only in name. It exists so that round-tripping a
|
|
184
|
+
``.cpn`` file preserves the declaration the modeller wrote.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class RealColourSet(ColourSet):
|
|
189
|
+
"""``colset R = real;`` -- floating point numbers, never enumerable."""
|
|
190
|
+
|
|
191
|
+
def __init__(self, name: str, timed: bool = False,
|
|
192
|
+
low: float | None = None, high: float | None = None) -> None:
|
|
193
|
+
super().__init__(name, timed)
|
|
194
|
+
self.low = low
|
|
195
|
+
self.high = high
|
|
196
|
+
|
|
197
|
+
def contains(self, value: Any) -> bool:
|
|
198
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
199
|
+
return False
|
|
200
|
+
value = float(value)
|
|
201
|
+
if self.low is not None and value < self.low:
|
|
202
|
+
return False
|
|
203
|
+
if self.high is not None and value > self.high:
|
|
204
|
+
return False
|
|
205
|
+
return True
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class StringColourSet(ColourSet):
|
|
209
|
+
"""``colset S = string;``, optionally constrained.
|
|
210
|
+
|
|
211
|
+
CPN Tools allows ``string with "a".."z"`` (restrict the character set) and
|
|
212
|
+
``and 1..8`` (restrict the length). Both are supported here.
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
def __init__(self, name: str, timed: bool = False,
|
|
216
|
+
char_low: str | None = None, char_high: str | None = None,
|
|
217
|
+
length_low: int | None = None, length_high: int | None = None) -> None:
|
|
218
|
+
super().__init__(name, timed)
|
|
219
|
+
self.char_low = char_low
|
|
220
|
+
self.char_high = char_high
|
|
221
|
+
self.length_low = length_low
|
|
222
|
+
self.length_high = length_high
|
|
223
|
+
|
|
224
|
+
def contains(self, value: Any) -> bool:
|
|
225
|
+
if not isinstance(value, str):
|
|
226
|
+
return False
|
|
227
|
+
if self.length_low is not None and len(value) < self.length_low:
|
|
228
|
+
return False
|
|
229
|
+
if self.length_high is not None and len(value) > self.length_high:
|
|
230
|
+
return False
|
|
231
|
+
if self.char_low is not None or self.char_high is not None:
|
|
232
|
+
low = self.char_low or "\x00"
|
|
233
|
+
high = self.char_high or "\U0010ffff"
|
|
234
|
+
if any(not (low <= ch <= high) for ch in value):
|
|
235
|
+
return False
|
|
236
|
+
return True
|
|
237
|
+
|
|
238
|
+
def is_finite(self) -> bool:
|
|
239
|
+
# Finite only when both the alphabet and the maximum length are bounded.
|
|
240
|
+
return (
|
|
241
|
+
self.char_low is not None
|
|
242
|
+
and self.char_high is not None
|
|
243
|
+
and self.length_high is not None
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
def members(self) -> Iterator[Any]:
|
|
247
|
+
if not self.is_finite():
|
|
248
|
+
return super().members()
|
|
249
|
+
alphabet = [chr(c) for c in range(ord(self.char_low), ord(self.char_high) + 1)] # type: ignore[arg-type]
|
|
250
|
+
low = self.length_low or 0
|
|
251
|
+
for length in range(low, self.length_high + 1): # type: ignore[arg-type]
|
|
252
|
+
for combination in itertools.product(alphabet, repeat=length):
|
|
253
|
+
yield "".join(combination)
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
class EnumColourSet(ColourSet):
|
|
257
|
+
"""``colset E = with red | green | blue;``.
|
|
258
|
+
|
|
259
|
+
Members are :class:`~openprocess.ml.values.Constructor` values without arguments.
|
|
260
|
+
The identifiers are also injected into the global evaluation environment as
|
|
261
|
+
constants, so an inscription can just write ``red``.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
def __init__(self, name: str, constants: Sequence[str], timed: bool = False) -> None:
|
|
265
|
+
super().__init__(name, timed)
|
|
266
|
+
self.constants = tuple(constants)
|
|
267
|
+
|
|
268
|
+
def contains(self, value: Any) -> bool:
|
|
269
|
+
return (
|
|
270
|
+
isinstance(value, Constructor)
|
|
271
|
+
and not value.has_argument
|
|
272
|
+
and value.name in self.constants
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
def members(self) -> Iterator[Any]:
|
|
276
|
+
for constant in self.constants:
|
|
277
|
+
yield Constructor(constant)
|
|
278
|
+
|
|
279
|
+
def is_finite(self) -> bool:
|
|
280
|
+
return True
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class IndexColourSet(ColourSet):
|
|
284
|
+
"""``colset X = index proc with 1..5;``.
|
|
285
|
+
|
|
286
|
+
Values are constructors carrying an integer, printed ``proc(1)``. This is
|
|
287
|
+
CPN Tools' idiomatic way of naming a finite family of otherwise identical
|
|
288
|
+
entities (processes, resources, sites).
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
def __init__(self, name: str, tag: str, low: int, high: int, timed: bool = False) -> None:
|
|
292
|
+
super().__init__(name, timed)
|
|
293
|
+
self.tag = tag
|
|
294
|
+
self.low = low
|
|
295
|
+
self.high = high
|
|
296
|
+
|
|
297
|
+
def contains(self, value: Any) -> bool:
|
|
298
|
+
return (
|
|
299
|
+
isinstance(value, Constructor)
|
|
300
|
+
and value.name == self.tag
|
|
301
|
+
and value.has_argument
|
|
302
|
+
and isinstance(value.argument, int)
|
|
303
|
+
and not isinstance(value.argument, bool)
|
|
304
|
+
and self.low <= value.argument <= self.high
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
def members(self) -> Iterator[Any]:
|
|
308
|
+
for index in range(self.low, self.high + 1):
|
|
309
|
+
yield Constructor(self.tag, index)
|
|
310
|
+
|
|
311
|
+
def is_finite(self) -> bool:
|
|
312
|
+
return True
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# ---------------------------------------------------------------------------
|
|
316
|
+
# Compound colour sets
|
|
317
|
+
# ---------------------------------------------------------------------------
|
|
318
|
+
class ProductColourSet(ColourSet):
|
|
319
|
+
"""``colset P = product A * B * C;`` -- Cartesian product, values are tuples."""
|
|
320
|
+
|
|
321
|
+
def __init__(self, name: str, components: Sequence[ColourSet], timed: bool = False) -> None:
|
|
322
|
+
super().__init__(name, timed)
|
|
323
|
+
self.components = tuple(components)
|
|
324
|
+
|
|
325
|
+
def contains(self, value: Any) -> bool:
|
|
326
|
+
return (
|
|
327
|
+
isinstance(value, tuple)
|
|
328
|
+
and len(value) == len(self.components)
|
|
329
|
+
and all(cs.contains(v) for cs, v in zip(self.components, value))
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
def is_finite(self) -> bool:
|
|
333
|
+
return all(cs.is_finite() for cs in self.components)
|
|
334
|
+
|
|
335
|
+
def members(self) -> Iterator[Any]:
|
|
336
|
+
if not self.is_finite():
|
|
337
|
+
return super().members()
|
|
338
|
+
# ``itertools.product`` materialises each component, which is fine
|
|
339
|
+
# because we have already established that each one is finite.
|
|
340
|
+
return itertools.product(*(list(cs.members()) for cs in self.components))
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class RecordColourSet(ColourSet):
|
|
344
|
+
"""``colset R = record name:STRING * age:INT;`` -- labelled product."""
|
|
345
|
+
|
|
346
|
+
def __init__(self, name: str, fields: Sequence[tuple[str, ColourSet]], timed: bool = False) -> None:
|
|
347
|
+
super().__init__(name, timed)
|
|
348
|
+
self.fields = tuple(fields)
|
|
349
|
+
|
|
350
|
+
def contains(self, value: Any) -> bool:
|
|
351
|
+
if not isinstance(value, Record):
|
|
352
|
+
return False
|
|
353
|
+
if value.names() != tuple(n for n, _ in self.fields):
|
|
354
|
+
return False
|
|
355
|
+
return all(cs.contains(v) for (_, cs), (_, v) in zip(self.fields, value.fields))
|
|
356
|
+
|
|
357
|
+
def is_finite(self) -> bool:
|
|
358
|
+
return all(cs.is_finite() for _, cs in self.fields)
|
|
359
|
+
|
|
360
|
+
def members(self) -> Iterator[Any]:
|
|
361
|
+
if not self.is_finite():
|
|
362
|
+
return super().members()
|
|
363
|
+
names = [n for n, _ in self.fields]
|
|
364
|
+
for combination in itertools.product(*(list(cs.members()) for _, cs in self.fields)):
|
|
365
|
+
yield Record(tuple(zip(names, combination)))
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class ListColourSet(ColourSet):
|
|
369
|
+
"""``colset L = list A;`` or ``list A with 0..3;`` (bounded length).
|
|
370
|
+
|
|
371
|
+
Unbounded lists are infinite even over a finite element colour set, so
|
|
372
|
+
enumeration requires the ``with`` bound.
|
|
373
|
+
"""
|
|
374
|
+
|
|
375
|
+
def __init__(self, name: str, element: ColourSet, timed: bool = False,
|
|
376
|
+
length_low: int | None = None, length_high: int | None = None) -> None:
|
|
377
|
+
super().__init__(name, timed)
|
|
378
|
+
self.element = element
|
|
379
|
+
self.length_low = length_low
|
|
380
|
+
self.length_high = length_high
|
|
381
|
+
|
|
382
|
+
def contains(self, value: Any) -> bool:
|
|
383
|
+
if not isinstance(value, MLList):
|
|
384
|
+
return False
|
|
385
|
+
if self.length_low is not None and len(value) < self.length_low:
|
|
386
|
+
return False
|
|
387
|
+
if self.length_high is not None and len(value) > self.length_high:
|
|
388
|
+
return False
|
|
389
|
+
return all(self.element.contains(v) for v in value)
|
|
390
|
+
|
|
391
|
+
def is_finite(self) -> bool:
|
|
392
|
+
return self.element.is_finite() and self.length_high is not None
|
|
393
|
+
|
|
394
|
+
def members(self) -> Iterator[Any]:
|
|
395
|
+
if not self.is_finite():
|
|
396
|
+
return super().members()
|
|
397
|
+
element_values = list(self.element.members())
|
|
398
|
+
low = self.length_low or 0
|
|
399
|
+
for length in range(low, self.length_high + 1): # type: ignore[arg-type]
|
|
400
|
+
for combination in itertools.product(element_values, repeat=length):
|
|
401
|
+
yield MLList(combination)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
class UnionColourSet(ColourSet):
|
|
405
|
+
"""``colset U = union Car:CARS + Bike:BIKES + Walk;`` -- tagged sum.
|
|
406
|
+
|
|
407
|
+
A constructor may be *nullary* (``Walk`` above), in which case its member is
|
|
408
|
+
a bare :class:`~openprocess.ml.values.Constructor` with no argument, exactly like
|
|
409
|
+
an enumeration constant.
|
|
410
|
+
"""
|
|
411
|
+
|
|
412
|
+
def __init__(self, name: str, variants: Sequence[tuple[str, ColourSet | None]],
|
|
413
|
+
timed: bool = False) -> None:
|
|
414
|
+
super().__init__(name, timed)
|
|
415
|
+
self.variants = tuple(variants)
|
|
416
|
+
|
|
417
|
+
def _variant(self, tag: str) -> tuple[str, ColourSet | None] | None:
|
|
418
|
+
for variant in self.variants:
|
|
419
|
+
if variant[0] == tag:
|
|
420
|
+
return variant
|
|
421
|
+
return None
|
|
422
|
+
|
|
423
|
+
def contains(self, value: Any) -> bool:
|
|
424
|
+
if not isinstance(value, Constructor):
|
|
425
|
+
return False
|
|
426
|
+
variant = self._variant(value.name)
|
|
427
|
+
if variant is None:
|
|
428
|
+
return False
|
|
429
|
+
_, payload_cs = variant
|
|
430
|
+
if payload_cs is None:
|
|
431
|
+
return not value.has_argument
|
|
432
|
+
return value.has_argument and payload_cs.contains(value.argument)
|
|
433
|
+
|
|
434
|
+
def is_finite(self) -> bool:
|
|
435
|
+
return all(cs is None or cs.is_finite() for _, cs in self.variants)
|
|
436
|
+
|
|
437
|
+
def members(self) -> Iterator[Any]:
|
|
438
|
+
if not self.is_finite():
|
|
439
|
+
return super().members()
|
|
440
|
+
for tag, payload_cs in self.variants:
|
|
441
|
+
if payload_cs is None:
|
|
442
|
+
yield Constructor(tag)
|
|
443
|
+
else:
|
|
444
|
+
for payload in payload_cs.members():
|
|
445
|
+
yield Constructor(tag, payload)
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
class SubsetColourSet(ColourSet):
|
|
449
|
+
"""``colset S = subset A by pred;`` -- members of ``A`` satisfying a predicate.
|
|
450
|
+
|
|
451
|
+
The predicate is supplied as a Python callable by the declaration compiler,
|
|
452
|
+
which wraps the user's CPN ML function. Enumeration filters the base set,
|
|
453
|
+
so a subset of an infinite colour set stays non-enumerable.
|
|
454
|
+
"""
|
|
455
|
+
|
|
456
|
+
def __init__(self, name: str, base: ColourSet, predicate: Callable[[Any], bool],
|
|
457
|
+
timed: bool = False, predicate_source: str = "",
|
|
458
|
+
members: Sequence[Any] | None = None) -> None:
|
|
459
|
+
super().__init__(name, timed)
|
|
460
|
+
self.base = base
|
|
461
|
+
self.predicate = predicate
|
|
462
|
+
# Kept so that the writer can reproduce the original declaration text.
|
|
463
|
+
self.predicate_source = predicate_source
|
|
464
|
+
#: ``subset A with [v1, v2]``: the listed values, in order. Finite
|
|
465
|
+
#: even when ``A`` is not.
|
|
466
|
+
self.listed = list(members) if members is not None else None
|
|
467
|
+
|
|
468
|
+
def contains(self, value: Any) -> bool:
|
|
469
|
+
return self.base.contains(value) and bool(self.predicate(value))
|
|
470
|
+
|
|
471
|
+
def is_finite(self) -> bool:
|
|
472
|
+
return self.listed is not None or self.base.is_finite()
|
|
473
|
+
|
|
474
|
+
def members(self) -> Iterator[Any]:
|
|
475
|
+
if self.listed is not None:
|
|
476
|
+
yield from self.listed
|
|
477
|
+
return
|
|
478
|
+
for value in self.base.members():
|
|
479
|
+
if self.predicate(value):
|
|
480
|
+
yield value
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
class AliasColourSet(ColourSet):
|
|
484
|
+
"""``colset MyInt = INT;`` -- a new name for an existing colour set.
|
|
485
|
+
|
|
486
|
+
Delegates every operation to the target. We keep the indirection rather
|
|
487
|
+
than collapsing it so that the declaration survives a save/load round trip
|
|
488
|
+
and so that error messages can name the alias the modeller actually used.
|
|
489
|
+
"""
|
|
490
|
+
|
|
491
|
+
def __init__(self, name: str, target: ColourSet, timed: bool | None = None) -> None:
|
|
492
|
+
super().__init__(name, target.timed if timed is None else timed)
|
|
493
|
+
self.target = target
|
|
494
|
+
|
|
495
|
+
def contains(self, value: Any) -> bool:
|
|
496
|
+
return self.target.contains(value)
|
|
497
|
+
|
|
498
|
+
def is_finite(self) -> bool:
|
|
499
|
+
return self.target.is_finite()
|
|
500
|
+
|
|
501
|
+
def members(self) -> Iterator[Any]:
|
|
502
|
+
return self.target.members()
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
# ---------------------------------------------------------------------------
|
|
506
|
+
# The standard colour sets that CPN Tools predeclares
|
|
507
|
+
# ---------------------------------------------------------------------------
|
|
508
|
+
def standard_colour_sets() -> dict[str, ColourSet]:
|
|
509
|
+
"""The colour sets available in every model without being declared.
|
|
510
|
+
|
|
511
|
+
CPN Tools makes ``UNIT``, ``BOOL``, ``INT``, ``INTINF``, ``REAL`` and
|
|
512
|
+
``STRING`` available implicitly. We create a fresh dictionary per call so
|
|
513
|
+
that two models can never share (and accidentally mutate) the same objects.
|
|
514
|
+
"""
|
|
515
|
+
return {
|
|
516
|
+
"UNIT": UnitColourSet("UNIT"),
|
|
517
|
+
"BOOL": BoolColourSet("BOOL"),
|
|
518
|
+
"INT": IntColourSet("INT"),
|
|
519
|
+
"INTINF": IntInfColourSet("INTINF"),
|
|
520
|
+
"REAL": RealColourSet("REAL"),
|
|
521
|
+
"STRING": StringColourSet("STRING"),
|
|
522
|
+
}
|
openprocess/ml/errors.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Exception hierarchy for the CPN ML subsystem.
|
|
2
|
+
|
|
3
|
+
Every failure that the user can cause by writing a bad model raises a subclass
|
|
4
|
+
of :class:`CPNMLError`. The GUI catches that one class and shows the message
|
|
5
|
+
next to the offending inscription, so it is important that nothing in the ML
|
|
6
|
+
layer raises a bare ``ValueError`` for a *user* mistake. Internal invariant
|
|
7
|
+
violations should still raise ``AssertionError`` -- those are our bugs, not the
|
|
8
|
+
modeller's.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CPNMLError(Exception):
|
|
15
|
+
"""Base class for all errors caused by the contents of a model."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, message: str, position: int | None = None) -> None:
|
|
18
|
+
super().__init__(message)
|
|
19
|
+
self.message = message
|
|
20
|
+
# Character offset into the source inscription, when we know it. The
|
|
21
|
+
# editor uses this to underline the exact token.
|
|
22
|
+
self.position = position
|
|
23
|
+
|
|
24
|
+
def __str__(self) -> str: # pragma: no cover - trivial
|
|
25
|
+
if self.position is None:
|
|
26
|
+
return self.message
|
|
27
|
+
return f"{self.message} (at character {self.position})"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class LexError(CPNMLError):
|
|
31
|
+
"""The inscription contains a character sequence that is not a token."""
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ParseError(CPNMLError):
|
|
35
|
+
"""The tokens are individually valid but do not form a legal expression."""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TypeError_(CPNMLError):
|
|
39
|
+
"""A value does not belong to the colour set it is being used as.
|
|
40
|
+
|
|
41
|
+
Named with a trailing underscore so it does not shadow the builtin
|
|
42
|
+
``TypeError``; exported as ``CPNTypeError`` for readability.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
CPNTypeError = TypeError_
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class EvalError(CPNMLError):
|
|
50
|
+
"""Evaluation failed: unbound identifier, division by zero, no matching
|
|
51
|
+
case branch, wrong number of arguments, and so on."""
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class MatchError(EvalError):
|
|
55
|
+
"""A pattern could not be matched against a value.
|
|
56
|
+
|
|
57
|
+
This is a *recoverable* error during binding search -- the binder catches
|
|
58
|
+
it and simply moves on to the next candidate token -- so it gets its own
|
|
59
|
+
class rather than being reported to the user directly.
|
|
60
|
+
"""
|