wirely 0.1.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.
Files changed (59) hide show
  1. wirely/__init__.py +406 -0
  2. wirely/__main__.py +45 -0
  3. wirely/ast.py +1135 -0
  4. wirely/block.py +542 -0
  5. wirely/block_type.py +342 -0
  6. wirely/blocks.lark +126 -0
  7. wirely/compiler.py +125 -0
  8. wirely/engine.py +418 -0
  9. wirely/enum.py +15 -0
  10. wirely/example.py +36 -0
  11. wirely/graphviz.py +107 -0
  12. wirely/knx/__init__.py +0 -0
  13. wirely/knx/connection_adapter.py +90 -0
  14. wirely/knx/connection_config.py +152 -0
  15. wirely/lib/abb_jra.wrl +64 -0
  16. wirely/lib/csv.wrl +32 -0
  17. wirely/lib/ext_prog.wrl +12 -0
  18. wirely/lib/gpio.wrl +23 -0
  19. wirely/lib/knx.wrl +80 -0
  20. wirely/lib/memory.wrl +45 -0
  21. wirely/lib/mqtt.wrl +66 -0
  22. wirely/lib/std.wrl +134 -0
  23. wirely/lib/time.wrl +93 -0
  24. wirely/lib/two_n.wrl +111 -0
  25. wirely/misc/__init__.py +0 -0
  26. wirely/misc/service.py +48 -0
  27. wirely/native/__init__.py +74 -0
  28. wirely/native/abb_jra.py +66 -0
  29. wirely/native/csv.py +194 -0
  30. wirely/native/ext_prog.py +77 -0
  31. wirely/native/gpio.py +125 -0
  32. wirely/native/knx.py +455 -0
  33. wirely/native/memory.py +113 -0
  34. wirely/native/mqtt.py +605 -0
  35. wirely/native/std.py +334 -0
  36. wirely/native/test_library.py +14 -0
  37. wirely/native/time.py +359 -0
  38. wirely/native/two_n.py +632 -0
  39. wirely/node.py +125 -0
  40. wirely/parser.py +175 -0
  41. wirely/program.py +155 -0
  42. wirely/run.py +143 -0
  43. wirely/service.py +65 -0
  44. wirely/test.py +569 -0
  45. wirely/time.py +172 -0
  46. wirely/two_n/__init__.py +0 -0
  47. wirely/two_n/cert.py +126 -0
  48. wirely/two_n/client.py +402 -0
  49. wirely/two_n/fake_server.py +415 -0
  50. wirely/two_n/grab_images.py +146 -0
  51. wirely/types.py +522 -0
  52. wirely/util.py +111 -0
  53. wirely/value.py +163 -0
  54. wirely-0.1.0.dist-info/METADATA +94 -0
  55. wirely-0.1.0.dist-info/RECORD +59 -0
  56. wirely-0.1.0.dist-info/WHEEL +5 -0
  57. wirely-0.1.0.dist-info/entry_points.txt +2 -0
  58. wirely-0.1.0.dist-info/licenses/LICENSE +21 -0
  59. wirely-0.1.0.dist-info/top_level.txt +1 -0
