bigraph-schema 1.0.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.
- bigraph_schema/__init__.py +112 -0
- bigraph_schema/core.py +759 -0
- bigraph_schema/edge.py +138 -0
- bigraph_schema/methods/__init__.py +12 -0
- bigraph_schema/methods/apply.py +276 -0
- bigraph_schema/methods/check.py +213 -0
- bigraph_schema/methods/default.py +204 -0
- bigraph_schema/methods/generalize.py +309 -0
- bigraph_schema/methods/handle_parameters.py +182 -0
- bigraph_schema/methods/infer.py +217 -0
- bigraph_schema/methods/jump.py +432 -0
- bigraph_schema/methods/merge.py +405 -0
- bigraph_schema/methods/realize.py +527 -0
- bigraph_schema/methods/resolve.py +692 -0
- bigraph_schema/methods/serialize.py +491 -0
- bigraph_schema/methods/validate.py +249 -0
- bigraph_schema/package/__init__.py +1 -0
- bigraph_schema/package/discover.py +122 -0
- bigraph_schema/parse.py +183 -0
- bigraph_schema/protocols.py +57 -0
- bigraph_schema/schema.py +370 -0
- bigraph_schema/units.py +133 -0
- bigraph_schema-1.0.0.dist-info/METADATA +66 -0
- bigraph_schema-1.0.0.dist-info/RECORD +28 -0
- bigraph_schema-1.0.0.dist-info/WHEEL +5 -0
- bigraph_schema-1.0.0.dist-info/licenses/AUTHORS.md +6 -0
- bigraph_schema-1.0.0.dist-info/licenses/LICENSE +201 -0
- bigraph_schema-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
from pprint import pformat as pf
|
|
2
|
+
from plum import dispatch
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from bigraph_schema.schema import (
|
|
6
|
+
Node,
|
|
7
|
+
Atom,
|
|
8
|
+
Empty,
|
|
9
|
+
Union,
|
|
10
|
+
Tuple,
|
|
11
|
+
Boolean,
|
|
12
|
+
Number,
|
|
13
|
+
Integer,
|
|
14
|
+
Float,
|
|
15
|
+
Delta,
|
|
16
|
+
Nonnegative,
|
|
17
|
+
String,
|
|
18
|
+
Enum,
|
|
19
|
+
Wrap,
|
|
20
|
+
Maybe,
|
|
21
|
+
Overwrite,
|
|
22
|
+
List,
|
|
23
|
+
Map,
|
|
24
|
+
Tree,
|
|
25
|
+
Array,
|
|
26
|
+
Key,
|
|
27
|
+
Path,
|
|
28
|
+
Wires,
|
|
29
|
+
Schema,
|
|
30
|
+
Link,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
from bigraph_schema.methods.check import check
|
|
34
|
+
from bigraph_schema.methods.serialize import render
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dispatch
|
|
38
|
+
def validate(core, schema: Empty, state):
|
|
39
|
+
if state is not None:
|
|
40
|
+
return f'Empty schema is not empty:\n\n{pf(state)}\n\n'
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dispatch
|
|
44
|
+
def validate(core, schema: Maybe, state):
|
|
45
|
+
if state is not None:
|
|
46
|
+
return validate(core, schema._value, state)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dispatch
|
|
50
|
+
def validate(core, schema: Wrap, state):
|
|
51
|
+
return validate(core, schema._value, state)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dispatch
|
|
55
|
+
def validate(core, schema: Union, state):
|
|
56
|
+
for option in schema._options:
|
|
57
|
+
result = validate(core, option, state)
|
|
58
|
+
if not result:
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
return f'Union values did not match state:\n\n{pf(render(schema))}\n\n{pf(state)}\n\n'
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def filter_nones(results):
|
|
65
|
+
return [
|
|
66
|
+
result
|
|
67
|
+
for result in results
|
|
68
|
+
if result is not None]
|
|
69
|
+
|
|
70
|
+
@dispatch
|
|
71
|
+
def validate(core, schema: Tuple, state):
|
|
72
|
+
if not isinstance(state, (list, tuple)):
|
|
73
|
+
return f'Tuple schema requires tuple values:\n\n{pf(render(schema))}\n\nnot:\n\n{pf(state)}\n\n'
|
|
74
|
+
|
|
75
|
+
elif len(schema._values) == len(state):
|
|
76
|
+
results = filter_nones([
|
|
77
|
+
validate(core, subschema, value)
|
|
78
|
+
for subschema, value in zip(schema._values, state)])
|
|
79
|
+
|
|
80
|
+
if results:
|
|
81
|
+
return results
|
|
82
|
+
|
|
83
|
+
else:
|
|
84
|
+
return f'Tuple schema and state are different lengths:\n\n{pf(render(schema))}\n\n{pf(state)}\n\n'
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dispatch
|
|
88
|
+
def validate(core, schema: Boolean, state):
|
|
89
|
+
if not isinstance(state, bool):
|
|
90
|
+
return f'Boolean schema but state is not boolean:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dispatch
|
|
94
|
+
def validate(core, schema: Integer, state):
|
|
95
|
+
if not isinstance(state, int):
|
|
96
|
+
return f'Integer schema but state is not an integer:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dispatch
|
|
100
|
+
def validate(core, schema: Float, state):
|
|
101
|
+
if not isinstance(state, float):
|
|
102
|
+
return f'Float schema but state is not a float:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dispatch
|
|
106
|
+
def validate(core, schema: Nonnegative, state):
|
|
107
|
+
if state < 0:
|
|
108
|
+
return f'Nonnegative schema but state is negative:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dispatch
|
|
112
|
+
def validate(core, schema: String, state):
|
|
113
|
+
if not isinstance(state, str):
|
|
114
|
+
return f'Float schema but state is not a float:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dispatch
|
|
118
|
+
def validate(core, schema: Enum, state):
|
|
119
|
+
if not isinstance(state, str):
|
|
120
|
+
return f'Enum schema but state is not a string:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
121
|
+
|
|
122
|
+
if not state in schema._values:
|
|
123
|
+
return f'Enum schema but state is not in the enumeration:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@dispatch
|
|
127
|
+
def validate(core, schema: List, state):
|
|
128
|
+
if not isinstance(state, (list, tuple)):
|
|
129
|
+
return f'List schema but state is not a list:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
130
|
+
|
|
131
|
+
results = filter_nones([
|
|
132
|
+
validate(core, schema._element, element)
|
|
133
|
+
for element in state])
|
|
134
|
+
|
|
135
|
+
if results:
|
|
136
|
+
return results
|
|
137
|
+
|
|
138
|
+
@dispatch
|
|
139
|
+
def validate(core, schema: Map, state):
|
|
140
|
+
if not isinstance(state, dict):
|
|
141
|
+
return f'Map schema but state is not a map:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
142
|
+
|
|
143
|
+
results = filter_nones([
|
|
144
|
+
validate(core, schema._value, value)
|
|
145
|
+
for value in state.values()])
|
|
146
|
+
|
|
147
|
+
if results:
|
|
148
|
+
return results
|
|
149
|
+
|
|
150
|
+
elif not isinstance(schema._key, String):
|
|
151
|
+
results = filter_nones([
|
|
152
|
+
validate(
|
|
153
|
+
core,
|
|
154
|
+
schema._key,
|
|
155
|
+
realize(
|
|
156
|
+
core,
|
|
157
|
+
schema._key,
|
|
158
|
+
key))
|
|
159
|
+
for key in state.keys()])
|
|
160
|
+
|
|
161
|
+
if results:
|
|
162
|
+
return results
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dispatch
|
|
166
|
+
def validate(core, schema: Tree, state):
|
|
167
|
+
leaf_validate = validate(core, schema._leaf, state)
|
|
168
|
+
|
|
169
|
+
if leaf_validate:
|
|
170
|
+
if isinstance(state, dict):
|
|
171
|
+
results = filter_nones([
|
|
172
|
+
validate(core, schema, branch)
|
|
173
|
+
for key, branch in state.items()])
|
|
174
|
+
if results:
|
|
175
|
+
return f'Tree schema but state matches neither leaf nor tree:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
176
|
+
|
|
177
|
+
else:
|
|
178
|
+
return f'Tree schema but state matches neither leaf nor tree:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@dispatch
|
|
182
|
+
def validate(core, schema: Array, state):
|
|
183
|
+
if not isinstance(state, np.ndarray):
|
|
184
|
+
return f'Array schema but state is not an array:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
185
|
+
|
|
186
|
+
shape_match = tuple(schema._shape) == state.shape
|
|
187
|
+
data_match = schema._data == state.dtype
|
|
188
|
+
|
|
189
|
+
if not shape_match:
|
|
190
|
+
return f'Array schema but shape does not match:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
191
|
+
if not data_match:
|
|
192
|
+
return f'Array schema but data does not match:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@dispatch
|
|
196
|
+
def validate(core, schema: Key, state):
|
|
197
|
+
if not isinstance(state, int) or isinstance(state, str):
|
|
198
|
+
return f'Key schema but state is not a key:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@dispatch
|
|
202
|
+
def validate(core, schema: Node, state):
|
|
203
|
+
fields = [
|
|
204
|
+
field
|
|
205
|
+
for field in schema.__dataclass_fields__
|
|
206
|
+
if not field.startswith('_')]
|
|
207
|
+
|
|
208
|
+
if fields:
|
|
209
|
+
if isinstance(state, dict):
|
|
210
|
+
result = {}
|
|
211
|
+
for key in schema.__dataclass_fields__:
|
|
212
|
+
if not key.startswith('_'):
|
|
213
|
+
if key not in state:
|
|
214
|
+
return f'Node schema but key "{key}" is not in state:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
215
|
+
else:
|
|
216
|
+
down = validate(
|
|
217
|
+
core,
|
|
218
|
+
getattr(schema, key),
|
|
219
|
+
state[key])
|
|
220
|
+
|
|
221
|
+
if down:
|
|
222
|
+
result[key] = down
|
|
223
|
+
if result:
|
|
224
|
+
return result
|
|
225
|
+
else:
|
|
226
|
+
subcheck = check(schema, state)
|
|
227
|
+
if not subcheck:
|
|
228
|
+
return f'Node schema but state does not match:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@dispatch
|
|
232
|
+
def validate(core, schema: dict, state):
|
|
233
|
+
result = {}
|
|
234
|
+
for key, subschema in schema.items():
|
|
235
|
+
if key not in state:
|
|
236
|
+
continue
|
|
237
|
+
# result[key] = f'Schema has key "{key}" but state does not:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
238
|
+
else:
|
|
239
|
+
subresult = validate(core, subschema, state[key])
|
|
240
|
+
if subresult:
|
|
241
|
+
result[key] = subresult
|
|
242
|
+
|
|
243
|
+
if result:
|
|
244
|
+
return result
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@dispatch
|
|
248
|
+
def validate(core, schema, state):
|
|
249
|
+
return f'Schema and state are not known:\n\nschema: {pf(render(schema))}\n\nstate: {pf(state)}\n\n'
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from bigraph_schema.package.discover import discover_packages
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import importlib.metadata
|
|
3
|
+
import pkgutil
|
|
4
|
+
import inspect
|
|
5
|
+
from typing import Dict, List, Tuple, Set, Type
|
|
6
|
+
|
|
7
|
+
from bigraph_schema import Edge
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def find_edges(mapping, module_name=None):
|
|
11
|
+
discovered = []
|
|
12
|
+
for _, cls in mapping:
|
|
13
|
+
# Only classes defined in this module (not imported into it)
|
|
14
|
+
if not inspect.isclass(cls):
|
|
15
|
+
continue
|
|
16
|
+
|
|
17
|
+
if module_name and cls.__module__ != module_name:
|
|
18
|
+
continue
|
|
19
|
+
|
|
20
|
+
if not issubclass(cls, Edge) or cls is Edge:
|
|
21
|
+
continue
|
|
22
|
+
|
|
23
|
+
# Use the true module path for a stable registration key
|
|
24
|
+
fq_name = f"{cls.__module__}.{cls.__name__}"
|
|
25
|
+
discovered.append((fq_name, cls))
|
|
26
|
+
|
|
27
|
+
return discovered
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def recursive_dynamic_import(
|
|
31
|
+
core,
|
|
32
|
+
module,
|
|
33
|
+
visited: Set[str] | None = None,
|
|
34
|
+
) -> tuple[object, List[tuple[str, Type[Edge]]]]:
|
|
35
|
+
if visited is None:
|
|
36
|
+
visited = set()
|
|
37
|
+
|
|
38
|
+
if inspect.ismodule(module):
|
|
39
|
+
adjusted = module.__name__
|
|
40
|
+
if adjusted in visited:
|
|
41
|
+
return core, [], visited
|
|
42
|
+
|
|
43
|
+
visited.add(adjusted)
|
|
44
|
+
|
|
45
|
+
if isinstance(module, str):
|
|
46
|
+
adjusted = core.distributions_packages.get(module, module)
|
|
47
|
+
if adjusted in visited:
|
|
48
|
+
return core, [], visited
|
|
49
|
+
visited.add(adjusted)
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
module = importlib.import_module(adjusted)
|
|
53
|
+
|
|
54
|
+
except ModuleNotFoundError as e:
|
|
55
|
+
# e.name is the missing module name
|
|
56
|
+
# If the missing name IS the module we tried to import, then it's truly not found.
|
|
57
|
+
# Otherwise, it's a dependency import failure inside that module.
|
|
58
|
+
print(f"module `{adjusted}` not found during dynamic import")
|
|
59
|
+
|
|
60
|
+
# Allow module to register types into core
|
|
61
|
+
if hasattr(module, "register_types"):
|
|
62
|
+
core = module.register_types(core)
|
|
63
|
+
|
|
64
|
+
mapping = inspect.getmembers(module, inspect.isclass)
|
|
65
|
+
discovered = find_edges(mapping)
|
|
66
|
+
|
|
67
|
+
# Recurse into submodules if this is a package
|
|
68
|
+
if hasattr(module, "__path__"):
|
|
69
|
+
for _, subname, _ in pkgutil.iter_modules(module.__path__):
|
|
70
|
+
submod = f"{adjusted}.{subname}"
|
|
71
|
+
core, sub_discovered, visited = recursive_dynamic_import(
|
|
72
|
+
core, submod, visited=visited)
|
|
73
|
+
discovered.extend(sub_discovered)
|
|
74
|
+
|
|
75
|
+
return core, discovered, visited
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def is_process_library(dist: importlib.metadata.Distribution) -> bool:
|
|
79
|
+
if dist.metadata["Name"] == "bigraph-schema":
|
|
80
|
+
return True
|
|
81
|
+
reqs = dist.requires or []
|
|
82
|
+
return any("bigraph-schema" in r for r in reqs)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def load_local_modules(core, top=None) -> tuple[object, List[tuple[str, Type[Edge]]]]:
|
|
86
|
+
processes = []
|
|
87
|
+
visited = set([])
|
|
88
|
+
|
|
89
|
+
for dist_name in core.distributions_packages:
|
|
90
|
+
dist = importlib.metadata.distribution(dist_name)
|
|
91
|
+
if not is_process_library(dist):
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
core, found, visited = recursive_dynamic_import(
|
|
95
|
+
core,
|
|
96
|
+
dist_name,
|
|
97
|
+
visited=visited)
|
|
98
|
+
|
|
99
|
+
processes.extend(found)
|
|
100
|
+
|
|
101
|
+
if top:
|
|
102
|
+
for key, value in top.items():
|
|
103
|
+
if inspect.isclass(value) and issubclass(value, Edge):
|
|
104
|
+
processes.append((key, value))
|
|
105
|
+
|
|
106
|
+
if key == 'register_types':
|
|
107
|
+
core = value(core)
|
|
108
|
+
|
|
109
|
+
return core, processes
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def discover_packages(core, top=None):
|
|
113
|
+
core, discovered = load_local_modules(core, top=top)
|
|
114
|
+
|
|
115
|
+
for fq_name, edge_cls in discovered:
|
|
116
|
+
core.register_link(fq_name, edge_cls)
|
|
117
|
+
|
|
118
|
+
short = fq_name.split(".")[-1]
|
|
119
|
+
if short not in core.link_registry:
|
|
120
|
+
core.register_link(short, edge_cls)
|
|
121
|
+
|
|
122
|
+
return core
|
bigraph_schema/parse.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"""
|
|
2
|
+
======================
|
|
3
|
+
Parse Bigraph Notation
|
|
4
|
+
======================
|
|
5
|
+
|
|
6
|
+
Parses and renders bigraph-style type expressions used in schema definitions,
|
|
7
|
+
such as parameterized types, nested trees, merges (`|`), and unions (`~`).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from parsimonious.grammar import Grammar
|
|
11
|
+
from parsimonious.nodes import NodeVisitor
|
|
12
|
+
|
|
13
|
+
# --- Example Expressions -----------------------------------------------------
|
|
14
|
+
parameter_examples = {
|
|
15
|
+
'no-parameters': 'simple',
|
|
16
|
+
'one-parameter': 'parameterized[A]',
|
|
17
|
+
'three-parameters': 'parameterized[A,B,C]',
|
|
18
|
+
'nested-parameters': 'nested[outer[inner]]',
|
|
19
|
+
'multiple-nested-parameters': 'nested[outer[inner],other,later[on,there[is],more]]',
|
|
20
|
+
'bars': 'a|b|c|zzzz',
|
|
21
|
+
'union': 'a~b~c~zzzz',
|
|
22
|
+
'typed': 'a:field[yellow,tree,snake]|b:(x:earth|y:cloud|z:sky)',
|
|
23
|
+
'typed_parameters': 'edge[a:int|b:(x:length|y:float),v[zz:float|xx:what]]',
|
|
24
|
+
'inputs_and_outputs': 'edge[input1:float|input2:int,output1:float|output2:int]',
|
|
25
|
+
'tuple': 'what[is,happening|(with:yellow|this:green)|this:now]',
|
|
26
|
+
'single': 'hello[(3),over]',
|
|
27
|
+
'double': 'hello[(3|4),over{owiehf;a832hf9237fh!@#(&$A(HFO@I#}]',
|
|
28
|
+
'double_default': 'hello[(3|4),over]{3,3,3,3,3}',
|
|
29
|
+
'units_type': 'length^2*mass/time^1_5',
|
|
30
|
+
'nothing': '()'}
|
|
31
|
+
|
|
32
|
+
# --- Grammar -----------------------------------------------------------------
|
|
33
|
+
parameter_grammar = Grammar(
|
|
34
|
+
"""
|
|
35
|
+
expression = merge / union / tree / nothing
|
|
36
|
+
merge = tree (bar tree)+
|
|
37
|
+
union = tree (tilde tree)+
|
|
38
|
+
tree = bigraph / type_name
|
|
39
|
+
bigraph = group / nest
|
|
40
|
+
group = paren_left expression paren_right
|
|
41
|
+
nest = symbol colon tree
|
|
42
|
+
type_name = symbol parameter_list? default_block?
|
|
43
|
+
parameter_list = square_left expression (comma expression)* square_right
|
|
44
|
+
default_block = curly_left default curly_right
|
|
45
|
+
default = ~r"[^}]*"
|
|
46
|
+
symbol = ~r"[\\w\\d-_/<>*&^%$#@!`+ ]+"
|
|
47
|
+
dot = "."
|
|
48
|
+
colon = ":"
|
|
49
|
+
bar = "|"
|
|
50
|
+
paren_left = "("
|
|
51
|
+
paren_right = ")"
|
|
52
|
+
square_left = "["
|
|
53
|
+
square_right = "]"
|
|
54
|
+
curly_left = "{"
|
|
55
|
+
curly_right = "}"
|
|
56
|
+
comma = ","
|
|
57
|
+
tilde = "~"
|
|
58
|
+
not_newline = ~r"[^\\n\\r]"*
|
|
59
|
+
newline = ~"[\\n\\r]+"
|
|
60
|
+
ws = ~r"\\s*"
|
|
61
|
+
nothing = ""
|
|
62
|
+
""")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# --- Visitor -----------------------------------------------------------------
|
|
66
|
+
class ParameterVisitor(NodeVisitor):
|
|
67
|
+
"""Visitor that walks a parsed tree and builds structured type expressions."""
|
|
68
|
+
|
|
69
|
+
def visit_expression(self, node, visit):
|
|
70
|
+
return visit[0]
|
|
71
|
+
|
|
72
|
+
def visit_union(self, node, visit):
|
|
73
|
+
head = [visit[0]]
|
|
74
|
+
tail = [tree['visit'][1] for tree in visit[1]['visit']]
|
|
75
|
+
return {'_union': head + tail}
|
|
76
|
+
|
|
77
|
+
def visit_merge(self, node, visit):
|
|
78
|
+
head = [visit[0]]
|
|
79
|
+
tail = [tree['visit'][1] for tree in visit[1]['visit']]
|
|
80
|
+
nodes = head + tail
|
|
81
|
+
|
|
82
|
+
if all(isinstance(tree, dict) for tree in nodes):
|
|
83
|
+
merged = {}
|
|
84
|
+
for tree in nodes:
|
|
85
|
+
merged.update(tree)
|
|
86
|
+
return merged
|
|
87
|
+
else:
|
|
88
|
+
return tuple(nodes)
|
|
89
|
+
|
|
90
|
+
def visit_tree(self, node, visit):
|
|
91
|
+
return visit[0]
|
|
92
|
+
|
|
93
|
+
def visit_bigraph(self, node, visit):
|
|
94
|
+
return visit[0]
|
|
95
|
+
|
|
96
|
+
def visit_group(self, node, visit):
|
|
97
|
+
group_value = visit[1]
|
|
98
|
+
return group_value if isinstance(group_value, (list, tuple, dict)) else (group_value,)
|
|
99
|
+
|
|
100
|
+
def visit_nest(self, node, visit):
|
|
101
|
+
return {visit[0]: visit[2]}
|
|
102
|
+
|
|
103
|
+
def visit_type_name(self, node, visit):
|
|
104
|
+
type_name = visit[0]
|
|
105
|
+
type_parameters = visit[1]['visit']
|
|
106
|
+
if type_parameters:
|
|
107
|
+
return [type_name, type_parameters[0]]
|
|
108
|
+
return type_name
|
|
109
|
+
|
|
110
|
+
def visit_parameter_list(self, node, visit):
|
|
111
|
+
first = [visit[1]]
|
|
112
|
+
rest = [inner['visit'][1] for inner in visit[2]['visit']]
|
|
113
|
+
return first + rest
|
|
114
|
+
|
|
115
|
+
def visit_symbol(self, node, visit):
|
|
116
|
+
return node.text
|
|
117
|
+
|
|
118
|
+
def visit_nothing(self, node, visit):
|
|
119
|
+
return {}
|
|
120
|
+
|
|
121
|
+
def generic_visit(self, node, visit):
|
|
122
|
+
return {'node': node, 'visit': visit}
|
|
123
|
+
|
|
124
|
+
# --- API ---------------------------------------------------------------------
|
|
125
|
+
def parsed_leftmost_leaf(parsed):
|
|
126
|
+
if len(parsed.children) == 0:
|
|
127
|
+
return parsed
|
|
128
|
+
else:
|
|
129
|
+
return parsed_leftmost_leaf(parsed.children[0])
|
|
130
|
+
|
|
131
|
+
def visit_expression(expression, visitor):
|
|
132
|
+
parsed = parameter_grammar.parse(expression)
|
|
133
|
+
leaf = parsed_leftmost_leaf(parsed)
|
|
134
|
+
if hasattr(leaf, 'match') and leaf.match[0] == expression:
|
|
135
|
+
return expression
|
|
136
|
+
else:
|
|
137
|
+
return visitor.visit(parsed)
|
|
138
|
+
|
|
139
|
+
def parse_expression(expression):
|
|
140
|
+
"""
|
|
141
|
+
Parse a bigraph-style type expression into a structured Python object.
|
|
142
|
+
"""
|
|
143
|
+
visitor = ParameterVisitor()
|
|
144
|
+
return visit_expression(expression, visitor)
|
|
145
|
+
|
|
146
|
+
def is_type_expression(expression):
|
|
147
|
+
"""
|
|
148
|
+
Return True if the expression is a parameterized type list.
|
|
149
|
+
"""
|
|
150
|
+
return isinstance(expression, list) and len(expression) == 2 and isinstance(expression[1], list)
|
|
151
|
+
|
|
152
|
+
def render_expression(expression):
|
|
153
|
+
"""
|
|
154
|
+
Render a structured type expression back into a bigraph-style string.
|
|
155
|
+
"""
|
|
156
|
+
if isinstance(expression, str):
|
|
157
|
+
return expression
|
|
158
|
+
elif isinstance(expression, list):
|
|
159
|
+
type_name, parameters = expression
|
|
160
|
+
inner = ','.join(render_expression(p) for p in parameters)
|
|
161
|
+
return f'{type_name}[{inner}]'
|
|
162
|
+
elif isinstance(expression, tuple):
|
|
163
|
+
return '(' + '|'.join(render_expression(e) for e in expression) + ')'
|
|
164
|
+
elif isinstance(expression, dict):
|
|
165
|
+
if '_union' in expression:
|
|
166
|
+
return '~'.join(render_expression(p) for p in expression['_union'])
|
|
167
|
+
else:
|
|
168
|
+
parts = [f'{k}:{render_expression(v)}' for k, v in expression.items()]
|
|
169
|
+
return f'({ "|".join(parts) })'
|
|
170
|
+
return str(expression) # fallback for unknown structures
|
|
171
|
+
|
|
172
|
+
# --- Debugging/Testing ---------------------------------------------------------
|
|
173
|
+
def test_parse_parameters():
|
|
174
|
+
"""Test parsing and rendering for all example expressions."""
|
|
175
|
+
for label, expr in parameter_examples.items():
|
|
176
|
+
parsed = parse_expression(expr)
|
|
177
|
+
print(f'{label}: {expr}')
|
|
178
|
+
if parsed:
|
|
179
|
+
print(f' Parsed: {parsed}')
|
|
180
|
+
print(f' Rendered: {render_expression(parsed)}')
|
|
181
|
+
|
|
182
|
+
if __name__ == '__main__':
|
|
183
|
+
test_parse_parameters()
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
=========
|
|
3
|
+
Protocols
|
|
4
|
+
=========
|
|
5
|
+
|
|
6
|
+
This module contains the protocols for retrieving processes from address.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
import inspect
|
|
11
|
+
import importlib
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def function_module(function):
|
|
15
|
+
"""
|
|
16
|
+
Retrieves the fully qualified name of a given function.
|
|
17
|
+
"""
|
|
18
|
+
module = inspect.getmodule(function)
|
|
19
|
+
|
|
20
|
+
return f'{module.__name__}.{function.__name__}'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def local_lookup_module(address):
|
|
24
|
+
"""Local Module Protocol
|
|
25
|
+
|
|
26
|
+
Retrieves local module
|
|
27
|
+
"""
|
|
28
|
+
if '.' in address:
|
|
29
|
+
module_name, class_name = address.rsplit('.', 1)
|
|
30
|
+
module = importlib.import_module(module_name)
|
|
31
|
+
return getattr(module, class_name)
|
|
32
|
+
else:
|
|
33
|
+
module = sys.modules[__name__]
|
|
34
|
+
if hasattr(module, address):
|
|
35
|
+
return getattr(sys.modules[__name__], address)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def local_lookup_registry(core, address):
|
|
39
|
+
"""Process Registry Protocol
|
|
40
|
+
|
|
41
|
+
Retrieves from the process registry
|
|
42
|
+
"""
|
|
43
|
+
return core.link_registry.get(address)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def local_lookup(core, address):
|
|
47
|
+
"""Local Lookup Protocol
|
|
48
|
+
|
|
49
|
+
Retrieves local processes, from the process registry or from a local module
|
|
50
|
+
"""
|
|
51
|
+
if address[0] == '!':
|
|
52
|
+
instantiate = local_lookup_module(address[1:])
|
|
53
|
+
else:
|
|
54
|
+
instantiate = local_lookup_registry(core, address)
|
|
55
|
+
return instantiate
|
|
56
|
+
|
|
57
|
+
|