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.
- wirely/__init__.py +406 -0
- wirely/__main__.py +45 -0
- wirely/ast.py +1135 -0
- wirely/block.py +542 -0
- wirely/block_type.py +342 -0
- wirely/blocks.lark +126 -0
- wirely/compiler.py +125 -0
- wirely/engine.py +418 -0
- wirely/enum.py +15 -0
- wirely/example.py +36 -0
- wirely/graphviz.py +107 -0
- wirely/knx/__init__.py +0 -0
- wirely/knx/connection_adapter.py +90 -0
- wirely/knx/connection_config.py +152 -0
- wirely/lib/abb_jra.wrl +64 -0
- wirely/lib/csv.wrl +32 -0
- wirely/lib/ext_prog.wrl +12 -0
- wirely/lib/gpio.wrl +23 -0
- wirely/lib/knx.wrl +80 -0
- wirely/lib/memory.wrl +45 -0
- wirely/lib/mqtt.wrl +66 -0
- wirely/lib/std.wrl +134 -0
- wirely/lib/time.wrl +93 -0
- wirely/lib/two_n.wrl +111 -0
- wirely/misc/__init__.py +0 -0
- wirely/misc/service.py +48 -0
- wirely/native/__init__.py +74 -0
- wirely/native/abb_jra.py +66 -0
- wirely/native/csv.py +194 -0
- wirely/native/ext_prog.py +77 -0
- wirely/native/gpio.py +125 -0
- wirely/native/knx.py +455 -0
- wirely/native/memory.py +113 -0
- wirely/native/mqtt.py +605 -0
- wirely/native/std.py +334 -0
- wirely/native/test_library.py +14 -0
- wirely/native/time.py +359 -0
- wirely/native/two_n.py +632 -0
- wirely/node.py +125 -0
- wirely/parser.py +175 -0
- wirely/program.py +155 -0
- wirely/run.py +143 -0
- wirely/service.py +65 -0
- wirely/test.py +569 -0
- wirely/time.py +172 -0
- wirely/two_n/__init__.py +0 -0
- wirely/two_n/cert.py +126 -0
- wirely/two_n/client.py +402 -0
- wirely/two_n/fake_server.py +415 -0
- wirely/two_n/grab_images.py +146 -0
- wirely/types.py +522 -0
- wirely/util.py +111 -0
- wirely/value.py +163 -0
- wirely-0.1.0.dist-info/METADATA +94 -0
- wirely-0.1.0.dist-info/RECORD +59 -0
- wirely-0.1.0.dist-info/WHEEL +5 -0
- wirely-0.1.0.dist-info/entry_points.txt +2 -0
- wirely-0.1.0.dist-info/licenses/LICENSE +21 -0
- wirely-0.1.0.dist-info/top_level.txt +1 -0
wirely/block_type.py
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import typing
|
|
2
|
+
|
|
3
|
+
import wirely as blocks
|
|
4
|
+
from wirely.util import check_parameters
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class BlockType(blocks.Scope):
|
|
8
|
+
@check_parameters
|
|
9
|
+
def __init__(self, specifiers: typing.List[blocks.BlockTypeSpecifier], name: typing.Optional[blocks.Identifier], declarations: typing.List[blocks.Declaration], *, source_position: typing.Optional[blocks.SourcePosition]):
|
|
10
|
+
super().__init__(source_position=source_position)
|
|
11
|
+
self.name = name
|
|
12
|
+
self.declarations = declarations
|
|
13
|
+
|
|
14
|
+
all_specifiers = ['native', 'singleton']
|
|
15
|
+
specifiers_status = dict({key: False for key in all_specifiers})
|
|
16
|
+
for specifier in specifiers:
|
|
17
|
+
if specifier.specifier_str not in all_specifiers:
|
|
18
|
+
raise Exception(f'Unknown specifier: {specifier.specifier_str!r}')
|
|
19
|
+
if specifiers_status[specifier.specifier_str]:
|
|
20
|
+
raise blocks.ProgramError(specifier, f'Got duplicate {specifier.specifier_str} specifier')
|
|
21
|
+
specifiers_status[specifier.specifier_str] = True
|
|
22
|
+
self.is_native = specifiers_status['native']
|
|
23
|
+
self.is_singleton = specifiers_status['singleton']
|
|
24
|
+
|
|
25
|
+
self.blocks = {}
|
|
26
|
+
# TODO: Remove one of block_type_interfaces / block_type_implementations
|
|
27
|
+
self.block_type_interfaces = {}
|
|
28
|
+
self.block_type_implementations = {}
|
|
29
|
+
self.procedures = {}
|
|
30
|
+
self.variables = {}
|
|
31
|
+
# TODO: Clarify what is the key here (name or input_name)
|
|
32
|
+
self.block_inputs = {}
|
|
33
|
+
self.block_outputs = {}
|
|
34
|
+
self.block_state_variables = {}
|
|
35
|
+
self.block_temporary_variables = {}
|
|
36
|
+
self.libraries = {}
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def make(*args, source_position: typing.Optional[blocks.SourcePosition]):
|
|
40
|
+
specifiers = args[:-2]
|
|
41
|
+
name = args[-2]
|
|
42
|
+
declarations = args[-1]
|
|
43
|
+
return BlockType(list(specifiers), name, declarations, source_position=source_position)
|
|
44
|
+
|
|
45
|
+
def compile_pass1(self, graph):
|
|
46
|
+
if hasattr(self, 'parent_graph'):
|
|
47
|
+
raise Exception("hasattr(self, 'parent_graph')")
|
|
48
|
+
self.parent_graph = graph
|
|
49
|
+
|
|
50
|
+
if self.parent_graph is not None:
|
|
51
|
+
if self.name.id_str in graph.block_type_interfaces:
|
|
52
|
+
hints = [
|
|
53
|
+
blocks.CompilerHint(graph.block_type_interfaces[self.name.id_str], 'Previous block type was here'),
|
|
54
|
+
]
|
|
55
|
+
raise blocks.ProgramError(self, f'Duplicate block type {self.name!r}', hints=hints)
|
|
56
|
+
graph.block_type_interfaces[self.name.id_str] = self
|
|
57
|
+
|
|
58
|
+
# TODO: Merge parsed_parameters and parameters?
|
|
59
|
+
parsed_parameters = {}
|
|
60
|
+
for decl in self.declarations:
|
|
61
|
+
decl.compile_pass1_btd(self)
|
|
62
|
+
|
|
63
|
+
# TODO: Clean up isinstance?
|
|
64
|
+
if not isinstance(decl, blocks.DeclarationParameter):
|
|
65
|
+
continue
|
|
66
|
+
with decl.context:
|
|
67
|
+
if self.is_singleton:
|
|
68
|
+
# TODO: Allow parameters for singleton blocks and create one instance for each parameter combination?
|
|
69
|
+
raise blocks.ProgramError(decl, f'Singleton block cannot have parameters')
|
|
70
|
+
|
|
71
|
+
name = decl.name.id_str
|
|
72
|
+
if name in parsed_parameters:
|
|
73
|
+
hints = [
|
|
74
|
+
blocks.CompilerHint(parsed_parameters[name], 'Previous parameter was here'),
|
|
75
|
+
]
|
|
76
|
+
raise blocks.ProgramError(decl, f'Duplicate parameter name {name!r}', hints=hints)
|
|
77
|
+
parsed_parameters[name] = decl
|
|
78
|
+
if hasattr(self, 'parsed_parameters'):
|
|
79
|
+
raise Exception("hasattr(self, 'parsed_parameters')")
|
|
80
|
+
self.parsed_parameters = parsed_parameters
|
|
81
|
+
|
|
82
|
+
# Former "implementation" part
|
|
83
|
+
|
|
84
|
+
for decl in self.declarations:
|
|
85
|
+
if isinstance(decl, blocks.Declaration):
|
|
86
|
+
with decl.context:
|
|
87
|
+
decl.compile_pass1(self)
|
|
88
|
+
|
|
89
|
+
if self.parent_graph is not None:
|
|
90
|
+
if self.name.id_str in self.parent_graph.block_type_implementations:
|
|
91
|
+
hints = [
|
|
92
|
+
blocks.CompilerHint(graph.block_type_implementations[self.name.id_str], 'Previous implementation was here'),
|
|
93
|
+
]
|
|
94
|
+
raise blocks.ProgramError(self, f'Duplicate block type implementation for {self.name!r}', hints=hints)
|
|
95
|
+
self.parent_graph.block_type_implementations[self.name.id_str] = self
|
|
96
|
+
|
|
97
|
+
def compile_pass1_5(self, graph):
|
|
98
|
+
self.compile_pass1_5_if_needed()
|
|
99
|
+
|
|
100
|
+
def compile_pass1_5_if_needed(self):
|
|
101
|
+
# TODO: Make this more generic, provide proper error messages on cycles
|
|
102
|
+
if hasattr(self, 'pass1_5_done'):
|
|
103
|
+
return
|
|
104
|
+
self.pass1_5_done = True
|
|
105
|
+
|
|
106
|
+
# Former "implementation" part
|
|
107
|
+
|
|
108
|
+
for decl in self.declarations:
|
|
109
|
+
with decl.context:
|
|
110
|
+
decl.create_io(self)
|
|
111
|
+
|
|
112
|
+
for decl in self.declarations:
|
|
113
|
+
if isinstance(decl, blocks.Declaration):
|
|
114
|
+
with decl.context:
|
|
115
|
+
decl.compile_pass1_5(self)
|
|
116
|
+
|
|
117
|
+
# Resolve Parameters
|
|
118
|
+
for par_name in self.parsed_parameters:
|
|
119
|
+
par = self.parsed_parameters[par_name]
|
|
120
|
+
|
|
121
|
+
par.resolve_type()
|
|
122
|
+
|
|
123
|
+
# TODO: Remove
|
|
124
|
+
self.parameters_by_name = self.parsed_parameters
|
|
125
|
+
|
|
126
|
+
def compile_pass2(self, graph):
|
|
127
|
+
for decl in self.declarations:
|
|
128
|
+
if isinstance(decl, blocks.Declaration):
|
|
129
|
+
with decl.context:
|
|
130
|
+
decl.compile_pass2(self)
|
|
131
|
+
|
|
132
|
+
# TODO: Allow interfaces without implementations?
|
|
133
|
+
# if self.name.id_str not in graph.block_type_implementations:
|
|
134
|
+
# raise blocks.ProgramError(self, f'Block type interface for {self.name!r} without implementation')
|
|
135
|
+
|
|
136
|
+
# TODO: Validate parameters
|
|
137
|
+
|
|
138
|
+
def create(self, *, name, graph, par_values, instantiation):
|
|
139
|
+
# Do this here to avoid problems with circular imports
|
|
140
|
+
import wirely.native
|
|
141
|
+
|
|
142
|
+
# print('create', name, graph, par_values)
|
|
143
|
+
block = blocks.Block(name=name, graph=graph, block_type=self, par_values=par_values, instantiation=instantiation)
|
|
144
|
+
# TODO: Remove
|
|
145
|
+
block.parameter_values = dict(par_values)
|
|
146
|
+
|
|
147
|
+
for par in self.parsed_parameters.values():
|
|
148
|
+
# print(par, par.is_optional)
|
|
149
|
+
if not par.is_optional and par not in block.parameter_values:
|
|
150
|
+
# TODO: Raise ProgramError and pass source location
|
|
151
|
+
raise Exception(f'Missing value for parameter {par.name!r}')
|
|
152
|
+
|
|
153
|
+
# TODO: Pass list of connections to constructor instead of getting them from self.graph
|
|
154
|
+
block.custom_inputs = {}
|
|
155
|
+
block.custom_outputs = {}
|
|
156
|
+
for input in self.block_inputs.values():
|
|
157
|
+
# TODO
|
|
158
|
+
# if not isinstance(input.conn_spec, blocks.ConnectorSpecificationData):
|
|
159
|
+
# continue
|
|
160
|
+
# TODO: Is the name of the block and the name of the input always the same?
|
|
161
|
+
new_input = blocks.Block.Input(block, input.input_name, input.is_list, input.var_type, block_input=input)
|
|
162
|
+
block.custom_inputs[input] = new_input
|
|
163
|
+
for output in self.block_outputs.values():
|
|
164
|
+
# TODO
|
|
165
|
+
# if not isinstance(output.conn_spec, blocks.ConnectorSpecificationData):
|
|
166
|
+
# continue
|
|
167
|
+
# TODO: Is the name of the block and the name of the output always the same?
|
|
168
|
+
new_output = blocks.Block.Output(block, output.output_name, output.var_type, block_output=output)
|
|
169
|
+
block.custom_outputs[output] = new_output
|
|
170
|
+
|
|
171
|
+
# TODO: Probably more stuff should be done at an earlier stage
|
|
172
|
+
state_variables = {}
|
|
173
|
+
for variable in self.variables.values():
|
|
174
|
+
if isinstance(variable, blocks.ast.DeclarationStateVariable):
|
|
175
|
+
var_type = self.resolve_type(variable.var_type, par_values=block.parameter_values)
|
|
176
|
+
initial_value = self.get_const_value(variable.initial_value, value_type=var_type, par_values=block.parameter_values)
|
|
177
|
+
# TODO: Can there be type parameters here?
|
|
178
|
+
initial_value = initial_value.value
|
|
179
|
+
block_var = blocks.Block.StateVariable(block, variable.name.id_str, var_type, initial_value)
|
|
180
|
+
if hasattr(var_type, 'block_var'):
|
|
181
|
+
raise Exception("hasattr(var_type, 'block_var')")
|
|
182
|
+
variable.block_var = block_var
|
|
183
|
+
state_variables[variable.name.id_str] = block_var
|
|
184
|
+
elif isinstance(variable, blocks.ast.DeclarationTemporaryVariable):
|
|
185
|
+
var_type = self.resolve_type(variable.var_type, par_values=block.parameter_values)
|
|
186
|
+
impl_var_type = var_type # TODO: ?
|
|
187
|
+
block_var = blocks.Block.Temporary(block, variable.name.id_str, var_type, impl_var_type=impl_var_type)
|
|
188
|
+
if hasattr(var_type, 'block_var'):
|
|
189
|
+
raise Exception("hasattr(var_type, 'block_var')")
|
|
190
|
+
variable.block_var = block_var
|
|
191
|
+
else:
|
|
192
|
+
raise Exception(f'Got unexpected variable: {variable}')
|
|
193
|
+
|
|
194
|
+
have_native = self.is_native
|
|
195
|
+
for procedure in self.procedures.values():
|
|
196
|
+
have_native = have_native or procedure.is_native
|
|
197
|
+
native_class = None
|
|
198
|
+
if have_native:
|
|
199
|
+
# TODO: Should this be done here? This will be called if 'self' is used in another block, even if that block is not instantiated. Probably looking up the native block type should be delayed until then to avoid loading the native module needed for that. Alternatively, the native class should be looked up even if 'self' is not used at all to do proper error checking.
|
|
200
|
+
native_class = wirely.native.get_native_block_type(self)
|
|
201
|
+
if self.is_native and not native_class.native_block_has_state:
|
|
202
|
+
# TODO: Allow this?
|
|
203
|
+
raise Exception(f'Block type {self.fullname!r} requires native state but native class is stateless')
|
|
204
|
+
if not self.is_native and native_class.native_block_has_state:
|
|
205
|
+
raise Exception(f'Block type {self.fullname!r} does not requires native state but native class is stateful')
|
|
206
|
+
block.native_class = native_class
|
|
207
|
+
|
|
208
|
+
# TODO: This is an ugly hack, native class resolution should be done per-type, not per-instance
|
|
209
|
+
if not hasattr(self, 'native_class'):
|
|
210
|
+
self.native_class = native_class
|
|
211
|
+
|
|
212
|
+
# TODO: Probably more stuff should be done at an earlier stage
|
|
213
|
+
for procedure in self.procedures.values():
|
|
214
|
+
# print(procedure, procedure.depends.get_resolved_dependencies(), procedure.produces.get_resolved_outputs())
|
|
215
|
+
sources = {}
|
|
216
|
+
targets = {}
|
|
217
|
+
inputs = []
|
|
218
|
+
procedures = {}
|
|
219
|
+
for dep in procedure.depends.get_resolved_dependencies():
|
|
220
|
+
# print(type(dep))
|
|
221
|
+
# TODO: Clean up
|
|
222
|
+
if isinstance(dep, blocks.DeclarationTemporaryVariable):
|
|
223
|
+
input = dep.block_var
|
|
224
|
+
inputs.append(input)
|
|
225
|
+
sources[dep.name.id_str] = input
|
|
226
|
+
elif isinstance(dep, blocks.BlockInput):
|
|
227
|
+
input = block.custom_inputs[dep]
|
|
228
|
+
inputs.append(input)
|
|
229
|
+
sources[dep.name] = input
|
|
230
|
+
elif isinstance(dep, blocks.Block):
|
|
231
|
+
# TODO: Clean up handling of implicit output of block, allow explicit output
|
|
232
|
+
input = dep.block_outputs_byname[''].temporary
|
|
233
|
+
inputs.append(input)
|
|
234
|
+
# TODO: What should be the name of this in sources?
|
|
235
|
+
sources[dep.name] = input
|
|
236
|
+
else:
|
|
237
|
+
raise Exception(f'Got unknown dependency: {dep}')
|
|
238
|
+
outputs = []
|
|
239
|
+
for out in procedure.produces.get_resolved_outputs():
|
|
240
|
+
# print(type(out))
|
|
241
|
+
if isinstance(out, blocks.ast.DeclarationStateVariable):
|
|
242
|
+
output = out.block_var
|
|
243
|
+
outputs.append(output)
|
|
244
|
+
targets[output.name] = output
|
|
245
|
+
elif isinstance(out, blocks.ast.DeclarationTemporaryVariable):
|
|
246
|
+
output = out.block_var
|
|
247
|
+
outputs.append(output)
|
|
248
|
+
targets[output.name] = output
|
|
249
|
+
elif isinstance(out, blocks.BlockOutput):
|
|
250
|
+
# TODO: Clean up
|
|
251
|
+
output = block.custom_outputs[out]
|
|
252
|
+
outputs.append(output)
|
|
253
|
+
targets[output.name] = output
|
|
254
|
+
# print(type(out.inp))
|
|
255
|
+
# print(out.inp)
|
|
256
|
+
# # outputs.append(out.inp)
|
|
257
|
+
# # targets[out.name] = out.inp
|
|
258
|
+
# temp = blocks.Block.Temporary(block, procedure.name.id_str + '__' + out.name, out.inp.var_type, impl_var_type=...)
|
|
259
|
+
# outputs.append(temp)
|
|
260
|
+
# targets[out.name] = temp
|
|
261
|
+
# # TODO: Remove this, connecting the different graphs should be done at instantiation time, not here
|
|
262
|
+
# # out.inp.connect(temp)
|
|
263
|
+
# # if out.inp.connected_source is not None:
|
|
264
|
+
# # raise Exception('out.inp.connected_source is not None')
|
|
265
|
+
# # out.inp.connected_source = temp
|
|
266
|
+
# # TODO: This is a hack to avoid a "Input graph1.Delay.implicit output. is not connected" exception, this should not be needed because graph1.Delay (without instantiation) should not be used for anything
|
|
267
|
+
# out.inp.connected_source = temp
|
|
268
|
+
else:
|
|
269
|
+
raise Exception(f'Unknown output: {out}')
|
|
270
|
+
|
|
271
|
+
static_context = blocks.StaticProcedureContext(sources=sources, targets=targets, state=state_variables, name=block.name, block_parameter_values={key.name.id_str: value for key, value in block.par_values.items()})
|
|
272
|
+
|
|
273
|
+
if not procedure.is_native:
|
|
274
|
+
raise Exception('Got non-native procedure')
|
|
275
|
+
else:
|
|
276
|
+
# TODO: Think about e.g. static stateful procedures
|
|
277
|
+
func = getattr(native_class, procedure.name.id_str, None)
|
|
278
|
+
if func is None:
|
|
279
|
+
raise Exception(f'Cannot find native procedure {procedure.name.id_str!r} in native block type {self.name!r}')
|
|
280
|
+
if self.is_native:
|
|
281
|
+
def run(*, context, native_state, func=func):
|
|
282
|
+
func(native_state, context=context)
|
|
283
|
+
else:
|
|
284
|
+
def run(*, context, native_state, func=func):
|
|
285
|
+
func(context=context)
|
|
286
|
+
# print('QQ', [type(x) for x in inputs], [type(x) for x in outputs])
|
|
287
|
+
block_procedure = blocks.Block.Procedure(block, inputs, outputs, run, name=procedure.name.id_str, static_context=static_context, declaration_procedure=procedure)
|
|
288
|
+
if procedure in procedures:
|
|
289
|
+
raise Exception('procedure in procedures')
|
|
290
|
+
procedures[procedure] = block_procedure
|
|
291
|
+
|
|
292
|
+
# print(block)
|
|
293
|
+
# print(block.block_inputs)
|
|
294
|
+
# print(block.block_outputs)
|
|
295
|
+
# print(block.all_block_inputs)
|
|
296
|
+
|
|
297
|
+
return block
|
|
298
|
+
|
|
299
|
+
def __repr__(self):
|
|
300
|
+
return self.fullname
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def fullname(self):
|
|
304
|
+
if self.name is None and self.parent_graph is None:
|
|
305
|
+
return '<global>'
|
|
306
|
+
elif self.parent_graph.name is None:
|
|
307
|
+
return self.name.id_str
|
|
308
|
+
else:
|
|
309
|
+
return self.parent_graph.fullname + '.' + self.name.id_str
|
|
310
|
+
|
|
311
|
+
@property
|
|
312
|
+
def fullname_tuple(self):
|
|
313
|
+
if self.name is None and self.parent_graph is None:
|
|
314
|
+
return ()
|
|
315
|
+
elif self.parent_graph.name is None:
|
|
316
|
+
return (self.name.id_str),
|
|
317
|
+
else:
|
|
318
|
+
return self.parent_graph.fullname_tuple + (self.name.id_str,)
|
|
319
|
+
|
|
320
|
+
# TODO: Clean up compilation passes
|
|
321
|
+
|
|
322
|
+
def compile_pass1_btd(self, interface):
|
|
323
|
+
pass
|
|
324
|
+
|
|
325
|
+
def create_io(self, new_graph):
|
|
326
|
+
pass
|
|
327
|
+
|
|
328
|
+
# TODO: Currently only used for singletons, clean this up, merge with block.create_native_state
|
|
329
|
+
def create_native_state(self, path, engine, static_block_context):
|
|
330
|
+
if len(path) != 1:
|
|
331
|
+
raise Exception('len(path) != 1')
|
|
332
|
+
if path[-1] != self:
|
|
333
|
+
raise Exception('path[-1] != self')
|
|
334
|
+
|
|
335
|
+
if self.is_native:
|
|
336
|
+
# print('QQQ', self.name)
|
|
337
|
+
return self.native_class(path, engine, static_block_context=static_block_context)
|
|
338
|
+
else:
|
|
339
|
+
# print('WWW', self.name)
|
|
340
|
+
pass
|
|
341
|
+
|
|
342
|
+
return None
|
wirely/blocks.lark
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
file_input: declaration*
|
|
2
|
+
|
|
3
|
+
declaration: "node" identifier ":" block_instantiation ";" -> declaration_block_instantiation
|
|
4
|
+
| "connect" reference_list "->" reference ";" -> declaration_connection
|
|
5
|
+
// TODO: Can this be done in a nicer way?
|
|
6
|
+
| block_type_specifier* "node" "type" identifier "{" declaration_list "}" -> block_type__make
|
|
7
|
+
| "namespace" identifier ("." identifier)* "{" declaration_list "}" -> declaration_library__make
|
|
8
|
+
| "state" identifier ":" value "=" value ";" -> declaration_state_variable
|
|
9
|
+
| "temporary" identifier ":" value ";" -> declaration_temporary_variable
|
|
10
|
+
| native_specifier "procedure" identifier stage_declaration depends_declaration produces_declaration ";" -> declaration_procedure
|
|
11
|
+
| block_connector_type identifier ":" connector_specification ";" -> declaration_connector__create
|
|
12
|
+
| "implicit" block_connector_type ":" connector_specification ";" -> declaration_connector__create_implicit
|
|
13
|
+
| [optional_specifier] "parameter" identifier ":" value ";" -> declaration_parameter
|
|
14
|
+
| "parameter" identifier ":" value "=" value ";" -> declaration_parameter__with_default_value
|
|
15
|
+
declaration_list: declaration* -> copy_list
|
|
16
|
+
|
|
17
|
+
block_instantiation: reference -> block_instantiation
|
|
18
|
+
| reference "(" block_parameter_list ")" -> block_instantiation
|
|
19
|
+
|
|
20
|
+
stage_declaration: -> stage_declaration__create_default
|
|
21
|
+
| "stage" "input" -> stage_declaration__create_input
|
|
22
|
+
| "stage" "output" -> stage_declaration__create_output
|
|
23
|
+
|
|
24
|
+
depends_declaration: -> depends_declaration
|
|
25
|
+
| "depends" "on" _separated_empty{reference, ","} -> depends_declaration
|
|
26
|
+
|
|
27
|
+
produces_declaration: -> produces_declaration
|
|
28
|
+
| "produces" _separated_empty{reference, ","} -> produces_declaration
|
|
29
|
+
|
|
30
|
+
block_parameter_list: _separated_empty{block_parameter, ","} -> copy_list
|
|
31
|
+
|
|
32
|
+
block_parameter: identifier "=" value
|
|
33
|
+
|
|
34
|
+
native_specifier: "native" -> native_specifier
|
|
35
|
+
|
|
36
|
+
optional_specifier: "optional" -> optional_specifier
|
|
37
|
+
|
|
38
|
+
block_type_specifier: native_specifier -> copy
|
|
39
|
+
| "singleton" -> block_type_specifier__create_singleton
|
|
40
|
+
block_type_specifier_list: block_type_specifier* -> copy_list
|
|
41
|
+
|
|
42
|
+
connector_specification: list_specifier connection_kind value -> connector_specification_data
|
|
43
|
+
// TODO: Make this more generic
|
|
44
|
+
| list_specifier "knx_bus" -> connector_specification_knx_bus
|
|
45
|
+
| list_specifier "mqtt_server" -> connector_specification_mqtt_server
|
|
46
|
+
| list_specifier "two_n_device" -> connector_specification_two_n_device
|
|
47
|
+
|
|
48
|
+
connection_kind: "value" -> connection_kind_specifier__create_value
|
|
49
|
+
| "event" -> connection_kind_specifier__create_event
|
|
50
|
+
|
|
51
|
+
block_connector_type: "input" -> block_connector_type_specifier__create_input
|
|
52
|
+
| "output" -> block_connector_type_specifier__create_output
|
|
53
|
+
|
|
54
|
+
list_specifier: "list" -> list_specifier__create_list
|
|
55
|
+
| () -> list_specifier__create_nolist
|
|
56
|
+
|
|
57
|
+
value: reference -> copy
|
|
58
|
+
| int_number -> copy
|
|
59
|
+
| float_number -> copy
|
|
60
|
+
| string_constant -> copy
|
|
61
|
+
| value "[" value "]" -> bracket_operator
|
|
62
|
+
| value "(" value_list ")" -> function_call
|
|
63
|
+
| "[" value_list "]" -> list_literal
|
|
64
|
+
|
|
65
|
+
value_list: _separated_empty{value, ","} -> copy_list
|
|
66
|
+
|
|
67
|
+
reference: identifier ("." identifier)* -> reference__from_list
|
|
68
|
+
| "implicit" block_connector_type -> reference__create_implicit
|
|
69
|
+
reference_list: _separated_empty{reference, ","} -> copy_list
|
|
70
|
+
|
|
71
|
+
int_number: DEC_NUMBER | HEX_NUMBER | OCT_NUMBER
|
|
72
|
+
float_number: FLOAT
|
|
73
|
+
string_constant: STRING | RAW_STRING
|
|
74
|
+
// Tokens
|
|
75
|
+
|
|
76
|
+
// COMMENT: /\/\/[^\n]*/
|
|
77
|
+
// | /\/\*([^\*]|\*[^\/])*\*\//
|
|
78
|
+
|
|
79
|
+
COMMENT: /\/\/[^\n]*/
|
|
80
|
+
%ignore COMMENT
|
|
81
|
+
|
|
82
|
+
// COMMENT_START, COMMENT_NEST, COMMENT_CONTENT and COMMENT_END are handled specially by the lexer
|
|
83
|
+
COMMENT_START: "/*"
|
|
84
|
+
// Similar to COMMENT_START, but allows mulitple slashes at the beginning
|
|
85
|
+
COMMENT_NEST: /\/+\*/
|
|
86
|
+
// A comment content consists of one or more of:
|
|
87
|
+
// - something which is neither * nor /
|
|
88
|
+
// - one or more * followed by something which is neither * nor /
|
|
89
|
+
// - one or more / followed by something which is neither * nor /
|
|
90
|
+
COMMENT_CONTENT: /([^\*\/]|\*+[^\*\/]|\/+[^\*\/])+/
|
|
91
|
+
// Note: Multiple stars are considered part of the comment end
|
|
92
|
+
COMMENT_END: /\*+\//
|
|
93
|
+
|
|
94
|
+
// %ignore is needed to prevent these terminals from being dropped as unused
|
|
95
|
+
%ignore COMMENT_START
|
|
96
|
+
%ignore COMMENT_NEST
|
|
97
|
+
%ignore COMMENT_CONTENT
|
|
98
|
+
%ignore COMMENT_END
|
|
99
|
+
|
|
100
|
+
// String: A quote, followed an arbitrary number of normal character (preceeded by an arbitrary number of backslashes) or quotes (preceeded by an odd number of backslashes), followed by a quote (preceeded by an even number of backslashes)
|
|
101
|
+
// https://doc.rust-lang.org/reference/tokens.html#r-lex.token.literal.str
|
|
102
|
+
STRING: /\"(\\*[^\"\\]|(\\\\)*\\\")*(\\\\)*\"/s
|
|
103
|
+
// https://doc.rust-lang.org/reference/tokens.html#r-lex.token.literal.str-raw
|
|
104
|
+
// TODO: Support raw strings with more than one #?
|
|
105
|
+
RAW_STRING.2: /r\"[^\"]*\"|r#\"(\"*[^\"#]|#)*\"*\"#/s
|
|
106
|
+
|
|
107
|
+
DEC_NUMBER: /[1-9]\d*l?/i
|
|
108
|
+
HEX_NUMBER: /0x[\da-f]*l?/i
|
|
109
|
+
OCT_NUMBER: /0o?[0-7]*l?/i
|
|
110
|
+
%import common.FLOAT -> FLOAT
|
|
111
|
+
|
|
112
|
+
%import common.CNAME
|
|
113
|
+
// https://doc.rust-lang.org/reference/identifiers.html
|
|
114
|
+
// https://doc.rust-lang.org/rust-by-example/compatibility/raw_identifiers.html
|
|
115
|
+
// https://lark-parser.readthedocs.io/en/latest/tree_construction.html#pinning-rule-terminals-with
|
|
116
|
+
!identifier: CNAME
|
|
117
|
+
| RAW_IDENTIFIER
|
|
118
|
+
// TODO: Which keywords should be "weak" keywords?
|
|
119
|
+
| "type"
|
|
120
|
+
| "value"
|
|
121
|
+
RAW_IDENTIFIER: "r#" CNAME
|
|
122
|
+
|
|
123
|
+
%ignore /[\t \f\r\n]+/
|
|
124
|
+
|
|
125
|
+
_separated_nonempty{x, sep}: x (sep x)*
|
|
126
|
+
_separated_empty{x, sep}: (x (sep x)*)?
|
wirely/compiler.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import traceback
|
|
4
|
+
|
|
5
|
+
import lark
|
|
6
|
+
|
|
7
|
+
import wirely as blocks
|
|
8
|
+
from wirely.util import check_parameters
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def compile_strs(filename_inpudatas, *, verbose_passes=None, always_print_stacktrace=None):
|
|
12
|
+
# TODO: Where should this import be done?
|
|
13
|
+
import wirely.parser
|
|
14
|
+
|
|
15
|
+
if verbose_passes is None:
|
|
16
|
+
verbose_passes = blocks.util.getEnvironmentBool('WIRELY_COMPILER_VERBOSE_PASSES', False)
|
|
17
|
+
if always_print_stacktrace is None:
|
|
18
|
+
always_print_stacktrace = blocks.util.getEnvironmentBool('WIRELY_COMPILER_ALWAYS_PRINT_STACKTRACE', False)
|
|
19
|
+
|
|
20
|
+
exc = None
|
|
21
|
+
try:
|
|
22
|
+
decls = []
|
|
23
|
+
for filename, input_data in filename_inpudatas:
|
|
24
|
+
decls += blocks.parser.parse_str(input_data, filename=filename)
|
|
25
|
+
filename = None
|
|
26
|
+
global_source_position = blocks.SourcePosition(filename='<global>', meta=None, input_data='')
|
|
27
|
+
global_block_type = wirely.block_type.BlockType([], None, decls, source_position=global_source_position)
|
|
28
|
+
if verbose_passes:
|
|
29
|
+
print('Starting pass 1', flush=True)
|
|
30
|
+
global_block_type.compile_pass1(None)
|
|
31
|
+
if verbose_passes:
|
|
32
|
+
print('Starting pass 1.5', flush=True)
|
|
33
|
+
global_block_type.compile_pass1_5(None)
|
|
34
|
+
if verbose_passes:
|
|
35
|
+
print('Starting pass 2', flush=True)
|
|
36
|
+
global_block_type.compile_pass2(None)
|
|
37
|
+
if verbose_passes:
|
|
38
|
+
print('Finished all passes', flush=True)
|
|
39
|
+
# TODO: Clean up
|
|
40
|
+
# for decl in decls:
|
|
41
|
+
# with decl.context:
|
|
42
|
+
# decl.compile_pass1(graph)
|
|
43
|
+
# for decl in decls:
|
|
44
|
+
# with decl.context:
|
|
45
|
+
# decl.compile_pass1_5(graph)
|
|
46
|
+
# for decl in decls:
|
|
47
|
+
# with decl.context:
|
|
48
|
+
# decl.compile_pass2(graph)
|
|
49
|
+
except lark.UnexpectedInput as e:
|
|
50
|
+
print(f'Error in {filename}:{e.line}')
|
|
51
|
+
print(e)
|
|
52
|
+
# TODO: Using input_data here is kind of a hack
|
|
53
|
+
print(e.get_context(input_data))
|
|
54
|
+
return None
|
|
55
|
+
except lark.exceptions.VisitError as e:
|
|
56
|
+
exc = e.orig_exc
|
|
57
|
+
except blocks.ProgramError as e:
|
|
58
|
+
exc = e
|
|
59
|
+
except Exception as e:
|
|
60
|
+
exc = e
|
|
61
|
+
|
|
62
|
+
if exc is None:
|
|
63
|
+
return global_block_type
|
|
64
|
+
|
|
65
|
+
if not isinstance(exc, blocks.ProgramError):
|
|
66
|
+
exc = blocks.InternalCompilerError(None, exc)
|
|
67
|
+
|
|
68
|
+
if isinstance(exc, blocks.InternalCompilerError):
|
|
69
|
+
if exc.source_element is not None:
|
|
70
|
+
print(f'Internal compiler error while compiling line {exc.source_element.source_position.start_line}: {exc.inner_exception}')
|
|
71
|
+
else:
|
|
72
|
+
print(f'Internal compiler error: {exc.inner_exception}')
|
|
73
|
+
else:
|
|
74
|
+
print(f'Error in {exc.source_element.source_position.filename}:{exc.source_element.source_position.start_line}: {exc}')
|
|
75
|
+
if exc.source_element is not None:
|
|
76
|
+
print(exc.get_context())
|
|
77
|
+
if isinstance(exc, blocks.InternalCompilerError):
|
|
78
|
+
traceback.print_exception(exc.inner_exception)
|
|
79
|
+
elif always_print_stacktrace:
|
|
80
|
+
traceback.print_exception(exc)
|
|
81
|
+
for hint in exc.hints:
|
|
82
|
+
print(f'Hint: {hint.source_element.source_position.filename}:{hint.source_element.source_position.start_line}: {hint}')
|
|
83
|
+
print(hint.get_context())
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def compile_files(input_filenames, *, add_std=True):
|
|
88
|
+
if add_std:
|
|
89
|
+
std_lib = [
|
|
90
|
+
'std',
|
|
91
|
+
'time',
|
|
92
|
+
'knx',
|
|
93
|
+
'abb_jra',
|
|
94
|
+
'mqtt',
|
|
95
|
+
'csv',
|
|
96
|
+
'two_n',
|
|
97
|
+
'gpio',
|
|
98
|
+
'memory',
|
|
99
|
+
'ext_prog',
|
|
100
|
+
]
|
|
101
|
+
input_filenames = [os.path.dirname(__file__) + '/lib/' + name + '.wrl' for name in std_lib] + input_filenames
|
|
102
|
+
|
|
103
|
+
filename_inpudatas = []
|
|
104
|
+
for input_filename in input_filenames:
|
|
105
|
+
with open(input_filename, 'r') as file:
|
|
106
|
+
filename_inpudatas.append((input_filename, file.read()))
|
|
107
|
+
return compile_strs(filename_inpudatas)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def run(args):
|
|
111
|
+
if args.create_engine and not args.create_program:
|
|
112
|
+
print('Error: --create-engine cannot be used with --no-create-program',
|
|
113
|
+
file=sys.stderr)
|
|
114
|
+
return 2
|
|
115
|
+
|
|
116
|
+
global_block_type = compile_files(args.input_filename)
|
|
117
|
+
if global_block_type is None:
|
|
118
|
+
return 1
|
|
119
|
+
|
|
120
|
+
if args.create_program:
|
|
121
|
+
prog = blocks.Program.compile(global_block_type)
|
|
122
|
+
if prog is None:
|
|
123
|
+
return 1
|
|
124
|
+
if args.create_engine:
|
|
125
|
+
blocks.Engine(prog)
|