wirely/__init__.py ADDED
@@ -0,0 +1,406 @@
1
+ import threading
2
+ import abc
3
+ import contextlib
4
+ import datetime
5
+ import wirely as blocks
6
+
7
+ from .time import MonotonicTime, Duration
8
+ from . import enum, types
9
+ from .types import Type, TypeParameter
10
+ from .value import Value, ConstantValue, ValueParameter
11
+
12
+ from .util import check_parameters
13
+
14
+ # TODO: Rename graph to block_type everywhere
15
+
16
+ from .node import Node, NodeInstance, BlockInput, BlockOutput, BlockTemporaryVariable, BlockStateVariable
17
+
18
+ from .block import Block, BlockInstance
19
+
20
+ def connect(source, target, source_element):
21
+ # TODO: Clean up
22
+ if not isinstance(source, Block.Output) and not isinstance(source, blocks.BlockInput) and not isinstance(source, blocks.BlockStateVariable) and not isinstance(source, blocks.BlockTemporaryVariable):
23
+ raise Exception('source parameter is not a Block.Output, BlockInput, BlockStateVariable, BlockTemporaryVariable')
24
+ # TODO: Clean up
25
+ if not isinstance(target, Block.Input) and not isinstance(target, blocks.BlockOutput):
26
+ raise Exception('target parameter is not a Block.Input and not a BlockOutput')
27
+
28
+ if source.block.graph != target.block.graph:
29
+ # TODO: Can this happen from program code? If yes, make error message clearer?
30
+ raise blocks.ProgramError(source_element, f'source has graph {source.block.graph} but target has graph {target.block.graph}')
31
+
32
+ # TODO: More checks for non-ConnectorSpecificationData objects, e.g. KNX connections
33
+ source_kind = None
34
+ if isinstance(source, blocks.BlockStateVariable):
35
+ source_kind = blocks.ConnectionKind.Value
36
+ elif isinstance(source, blocks.BlockTemporaryVariable):
37
+ source_kind = blocks.ConnectionKind.Value
38
+ elif isinstance(source.conn_spec, blocks.ConnectorSpecificationData):
39
+ source_kind = source.conn_spec.kind_spec.kind
40
+ target_kind = None
41
+ if isinstance(target.conn_spec, blocks.ConnectorSpecificationData):
42
+ target_kind = target.conn_spec.kind_spec.kind
43
+ # print(source_kind, target_kind)
44
+ if source_kind != target_kind:
45
+ hints = [
46
+ # TODO: Does this always work?
47
+ blocks.CompilerHint(source.block_output.conn_spec, 'Source was declared here'),
48
+ blocks.CompilerHint(target.block_input.conn_spec, 'Target was declared here'),
49
+ ]
50
+ raise ProgramError(source_element, f'source has kind {source_kind} but target has kind {target_kind}', hints=hints)
51
+
52
+ # TODO: Allow automatic type conversions?
53
+ # TODO: Raise this error only when compiling?
54
+ source_type = source.var_type
55
+ if isinstance(source.block, blocks.Block) and source_type is not None:
56
+ source_type = source_type.resolve_self(source.block.par_values)
57
+ target_type = target.var_type
58
+ if isinstance(target.block, blocks.Block) and target_type is not None:
59
+ target_type = target_type.resolve_self(target.block.par_values)
60
+ # TODO: Handle knx_bus connections better than just setting the type to None
61
+ if source_type != target_type:
62
+ hints = [
63
+ # TODO: Does this always work?
64
+ # TODO: Point to type, not to conn_spec
65
+ blocks.CompilerHint(source.block_output.conn_spec, 'Source was declared here'),
66
+ blocks.CompilerHint(target.block_input.conn_spec, 'Target was declared here'),
67
+ ]
68
+ raise ProgramError(source_element, f'source has type {source_type} but target has type {target_type}', hints=hints)
69
+
70
+ target.connect(source)
71
+
72
+
73
+ debug_variable_resolution = False
74
+
75
+
76
+ # TODO: Create "BlockInstance" class to replace the "path" values everywhere? Should there be exactly one such instance or can there be multiple BlockInstances with the same path?
77
+
78
+ def path_resolve_singleton(path):
79
+ if isinstance(path[-1], blocks.Block) and path[-1].block_type.is_singleton:
80
+ return (path[-1].block_type,)
81
+ else:
82
+ return path
83
+
84
+
85
+ # Return the actual source for var in the context path
86
+ def resolve_variable(path, var):
87
+ if debug_variable_resolution:
88
+ print(f'resolve_variable({path}, {var})')
89
+
90
+ if isinstance(path[-1], blocks.Block) and path[-1].block_type.is_singleton:
91
+ raise Exception('isinstance(path[-1], blocks.Blocks) and path[-1].block_type.is_singleton')
92
+
93
+ if isinstance(path[-1], Block): # TODO: Handle singletons here?
94
+ # TODO: Clean this up?
95
+ # print(path, var)
96
+ if path[-1] == var.block:
97
+ block = var.block
98
+ elif var.block.graph == path[-1].block_type:
99
+ # Dependency on the result of a child block
100
+ # TODO: Make this more generic
101
+ # TODO: Modify path here?
102
+ path = path_resolve_singleton(path + (var.block,))
103
+ block = var.block
104
+ else:
105
+ raise Exception('path[-1] != var.block')
106
+ # print(type(var), type(var.block))
107
+
108
+ # TODO: This is somewhat of a hack
109
+ if (isinstance(var, blocks.Block.Output) or isinstance(var, blocks.Block.Temporary)) and isinstance(block, blocks.BlockInput):
110
+ input_name = block.input_name
111
+ new_path = path[:-1]
112
+ parent_block = new_path[-1]
113
+ input_var = parent_block.block_inputs_byname[input_name]
114
+ return resolve_variable(path_resolve_singleton(new_path), input_var)
115
+
116
+ # TODO: This is somewhat of a hack
117
+ if (isinstance(var, blocks.Block.Input)) and isinstance(block, blocks.BlockOutput):
118
+ output_name = block.output_name
119
+ new_path = path[:-1]
120
+ parent_block = new_path[-1]
121
+ output_var = parent_block.block_outputs_byname[output_name]
122
+ return resolve_variable(path_resolve_singleton(new_path), output_var)
123
+
124
+ if isinstance(var, blocks.Block.Input):
125
+ src = var.connected_source
126
+ if src is None:
127
+ raise Exception('src is None')
128
+ new_path = path[:-1] + (src.block,)
129
+ # print(src, type(src), src.block, new_path)
130
+ return resolve_variable(path_resolve_singleton(new_path), src)
131
+
132
+ if isinstance(var, blocks.Block.Output):
133
+ # TODO: Handle this differently? This is e.g. for Bus objects
134
+ if var.var_type is None:
135
+ return path, var
136
+ # Normal output
137
+ # TODO: Hack, should use var.temporary, but that is overwritten somewhere in some cases
138
+ temporary = var.orig_temporary
139
+ if temporary.block != path[-1]:
140
+ if isinstance(path[-1], blocks.Block): # TODO: Handle singletons here?
141
+ raise Exception(f'temporary.block {temporary.block} != path[-1] {path[-1]}')
142
+ if temporary.producer is None:
143
+ raise Exception(f'Temporary for output {var} does not have a producer')
144
+ if debug_variable_resolution:
145
+ print(f'resolve_variable: returning {path}, {temporary}')
146
+ return path, temporary
147
+
148
+ if isinstance(var, blocks.Block.Temporary):
149
+ temporary = var.temporary
150
+ if temporary != var:
151
+ raise Exception('temporary != var')
152
+ if temporary.block != path[-1]:
153
+ if isinstance(path[-1], blocks.Block): # TODO: Handle singletons here?
154
+ raise Exception(f'temporary.block {temporary.block} != path[-1] {path[-1]}')
155
+ if temporary.producer is None:
156
+ raise Exception('temporary.producer is None')
157
+ if debug_variable_resolution:
158
+ print(f'resolve_variable: returning {path}, {temporary}')
159
+ return path, temporary
160
+
161
+ if isinstance(var, blocks.BlockTemporaryVariable):
162
+ # TODO: Clean up
163
+ if path[-1] != var:
164
+ raise Exception('path[-1] != var')
165
+ block = path[-2]
166
+ var_inst = None
167
+ for t in block.block_temporaries:
168
+ if t.name == var.name:
169
+ var_inst = t
170
+ break
171
+ if var_inst is None:
172
+ raise Exception(f'Cannot find temporary {var.name!r}')
173
+ temporary = var_inst.temporary
174
+ if temporary != var_inst:
175
+ raise Exception('temporary != var_inst')
176
+ if temporary.block != path[-2]:
177
+ if isinstance(path[-2], blocks.Block): # TODO: Handle singletons here?
178
+ raise Exception(f'temporary.block {temporary.block} != path[-1] {path[-1]}')
179
+ if temporary.producer is None:
180
+ raise Exception('temporary.producer is None')
181
+ if debug_variable_resolution:
182
+ print(f'resolve_variable: returning {path[:-1]}, {temporary}')
183
+ return path[:-1], temporary
184
+
185
+ # TODO: Clean up
186
+ if isinstance(var, blocks.BlockInput):
187
+ if path[-1] != var:
188
+ raise Exception('path[-1] != var')
189
+ input_name = var.input_name
190
+ new_path = path[:-1]
191
+ parent_block = new_path[-1]
192
+ input_var = parent_block.block_inputs_byname[input_name]
193
+ return resolve_variable(path_resolve_singleton(new_path), input_var)
194
+
195
+ # TODO: Clean up
196
+ if isinstance(var, blocks.BlockStateVariable):
197
+ if path[-1] != var:
198
+ raise Exception('path[-1] != var')
199
+ # TODO: Implement this
200
+ return path, var
201
+
202
+ print(f'resolve_variable: Unhandled {path}, {var} {type(var)} {type(var.block)}')
203
+
204
+ if debug_variable_resolution:
205
+ print(f'resolve_variable: returning {path}, {var.temporary}')
206
+ # TODO: This code seems to be unused
207
+ # TODO: Avoid using .temporary (should be handled here)
208
+ return path, var.temporary
209
+
210
+
211
+ # TODO: Rename to NativeBlockState?
212
+ class NativeState:
213
+ def __init__(self, path, engine, static_block_context):
214
+ self.block_path = path
215
+ self.engine = engine
216
+ self.static_block_context = static_block_context
217
+
218
+ @property
219
+ def block_type(self):
220
+ value = self.block_path[-1]
221
+ if isinstance(value, blocks.Block):
222
+ return value.block_type
223
+ else:
224
+ return value
225
+
226
+ @property
227
+ def block(self):
228
+ value = self.block_path[-1]
229
+ if isinstance(value, blocks.Block):
230
+ return value
231
+ else:
232
+ raise Exception(f'Trying to get block for a singleton block type ({value})')
233
+
234
+ # TODO: Remove, this is a hack
235
+ @property
236
+ def block_or_singleton(self):
237
+ value = self.block_path[-1]
238
+ if isinstance(value, blocks.Block):
239
+ return value
240
+ else:
241
+ return value.singleton_instance
242
+
243
+ def start_tasks(self, task_group, async_exit_stack: contextlib.AsyncExitStack):
244
+ pass
245
+
246
+ # TODO: Merge start_tasks and start_tasks_async?
247
+ # TODO: Allow blocking behavior during startup?
248
+ # TODO: Use an AsyncExitStack or an ExitStack?
249
+ async def start_tasks_async(self, task_group, async_exit_stack: contextlib.AsyncExitStack):
250
+ pass
251
+
252
+ @staticmethod
253
+ @contextlib.contextmanager
254
+ def start_task_context(task_group, coroutine):
255
+ import wirely.misc.service
256
+
257
+ task = task_group.create_task(wirely.misc.service.should_never_return(coroutine))
258
+ try:
259
+ yield task
260
+ finally:
261
+ task.cancel()
262
+
263
+ @staticmethod
264
+ def start_task(task_group, async_exit_stack, coroutine):
265
+ return async_exit_stack.enter_context(NativeState.start_task_context(task_group, coroutine))
266
+
267
+
268
+ class Variable:
269
+ def __init__(self, name, var_type):
270
+ if not isinstance(name, str):
271
+ raise Exception('name parameter is not a str')
272
+ if var_type.contains_parameters:
273
+ raise Exception('var_type parameter is not a concrete type')
274
+
275
+ self.name = str(name)
276
+ self.var_type = var_type
277
+
278
+ def __repr__(self):
279
+ return f'Variable[{self}]'
280
+
281
+ def __str__(self):
282
+ return f'{self.name}'
283
+
284
+
285
+ class VariableBlock:
286
+ def __init__(self, variables, name):
287
+ self.name = name
288
+
289
+ self.variables = {}
290
+ self.variable_set = set()
291
+
292
+ for var in variables:
293
+ if not isinstance(var, Variable):
294
+ raise Exception('var parameter contains object that is not a Variable')
295
+ name = var.name
296
+ if name in self.variables:
297
+ raise Exception(f'Duplicate variable name {name!r}')
298
+ self.variables[name] = var
299
+ self.variable_set.add(var)
300
+
301
+
302
+ class VariableBlockInstance:
303
+ def __init__(self, variable_block):
304
+ if not isinstance(variable_block, VariableBlock):
305
+ raise Exception('variable_block parameter is not a VariableBlock')
306
+ self.variable_block = variable_block
307
+
308
+ self.data = {}
309
+
310
+ def add(self, variable, value):
311
+ # print(f'Setting {variable}')
312
+ if variable not in self.variable_block.variable_set:
313
+ raise Exception(f'Variable {variable!r} not in VariableBlock {self.variable_block.name}')
314
+ if variable in self.data:
315
+ raise Exception(f'Variable {variable} already set')
316
+ self.data[variable] = variable.var_type.verify_value(value)
317
+
318
+ def __getitem__(self, variable):
319
+ # print(f'Getting {variable}')
320
+ if variable not in self.variable_block.variable_set:
321
+ raise Exception(f'Variable {variable!r} not in VariableBlock {self.variable_block.name}')
322
+ if variable not in self.data:
323
+ raise Exception(f'Variable {variable} not yet set')
324
+ return self.data[variable]
325
+
326
+
327
+ # Things which will stay the same for one block instance
328
+ # TODO: Prevent modifications, more checks
329
+ # TODO: Merge this into BlockInstance? Or should this data (which will be needed at runtime) be kept separately?
330
+ class StaticBlockContext:
331
+ def __init__(self, *, parameter_values, parsed_native_instance):
332
+ self.parameter_values = parameter_values
333
+ # TODO: Rename?
334
+ self.parsed_native_instance = parsed_native_instance
335
+
336
+ def __repr__(self):
337
+ return f'StaticBlockContext({self.__dict__!r})'
338
+
339
+
340
+ # Things which will stay the same if the same instance of a procedure is executed multiple times
341
+ # TODO: Prevent modifications, more checks
342
+ class StaticProcedureContext:
343
+ def __init__(self, *, sources, targets, state, name, block_parameter_values, block=None):
344
+ self.sources = sources
345
+ self.targets = targets
346
+ self.state = state
347
+ self.name = name
348
+ self.block = block
349
+ self.block_parameter_values = block_parameter_values
350
+
351
+ def __repr__(self):
352
+ return f'StaticProcedureContext({self.__dict__!r})'
353
+
354
+
355
+ # TODO: Prevent modifications, more checks (also e.g. on assignments to results etc.)
356
+ class ProcedureContext:
357
+ def __init__(self, *, dep_values, results, state_old, state_new, static, step_info):
358
+ self.dep_values = dep_values
359
+ self.results = results
360
+ self.state_old = state_old
361
+ self.state_new = state_new
362
+ self.static = static
363
+ self.step_info = step_info
364
+ self.wakeup_time = None
365
+
366
+ def __repr__(self):
367
+ return f'ProcedureContext({self.__dict__!r})'
368
+
369
+
370
+ # TODO: Rename to CycleContext?
371
+ class StepContext:
372
+ def __init__(self):
373
+ self.stage_start_time_monotonic = {}
374
+ self.stage_start_time_utc = {}
375
+ self.stage_start_time = {}
376
+
377
+ def start_stage(self, engine, stage):
378
+ if stage in self.stage_start_time:
379
+ raise Exception(f'Stage {stage} already started')
380
+
381
+ now_monotonic = blocks.MonotonicTime.current_time()
382
+ if engine.fake_monotonic_time_offset is not None:
383
+ now_monotonic += engine.fake_monotonic_time_offset
384
+ # print(f'StepContext for {stage} started at {now_monotonic}')
385
+
386
+ now_utc = datetime.datetime.now(datetime.timezone.utc)
387
+ # Note: This should work with all python versions, even without PEP 495
388
+ # <https://peps.python.org/pep-0495/>
389
+ # Starting with Python 3.6 (PEP 495)
390
+ # datetime.datetime.now().astimezone() should return the same value.
391
+ now = now_utc.astimezone()
392
+
393
+ self.stage_start_time_monotonic[stage] = now_monotonic
394
+ self.stage_start_time_utc[stage] = now_utc
395
+ self.stage_start_time[stage] = now
396
+
397
+ def __repr__(self):
398
+ return f'StepContext({self.__dict__!r})'
399
+
400
+
401
+ from wirely.program import Program
402
+ from wirely.engine import Engine
403
+
404
+ from .ast import ProgramError, InternalCompilerError, CompilerHint, SourcePosition, Declaration, BlockConnectorTypeSpecifier, ListSpecifier, ProgramError, Identifier, IntNumber, FloatNumber, StringConstant, Reference, Declaration, DeclarationConnector, DeclarationParameter, BlockInstantiation, DeclarationBlockInstantiation, DeclarationConnection, DeclarationStateVariable, DeclarationTemporaryVariable, DeclarationProcedure, NativeSpecifier, OptionalSpecifier, StageDeclaration, DependsDeclaration, ProducesDeclaration, ConnectionKindSpecifier, ConnectorSpecification, ConnectorSpecificationData, ConnectorSpecificationKnxBus, ConnectorSpecificationMqttServer, ConnectorSpecificationTwoNDevice, DeclarationLibrary, BlockParameter, Scope, BlockTypeSpecifier, ListLiteral, FunctionCall, BracketOperator, ConnectionKind, RunStage
405
+
406
+ from .block_type import BlockType
wirely/__main__.py ADDED
@@ -0,0 +1,45 @@
1
+ import sys
2
+ import argparse
3
+ import importlib
4
+ import asyncio
5
+
6
+
7
+ def main():
8
+ parser = argparse.ArgumentParser()
9
+ subparsers = parser.add_subparsers(title='subcommands', dest='command', required=True)
10
+
11
+ run_service_parser = subparsers.add_parser('run-service')
12
+ run_service_parser.add_argument('--verbose', action='store_true')
13
+ run_service_parser.add_argument('--debug', action='store_true')
14
+ run_service_parser.add_argument('input_filename', type=str, nargs='+')
15
+ run_service_parser.set_defaults(module_name='wirely.service')
16
+
17
+ compile_parser = subparsers.add_parser('compile')
18
+ program_group = compile_parser.add_mutually_exclusive_group()
19
+ program_group.add_argument('--create-program', action='store_true')
20
+ program_group.add_argument('--no-create-program', dest='create_program', action='store_false')
21
+ engine_group = compile_parser.add_mutually_exclusive_group()
22
+ engine_group.add_argument('--create-engine', action='store_true')
23
+ engine_group.add_argument('--no-create-engine', dest='create_engine', action='store_false')
24
+ compile_parser.add_argument('input_filename', nargs='+')
25
+ compile_parser.set_defaults(module_name='wirely.compiler',
26
+ create_program=True, create_engine=False)
27
+
28
+ show_graph_parser = subparsers.add_parser('show-graph')
29
+ show_graph_parser.add_argument('input_filename')
30
+ show_graph_parser.set_defaults(module_name='wirely.graphviz')
31
+
32
+ args = parser.parse_args()
33
+ module = importlib.import_module(args.module_name)
34
+ has_async_run = hasattr(module, 'async_run')
35
+ has_run = hasattr(module, 'run')
36
+ if has_async_run == has_run:
37
+ raise RuntimeError(f'{args.module_name} must define exactly one of run() or async_run()')
38
+ if has_async_run:
39
+ sys.exit(asyncio.run(module.async_run(args)))
40
+ else:
41
+ sys.exit(module.run(args))
42
+
43
+
44
+ if __name__ == '__main__':
45
+ main()