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/ast.py
ADDED
|
@@ -0,0 +1,1135 @@
|
|
|
1
|
+
import enum
|
|
2
|
+
import typing
|
|
3
|
+
import abc
|
|
4
|
+
import contextlib
|
|
5
|
+
import ast
|
|
6
|
+
import re
|
|
7
|
+
import datetime
|
|
8
|
+
|
|
9
|
+
import lark
|
|
10
|
+
|
|
11
|
+
import wirely as blocks
|
|
12
|
+
from wirely.util import check_parameters
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SourcePosition:
|
|
16
|
+
@check_parameters
|
|
17
|
+
def __init__(self, *, filename, meta, input_data):
|
|
18
|
+
self.filename = filename
|
|
19
|
+
self.meta = meta
|
|
20
|
+
self.input_data = input_data
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def start_pos(self):
|
|
24
|
+
# TODO: Handle source elements which were created without any token where e.g. start_pos is not set?
|
|
25
|
+
return self.meta.start_pos
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def start_line(self):
|
|
29
|
+
return self.meta.line
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SourceElement:
|
|
33
|
+
@check_parameters
|
|
34
|
+
def __init__(self, *, source_position: SourcePosition):
|
|
35
|
+
self.source_position = source_position
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
@contextlib.contextmanager
|
|
39
|
+
def context(self):
|
|
40
|
+
exc = None
|
|
41
|
+
try:
|
|
42
|
+
yield None
|
|
43
|
+
except Exception as e:
|
|
44
|
+
exc = e
|
|
45
|
+
if isinstance(exc, ProgramError):
|
|
46
|
+
raise
|
|
47
|
+
if exc is not None:
|
|
48
|
+
raise InternalCompilerError(self, exc)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CompilerHint:
|
|
52
|
+
@check_parameters
|
|
53
|
+
def __init__(self, source_element: SourceElement, msg):
|
|
54
|
+
self.source_element = source_element
|
|
55
|
+
self.msg = msg
|
|
56
|
+
self.fake_ui = lark.exceptions.UnexpectedInput()
|
|
57
|
+
self.fake_ui.pos_in_stream = self.source_element.source_position.start_pos
|
|
58
|
+
|
|
59
|
+
def __str__(self):
|
|
60
|
+
return self.msg
|
|
61
|
+
|
|
62
|
+
def get_context(self):
|
|
63
|
+
return self.fake_ui.get_context(self.source_element.source_position.input_data)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ProgramError(Exception):
|
|
67
|
+
@check_parameters
|
|
68
|
+
def __init__(self, source_element: typing.Optional[SourceElement], msg, inner_exception=None, *, hints: typing.List[CompilerHint] = []):
|
|
69
|
+
# raise Exception()
|
|
70
|
+
self.source_element = source_element
|
|
71
|
+
self.msg = msg
|
|
72
|
+
self.inner_exception = inner_exception
|
|
73
|
+
self.hints = hints
|
|
74
|
+
if self.source_element is not None:
|
|
75
|
+
self.fake_ui = lark.exceptions.UnexpectedInput()
|
|
76
|
+
self.fake_ui.pos_in_stream = self.source_element.source_position.start_pos
|
|
77
|
+
|
|
78
|
+
if self.source_element is None and not isinstance(self, InternalCompilerError):
|
|
79
|
+
raise Exception('self.source_element is None and not isinstance(self, InternalCompilerError)')
|
|
80
|
+
|
|
81
|
+
def __str__(self):
|
|
82
|
+
return self.msg
|
|
83
|
+
|
|
84
|
+
def get_context(self):
|
|
85
|
+
return self.fake_ui.get_context(self.source_element.source_position.input_data)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class InternalCompilerError(ProgramError):
|
|
89
|
+
@check_parameters
|
|
90
|
+
def __init__(self, source_element: typing.Optional[SourceElement], inner_exception, *, msg=None):
|
|
91
|
+
if msg is None:
|
|
92
|
+
msg = 'Internal Compiler Error: ' + str(inner_exception)
|
|
93
|
+
super().__init__(source_element, msg, inner_exception)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class Identifier(SourceElement):
|
|
97
|
+
@check_parameters
|
|
98
|
+
def __init__(self, s: str, *, source_position: SourcePosition):
|
|
99
|
+
super().__init__(source_position=source_position)
|
|
100
|
+
if s.startswith('r#'):
|
|
101
|
+
s = s[2:]
|
|
102
|
+
self.id_str = s
|
|
103
|
+
|
|
104
|
+
def __repr__(self):
|
|
105
|
+
# return f'{self.id_str} [{self.source_position.start_line}]'
|
|
106
|
+
return self.id_str
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class RefType(enum.Enum):
|
|
110
|
+
BlockInput = enum.auto()
|
|
111
|
+
BlockOutput = enum.auto()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# TODO: Merge with RefType?
|
|
115
|
+
class BlockConnectorType(enum.Enum):
|
|
116
|
+
BlockInput = enum.auto()
|
|
117
|
+
BlockOutput = enum.auto()
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class RunStage(enum.Enum):
|
|
121
|
+
Input = enum.auto()
|
|
122
|
+
Default = enum.auto()
|
|
123
|
+
Output = enum.auto()
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class BlockConnectorTypeSpecifier(SourceElement):
|
|
127
|
+
@check_parameters
|
|
128
|
+
def __init__(self, conn_type: BlockConnectorType, *, source_position: SourcePosition):
|
|
129
|
+
super().__init__(source_position=source_position)
|
|
130
|
+
self.conn_type = conn_type
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
@check_parameters
|
|
134
|
+
def create_input(*, source_position: SourcePosition):
|
|
135
|
+
return BlockConnectorTypeSpecifier(BlockConnectorType.BlockInput, source_position=source_position)
|
|
136
|
+
|
|
137
|
+
@staticmethod
|
|
138
|
+
@check_parameters
|
|
139
|
+
def create_output(*, source_position: SourcePosition):
|
|
140
|
+
return BlockConnectorTypeSpecifier(BlockConnectorType.BlockOutput, source_position=source_position)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class ListSpecifier(SourceElement):
|
|
144
|
+
@check_parameters
|
|
145
|
+
def __init__(self, is_list: bool, *, source_position: SourcePosition):
|
|
146
|
+
super().__init__(source_position=source_position)
|
|
147
|
+
self.is_list = is_list
|
|
148
|
+
|
|
149
|
+
@staticmethod
|
|
150
|
+
@check_parameters
|
|
151
|
+
def create_list(*, source_position: SourcePosition):
|
|
152
|
+
return ListSpecifier(True, source_position=source_position)
|
|
153
|
+
|
|
154
|
+
@staticmethod
|
|
155
|
+
@check_parameters
|
|
156
|
+
def create_nolist(*, source_position: SourcePosition):
|
|
157
|
+
return ListSpecifier(False, source_position=source_position)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# TODO: Rename Value to Level or State or keep Value?
|
|
161
|
+
# TODO: Rename Signal to Impulse or Trigger or Event?
|
|
162
|
+
class ConnectionKind(enum.Enum):
|
|
163
|
+
Value = enum.auto()
|
|
164
|
+
Event = enum.auto()
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class ConnectionKindSpecifier(SourceElement):
|
|
168
|
+
@check_parameters
|
|
169
|
+
def __init__(self, kind: ConnectionKind, *, source_position: SourcePosition):
|
|
170
|
+
super().__init__(source_position=source_position)
|
|
171
|
+
self.kind = kind
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
@check_parameters
|
|
175
|
+
def create_value(*, source_position: SourcePosition):
|
|
176
|
+
return ConnectionKindSpecifier(ConnectionKind.Value, source_position=source_position)
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
@check_parameters
|
|
180
|
+
def create_event(*, source_position: SourcePosition):
|
|
181
|
+
return ConnectionKindSpecifier(ConnectionKind.Event, source_position=source_position)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class Value(SourceElement):
|
|
185
|
+
@check_parameters
|
|
186
|
+
def __init__(self, *, source_position: SourcePosition):
|
|
187
|
+
super().__init__(source_position=source_position)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class ConnectorSpecification(SourceElement):
|
|
191
|
+
@check_parameters
|
|
192
|
+
def __init__(self, list_spec: ListSpecifier, *, source_position: SourcePosition):
|
|
193
|
+
super().__init__(source_position=source_position)
|
|
194
|
+
self.list_spec = list_spec
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class ConnectorSpecificationData(ConnectorSpecification):
|
|
198
|
+
@check_parameters
|
|
199
|
+
def __init__(self, list_spec: ListSpecifier, kind_spec: ConnectionKindSpecifier, ty: Value, *, source_position: SourcePosition):
|
|
200
|
+
super().__init__(list_spec=list_spec, source_position=source_position)
|
|
201
|
+
self.kind_spec = kind_spec
|
|
202
|
+
self.ty = ty
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class ConnectorSpecificationKnxBus(ConnectorSpecification):
|
|
206
|
+
@check_parameters
|
|
207
|
+
def __init__(self, list_spec: ListSpecifier, *, source_position: SourcePosition):
|
|
208
|
+
super().__init__(list_spec=list_spec, source_position=source_position)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class ConnectorSpecificationMqttServer(ConnectorSpecification):
|
|
212
|
+
@check_parameters
|
|
213
|
+
def __init__(self, list_spec: ListSpecifier, *, source_position: SourcePosition):
|
|
214
|
+
super().__init__(list_spec=list_spec, source_position=source_position)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class ConnectorSpecificationTwoNDevice(ConnectorSpecification):
|
|
218
|
+
@check_parameters
|
|
219
|
+
def __init__(self, list_spec: ListSpecifier, *, source_position: SourcePosition):
|
|
220
|
+
super().__init__(list_spec=list_spec, source_position=source_position)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class BlockTypeSpecifier(SourceElement):
|
|
224
|
+
@check_parameters
|
|
225
|
+
def __init__(self, specifier_str, *, source_position: SourcePosition):
|
|
226
|
+
super().__init__(source_position=source_position)
|
|
227
|
+
|
|
228
|
+
self.specifier_str = specifier_str
|
|
229
|
+
|
|
230
|
+
@staticmethod
|
|
231
|
+
def create_singleton(*, source_position: SourcePosition):
|
|
232
|
+
return BlockTypeSpecifier(specifier_str='singleton', source_position=source_position)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class NativeSpecifier(BlockTypeSpecifier):
|
|
236
|
+
@check_parameters
|
|
237
|
+
def __init__(self, *, source_position: SourcePosition):
|
|
238
|
+
super().__init__(specifier_str='native', source_position=source_position)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class OptionalSpecifier(BlockTypeSpecifier):
|
|
242
|
+
@check_parameters
|
|
243
|
+
def __init__(self, *, source_position: SourcePosition):
|
|
244
|
+
super().__init__(specifier_str='optional', source_position=source_position)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class IntNumber(Value):
|
|
248
|
+
@check_parameters
|
|
249
|
+
def __init__(self, token: lark.Token, *, source_position: SourcePosition):
|
|
250
|
+
super().__init__(source_position=source_position)
|
|
251
|
+
self.token = token
|
|
252
|
+
self.value = int(token)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class FloatNumber(Value):
|
|
256
|
+
@check_parameters
|
|
257
|
+
def __init__(self, token: lark.Token, *, source_position: SourcePosition):
|
|
258
|
+
super().__init__(source_position=source_position)
|
|
259
|
+
self.token = token
|
|
260
|
+
# TODO: Does this parse the value correctly?
|
|
261
|
+
self.value = float(token)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
class StringConstant(Value):
|
|
265
|
+
@check_parameters
|
|
266
|
+
def __init__(self, token: lark.Token, *, source_position: SourcePosition):
|
|
267
|
+
super().__init__(source_position=source_position)
|
|
268
|
+
self.token = token
|
|
269
|
+
|
|
270
|
+
val = token.value
|
|
271
|
+
# https://doc.rust-lang.org/reference/tokens.html#r-lex.token.literal.str
|
|
272
|
+
# https://doc.rust-lang.org/reference/tokens.html#r-lex.token.literal.str-raw
|
|
273
|
+
if val.startswith('r"'):
|
|
274
|
+
if len(val) < 3 or val[-1] != '"':
|
|
275
|
+
raise Exception(f'Error while getting string constant {val!r}')
|
|
276
|
+
value = val[2:-1]
|
|
277
|
+
if '"' in value:
|
|
278
|
+
# Should never happen, the lexer should not return such strings
|
|
279
|
+
raise Exception('Error while parsing string constant: " in value')
|
|
280
|
+
self.value = value
|
|
281
|
+
# TODO: Also allow different # levels (support also needed in lexer)
|
|
282
|
+
elif val.startswith('r#"'):
|
|
283
|
+
if len(val) < 5 or val[-2:] != '"#':
|
|
284
|
+
raise Exception(f'Error while getting string constant {val!r}')
|
|
285
|
+
value = val[3:-2]
|
|
286
|
+
if '"#' in value:
|
|
287
|
+
# Should never happen, the lexer should not return such strings
|
|
288
|
+
raise Exception('Error while parsing string constant: "# in value')
|
|
289
|
+
self.value = value
|
|
290
|
+
else:
|
|
291
|
+
if len(val) < 2 or val[0] != '"' or val[-1] != '"':
|
|
292
|
+
raise Exception(f'Error while getting string constant {val!r}')
|
|
293
|
+
# TODO: Do this manually and use rust rules instead of python
|
|
294
|
+
self.value = ast.literal_eval(val)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
class Reference(Value):
|
|
298
|
+
@check_parameters
|
|
299
|
+
def __init__(self, identifiers: typing.List[Identifier], *, source_position: SourcePosition):
|
|
300
|
+
super().__init__(source_position=source_position)
|
|
301
|
+
self.identifiers = identifiers
|
|
302
|
+
|
|
303
|
+
@staticmethod
|
|
304
|
+
@check_parameters
|
|
305
|
+
def from_list(*identifiers, source_position: SourcePosition):
|
|
306
|
+
return Reference(list(identifiers), source_position=source_position)
|
|
307
|
+
|
|
308
|
+
@staticmethod
|
|
309
|
+
@check_parameters
|
|
310
|
+
def create_implicit(conn_type_spec: BlockConnectorTypeSpecifier, *, source_position: SourcePosition):
|
|
311
|
+
conn_type = conn_type_spec.conn_type
|
|
312
|
+
# TODO: Clean up
|
|
313
|
+
if conn_type == BlockConnectorType.BlockInput:
|
|
314
|
+
name = 'implicit input'
|
|
315
|
+
elif conn_type == BlockConnectorType.BlockOutput:
|
|
316
|
+
name = 'implicit output'
|
|
317
|
+
else:
|
|
318
|
+
raise Exception(f'Unknown value for conn_type: {conn_type}')
|
|
319
|
+
identifier = Identifier(name, source_position=source_position)
|
|
320
|
+
return Reference([identifier], source_position=source_position)
|
|
321
|
+
|
|
322
|
+
def __repr__(self):
|
|
323
|
+
# TODO: Somehow show which enclosing block a parameter belongs to?
|
|
324
|
+
# return '.'.join([i.id_str for i in self.identifiers]) + f' [{self.source_position.start_line}]'
|
|
325
|
+
return '.'.join([i.id_str for i in self.identifiers])
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def length(self):
|
|
329
|
+
return len(self.identifiers)
|
|
330
|
+
|
|
331
|
+
@check_parameters
|
|
332
|
+
def resolve(self, graph, ref_type: RefType, purpose: str):
|
|
333
|
+
if ref_type in [RefType.BlockInput, RefType.BlockOutput]:
|
|
334
|
+
if self.length == 1:
|
|
335
|
+
block_name_id = self.identifiers[0]
|
|
336
|
+
member_name_id = None
|
|
337
|
+
elif self.length == 2:
|
|
338
|
+
block_name_id = self.identifiers[0]
|
|
339
|
+
member_name_id = self.identifiers[1]
|
|
340
|
+
else:
|
|
341
|
+
raise ProgramError(self, f'Got unexpected number of identifiers in {purpose}: {self}')
|
|
342
|
+
return ResolvedReferenceBlockMember(graph, ref_type, block_name_id, member_name_id, purpose)
|
|
343
|
+
else:
|
|
344
|
+
raise Exception(f'Unknown RefType: {ref_type!r}')
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class ListLiteral(Value):
|
|
348
|
+
@check_parameters
|
|
349
|
+
def __init__(self, values: list[Value], *, source_position: SourcePosition):
|
|
350
|
+
super().__init__(source_position=source_position)
|
|
351
|
+
self.values = values
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
class FunctionCall(Value):
|
|
355
|
+
@check_parameters
|
|
356
|
+
def __init__(self, fun: Value, parameters: list[Value], *, source_position: SourcePosition):
|
|
357
|
+
super().__init__(source_position=source_position)
|
|
358
|
+
self.fun = fun
|
|
359
|
+
self.parameters = parameters
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
class BracketOperator(Value):
|
|
363
|
+
@check_parameters
|
|
364
|
+
def __init__(self, base: Value, parameter: Value, *, source_position: SourcePosition):
|
|
365
|
+
super().__init__(source_position=source_position)
|
|
366
|
+
self.base = base
|
|
367
|
+
self.parameter = parameter
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
# TODO: Remove and merge into Reference?
|
|
371
|
+
class ResolvedReferenceBlockMember(SourceElement):
|
|
372
|
+
@check_parameters
|
|
373
|
+
# TODO: member_name should not be typing.Optional
|
|
374
|
+
def __init__(self, graph, ref_type: RefType, block_name: Identifier, member_name: typing.Optional[Identifier], purpose: str):
|
|
375
|
+
if ref_type == RefType.BlockInput:
|
|
376
|
+
member_type = 'input'
|
|
377
|
+
member_dict_lambda = lambda block: block.block_inputs_byname
|
|
378
|
+
elif ref_type == RefType.BlockOutput:
|
|
379
|
+
member_type = 'output'
|
|
380
|
+
member_dict_lambda = lambda block: block.block_outputs_byname
|
|
381
|
+
else:
|
|
382
|
+
raise Exception(f'Unknown RefType: {ref_type!r}')
|
|
383
|
+
|
|
384
|
+
self.block_name = block_name
|
|
385
|
+
self.member_name = member_name
|
|
386
|
+
|
|
387
|
+
self.block_name_str = self.block_name.id_str
|
|
388
|
+
if self.member_name is None:
|
|
389
|
+
# TODO: Clean up
|
|
390
|
+
if ref_type == RefType.BlockInput:
|
|
391
|
+
# self.member_name_str = 'implicit input'
|
|
392
|
+
self.member_name_str = ''
|
|
393
|
+
elif ref_type == RefType.BlockOutput:
|
|
394
|
+
# self.member_name_str = 'implicit output'
|
|
395
|
+
self.member_name_str = ''
|
|
396
|
+
else:
|
|
397
|
+
raise Exception(f'Unknown value for ref_type: {ref_type}')
|
|
398
|
+
# TODO: Remove member_name_elem (Hack)
|
|
399
|
+
self.member_name_elem = block_name
|
|
400
|
+
else:
|
|
401
|
+
self.member_name_str = self.member_name.id_str
|
|
402
|
+
self.member_name_elem = self.member_name
|
|
403
|
+
|
|
404
|
+
# print(ref_type, repr(self.block_name_str), repr(self.member_name_str))
|
|
405
|
+
# TODO: Should this be here?
|
|
406
|
+
if ref_type == RefType.BlockInput:
|
|
407
|
+
if self.block_name_str in graph.block_outputs and self.member_name_str == '':
|
|
408
|
+
self.member = graph.block_outputs[self.block_name_str]
|
|
409
|
+
# TODO: This is a hack
|
|
410
|
+
return
|
|
411
|
+
if ref_type == RefType.BlockOutput:
|
|
412
|
+
if self.block_name_str in graph.block_inputs and self.member_name_str == '':
|
|
413
|
+
self.member = graph.block_inputs[self.block_name_str]
|
|
414
|
+
# TODO: This is a hack
|
|
415
|
+
return
|
|
416
|
+
if self.block_name_str in graph.block_state_variables and self.member_name_str == '':
|
|
417
|
+
self.member = graph.block_state_variables[self.block_name_str]
|
|
418
|
+
# TODO: This is a hack
|
|
419
|
+
return
|
|
420
|
+
if self.block_name_str in graph.block_temporary_variables and self.member_name_str == '':
|
|
421
|
+
self.member = graph.block_state_variables[self.block_name_str]
|
|
422
|
+
# TODO: This is a hack
|
|
423
|
+
return
|
|
424
|
+
|
|
425
|
+
if self.block_name_str not in graph.blocks:
|
|
426
|
+
raise ProgramError(self.block_name, f'Unable to find {purpose} block {self.block_name_str!r}')
|
|
427
|
+
self.block = graph.blocks[self.block_name_str]
|
|
428
|
+
member_dict = member_dict_lambda(self.block)
|
|
429
|
+
if self.member_name_str in member_dict:
|
|
430
|
+
self.member = member_dict[self.member_name_str]
|
|
431
|
+
# TODO: Clean this up. This falls back to looking for BlockInput / BlockOutput nodes because knx_bus connectors don't create Block.Input and Block.Output objects
|
|
432
|
+
else:
|
|
433
|
+
if self.member_name_str == '':
|
|
434
|
+
raise ProgramError(self.block_name, f'{purpose} block {self.block_name_str} does not have an implicit {member_type}')
|
|
435
|
+
else:
|
|
436
|
+
raise ProgramError(self.member_name_elem, f'Unable to find {purpose} block {member_type} {self.block_name_str}.{self.member_name_str}')
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
class Declaration(SourceElement, abc.ABC):
|
|
440
|
+
@check_parameters
|
|
441
|
+
def __init__(self, source_position: SourcePosition):
|
|
442
|
+
super().__init__(source_position=source_position)
|
|
443
|
+
|
|
444
|
+
# TODO: Move implementation here?
|
|
445
|
+
@abc.abstractmethod
|
|
446
|
+
def compile_pass1_btd(self, interface):
|
|
447
|
+
...
|
|
448
|
+
|
|
449
|
+
@abc.abstractmethod
|
|
450
|
+
def create_io(self, new_graph):
|
|
451
|
+
...
|
|
452
|
+
|
|
453
|
+
@abc.abstractmethod
|
|
454
|
+
def compile_pass1(self, graph):
|
|
455
|
+
...
|
|
456
|
+
|
|
457
|
+
@abc.abstractmethod
|
|
458
|
+
def compile_pass1_5(self, graph):
|
|
459
|
+
...
|
|
460
|
+
|
|
461
|
+
@abc.abstractmethod
|
|
462
|
+
def compile_pass2(self, graph):
|
|
463
|
+
...
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
# This class contains methods for resolving references in a scope
|
|
467
|
+
class Scope(Declaration):
|
|
468
|
+
@check_parameters
|
|
469
|
+
def __init__(self, source_position: SourcePosition):
|
|
470
|
+
super().__init__(source_position=source_position)
|
|
471
|
+
|
|
472
|
+
# TODO: Remove one of block_type_interfaces / block_type_implementations
|
|
473
|
+
self.block_type_interfaces = {}
|
|
474
|
+
self.block_type_implementations = {}
|
|
475
|
+
|
|
476
|
+
# TODO: Move to Declaration?
|
|
477
|
+
@property
|
|
478
|
+
@abc.abstractmethod
|
|
479
|
+
def fullname(self):
|
|
480
|
+
...
|
|
481
|
+
|
|
482
|
+
# TODO: Handle parameters for parent types properly? Does this even make sense?
|
|
483
|
+
def resolve_type(self, ty, par_values={}):
|
|
484
|
+
with ty.context:
|
|
485
|
+
# TODO: Clean up
|
|
486
|
+
if isinstance(ty, BracketOperator):
|
|
487
|
+
if not isinstance(ty.base, Reference):
|
|
488
|
+
raise blocks.ProgramError(ty, f'Expected Reference as generic type base, got {ty}')
|
|
489
|
+
if ty.base.length == 1 and ty.base.identifiers[0].id_str == 'Option':
|
|
490
|
+
gen_type = blocks.types.TypeOption
|
|
491
|
+
elif ty.base.length == 1 and ty.base.identifiers[0].id_str == 'List':
|
|
492
|
+
gen_type = blocks.types.TypeList
|
|
493
|
+
else:
|
|
494
|
+
raise blocks.ProgramError(ty, f'Cannot find generic type {ty.base}')
|
|
495
|
+
# if len(ty.parameters) != 1:
|
|
496
|
+
# raise blocks.ProgramError(ty, f'Expected 1 type parameter, got {len(ty.parameters)}: {ty}')
|
|
497
|
+
par_type = self.resolve_type(ty.parameter, par_values=par_values)
|
|
498
|
+
return gen_type(par_type)
|
|
499
|
+
elif not isinstance(ty, Reference):
|
|
500
|
+
raise blocks.ProgramError(ty, f'Expected Reference or BracketOperator as type name, got {ty}')
|
|
501
|
+
|
|
502
|
+
# TODO: Move somewhere else, clean up
|
|
503
|
+
if ty.length == 1:
|
|
504
|
+
name = ty.identifiers[0].id_str
|
|
505
|
+
# TODO: Avoid hasattr
|
|
506
|
+
if hasattr(self, 'parsed_parameters') and name in self.parsed_parameters:
|
|
507
|
+
par = self.parsed_parameters[name]
|
|
508
|
+
# TODO
|
|
509
|
+
# if par.ty == blocks.types.ValueType:
|
|
510
|
+
if par.ty.length == 1 and par.ty.identifiers[0].id_str == 'ValueType':
|
|
511
|
+
# TODO: Handle par_values=None?
|
|
512
|
+
# TODO: More checks?
|
|
513
|
+
if par not in par_values:
|
|
514
|
+
# TODO: Should TypeParameter creation happen here?
|
|
515
|
+
return blocks.TypeParameter(par)
|
|
516
|
+
return par_values[par]
|
|
517
|
+
if name in blocks.types.type_by_name:
|
|
518
|
+
return blocks.types.type_by_name[name]
|
|
519
|
+
raise blocks.ProgramError(ty, f'Cannot find type {ty}')
|
|
520
|
+
|
|
521
|
+
# TODO: Should this be named get_const_value()? (Also returns e.g. ValueFunctionCall)
|
|
522
|
+
def get_const_value(self, value, value_type, par_values={}):
|
|
523
|
+
with value.context:
|
|
524
|
+
if isinstance(value, blocks.FunctionCall):
|
|
525
|
+
if not isinstance(value.fun, blocks.Reference) or len(value.fun.identifiers) != 1:
|
|
526
|
+
raise blocks.ProgramError(value.fun, f'Function is not a single identifier')
|
|
527
|
+
function_name = value.fun.identifiers[0].id_str
|
|
528
|
+
if function_name not in blocks.value.functions:
|
|
529
|
+
raise blocks.ProgramError(value.fun, f'Cannot find function {function_name!r}')
|
|
530
|
+
function = blocks.value.functions[function_name]
|
|
531
|
+
if len(value.parameters) != len(function.parameter_types):
|
|
532
|
+
raise blocks.ProgramError(value, f'Function {function_name!r} expected {len(function.parameter_types)} parameters, but got {len(value.parameters)}')
|
|
533
|
+
parameters = [self.get_const_value(parameter, function.parameter_types[i], par_values=par_values) for i, parameter in enumerate(value.parameters)]
|
|
534
|
+
return blocks.value.ValueFunctionCall(function=function, parameters=parameters, ast_element=value)
|
|
535
|
+
|
|
536
|
+
# TODO: Clean up
|
|
537
|
+
found = True
|
|
538
|
+
while found:
|
|
539
|
+
found = False
|
|
540
|
+
if isinstance(value, blocks.Reference) and value.length == 1:
|
|
541
|
+
name = value.identifiers[0].id_str
|
|
542
|
+
if name in self.parsed_parameters:
|
|
543
|
+
par = self.parsed_parameters[name]
|
|
544
|
+
if not(par.ty.length == 1 and par.ty.identifiers[0].id_str == 'ValueType'):
|
|
545
|
+
# value = par_values[name]
|
|
546
|
+
# found = True
|
|
547
|
+
# print(f'{par.name} in {par_values}')
|
|
548
|
+
# TODO: Clean up / Check whether this really is a parameter
|
|
549
|
+
if par not in par_values:
|
|
550
|
+
return blocks.ValueParameter(value_type, par, ast_element=value)
|
|
551
|
+
return blocks.ConstantValue(value_type, value_type.verify_value(par_values[par].value), ast_element=value)
|
|
552
|
+
return blocks.ConstantValue(value_type, value_type.parse_value(value), ast_element=value)
|
|
553
|
+
# raise blocks.ProgramError(value, f'Cannot turn {value!r} into constant value of type {value_type}')
|
|
554
|
+
|
|
555
|
+
def resolve_block_type(self, name, par_values={}):
|
|
556
|
+
# print(self, 'resolve_block_type', name)
|
|
557
|
+
|
|
558
|
+
if name.length >= 2:
|
|
559
|
+
library = self.libraries.get(name.identifiers[0].id_str)
|
|
560
|
+
for identifier in name.identifiers[1:-1]:
|
|
561
|
+
if library is None:
|
|
562
|
+
break
|
|
563
|
+
library = library.libraries.get(identifier.id_str)
|
|
564
|
+
if library is not None:
|
|
565
|
+
block_name = name.identifiers[-1].id_str
|
|
566
|
+
if block_name in library.block_type_implementations:
|
|
567
|
+
return library.block_type_implementations[block_name]
|
|
568
|
+
|
|
569
|
+
if name.length == 1:
|
|
570
|
+
block_type_name_id = name.identifiers[0]
|
|
571
|
+
|
|
572
|
+
if block_type_name_id.id_str in self.block_type_implementations:
|
|
573
|
+
return self.block_type_implementations[block_type_name_id.id_str]
|
|
574
|
+
|
|
575
|
+
if self.parent_graph is None:
|
|
576
|
+
raise blocks.ProgramError(name, f'Cannot find block type {name}')
|
|
577
|
+
return self.parent_graph.resolve_block_type(name, par_values=par_values)
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
class DeclarationConnector(Declaration):
|
|
581
|
+
@check_parameters
|
|
582
|
+
def __init__(self, name: typing.Optional[Identifier], conn_type_spec: BlockConnectorTypeSpecifier, conn_spec: ConnectorSpecification, *, source_position: SourcePosition):
|
|
583
|
+
super().__init__(source_position=source_position)
|
|
584
|
+
self.name = name
|
|
585
|
+
self.conn_type_spec = conn_type_spec
|
|
586
|
+
self.conn_spec = conn_spec
|
|
587
|
+
|
|
588
|
+
# TODO: Remove?
|
|
589
|
+
self.list_spec = self.conn_spec.list_spec
|
|
590
|
+
if isinstance(self.conn_spec, ConnectorSpecificationData):
|
|
591
|
+
self.ty = self.conn_spec.ty
|
|
592
|
+
self.kind_spec = self.conn_spec.kind_spec
|
|
593
|
+
|
|
594
|
+
self.kind = self.kind_spec.kind
|
|
595
|
+
|
|
596
|
+
self.conn_type = self.conn_type_spec.conn_type
|
|
597
|
+
|
|
598
|
+
if self.list_spec.is_list and self.conn_type != BlockConnectorType.BlockInput:
|
|
599
|
+
raise ProgramError(self, f'Got list specifier for non-input')
|
|
600
|
+
|
|
601
|
+
if self.name is None:
|
|
602
|
+
if self.conn_type == BlockConnectorType.BlockInput:
|
|
603
|
+
self.name_str = 'implicit input'
|
|
604
|
+
elif self.conn_type == BlockConnectorType.BlockOutput:
|
|
605
|
+
self.name_str = 'implicit output'
|
|
606
|
+
else:
|
|
607
|
+
raise Exception(f'Unknown value for conn_type: {self.conn_type}')
|
|
608
|
+
else:
|
|
609
|
+
self.name_str = self.name.id_str
|
|
610
|
+
|
|
611
|
+
@staticmethod
|
|
612
|
+
@check_parameters
|
|
613
|
+
def create(conn_type_spec: BlockConnectorTypeSpecifier, name: Identifier, conn_spec : ConnectorSpecification, *, source_position: SourcePosition):
|
|
614
|
+
return DeclarationConnector(name, conn_spec=conn_spec, conn_type_spec=conn_type_spec, source_position=source_position)
|
|
615
|
+
|
|
616
|
+
@staticmethod
|
|
617
|
+
@check_parameters
|
|
618
|
+
def create_implicit(conn_type_spec: BlockConnectorTypeSpecifier, conn_spec : ConnectorSpecification, *, source_position: SourcePosition):
|
|
619
|
+
return DeclarationConnector(None, conn_spec=conn_spec, conn_type_spec=conn_type_spec, source_position=source_position)
|
|
620
|
+
|
|
621
|
+
@check_parameters
|
|
622
|
+
def compile_pass1_btd(self, interface):
|
|
623
|
+
self.interface = interface
|
|
624
|
+
|
|
625
|
+
if self.interface.is_singleton and self.conn_type_spec.conn_type == BlockConnectorType.BlockInput:
|
|
626
|
+
raise blocks.ProgramError(self, f'Singleton block cannot have inputs')
|
|
627
|
+
|
|
628
|
+
def create_io(self, new_graph):
|
|
629
|
+
# TODO: Use self.kind ConnectionKind
|
|
630
|
+
# TODO: Pass par_values to resolve_type?
|
|
631
|
+
# TODO: Should the entires conn_spec instance be passed?
|
|
632
|
+
if isinstance(self.conn_spec, ConnectorSpecificationData):
|
|
633
|
+
ty = self.interface.resolve_type(self.ty)
|
|
634
|
+
else:
|
|
635
|
+
ty = None
|
|
636
|
+
if self.conn_type == BlockConnectorType.BlockInput:
|
|
637
|
+
name = self.name_str
|
|
638
|
+
if name == '':
|
|
639
|
+
name = 'implicit input'
|
|
640
|
+
blocks.BlockInput(new_graph, ty, name=name, input_name=self.name_str, is_list=self.list_spec.is_list, conn_spec=self.conn_spec)
|
|
641
|
+
elif self.conn_type == BlockConnectorType.BlockOutput:
|
|
642
|
+
name = self.name_str
|
|
643
|
+
if name == '':
|
|
644
|
+
name = 'implicit output'
|
|
645
|
+
blocks.BlockOutput(new_graph, ty, name=name, output_name=self.name_str, conn_spec=self.conn_spec)
|
|
646
|
+
else:
|
|
647
|
+
raise Exception(f'Unknown conn_type: {self.conn_type!r}')
|
|
648
|
+
|
|
649
|
+
# TODO: Clean up compilation passes
|
|
650
|
+
|
|
651
|
+
def compile_pass1(self, graph):
|
|
652
|
+
pass
|
|
653
|
+
|
|
654
|
+
def compile_pass1_5(self, graph):
|
|
655
|
+
pass
|
|
656
|
+
|
|
657
|
+
def compile_pass2(self, graph):
|
|
658
|
+
pass
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
class DeclarationParameter(Declaration):
|
|
662
|
+
@check_parameters
|
|
663
|
+
def __init__(self, optional_specifier: typing.Optional[OptionalSpecifier], name: Identifier, ty, *, default_value=None, source_position: SourcePosition):
|
|
664
|
+
super().__init__(source_position=source_position)
|
|
665
|
+
self.name = name
|
|
666
|
+
self.ty = ty
|
|
667
|
+
self.optional_specifier = optional_specifier
|
|
668
|
+
self.default_value = default_value
|
|
669
|
+
|
|
670
|
+
if self.optional_specifier is not None and self.default_value is not None:
|
|
671
|
+
raise Exception('self.optional_specifier is not None and self.default_value is not None')
|
|
672
|
+
|
|
673
|
+
self.is_optional = self.optional_specifier is not None or self.default_value is not None
|
|
674
|
+
|
|
675
|
+
@staticmethod
|
|
676
|
+
def with_default_value(name: Identifier, ty, default_value, *, source_position: SourcePosition):
|
|
677
|
+
return DeclarationParameter(optional_specifier=None, name=name, ty=ty, default_value=default_value, source_position=source_position)
|
|
678
|
+
|
|
679
|
+
@check_parameters
|
|
680
|
+
def compile_pass1_btd(self, interface):
|
|
681
|
+
self.interface = interface
|
|
682
|
+
|
|
683
|
+
def create_io(self, new_graph):
|
|
684
|
+
pass
|
|
685
|
+
|
|
686
|
+
def resolve_type(self):
|
|
687
|
+
if hasattr(self, 'type_resolved'):
|
|
688
|
+
raise Exception("hasattr(self, 'type_resolved')")
|
|
689
|
+
|
|
690
|
+
# TODO: Is this correct?
|
|
691
|
+
self.type_resolved = self.interface.resolve_type(self.ty)
|
|
692
|
+
|
|
693
|
+
def __repr__(self):
|
|
694
|
+
# return f'Parameter <{self.name}> [{self.name.source_position.start_line}] : <{self.ty}>'
|
|
695
|
+
if not hasattr(self, 'interface'):
|
|
696
|
+
# return f'Parameter <(unknown block).{self.name}> : <{self.ty}>'
|
|
697
|
+
return f'(unknown block).{self.name}'
|
|
698
|
+
else:
|
|
699
|
+
return f'{self.interface.name}.{self.name}'
|
|
700
|
+
|
|
701
|
+
# TODO: Clean up compilation passes
|
|
702
|
+
|
|
703
|
+
def compile_pass1(self, graph):
|
|
704
|
+
pass
|
|
705
|
+
|
|
706
|
+
def compile_pass1_5(self, graph):
|
|
707
|
+
pass
|
|
708
|
+
|
|
709
|
+
def compile_pass2(self, graph):
|
|
710
|
+
pass
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
class BlockParameter(SourceElement):
|
|
714
|
+
@check_parameters
|
|
715
|
+
def __init__(self, name: Identifier, value: Value, *, source_position: SourcePosition):
|
|
716
|
+
super().__init__(source_position=source_position)
|
|
717
|
+
self.name = name
|
|
718
|
+
self.value = value
|
|
719
|
+
|
|
720
|
+
def __repr__(self):
|
|
721
|
+
return f'Parameter <{self.name}> = <{self.value}>'
|
|
722
|
+
# return f'Parameter <{self.name}> [{self.name.source_position.start_line}] = <{self.value}>'
|
|
723
|
+
|
|
724
|
+
|
|
725
|
+
class BlockInstantiation(SourceElement):
|
|
726
|
+
@check_parameters
|
|
727
|
+
def __init__(self, block_class_name: Reference, parameters: typing.List[BlockParameter]=[], *, source_position: SourcePosition):
|
|
728
|
+
super().__init__(source_position=source_position)
|
|
729
|
+
self.block_class_name = block_class_name
|
|
730
|
+
self.parameters = parameters
|
|
731
|
+
|
|
732
|
+
|
|
733
|
+
class DeclarationBlockInstantiation(Declaration):
|
|
734
|
+
@check_parameters
|
|
735
|
+
def __init__(self, name: Identifier, instantiation: BlockInstantiation, *, source_position: SourcePosition):
|
|
736
|
+
super().__init__(source_position=source_position)
|
|
737
|
+
self.name = name
|
|
738
|
+
self.instantiation = instantiation
|
|
739
|
+
|
|
740
|
+
def compile_pass1(self, graph):
|
|
741
|
+
pass
|
|
742
|
+
|
|
743
|
+
def compile_pass1_5(self, graph):
|
|
744
|
+
name = self.name
|
|
745
|
+
inst = self.instantiation
|
|
746
|
+
|
|
747
|
+
self.block_name = name.id_str
|
|
748
|
+
block_arg = inst.parameters
|
|
749
|
+
|
|
750
|
+
self.block_type = graph.resolve_block_type(inst.block_class_name)
|
|
751
|
+
# print(self.block_name, self.block_type, block_arg)
|
|
752
|
+
|
|
753
|
+
# TODO: Is this needed?
|
|
754
|
+
# self.block_type.compile_pass1_5_if_needed()
|
|
755
|
+
|
|
756
|
+
self.par_values = {}
|
|
757
|
+
for parameter in block_arg:
|
|
758
|
+
key = parameter.name
|
|
759
|
+
value = parameter.value
|
|
760
|
+
|
|
761
|
+
if key.id_str not in self.block_type.parameters_by_name:
|
|
762
|
+
raise ProgramError(key, f'Unknown parameter for {self.block_type.name.id_str}: {key}')
|
|
763
|
+
par = self.block_type.parameters_by_name[key.id_str]
|
|
764
|
+
par_type = par.type_resolved.resolve_self(self.par_values)
|
|
765
|
+
# TODO: Is passing par_values=self.par_values here the correct thing to do?
|
|
766
|
+
if par_type == blocks.types.ValueType:
|
|
767
|
+
ty = graph.resolve_type(parameter.value, par_values=self.par_values)
|
|
768
|
+
self.par_values[par] = ty
|
|
769
|
+
else:
|
|
770
|
+
val = graph.get_const_value(value, par_type, par_values=self.par_values)
|
|
771
|
+
self.par_values[par] = val
|
|
772
|
+
|
|
773
|
+
def compile_pass2(self, graph):
|
|
774
|
+
block = self.block_type.create(graph=graph, par_values=self.par_values, name=self.block_name, instantiation=self)
|
|
775
|
+
|
|
776
|
+
# TODO: Clean up compilation passes
|
|
777
|
+
|
|
778
|
+
def compile_pass1_btd(self, interface):
|
|
779
|
+
pass
|
|
780
|
+
|
|
781
|
+
def create_io(self, new_graph):
|
|
782
|
+
pass
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
class DeclarationConnection(Declaration):
|
|
786
|
+
@check_parameters
|
|
787
|
+
def __init__(self, sources: typing.List[Reference], target: Reference, *, source_position: SourcePosition):
|
|
788
|
+
super().__init__(source_position=source_position)
|
|
789
|
+
self.sources = sources
|
|
790
|
+
self.target = target
|
|
791
|
+
|
|
792
|
+
def compile_pass1(self, graph):
|
|
793
|
+
pass
|
|
794
|
+
|
|
795
|
+
def compile_pass1_5(self, graph):
|
|
796
|
+
pass
|
|
797
|
+
|
|
798
|
+
def compile_pass2(self, graph):
|
|
799
|
+
sources = self.sources
|
|
800
|
+
target = self.target
|
|
801
|
+
|
|
802
|
+
# print(source_block_name, source_output_name, target_block_name, target_input_name)
|
|
803
|
+
res_sources = [source.resolve(graph, RefType.BlockOutput, 'source') for source in sources]
|
|
804
|
+
|
|
805
|
+
res_target = target.resolve(graph, RefType.BlockInput, 'target')
|
|
806
|
+
|
|
807
|
+
# TODO: Disallow multiple connect statements to a list input?
|
|
808
|
+
with self.context:
|
|
809
|
+
for source in res_sources:
|
|
810
|
+
blocks.connect(source.member, res_target.member, self)
|
|
811
|
+
|
|
812
|
+
# TODO: Clean up compilation passes
|
|
813
|
+
|
|
814
|
+
def compile_pass1_btd(self, interface):
|
|
815
|
+
pass
|
|
816
|
+
|
|
817
|
+
def create_io(self, new_graph):
|
|
818
|
+
pass
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
class DeclarationStateVariable(Declaration):
|
|
822
|
+
@check_parameters
|
|
823
|
+
def __init__(self, name: Identifier, var_type: Value, initial_value: Value, *, source_position: SourcePosition):
|
|
824
|
+
super().__init__(source_position=source_position)
|
|
825
|
+
self.name = name
|
|
826
|
+
self.var_type = var_type
|
|
827
|
+
self.initial_value = initial_value
|
|
828
|
+
|
|
829
|
+
self.name_str = self.name.id_str
|
|
830
|
+
|
|
831
|
+
def compile_pass1(self, graph):
|
|
832
|
+
name = self.name.id_str
|
|
833
|
+
if name in graph.variables:
|
|
834
|
+
hints = [
|
|
835
|
+
CompilerHint(graph.variables[name], 'Previous variable was here'),
|
|
836
|
+
]
|
|
837
|
+
raise ProgramError(self, f'Duplicate variable name {name} in {graph}', hints=hints)
|
|
838
|
+
graph.variables[name] = self
|
|
839
|
+
|
|
840
|
+
def compile_pass1_5(self, graph):
|
|
841
|
+
pass
|
|
842
|
+
|
|
843
|
+
def compile_pass2(self, graph):
|
|
844
|
+
pass
|
|
845
|
+
|
|
846
|
+
# TODO: Clean up compilation passes
|
|
847
|
+
|
|
848
|
+
def compile_pass1_btd(self, interface):
|
|
849
|
+
self.interface = interface
|
|
850
|
+
|
|
851
|
+
def create_io(self, new_graph):
|
|
852
|
+
ty = self.interface.resolve_type(self.var_type)
|
|
853
|
+
# TODO: Resolve initial_value
|
|
854
|
+
blocks.BlockStateVariable(new_graph, ty, name=self.name_str, initial_value=self.initial_value)
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
class DeclarationTemporaryVariable(Declaration):
|
|
858
|
+
@check_parameters
|
|
859
|
+
def __init__(self, name: Identifier, var_type: Reference, *, source_position: SourcePosition):
|
|
860
|
+
super().__init__(source_position=source_position)
|
|
861
|
+
self.name = name
|
|
862
|
+
self.var_type = var_type
|
|
863
|
+
|
|
864
|
+
self.name_str = self.name.id_str
|
|
865
|
+
|
|
866
|
+
def compile_pass1(self, graph):
|
|
867
|
+
name = self.name.id_str
|
|
868
|
+
if name in graph.variables:
|
|
869
|
+
hints = [
|
|
870
|
+
CompilerHint(graph.variables[name], 'Previous variable was here'),
|
|
871
|
+
]
|
|
872
|
+
raise ProgramError(self, f'Duplicate variable name {name} in {graph}', hints=hints)
|
|
873
|
+
graph.variables[name] = self
|
|
874
|
+
|
|
875
|
+
def compile_pass1_5(self, graph):
|
|
876
|
+
pass
|
|
877
|
+
|
|
878
|
+
def compile_pass2(self, graph):
|
|
879
|
+
pass
|
|
880
|
+
|
|
881
|
+
# TODO: Clean up compilation passes
|
|
882
|
+
|
|
883
|
+
def compile_pass1_btd(self, interface):
|
|
884
|
+
self.interface = interface
|
|
885
|
+
|
|
886
|
+
def create_io(self, new_graph):
|
|
887
|
+
ty = self.interface.resolve_type(self.var_type)
|
|
888
|
+
blocks.BlockTemporaryVariable(new_graph, ty, name=self.name_str)
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
class StageDeclaration(SourceElement):
|
|
892
|
+
@check_parameters
|
|
893
|
+
def __init__(self, stage: RunStage, source_position: SourcePosition):
|
|
894
|
+
super().__init__(source_position=source_position)
|
|
895
|
+
self.stage = stage
|
|
896
|
+
|
|
897
|
+
@staticmethod
|
|
898
|
+
def create_input(*, source_position: SourcePosition):
|
|
899
|
+
return StageDeclaration(stage=RunStage.Input, source_position=source_position)
|
|
900
|
+
|
|
901
|
+
@staticmethod
|
|
902
|
+
def create_default(*, source_position: SourcePosition):
|
|
903
|
+
return StageDeclaration(stage=RunStage.Default, source_position=source_position)
|
|
904
|
+
|
|
905
|
+
@staticmethod
|
|
906
|
+
def create_output(*, source_position: SourcePosition):
|
|
907
|
+
return StageDeclaration(stage=RunStage.Output, source_position=source_position)
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
class DependsDeclaration(SourceElement):
|
|
911
|
+
# TODO: Allow dependencies on the old and/or new values of state variables
|
|
912
|
+
# Should dependencies on old state variables be gathered automatically?
|
|
913
|
+
# (Similar to a dependency on the current clock value, i.e. that the dependency is recorded while running, because it cannot affect execution order.)
|
|
914
|
+
# Then dependencies on the new values of state variables would not need a '.new', only when using the new/old values of state variables this would have to be distinguished somehow.
|
|
915
|
+
# Or: In "depends on" and "produces" .new is implicit, for "connect" and using in procedures ".new" and ".old" is needed.
|
|
916
|
+
|
|
917
|
+
@check_parameters
|
|
918
|
+
def __init__(self, *dependencies: typing.List[Reference], source_position: SourcePosition):
|
|
919
|
+
super().__init__(source_position=source_position)
|
|
920
|
+
self.dependencies = dependencies
|
|
921
|
+
|
|
922
|
+
self.resolved_dependencies_value = None
|
|
923
|
+
self.resolved_dependencies_value_in_process = False
|
|
924
|
+
|
|
925
|
+
def compile_pass1(self, graph, proc):
|
|
926
|
+
self.graph = graph
|
|
927
|
+
self.proc = proc
|
|
928
|
+
|
|
929
|
+
# Trigger resolution of dependencies and return them
|
|
930
|
+
def get_resolved_dependencies(self):
|
|
931
|
+
if self.resolved_dependencies_value is not None:
|
|
932
|
+
return self.resolved_dependencies_value
|
|
933
|
+
if self.resolved_dependencies_value_in_process:
|
|
934
|
+
raise Exception('self.resolved_dependencies_value_in_process')
|
|
935
|
+
self.resolved_dependencies_value_in_process = True
|
|
936
|
+
|
|
937
|
+
resolved = []
|
|
938
|
+
for dep in self.dependencies:
|
|
939
|
+
if dep.length != 1:
|
|
940
|
+
raise ProgramError(dep, f'Dependency does not have exactly one component')
|
|
941
|
+
identifier = dep.identifiers[0]
|
|
942
|
+
name = identifier.id_str
|
|
943
|
+
# TODO: Should not be done using blocks
|
|
944
|
+
if name in self.graph.blocks:
|
|
945
|
+
res = self.graph.blocks[name]
|
|
946
|
+
elif name in self.graph.variables:
|
|
947
|
+
# TODO: Allow state variables here?
|
|
948
|
+
res = self.graph.variables[name]
|
|
949
|
+
elif name in self.graph.block_inputs:
|
|
950
|
+
res = self.graph.block_inputs[name]
|
|
951
|
+
else:
|
|
952
|
+
raise ProgramError(dep, f'Could not find dependency')
|
|
953
|
+
resolved.append(res)
|
|
954
|
+
|
|
955
|
+
self.resolved_dependencies_value = resolved
|
|
956
|
+
return self.resolved_dependencies_value
|
|
957
|
+
|
|
958
|
+
def compile_pass2(self, graph, proc):
|
|
959
|
+
# Make sure get_resolved_dependencies() is executed at some point
|
|
960
|
+
self.get_resolved_dependencies()
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
class ProducesDeclaration(SourceElement):
|
|
964
|
+
@check_parameters
|
|
965
|
+
def __init__(self, *outputs: typing.List[Reference], source_position: SourcePosition):
|
|
966
|
+
super().__init__(source_position=source_position)
|
|
967
|
+
self.outputs = outputs
|
|
968
|
+
|
|
969
|
+
self.resolved_outputs_value = None
|
|
970
|
+
self.resolved_outputs_value_in_process = False
|
|
971
|
+
|
|
972
|
+
def compile_pass1(self, graph, proc):
|
|
973
|
+
self.graph = graph
|
|
974
|
+
self.proc = proc
|
|
975
|
+
|
|
976
|
+
def get_resolved_outputs(self):
|
|
977
|
+
if self.resolved_outputs_value is not None:
|
|
978
|
+
return self.resolved_outputs_value
|
|
979
|
+
if self.resolved_outputs_value_in_process:
|
|
980
|
+
raise Exception('self.resolved_outputs_value_in_process')
|
|
981
|
+
self.resolved_outputs_value_in_process = True
|
|
982
|
+
|
|
983
|
+
resolved = []
|
|
984
|
+
for out in self.outputs:
|
|
985
|
+
if out.length != 1:
|
|
986
|
+
raise ProgramError(out, f'Output does not have exactly one component')
|
|
987
|
+
identifier = out.identifiers[0]
|
|
988
|
+
name = identifier.id_str
|
|
989
|
+
# TODO: Should not be done using blocks
|
|
990
|
+
if name in self.graph.blocks:
|
|
991
|
+
res = self.graph.blocks[name]
|
|
992
|
+
elif name in self.graph.variables:
|
|
993
|
+
res = self.graph.variables[name]
|
|
994
|
+
elif name in self.graph.block_outputs:
|
|
995
|
+
res = self.graph.block_outputs[name]
|
|
996
|
+
else:
|
|
997
|
+
raise ProgramError(out, f'Could not find output')
|
|
998
|
+
resolved.append(res)
|
|
999
|
+
|
|
1000
|
+
self.resolved_outputs_value = resolved
|
|
1001
|
+
return self.resolved_outputs_value
|
|
1002
|
+
|
|
1003
|
+
def compile_pass2(self, graph, proc):
|
|
1004
|
+
# Make sure get_resolved_outputs() is executed at some point
|
|
1005
|
+
self.get_resolved_outputs()
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
class DeclarationProcedure(Declaration):
|
|
1009
|
+
@check_parameters
|
|
1010
|
+
def __init__(self, native_spec: typing.Optional[NativeSpecifier], name: Identifier, stage: StageDeclaration, depends: DependsDeclaration, produces: ProducesDeclaration, *, source_position: SourcePosition):
|
|
1011
|
+
super().__init__(source_position=source_position)
|
|
1012
|
+
self.is_native = native_spec is not None
|
|
1013
|
+
self.name = name
|
|
1014
|
+
self.stage = stage
|
|
1015
|
+
self.depends = depends
|
|
1016
|
+
self.produces = produces
|
|
1017
|
+
|
|
1018
|
+
if self.stage.stage == RunStage.Input and len(self.depends.dependencies) > 0:
|
|
1019
|
+
# TODO: Allow this?
|
|
1020
|
+
hints = [
|
|
1021
|
+
CompilerHint(self.depends, 'Dependencies declared here'),
|
|
1022
|
+
]
|
|
1023
|
+
raise ProgramError(self.stage, f'Procedure name {self.name} is declared as input stage but depends on something else', hints=hints)
|
|
1024
|
+
|
|
1025
|
+
# TODO: Allow this? In which cases? Does this need checks that the outputs are not used in earlier stages?
|
|
1026
|
+
# Currently this is used to update a state variable after writing to a CSV file. The new value for the state variable might depend on the result (success or not) of writing to the file. Probably at least updates to state variables should be allowed in the output stage.
|
|
1027
|
+
# Disallowing this would make it possible to save the state variables before the output stage (and therefore before any results of the current step become externally visible). Or state variables could be saved twice, once before and once after the output stage.
|
|
1028
|
+
# if self.stage.stage == RunStage.Output and len(self.produces.outputs) > 0:
|
|
1029
|
+
# hints = [
|
|
1030
|
+
# CompilerHint(self.depends, 'Output declared here'),
|
|
1031
|
+
# ]
|
|
1032
|
+
# raise ProgramError(self.stage, f'Procedure name {self.name} is declared as output stage but produces an output', hints=hints)
|
|
1033
|
+
|
|
1034
|
+
def compile_pass1(self, graph):
|
|
1035
|
+
name = self.name.id_str
|
|
1036
|
+
if name in graph.procedures:
|
|
1037
|
+
hints = [
|
|
1038
|
+
CompilerHint(graph.procedures[name], 'Previous procedure was here'),
|
|
1039
|
+
]
|
|
1040
|
+
raise ProgramError(self, f'Duplicate procedure name {name} in {graph}', hints=hints)
|
|
1041
|
+
graph.procedures[name] = self
|
|
1042
|
+
|
|
1043
|
+
self.depends.compile_pass1(graph, self)
|
|
1044
|
+
self.produces.compile_pass1(graph, self)
|
|
1045
|
+
|
|
1046
|
+
def compile_pass1_5(self, graph):
|
|
1047
|
+
pass
|
|
1048
|
+
|
|
1049
|
+
def compile_pass2(self, graph):
|
|
1050
|
+
self.depends.compile_pass2(graph, self)
|
|
1051
|
+
self.produces.compile_pass2(graph, self)
|
|
1052
|
+
|
|
1053
|
+
# TODO: Clean up compilation passes
|
|
1054
|
+
|
|
1055
|
+
def compile_pass1_btd(self, interface):
|
|
1056
|
+
pass
|
|
1057
|
+
|
|
1058
|
+
def create_io(self, new_graph):
|
|
1059
|
+
pass
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
# TODO: Should this to some extend be merged with BlockType? Should there be a common base class?
|
|
1063
|
+
class DeclarationLibrary(Scope):
|
|
1064
|
+
@check_parameters
|
|
1065
|
+
def __init__(self, name: Identifier, declarations: typing.List[Declaration], *, source_position: SourcePosition):
|
|
1066
|
+
super().__init__(source_position=source_position)
|
|
1067
|
+
self.name = name
|
|
1068
|
+
self.declarations = declarations
|
|
1069
|
+
|
|
1070
|
+
# TODO?
|
|
1071
|
+
self.libraries = {}
|
|
1072
|
+
|
|
1073
|
+
@staticmethod
|
|
1074
|
+
def make(*args, source_position: SourcePosition):
|
|
1075
|
+
names = args[:-1]
|
|
1076
|
+
declarations = args[-1]
|
|
1077
|
+
for name in reversed(names):
|
|
1078
|
+
declarations = [DeclarationLibrary(
|
|
1079
|
+
name, declarations, source_position=source_position)]
|
|
1080
|
+
return declarations[0]
|
|
1081
|
+
|
|
1082
|
+
@property
|
|
1083
|
+
def fullname(self):
|
|
1084
|
+
if isinstance(self.parent_graph, DeclarationLibrary):
|
|
1085
|
+
return self.parent_graph.fullname + '.' + self.name.id_str
|
|
1086
|
+
return self.name.id_str
|
|
1087
|
+
|
|
1088
|
+
@property
|
|
1089
|
+
def fullname_tuple(self):
|
|
1090
|
+
if isinstance(self.parent_graph, DeclarationLibrary):
|
|
1091
|
+
return self.parent_graph.fullname_tuple + (self.name.id_str,)
|
|
1092
|
+
return (self.name.id_str,)
|
|
1093
|
+
|
|
1094
|
+
def compile_pass1(self, graph):
|
|
1095
|
+
if hasattr(self, 'parent_graph'):
|
|
1096
|
+
raise Exception("hasattr(self, 'parent_graph')")
|
|
1097
|
+
self.parent_graph = graph
|
|
1098
|
+
# TODO: What should be allowed here?
|
|
1099
|
+
if not isinstance(graph, DeclarationLibrary) and graph.name is not None:
|
|
1100
|
+
raise ProgramError(self, 'Attempt to put namespace into invalid context')
|
|
1101
|
+
|
|
1102
|
+
self.compiled_scope = graph.libraries.setdefault(self.name.id_str, self)
|
|
1103
|
+
|
|
1104
|
+
for decl in self.declarations:
|
|
1105
|
+
with decl.context:
|
|
1106
|
+
# TODO: What is allowed here?
|
|
1107
|
+
if not isinstance(decl, (blocks.BlockType, DeclarationLibrary)):
|
|
1108
|
+
hints = [
|
|
1109
|
+
CompilerHint(self, 'In this namespace'),
|
|
1110
|
+
]
|
|
1111
|
+
raise ProgramError(decl, 'Declaration not allowed in namespace', hints=hints)
|
|
1112
|
+
|
|
1113
|
+
decl.compile_pass1(self.compiled_scope)
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
def compile_pass1_5(self, graph):
|
|
1117
|
+
for decl in self.declarations:
|
|
1118
|
+
with decl.context:
|
|
1119
|
+
decl.compile_pass1_5(self.compiled_scope)
|
|
1120
|
+
|
|
1121
|
+
def compile_pass2(self, graph):
|
|
1122
|
+
for decl in self.declarations:
|
|
1123
|
+
with decl.context:
|
|
1124
|
+
decl.compile_pass2(self.compiled_scope)
|
|
1125
|
+
|
|
1126
|
+
# TODO: Clean up compilation passes
|
|
1127
|
+
|
|
1128
|
+
def compile_pass1_btd(self, interface):
|
|
1129
|
+
pass
|
|
1130
|
+
|
|
1131
|
+
def create_io(self, new_graph):
|
|
1132
|
+
pass
|
|
1133
|
+
|
|
1134
|
+
def __repr__(self):
|
|
1135
|
+
return self.fullname
|