fprime-gds 3.6.2a1__py3-none-any.whl → 4.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fprime_gds/common/communication/adapters/ip.py +14 -9
- fprime_gds/common/communication/adapters/uart.py +34 -25
- fprime_gds/common/communication/ccsds/__init__.py +0 -0
- fprime_gds/common/communication/ccsds/apid.py +19 -0
- fprime_gds/common/communication/ccsds/chain.py +106 -0
- fprime_gds/common/communication/ccsds/space_data_link.py +196 -0
- fprime_gds/common/communication/ccsds/space_packet.py +129 -0
- fprime_gds/common/communication/framing.py +27 -32
- fprime_gds/common/decoders/ch_decoder.py +1 -1
- fprime_gds/common/decoders/event_decoder.py +9 -2
- fprime_gds/common/decoders/pkt_decoder.py +1 -1
- fprime_gds/common/distributor/distributor.py +6 -3
- fprime_gds/common/encoders/ch_encoder.py +2 -2
- fprime_gds/common/encoders/cmd_encoder.py +2 -2
- fprime_gds/common/encoders/event_encoder.py +2 -2
- fprime_gds/common/encoders/pkt_encoder.py +2 -2
- fprime_gds/common/encoders/seq_writer.py +2 -2
- fprime_gds/common/fpy/README.md +56 -0
- fprime_gds/common/fpy/SPEC.md +69 -0
- fprime_gds/common/fpy/__init__.py +0 -0
- fprime_gds/common/fpy/bytecode/__init__.py +0 -0
- fprime_gds/common/fpy/bytecode/directives.py +490 -0
- fprime_gds/common/fpy/codegen.py +1687 -0
- fprime_gds/common/fpy/grammar.lark +88 -0
- fprime_gds/common/fpy/main.py +40 -0
- fprime_gds/common/fpy/parser.py +239 -0
- fprime_gds/common/gds_cli/base_commands.py +1 -1
- fprime_gds/common/handlers.py +39 -0
- fprime_gds/common/loaders/fw_type_json_loader.py +54 -0
- fprime_gds/common/loaders/pkt_json_loader.py +125 -0
- fprime_gds/common/loaders/prm_json_loader.py +85 -0
- fprime_gds/common/logger/__init__.py +2 -2
- fprime_gds/common/pipeline/dictionaries.py +28 -2
- fprime_gds/common/pipeline/encoding.py +19 -0
- fprime_gds/common/pipeline/histories.py +4 -0
- fprime_gds/common/pipeline/standard.py +16 -2
- fprime_gds/common/templates/cmd_template.py +8 -0
- fprime_gds/common/templates/prm_template.py +81 -0
- fprime_gds/common/testing_fw/api.py +148 -1
- fprime_gds/common/testing_fw/pytest_integration.py +37 -3
- fprime_gds/common/tools/README.md +34 -0
- fprime_gds/common/tools/params.py +246 -0
- fprime_gds/common/utils/config_manager.py +6 -6
- fprime_gds/common/utils/data_desc_type.py +6 -1
- fprime_gds/executables/apps.py +189 -11
- fprime_gds/executables/cli.py +468 -127
- fprime_gds/executables/comm.py +5 -2
- fprime_gds/executables/data_product_writer.py +164 -165
- fprime_gds/executables/fprime_cli.py +3 -3
- fprime_gds/executables/run_deployment.py +13 -5
- fprime_gds/flask/static/js/vue-support/channel.js +1 -1
- fprime_gds/flask/static/js/vue-support/event.js +1 -1
- fprime_gds/plugin/definitions.py +86 -8
- fprime_gds/plugin/system.py +172 -58
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0.dist-info}/METADATA +23 -21
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0.dist-info}/RECORD +61 -41
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0.dist-info}/WHEEL +1 -1
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0.dist-info}/entry_points.txt +2 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0.dist-info/licenses}/LICENSE.txt +0 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0.dist-info/licenses}/NOTICE.txt +0 -0
- {fprime_gds-3.6.2a1.dist-info → fprime_gds-4.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1687 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
from abc import ABC
|
3
|
+
import inspect
|
4
|
+
from dataclasses import dataclass, field, fields
|
5
|
+
import traceback
|
6
|
+
from typing import Callable
|
7
|
+
import typing
|
8
|
+
|
9
|
+
from fprime_gds.common.fpy.bytecode.directives import (
|
10
|
+
FLOAT_INEQUALITY_DIRECTIVES,
|
11
|
+
MAX_SERIALIZABLE_REGISTER_SIZE,
|
12
|
+
INT_SIGNED_INEQUALITY_DIRECTIVES,
|
13
|
+
INT_UNSIGNED_INEQUALITY_DIRECTIVES,
|
14
|
+
AndDirective,
|
15
|
+
CmdDirective,
|
16
|
+
DeserSerReg1Directive,
|
17
|
+
DeserSerReg2Directive,
|
18
|
+
DeserSerReg4Directive,
|
19
|
+
DeserSerReg8Directive,
|
20
|
+
Directive,
|
21
|
+
FloatExtendDirective,
|
22
|
+
IntEqualDirective,
|
23
|
+
ExitDirective,
|
24
|
+
FloatEqualDirective,
|
25
|
+
FloatNotEqualDirective,
|
26
|
+
GetPrmDirective,
|
27
|
+
GetTlmDirective,
|
28
|
+
GotoDirective,
|
29
|
+
IfDirective,
|
30
|
+
NotDirective,
|
31
|
+
IntNotEqualDirective,
|
32
|
+
OrDirective,
|
33
|
+
SetSerRegDirective,
|
34
|
+
SetRegDirective,
|
35
|
+
SignedIntToFloatDirective,
|
36
|
+
UnsignedIntToFloatDirective,
|
37
|
+
WaitAbsDirective,
|
38
|
+
WaitRelDirective,
|
39
|
+
)
|
40
|
+
from fprime_gds.common.loaders.ch_json_loader import ChJsonLoader
|
41
|
+
from fprime_gds.common.loaders.cmd_json_loader import CmdJsonLoader
|
42
|
+
from fprime_gds.common.loaders.prm_json_loader import PrmJsonLoader
|
43
|
+
from fprime_gds.common.templates.ch_template import ChTemplate
|
44
|
+
from fprime_gds.common.templates.cmd_template import CmdTemplate
|
45
|
+
from fprime_gds.common.templates.prm_template import PrmTemplate
|
46
|
+
from fprime.common.models.serialize.time_type import TimeType
|
47
|
+
from fprime.common.models.serialize.enum_type import EnumType
|
48
|
+
from fprime.common.models.serialize.serializable_type import SerializableType
|
49
|
+
from fprime.common.models.serialize.array_type import ArrayType
|
50
|
+
from fprime.common.models.serialize.numerical_types import (
|
51
|
+
U32Type,
|
52
|
+
U16Type,
|
53
|
+
U64Type,
|
54
|
+
U8Type,
|
55
|
+
I16Type,
|
56
|
+
I32Type,
|
57
|
+
I64Type,
|
58
|
+
I8Type,
|
59
|
+
F32Type,
|
60
|
+
F64Type,
|
61
|
+
FloatType,
|
62
|
+
IntegerType,
|
63
|
+
NumericalType,
|
64
|
+
)
|
65
|
+
from fprime.common.models.serialize.string_type import StringType
|
66
|
+
from fprime.common.models.serialize.bool_type import BoolType
|
67
|
+
from fprime_gds.common.fpy.parser import (
|
68
|
+
AstAnd,
|
69
|
+
AstBoolean,
|
70
|
+
AstComparison,
|
71
|
+
AstElif,
|
72
|
+
AstElifs,
|
73
|
+
AstExpr,
|
74
|
+
AstGetAttr,
|
75
|
+
AstGetItem,
|
76
|
+
AstNot,
|
77
|
+
AstNumber,
|
78
|
+
AstOr,
|
79
|
+
AstReference,
|
80
|
+
AstString,
|
81
|
+
Ast,
|
82
|
+
AstTest,
|
83
|
+
AstBody,
|
84
|
+
AstLiteral,
|
85
|
+
AstIf,
|
86
|
+
AstAssign,
|
87
|
+
AstFuncCall,
|
88
|
+
AstVar,
|
89
|
+
)
|
90
|
+
from fprime.common.models.serialize.type_base import BaseType as FppType
|
91
|
+
|
92
|
+
NUMERIC_TYPES = (
|
93
|
+
U32Type,
|
94
|
+
U16Type,
|
95
|
+
U64Type,
|
96
|
+
U8Type,
|
97
|
+
I16Type,
|
98
|
+
I32Type,
|
99
|
+
I64Type,
|
100
|
+
I8Type,
|
101
|
+
F32Type,
|
102
|
+
F64Type,
|
103
|
+
)
|
104
|
+
INTEGER_TYPES = (
|
105
|
+
U32Type,
|
106
|
+
U16Type,
|
107
|
+
U64Type,
|
108
|
+
U8Type,
|
109
|
+
I16Type,
|
110
|
+
I32Type,
|
111
|
+
I64Type,
|
112
|
+
I8Type,
|
113
|
+
)
|
114
|
+
SIGNED_INTEGER_TYPES = (
|
115
|
+
I16Type,
|
116
|
+
I32Type,
|
117
|
+
I64Type,
|
118
|
+
I8Type,
|
119
|
+
)
|
120
|
+
UNSIGNED_INTEGER_TYPES = (
|
121
|
+
U32Type,
|
122
|
+
U16Type,
|
123
|
+
U64Type,
|
124
|
+
U8Type,
|
125
|
+
)
|
126
|
+
FLOAT_TYPES = (
|
127
|
+
F32Type,
|
128
|
+
F64Type,
|
129
|
+
)
|
130
|
+
|
131
|
+
|
132
|
+
# a value of type FppTypeClass is a Python `type` object representing
|
133
|
+
# the type of an Fprime value
|
134
|
+
FppTypeClass = type[FppType]
|
135
|
+
|
136
|
+
|
137
|
+
class NothingType(ABC):
|
138
|
+
"""a type which has no valid values in fprime. used to denote
|
139
|
+
a function which doesn't return a value"""
|
140
|
+
@classmethod
|
141
|
+
def __subclasscheck__(cls, subclass):
|
142
|
+
return False
|
143
|
+
|
144
|
+
|
145
|
+
# the `type` object representing the NothingType class
|
146
|
+
NothingTypeClass = type[NothingType]
|
147
|
+
|
148
|
+
|
149
|
+
class CompileException(BaseException):
|
150
|
+
def __init__(self, msg, node: Ast):
|
151
|
+
self.msg = msg
|
152
|
+
self.node = node
|
153
|
+
self.stack_trace = "\n".join(traceback.format_stack(limit=8)[:-1])
|
154
|
+
|
155
|
+
def __str__(self):
|
156
|
+
return f'{self.stack_trace}\nAt line {self.node.meta.line} "{self.node.node_text}": {self.msg}'
|
157
|
+
|
158
|
+
|
159
|
+
@dataclass
|
160
|
+
class FpyCallable:
|
161
|
+
return_type: FppTypeClass | NothingTypeClass
|
162
|
+
args: list[tuple[str, FppTypeClass]]
|
163
|
+
|
164
|
+
|
165
|
+
@dataclass
|
166
|
+
class FpyCmd(FpyCallable):
|
167
|
+
cmd: CmdTemplate
|
168
|
+
|
169
|
+
|
170
|
+
@dataclass
|
171
|
+
class FpyMacro(FpyCallable):
|
172
|
+
instantiate_macro: Callable[[list[FppType]], list[Directive]]
|
173
|
+
"""a function which instantiates the macro given the argument values"""
|
174
|
+
|
175
|
+
|
176
|
+
MACROS: dict[str, FpyMacro] = {
|
177
|
+
"sleep": FpyMacro(
|
178
|
+
NothingType, [("seconds", F64Type)], lambda args: [WaitRelDirective(int(args[0].val), int(args[0].val * 1000000) % 1000000)]
|
179
|
+
),
|
180
|
+
"sleep_until": FpyMacro(
|
181
|
+
NothingType, [("wakeup_time", TimeType)], lambda args: [WaitAbsDirective(args[0])]
|
182
|
+
),
|
183
|
+
"exit": FpyMacro(NothingType, [("success", BoolType)], lambda args: [ExitDirective(args[0].val)]),
|
184
|
+
}
|
185
|
+
|
186
|
+
|
187
|
+
@dataclass
|
188
|
+
class FpyTypeCtor(FpyCallable):
|
189
|
+
type: FppTypeClass
|
190
|
+
|
191
|
+
|
192
|
+
@dataclass
|
193
|
+
class FieldReference:
|
194
|
+
"""a reference to a field/index of an fprime type"""
|
195
|
+
|
196
|
+
parent: "FpyReference"
|
197
|
+
"""the qualifier"""
|
198
|
+
type: FppTypeClass
|
199
|
+
"""the fprime type of this reference"""
|
200
|
+
offset: int
|
201
|
+
"""the constant offset in the parent type at which to find this field"""
|
202
|
+
name: str = None
|
203
|
+
"""the name of the field, if applicable"""
|
204
|
+
idx: int = None
|
205
|
+
"""the index of the field, if applicable"""
|
206
|
+
|
207
|
+
def get_from(self, parent_val: FppType) -> FppType:
|
208
|
+
"""gets the field value from the parent value"""
|
209
|
+
assert isinstance(parent_val, self.type)
|
210
|
+
assert self.name is not None or self.idx is not None
|
211
|
+
value = None
|
212
|
+
if self.name is not None:
|
213
|
+
if isinstance(parent_val, SerializableType):
|
214
|
+
value = parent_val.val[self.name]
|
215
|
+
elif isinstance(parent_val, TimeType):
|
216
|
+
if self.name == "seconds":
|
217
|
+
value = parent_val.__secs
|
218
|
+
elif self.name == "useconds":
|
219
|
+
value = parent_val.__usecs
|
220
|
+
elif self.name == "time_base":
|
221
|
+
value = parent_val.__timeBase
|
222
|
+
elif self.name == "time_context":
|
223
|
+
value = parent_val.__timeContext
|
224
|
+
else:
|
225
|
+
assert False, self.name
|
226
|
+
else:
|
227
|
+
assert False, parent_val
|
228
|
+
|
229
|
+
else:
|
230
|
+
|
231
|
+
assert isinstance(parent_val, ArrayType), parent_val
|
232
|
+
|
233
|
+
value = parent_val._val[self.idx]
|
234
|
+
|
235
|
+
assert isinstance(value, self.type), (value, self.type)
|
236
|
+
return value
|
237
|
+
|
238
|
+
|
239
|
+
# named variables can be tlm chans, prms, callables, or directly referenced consts (usually enums)
|
240
|
+
@dataclass
|
241
|
+
class FpyVariable:
|
242
|
+
"""a mutable, typed value referenced by an unqualified name"""
|
243
|
+
|
244
|
+
type_ref: AstExpr
|
245
|
+
"""the expression denoting the var's type"""
|
246
|
+
type: FppTypeClass | None = None
|
247
|
+
"""the resolved type of the variable. None if type unsure at the moment"""
|
248
|
+
sreg_idx: int | None = None
|
249
|
+
"""the index of the sreg it is stored in"""
|
250
|
+
|
251
|
+
|
252
|
+
# a scope
|
253
|
+
FpyScope = dict[str, "FpyReference"]
|
254
|
+
|
255
|
+
|
256
|
+
def create_scope(
|
257
|
+
references: dict[str, "FpyReference"],
|
258
|
+
) -> FpyScope:
|
259
|
+
"""from a flat dict of strs to references, creates a hierarchical, scoped
|
260
|
+
dict. no two leaf nodes may have the same name"""
|
261
|
+
|
262
|
+
base = {}
|
263
|
+
|
264
|
+
for fqn, ref in references.items():
|
265
|
+
names_strs = fqn.split(".")
|
266
|
+
|
267
|
+
ns = base
|
268
|
+
while len(names_strs) > 1:
|
269
|
+
existing_child = ns.get(names_strs[0], None)
|
270
|
+
if existing_child is None:
|
271
|
+
# this scope is not defined atm
|
272
|
+
existing_child = {}
|
273
|
+
ns[names_strs[0]] = existing_child
|
274
|
+
|
275
|
+
if not isinstance(existing_child, dict):
|
276
|
+
# something else already has this name
|
277
|
+
print(
|
278
|
+
f"WARNING: {fqn} is already defined as {existing_child}, tried to redefine it as {ref}"
|
279
|
+
)
|
280
|
+
break
|
281
|
+
|
282
|
+
ns = existing_child
|
283
|
+
names_strs = names_strs[1:]
|
284
|
+
|
285
|
+
if len(names_strs) != 1:
|
286
|
+
# broke early. skip this loop
|
287
|
+
continue
|
288
|
+
|
289
|
+
# okay, now ns is the complete scope of the attribute
|
290
|
+
# i.e. everything up until the last '.'
|
291
|
+
name = names_strs[0]
|
292
|
+
|
293
|
+
existing_child = ns.get(name, None)
|
294
|
+
|
295
|
+
if existing_child is not None:
|
296
|
+
# uh oh, something already had this name with a diff value
|
297
|
+
print(
|
298
|
+
f"WARNING: {fqn} is already defined as {existing_child}, tried to redefine it as {ref}"
|
299
|
+
)
|
300
|
+
continue
|
301
|
+
|
302
|
+
ns[name] = ref
|
303
|
+
|
304
|
+
return base
|
305
|
+
|
306
|
+
|
307
|
+
def union_scope(lhs: FpyScope, rhs: FpyScope) -> FpyScope:
|
308
|
+
"""returns the two scopes, joined into one. if there is a conflict, chooses lhs over rhs"""
|
309
|
+
lhs_keys = set(lhs.keys())
|
310
|
+
rhs_keys = set(rhs.keys())
|
311
|
+
common_keys = lhs_keys.intersection(rhs_keys)
|
312
|
+
|
313
|
+
only_lhs_keys = lhs_keys.difference(common_keys)
|
314
|
+
only_rhs_keys = rhs_keys.difference(common_keys)
|
315
|
+
|
316
|
+
new = FpyScope()
|
317
|
+
|
318
|
+
for key in common_keys:
|
319
|
+
if not isinstance(lhs[key], dict) or not isinstance(rhs[key], dict):
|
320
|
+
# cannot be merged cleanly. one of the two is not a scope
|
321
|
+
print(f"WARNING: {key} is defined as {lhs[key]}, ignoring {rhs[key]}")
|
322
|
+
new[key] = lhs[key]
|
323
|
+
continue
|
324
|
+
|
325
|
+
new[key] = union_scope(lhs[key], rhs[key])
|
326
|
+
|
327
|
+
for key in only_lhs_keys:
|
328
|
+
new[key] = lhs[key]
|
329
|
+
for key in only_rhs_keys:
|
330
|
+
new[key] = rhs[key]
|
331
|
+
|
332
|
+
return new
|
333
|
+
|
334
|
+
|
335
|
+
FpyReference = (
|
336
|
+
ChTemplate
|
337
|
+
| PrmTemplate
|
338
|
+
| FppType
|
339
|
+
| FpyCallable
|
340
|
+
| FppTypeClass
|
341
|
+
| FpyVariable
|
342
|
+
| FieldReference
|
343
|
+
| dict # FpyReference
|
344
|
+
)
|
345
|
+
"""some named concept in fpy"""
|
346
|
+
|
347
|
+
|
348
|
+
def get_ref_fpp_type_class(ref: FpyReference) -> FppTypeClass:
|
349
|
+
"""returns the fprime type of the ref, if it were to be evaluated as an expression"""
|
350
|
+
if isinstance(ref, ChTemplate):
|
351
|
+
result_type = ref.ch_type_obj
|
352
|
+
elif isinstance(ref, PrmTemplate):
|
353
|
+
result_type = ref.prm_type_obj
|
354
|
+
elif isinstance(ref, FppType):
|
355
|
+
# constant value
|
356
|
+
result_type = type(ref)
|
357
|
+
elif isinstance(ref, FpyCallable):
|
358
|
+
# a reference to a callable isn't a type in and of itself
|
359
|
+
# it has a return type but you have to call it (with an AstFuncCall)
|
360
|
+
# consider making a separate "reference" type
|
361
|
+
result_type = NothingType
|
362
|
+
elif isinstance(ref, FpyVariable):
|
363
|
+
result_type = ref.type
|
364
|
+
elif isinstance(ref, type):
|
365
|
+
# a reference to a type doesn't have a value, and so doesn't have a type,
|
366
|
+
# in and of itself. if this were a function call to the type's ctor then
|
367
|
+
# it would have a value and thus a type
|
368
|
+
result_type = NothingType
|
369
|
+
elif isinstance(ref, FieldReference):
|
370
|
+
result_type = ref.type
|
371
|
+
elif isinstance(ref, dict):
|
372
|
+
# reference to a scope. scopes don't have values
|
373
|
+
result_type = NothingType
|
374
|
+
else:
|
375
|
+
assert False, ref
|
376
|
+
|
377
|
+
return result_type
|
378
|
+
|
379
|
+
|
380
|
+
@dataclass
|
381
|
+
class CompileState:
|
382
|
+
"""a collection of input, internal and output state variables and maps"""
|
383
|
+
|
384
|
+
types: FpyScope
|
385
|
+
"""a scope whose leaf nodes are subclasses of BaseType"""
|
386
|
+
callables: FpyScope
|
387
|
+
"""a scope whose leaf nodes are FpyCallable instances"""
|
388
|
+
tlms: FpyScope
|
389
|
+
"""a scope whose leaf nodes are ChTemplates"""
|
390
|
+
prms: FpyScope
|
391
|
+
"""a scope whose leaf nodes are PrmTemplates"""
|
392
|
+
consts: FpyScope
|
393
|
+
"""a scope whose leaf nodes are instances of subclasses of BaseType"""
|
394
|
+
variables: FpyScope = field(default_factory=dict)
|
395
|
+
"""a scope whose leaf nodes are FpyVariables"""
|
396
|
+
runtime_values: FpyScope = None
|
397
|
+
"""a scope whose leaf nodes are tlms/prms/consts/variables, all of which
|
398
|
+
have some value at runtime."""
|
399
|
+
|
400
|
+
def __post_init__(self):
|
401
|
+
self.runtime_values = union_scope(
|
402
|
+
self.tlms,
|
403
|
+
union_scope(self.prms, union_scope(self.consts, self.variables)),
|
404
|
+
)
|
405
|
+
|
406
|
+
resolved_references: dict[AstReference, FpyReference] = field(
|
407
|
+
default_factory=dict, repr=False
|
408
|
+
)
|
409
|
+
"""reference to its singular resolution"""
|
410
|
+
|
411
|
+
expr_types: dict[AstExpr, FppTypeClass | NothingTypeClass] = field(
|
412
|
+
default_factory=dict
|
413
|
+
)
|
414
|
+
"""expr to its fprime type, or nothing type if none"""
|
415
|
+
|
416
|
+
expr_values: dict[AstExpr, FppType | NothingType | None] = field(
|
417
|
+
default_factory=dict
|
418
|
+
)
|
419
|
+
"""expr to its fprime value, or nothing if no value, or None if unsure at compile time"""
|
420
|
+
|
421
|
+
expr_registers: dict[AstExpr, int] = field(default_factory=dict)
|
422
|
+
"""expr to the register it's stored in"""
|
423
|
+
|
424
|
+
directives: dict[Ast, list[Directive] | None] = field(default_factory=dict)
|
425
|
+
"""a list of code generated by each node, or None/empty list if no directives"""
|
426
|
+
|
427
|
+
node_dir_counts: dict[Ast, int] = field(default_factory=dict)
|
428
|
+
"""node to the number of directives generated by it"""
|
429
|
+
|
430
|
+
next_register: int = 0
|
431
|
+
"""the index of the next free register"""
|
432
|
+
next_sreg: int = 0
|
433
|
+
"""the index of the next free serializable register"""
|
434
|
+
|
435
|
+
start_line_idx: dict[Ast, int] = field(default_factory=dict)
|
436
|
+
"""the line index at which each node's directives will be included in the output"""
|
437
|
+
|
438
|
+
errors: list[CompileException] = field(default_factory=list)
|
439
|
+
"""a list of all compile exceptions generated by passes"""
|
440
|
+
|
441
|
+
def err(self, msg, n):
|
442
|
+
"""adds a compile exception to internal state"""
|
443
|
+
self.errors.append(CompileException(msg, n))
|
444
|
+
|
445
|
+
|
446
|
+
class Visitor:
|
447
|
+
"""visits each class, calling a custom visit function, if one is defined, for each
|
448
|
+
node type"""
|
449
|
+
|
450
|
+
def _find_custom_visit_func(self, node: Ast):
|
451
|
+
for name, func in inspect.getmembers(type(self), inspect.isfunction):
|
452
|
+
if not name.startswith("visit") or name == "visit_default":
|
453
|
+
# not a visitor, or the default visit func
|
454
|
+
continue
|
455
|
+
signature = inspect.signature(func)
|
456
|
+
params = list(signature.parameters.values())
|
457
|
+
assert len(params) == 3
|
458
|
+
assert params[1].annotation is not None
|
459
|
+
annotations = typing.get_type_hints(func)
|
460
|
+
param_type = annotations[params[1].name]
|
461
|
+
if isinstance(node, param_type):
|
462
|
+
return func
|
463
|
+
else:
|
464
|
+
# call the default
|
465
|
+
return type(self).visit_default
|
466
|
+
|
467
|
+
def _visit(self, node: Ast, state: CompileState):
|
468
|
+
visit_func = self._find_custom_visit_func(node)
|
469
|
+
visit_func(self, node, state)
|
470
|
+
|
471
|
+
def visit_default(self, node: Ast, state: CompileState):
|
472
|
+
pass
|
473
|
+
|
474
|
+
def run(self, start: Ast, state: CompileState):
|
475
|
+
"""runs the visitor, starting at the given node, descending depth-first"""
|
476
|
+
|
477
|
+
def _descend(node: Ast):
|
478
|
+
if not isinstance(node, Ast):
|
479
|
+
return
|
480
|
+
children = []
|
481
|
+
for field in fields(node):
|
482
|
+
field_val = getattr(node, field.name)
|
483
|
+
if isinstance(field_val, list):
|
484
|
+
children.extend(field_val)
|
485
|
+
else:
|
486
|
+
children.append(field_val)
|
487
|
+
|
488
|
+
for child in children:
|
489
|
+
if not isinstance(child, Ast):
|
490
|
+
continue
|
491
|
+
_descend(child)
|
492
|
+
if len(state.errors) != 0:
|
493
|
+
break
|
494
|
+
self._visit(child, state)
|
495
|
+
if len(state.errors) != 0:
|
496
|
+
break
|
497
|
+
|
498
|
+
_descend(start)
|
499
|
+
self._visit(start, state)
|
500
|
+
|
501
|
+
|
502
|
+
class TopDownVisitor(Visitor):
|
503
|
+
|
504
|
+
def run(self, start: Ast, state: CompileState):
|
505
|
+
"""runs the visitor, starting at the given node, descending breadth-first"""
|
506
|
+
|
507
|
+
def _descend(node: Ast):
|
508
|
+
if not isinstance(node, Ast):
|
509
|
+
return
|
510
|
+
children = []
|
511
|
+
for field in fields(node):
|
512
|
+
field_val = getattr(node, field.name)
|
513
|
+
if isinstance(field_val, list):
|
514
|
+
children.extend(field_val)
|
515
|
+
else:
|
516
|
+
children.append(field_val)
|
517
|
+
|
518
|
+
for child in children:
|
519
|
+
if not isinstance(child, Ast):
|
520
|
+
continue
|
521
|
+
self._visit(child, state)
|
522
|
+
if len(state.errors) != 0:
|
523
|
+
break
|
524
|
+
_descend(child)
|
525
|
+
if len(state.errors) != 0:
|
526
|
+
break
|
527
|
+
|
528
|
+
self._visit(start, state)
|
529
|
+
_descend(start)
|
530
|
+
|
531
|
+
|
532
|
+
class AssignIds(TopDownVisitor):
|
533
|
+
"""assigns a unique id to each node to allow it to be indexed in a dict"""
|
534
|
+
|
535
|
+
def __init__(self):
|
536
|
+
self.next_id = 0
|
537
|
+
|
538
|
+
def visit_default(self, node, state):
|
539
|
+
node.id = self.next_id
|
540
|
+
self.next_id += 1
|
541
|
+
|
542
|
+
|
543
|
+
class CreateVariables(Visitor):
|
544
|
+
"""finds all variable declarations and adds them to the variable scope"""
|
545
|
+
|
546
|
+
def visit_AstAssign(self, node: AstAssign, state: CompileState):
|
547
|
+
existing = state.variables.get(node.variable.var, None)
|
548
|
+
if not existing:
|
549
|
+
# idk what this var is. make sure it's a valid declaration
|
550
|
+
if node.var_type is None:
|
551
|
+
# error because this isn't an annotated assignment. right now all declarations must be annotated
|
552
|
+
state.err(
|
553
|
+
"Must provide a type annotation for new variables", node.variable
|
554
|
+
)
|
555
|
+
return
|
556
|
+
|
557
|
+
var = FpyVariable(node.var_type, None)
|
558
|
+
# new var. put it in the table under this scope
|
559
|
+
state.variables[node.variable.var] = var
|
560
|
+
state.runtime_values[node.variable.var] = var
|
561
|
+
|
562
|
+
if existing and node.var_type is not None:
|
563
|
+
# redeclaring an existing variable
|
564
|
+
state.err(f"{node.variable.var} already declared", node)
|
565
|
+
return
|
566
|
+
|
567
|
+
|
568
|
+
class ResolveReferences(Visitor):
|
569
|
+
"""for each reference, resolve it in a specific scope based on its
|
570
|
+
syntactic position, or fail if could not resolve"""
|
571
|
+
|
572
|
+
def is_type_constant_size(self, type: FppTypeClass) -> bool:
|
573
|
+
"""return true if the type is statically sized"""
|
574
|
+
if issubclass(type, StringType):
|
575
|
+
return False
|
576
|
+
|
577
|
+
if issubclass(type, ArrayType):
|
578
|
+
return self.is_type_constant_size(type.MEMBER_TYPE)
|
579
|
+
|
580
|
+
if issubclass(type, SerializableType):
|
581
|
+
for _, arg_type, _, _ in type.MEMBER_LIST:
|
582
|
+
if not self.is_type_constant_size(arg_type):
|
583
|
+
return False
|
584
|
+
return True
|
585
|
+
|
586
|
+
return True
|
587
|
+
|
588
|
+
def get_attr_of_ref(
|
589
|
+
self, parent: FpyReference, node: AstGetAttr, state: CompileState
|
590
|
+
) -> FpyReference | None:
|
591
|
+
"""resolve a GetAttr node relative to a given FpyReference. return the
|
592
|
+
resolved ref, or None if none could be found. Will raise errors if not found"""
|
593
|
+
|
594
|
+
if isinstance(parent, (FpyCallable, type)):
|
595
|
+
# right now we don't support resolving something after a callable/type
|
596
|
+
state.err("Invalid syntax", node)
|
597
|
+
return None
|
598
|
+
|
599
|
+
if isinstance(parent, dict):
|
600
|
+
# parent is a scope
|
601
|
+
attr = parent.get(node.attr, None)
|
602
|
+
if attr is None:
|
603
|
+
state.err("Unknown attribute", node)
|
604
|
+
return None
|
605
|
+
return attr
|
606
|
+
|
607
|
+
# parent is a ch, prm, const, var or field
|
608
|
+
|
609
|
+
value_type = get_ref_fpp_type_class(parent)
|
610
|
+
|
611
|
+
assert value_type != NothingType
|
612
|
+
|
613
|
+
if not issubclass(value_type, (SerializableType, TimeType)):
|
614
|
+
# trying to do arr.x, but arr is not a struct
|
615
|
+
state.err(
|
616
|
+
"Invalid syntax (tried to access named member of a non-struct type)",
|
617
|
+
node,
|
618
|
+
)
|
619
|
+
return None
|
620
|
+
|
621
|
+
if not self.is_type_constant_size(value_type):
|
622
|
+
state.err(
|
623
|
+
f"{value_type} has non-constant sized members, cannot access members",
|
624
|
+
node,
|
625
|
+
)
|
626
|
+
return None
|
627
|
+
|
628
|
+
member_list: list[tuple[str, FppTypeClass]] = None
|
629
|
+
if issubclass(value_type, SerializableType):
|
630
|
+
member_list = [t[0:2] for t in value_type.MEMBER_LIST]
|
631
|
+
else:
|
632
|
+
# if it is a time type, there are some "implied" members
|
633
|
+
member_list = []
|
634
|
+
member_list.append(("time_base", U16Type))
|
635
|
+
member_list.append(("time_context", U8Type))
|
636
|
+
member_list.append(("seconds", U32Type))
|
637
|
+
member_list.append(("useconds", U32Type))
|
638
|
+
|
639
|
+
offset = 0
|
640
|
+
for arg_name, arg_type in member_list:
|
641
|
+
if arg_name == node.attr:
|
642
|
+
return FieldReference(parent, arg_type, offset, name=arg_name)
|
643
|
+
offset += arg_type.getMaxSize()
|
644
|
+
|
645
|
+
state.err(f"Unknown member {node.attr}", node)
|
646
|
+
return None
|
647
|
+
|
648
|
+
def get_item_of_ref(
|
649
|
+
self, parent: FpyReference, node: AstGetItem, state: CompileState
|
650
|
+
) -> FpyReference | None:
|
651
|
+
"""resolve a GetItem node relative to a given FpyReference. return the
|
652
|
+
resolved ref, or None if none could be found. Will raise errors if not found"""
|
653
|
+
|
654
|
+
if isinstance(parent, (FpyCallable, type, dict)):
|
655
|
+
# right now we don't support resolving index after a callable/type/scope
|
656
|
+
state.err("Invalid syntax", node)
|
657
|
+
return None
|
658
|
+
|
659
|
+
# parent is a ch, prm, const, var or field
|
660
|
+
|
661
|
+
value_type = get_ref_fpp_type_class(parent)
|
662
|
+
|
663
|
+
assert value_type != NothingType
|
664
|
+
|
665
|
+
if not issubclass(value_type, ArrayType):
|
666
|
+
# trying to do struct[0], but struct is not an array
|
667
|
+
state.err(
|
668
|
+
"Invalid syntax (tried to access indexed member of a non-array type)",
|
669
|
+
node.item,
|
670
|
+
)
|
671
|
+
return None
|
672
|
+
|
673
|
+
if not self.is_type_constant_size(value_type):
|
674
|
+
state.err(
|
675
|
+
f"{value_type} has non-constant sized members, cannot access members",
|
676
|
+
node,
|
677
|
+
)
|
678
|
+
return None
|
679
|
+
|
680
|
+
offset = 0
|
681
|
+
for i in range(0, value_type.LENGTH):
|
682
|
+
if i == node.item.value:
|
683
|
+
return FieldReference(parent, value_type.MEMBER_TYPE, offset, idx=i)
|
684
|
+
offset += value_type.MEMBER_TYPE.getMaxSize()
|
685
|
+
|
686
|
+
state.err(
|
687
|
+
f"Array access out-of-bounds (access: {node.item}, array size: {value_type.LENGTH})",
|
688
|
+
node.item,
|
689
|
+
)
|
690
|
+
return None
|
691
|
+
|
692
|
+
def resolve_if_ref(
|
693
|
+
self, node: AstExpr, ns: FpyScope, state: CompileState
|
694
|
+
) -> bool:
|
695
|
+
"""if the node is a reference, try to resolve it in the given scope, and return true if success.
|
696
|
+
otherwise, if it is not a reference, return true as it doesn't need to be resolved"""
|
697
|
+
if not isinstance(node, AstReference):
|
698
|
+
return True
|
699
|
+
|
700
|
+
return self.resolve_ref_in_ns(node, ns, state) is not None
|
701
|
+
|
702
|
+
def resolve_ref_in_ns(
|
703
|
+
self, node: AstExpr, ns: FpyScope, state: CompileState
|
704
|
+
) -> FpyReference | None:
|
705
|
+
"""recursively resolves a reference in a scope, returning the resolved ref
|
706
|
+
or none if none could be found."""
|
707
|
+
if isinstance(node, AstVar):
|
708
|
+
if not isinstance(ns, dict):
|
709
|
+
return None
|
710
|
+
ref = ns.get(node.var, None)
|
711
|
+
if ref is None:
|
712
|
+
return None
|
713
|
+
state.resolved_references[node] = ref
|
714
|
+
return ref
|
715
|
+
|
716
|
+
parent = self.resolve_ref_in_ns(node.parent, ns, state)
|
717
|
+
if parent is None:
|
718
|
+
# couldn't resolve parent
|
719
|
+
return None
|
720
|
+
|
721
|
+
if isinstance(node, AstGetItem):
|
722
|
+
ref = self.get_item_of_ref(parent, node, state)
|
723
|
+
state.resolved_references[node] = ref
|
724
|
+
return ref
|
725
|
+
|
726
|
+
assert isinstance(node, AstGetAttr)
|
727
|
+
ref = self.get_attr_of_ref(parent, node, state)
|
728
|
+
state.resolved_references[node] = ref
|
729
|
+
return ref
|
730
|
+
|
731
|
+
def visit_AstFuncCall(self, node: AstFuncCall, state: CompileState):
|
732
|
+
# function refs must be callables
|
733
|
+
if not self.resolve_ref_in_ns(node.func, state.callables, state):
|
734
|
+
state.err("Unknown callable", node.func)
|
735
|
+
return
|
736
|
+
|
737
|
+
for arg in node.args if node.args is not None else []:
|
738
|
+
# arg value refs must be consts
|
739
|
+
if not self.resolve_if_ref(arg, state.consts, state):
|
740
|
+
state.err("Unknown const", arg)
|
741
|
+
return
|
742
|
+
|
743
|
+
def visit_AstIf_AstElif(self, node: AstIf | AstElif, state: CompileState):
|
744
|
+
# if condition expr refs must be "runtime values" (tlm/prm/const/etc)
|
745
|
+
if not self.resolve_if_ref(node.condition, state.runtime_values, state):
|
746
|
+
state.err("Unknown runtime value", node.condition)
|
747
|
+
return
|
748
|
+
|
749
|
+
def visit_AstComparison(self, node: AstComparison, state: CompileState):
|
750
|
+
# lhs/rhs side of comparison, if they are refs, must be refs to "runtime vals"
|
751
|
+
if not self.resolve_if_ref(node.lhs, state.runtime_values, state):
|
752
|
+
state.err("Unknown runtime value", node.lhs)
|
753
|
+
return
|
754
|
+
if not self.resolve_if_ref(node.rhs, state.runtime_values, state):
|
755
|
+
state.err("Unknown runtime value", node.rhs)
|
756
|
+
return
|
757
|
+
|
758
|
+
def visit_AstAnd_AstOr(self, node: AstAnd | AstOr, state: CompileState):
|
759
|
+
for val in node.values:
|
760
|
+
if not self.resolve_if_ref(val, state.runtime_values, state):
|
761
|
+
state.err("Unknown runtime value", val)
|
762
|
+
return
|
763
|
+
|
764
|
+
def visit_AstNot(self, node: AstNot, state: CompileState):
|
765
|
+
if not self.resolve_if_ref(node.value, state.runtime_values, state):
|
766
|
+
state.err("Unknown runtime value", node.value)
|
767
|
+
return
|
768
|
+
|
769
|
+
def visit_AstAssign(self, node: AstAssign, state: CompileState):
|
770
|
+
var = self.resolve_ref_in_ns(node.variable, state.variables, state)
|
771
|
+
if not var:
|
772
|
+
state.err("Unknown variable", node.variable)
|
773
|
+
return
|
774
|
+
|
775
|
+
if node.var_type is not None:
|
776
|
+
type = self.resolve_ref_in_ns(node.var_type, state.types, state)
|
777
|
+
if not type:
|
778
|
+
state.err("Unknown type", node.var_type)
|
779
|
+
return
|
780
|
+
var.type = type
|
781
|
+
|
782
|
+
if not self.resolve_if_ref(node.value, state.consts, state):
|
783
|
+
state.err("Unknown const", node.value)
|
784
|
+
return
|
785
|
+
|
786
|
+
|
787
|
+
class CalculateExprTypes(Visitor):
|
788
|
+
"""stores in state the fprime type of each expression, or NothingType if the expr had no type"""
|
789
|
+
|
790
|
+
def visit_AstNumber(self, node: AstNumber, state: CompileState):
|
791
|
+
if isinstance(node.value, float):
|
792
|
+
result_type = FloatType
|
793
|
+
elif isinstance(node.value, int):
|
794
|
+
result_type = IntegerType
|
795
|
+
else:
|
796
|
+
assert False, node.value
|
797
|
+
state.expr_types[node] = result_type
|
798
|
+
|
799
|
+
def visit_AstString(self, node: AstString, state: CompileState):
|
800
|
+
state.expr_types[node] = StringType
|
801
|
+
|
802
|
+
def visit_AstBoolean(self, node: AstBoolean, state: CompileState):
|
803
|
+
state.expr_types[node] = BoolType
|
804
|
+
|
805
|
+
def visit_AstReference(self, node: AstReference, state: CompileState):
|
806
|
+
ref = state.resolved_references[node]
|
807
|
+
state.expr_types[node] = get_ref_fpp_type_class(ref)
|
808
|
+
|
809
|
+
def visit_AstFuncCall(self, node: AstFuncCall, state: CompileState):
|
810
|
+
ref = state.resolved_references[node.func]
|
811
|
+
assert isinstance(ref, FpyCallable)
|
812
|
+
state.expr_types[node] = ref.return_type
|
813
|
+
|
814
|
+
def visit_AstOr_AstAnd_AstNot_AstComparison(
|
815
|
+
self, node: AstOr | AstAnd | AstNot | AstComparison, state: CompileState
|
816
|
+
):
|
817
|
+
state.expr_types[node] = BoolType
|
818
|
+
|
819
|
+
def visit_default(self, node, state):
|
820
|
+
# coding error, missed an expr
|
821
|
+
assert not isinstance(node, AstExpr), node
|
822
|
+
|
823
|
+
|
824
|
+
class CheckAndResolveArgumentTypes(Visitor):
|
825
|
+
"""for each syntactic node with arguments (ands/ors/nots/cmps/funcs), check that the argument
|
826
|
+
types are right"""
|
827
|
+
|
828
|
+
def is_literal_convertible_to(
|
829
|
+
self, node: AstLiteral, to_type: FppTypeClass, state: CompileState
|
830
|
+
) -> bool:
|
831
|
+
|
832
|
+
if isinstance(node, AstBoolean):
|
833
|
+
return to_type == BoolType
|
834
|
+
|
835
|
+
if isinstance(node, AstString):
|
836
|
+
return issubclass(to_type, StringType)
|
837
|
+
|
838
|
+
if isinstance(node, AstNumber):
|
839
|
+
if isinstance(node.value, float):
|
840
|
+
return issubclass(to_type, FloatType)
|
841
|
+
if isinstance(node.value, int):
|
842
|
+
# int literal can be converted into float or int
|
843
|
+
return issubclass(to_type, (FloatType, IntegerType))
|
844
|
+
|
845
|
+
assert False, node.value
|
846
|
+
|
847
|
+
assert False, node
|
848
|
+
|
849
|
+
def visit_AstComparison(self, node: AstComparison, state: CompileState):
|
850
|
+
|
851
|
+
lhs_type = state.expr_types[node.lhs]
|
852
|
+
rhs_type = state.expr_types[node.rhs]
|
853
|
+
|
854
|
+
if not issubclass(lhs_type, NumericalType):
|
855
|
+
state.err(f"Cannot compare non-numeric type {lhs_type}", node.lhs)
|
856
|
+
return
|
857
|
+
if not issubclass(rhs_type, NumericalType):
|
858
|
+
state.err(f"Cannot compare non-numeric type {rhs_type}", node.rhs)
|
859
|
+
return
|
860
|
+
|
861
|
+
# args are both numeric
|
862
|
+
|
863
|
+
# if either is generic float, pick F64. we want F64 cuz otherwise we need
|
864
|
+
# an FPEXT to convert to F64
|
865
|
+
if lhs_type == FloatType:
|
866
|
+
state.expr_types[node.lhs] = F64Type
|
867
|
+
if rhs_type == FloatType:
|
868
|
+
state.expr_types[node.rhs] = F64Type
|
869
|
+
|
870
|
+
if lhs_type == IntegerType and rhs_type == IntegerType:
|
871
|
+
# use i64
|
872
|
+
state.expr_types[node.lhs] = I64Type
|
873
|
+
state.expr_types[node.rhs] = I64Type
|
874
|
+
return
|
875
|
+
|
876
|
+
if lhs_type == IntegerType:
|
877
|
+
# try to interpret it as the rhs_type if rhs_type is integer
|
878
|
+
if issubclass(rhs_type, IntegerType):
|
879
|
+
state.expr_types[node.lhs] = state.expr_types[node.rhs]
|
880
|
+
else:
|
881
|
+
# otherwise rhs is a float. just use i64
|
882
|
+
state.expr_types[node.lhs] = I64Type
|
883
|
+
|
884
|
+
if rhs_type == IntegerType:
|
885
|
+
# try to interpret it as the rhs_type if rhs_type is integer
|
886
|
+
if issubclass(lhs_type, IntegerType):
|
887
|
+
state.expr_types[node.rhs] = state.expr_types[node.lhs]
|
888
|
+
else:
|
889
|
+
# otherwise lhs is a float. just use i64
|
890
|
+
state.expr_types[node.rhs] = I64Type
|
891
|
+
|
892
|
+
def visit_AstFuncCall(self, node: AstFuncCall, state: CompileState):
|
893
|
+
func = state.resolved_references[node.func]
|
894
|
+
func_args = func.args
|
895
|
+
node_args = node.args if node.args else []
|
896
|
+
|
897
|
+
if len(node_args) < len(func_args):
|
898
|
+
state.errors.append(
|
899
|
+
CompileException(
|
900
|
+
f"Missing arguments (expected {len(func_args)} found {len(node_args)})",
|
901
|
+
node,
|
902
|
+
)
|
903
|
+
)
|
904
|
+
return
|
905
|
+
if len(node_args) > len(func_args):
|
906
|
+
state.errors.append(
|
907
|
+
CompileException(
|
908
|
+
f"Too many arguments (expected {len(func_args)} found {len(node_args)})",
|
909
|
+
node,
|
910
|
+
)
|
911
|
+
)
|
912
|
+
return
|
913
|
+
|
914
|
+
for value_expr, arg in zip(node_args, func_args):
|
915
|
+
arg_name, arg_type = arg
|
916
|
+
|
917
|
+
value_expr_type = state.expr_types[value_expr]
|
918
|
+
|
919
|
+
if value_expr_type == arg_type or (
|
920
|
+
isinstance(value_expr, AstLiteral)
|
921
|
+
and self.is_literal_convertible_to(value_expr, arg_type, state)
|
922
|
+
):
|
923
|
+
# arg type is good!
|
924
|
+
state.expr_types[value_expr] = arg_type
|
925
|
+
continue
|
926
|
+
|
927
|
+
# it is not. these are not compatible
|
928
|
+
state.errors.append(
|
929
|
+
CompileException(
|
930
|
+
f"Cannot convert {value_expr} ({value_expr_type}) to {arg_type}",
|
931
|
+
value_expr,
|
932
|
+
)
|
933
|
+
)
|
934
|
+
return
|
935
|
+
|
936
|
+
# got thru all args successfully
|
937
|
+
|
938
|
+
def visit_AstOr_AstAnd(self, node: AstOr | AstAnd, state: CompileState):
|
939
|
+
# "or/and" can have as many args as you want. they all need to be bools tho
|
940
|
+
for val in node.values:
|
941
|
+
val_type = state.expr_types[val]
|
942
|
+
if val_type != BoolType:
|
943
|
+
state.err(f"Arguments to 'and'/'or' must be booleans", val)
|
944
|
+
return
|
945
|
+
state.expr_types[val] = BoolType
|
946
|
+
|
947
|
+
def visit_AstNot(self, node: AstNot, state: CompileState):
|
948
|
+
val_type = state.expr_types[node.value]
|
949
|
+
if val_type != BoolType:
|
950
|
+
state.err(f"Argument to 'not' must be boolean", node.value)
|
951
|
+
return
|
952
|
+
state.expr_types[node.value] = BoolType
|
953
|
+
|
954
|
+
def visit_AstAssign(self, node: AstAssign, state: CompileState):
|
955
|
+
var_type = state.resolved_references[node.variable].type
|
956
|
+
value_type = state.expr_types[node.value]
|
957
|
+
if var_type != value_type:
|
958
|
+
if not (
|
959
|
+
isinstance(node.value, AstLiteral)
|
960
|
+
and self.is_literal_convertible_to(node.value, var_type, state)
|
961
|
+
):
|
962
|
+
state.err(f"Cannot interpret {node.value} as {var_type}", node.value)
|
963
|
+
return
|
964
|
+
|
965
|
+
state.expr_types[node.value] = var_type
|
966
|
+
|
967
|
+
if var_type.getMaxSize() > MAX_SERIALIZABLE_REGISTER_SIZE:
|
968
|
+
state.err(f"{var_type} is too big to fit in a variable", node)
|
969
|
+
return
|
970
|
+
|
971
|
+
def visit_AstGetItem(self, node: AstGetItem, state: CompileState):
|
972
|
+
# the node of the index number has no expression value, it's an arg
|
973
|
+
# but only at syntax level
|
974
|
+
state.expr_types[node.item] = NothingType
|
975
|
+
|
976
|
+
|
977
|
+
class CalculateExprValues(Visitor):
|
978
|
+
"""for each expr, try to calculate its constant value and store it in a map. stores None if no value could be
|
979
|
+
calculated at compile time, and NothingType if the expr had no value"""
|
980
|
+
|
981
|
+
def visit_AstLiteral(self, node: AstLiteral, state: CompileState):
|
982
|
+
literal_type = state.expr_types[node]
|
983
|
+
if literal_type != NothingType:
|
984
|
+
assert (
|
985
|
+
literal_type in NUMERIC_TYPES
|
986
|
+
or issubclass(literal_type, StringType)
|
987
|
+
or literal_type == BoolType
|
988
|
+
), literal_type
|
989
|
+
state.expr_values[node] = literal_type(node.value)
|
990
|
+
else:
|
991
|
+
state.expr_values[node] = literal_type()
|
992
|
+
|
993
|
+
def visit_AstReference(self, node: AstReference, state: CompileState):
|
994
|
+
ref = state.resolved_references[node]
|
995
|
+
|
996
|
+
if isinstance(ref, (ChTemplate, PrmTemplate, FpyVariable)):
|
997
|
+
# we do not try to calculate or predict these values at compile time
|
998
|
+
expr_value = None
|
999
|
+
elif isinstance(ref, FieldReference):
|
1000
|
+
if isinstance(ref.parent, FppType):
|
1001
|
+
# ref to a field of a constant
|
1002
|
+
# get the field
|
1003
|
+
expr_value = ref.get_from(ref.parent)
|
1004
|
+
else:
|
1005
|
+
# ref to a field of smth else. no runtime val
|
1006
|
+
expr_value = None
|
1007
|
+
elif isinstance(ref, FppType):
|
1008
|
+
# constant value
|
1009
|
+
expr_value = ref
|
1010
|
+
elif isinstance(ref, FpyCallable):
|
1011
|
+
# a reference to a callable doesn't have a value, you have to actually
|
1012
|
+
# call the func
|
1013
|
+
expr_value = NothingType()
|
1014
|
+
elif isinstance(ref, type):
|
1015
|
+
# a reference to a type doesn't have a value, and so doesn't have a type,
|
1016
|
+
# in and of itself. if this were a function call to the type's ctor then
|
1017
|
+
# it would have a value
|
1018
|
+
expr_value = NothingType()
|
1019
|
+
elif isinstance(ref, dict):
|
1020
|
+
# a ref to a scope doesn't have a value
|
1021
|
+
expr_value = NothingType()
|
1022
|
+
else:
|
1023
|
+
assert False, ref
|
1024
|
+
|
1025
|
+
assert expr_value is None or isinstance(expr_value, state.expr_types[node]), (
|
1026
|
+
expr_value,
|
1027
|
+
state.expr_types[node],
|
1028
|
+
)
|
1029
|
+
state.expr_values[node] = expr_value
|
1030
|
+
|
1031
|
+
def visit_AstFuncCall(self, node: AstFuncCall, state: CompileState):
|
1032
|
+
func = state.resolved_references[node.func]
|
1033
|
+
assert isinstance(func, FpyCallable)
|
1034
|
+
# gather arg values
|
1035
|
+
arg_values = [
|
1036
|
+
state.expr_values[e] for e in (node.args if node.args is not None else [])
|
1037
|
+
]
|
1038
|
+
unknown_value = any(v for v in arg_values if v is None)
|
1039
|
+
if unknown_value:
|
1040
|
+
state.expr_values[node] = None
|
1041
|
+
return
|
1042
|
+
|
1043
|
+
if isinstance(func, FpyTypeCtor):
|
1044
|
+
# actually construct the type
|
1045
|
+
if issubclass(func.type, SerializableType):
|
1046
|
+
instance = func.type()
|
1047
|
+
# pass in args as a dict
|
1048
|
+
# t[0] is the arg name
|
1049
|
+
arg_dict = {t[0]: v for t, v in zip(func.type.MEMBER_LIST, arg_values)}
|
1050
|
+
instance._val = arg_dict
|
1051
|
+
state.expr_values[node] = instance
|
1052
|
+
|
1053
|
+
elif issubclass(func.type, ArrayType):
|
1054
|
+
instance = func.type()
|
1055
|
+
instance._val = arg_values
|
1056
|
+
state.expr_values[node] = instance
|
1057
|
+
|
1058
|
+
elif func.type == TimeType:
|
1059
|
+
state.expr_values[node] = TimeType(*arg_values)
|
1060
|
+
|
1061
|
+
else:
|
1062
|
+
# no other FppTypeClasses have ctors
|
1063
|
+
assert False, func.return_type
|
1064
|
+
else:
|
1065
|
+
# don't try to calculate the value of this function call
|
1066
|
+
# it's something like a cmd or macro
|
1067
|
+
state.expr_values[node] = None
|
1068
|
+
|
1069
|
+
def visit_AstTest(self, node: AstTest, state: CompileState):
|
1070
|
+
# we do not calculate compile time value of or/and/nots/cmps at the moment
|
1071
|
+
state.expr_values[node] = None
|
1072
|
+
|
1073
|
+
def visit_default(self, node, state):
|
1074
|
+
# coding error, missed an expr
|
1075
|
+
assert not isinstance(node, AstExpr), node
|
1076
|
+
|
1077
|
+
|
1078
|
+
class GenerateVariableDirectives(Visitor):
|
1079
|
+
"""for each variable assignment or declaration, check the rhs was known
|
1080
|
+
at compile time, and generate a directive"""
|
1081
|
+
|
1082
|
+
def visit_AstAssign(self, node: AstAssign, state: CompileState):
|
1083
|
+
existing_var = state.resolved_references[node.variable]
|
1084
|
+
# should already have been put in var table
|
1085
|
+
assert existing_var is not None
|
1086
|
+
|
1087
|
+
# we should have type info about the variable
|
1088
|
+
assert existing_var.type is not None
|
1089
|
+
|
1090
|
+
value_type = state.expr_types[node.value]
|
1091
|
+
value = state.expr_values[node.value]
|
1092
|
+
|
1093
|
+
# already type checked
|
1094
|
+
assert value_type == type(value), (value_type, type(value))
|
1095
|
+
|
1096
|
+
if value is None:
|
1097
|
+
# expr value is unknown at this point in compile
|
1098
|
+
state.err(
|
1099
|
+
f"Cannot assign {node.variable.var}: {existing_var.type} to {node.value}, as its value was not known at compile time",
|
1100
|
+
node.value,
|
1101
|
+
)
|
1102
|
+
return
|
1103
|
+
|
1104
|
+
sreg_idx = existing_var.sreg_idx
|
1105
|
+
if sreg_idx is None:
|
1106
|
+
# doesn't have an sreg idx, allocate one
|
1107
|
+
sreg_idx = state.next_sreg
|
1108
|
+
state.next_sreg += 1
|
1109
|
+
existing_var.sreg_idx = sreg_idx
|
1110
|
+
val_bytes = value.serialize()
|
1111
|
+
assert len(val_bytes) == value.getMaxSize(), (
|
1112
|
+
len(val_bytes),
|
1113
|
+
value.getMaxSize(),
|
1114
|
+
value,
|
1115
|
+
)
|
1116
|
+
assert len(val_bytes) <= MAX_SERIALIZABLE_REGISTER_SIZE, len(val_bytes)
|
1117
|
+
state.directives[node] = [SetSerRegDirective(sreg_idx, val_bytes)]
|
1118
|
+
|
1119
|
+
|
1120
|
+
class GenerateConstCmdDirectives(Visitor):
|
1121
|
+
"""for each command or macro whose arguments were const at runtime (should be all at the moment),
|
1122
|
+
generate a directive"""
|
1123
|
+
def visit_AstFuncCall(self, node: AstFuncCall, state: CompileState):
|
1124
|
+
func = state.resolved_references[node.func]
|
1125
|
+
if isinstance(func, FpyCmd):
|
1126
|
+
arg_bytes = bytes()
|
1127
|
+
for arg_node in node.args if node.args is not None else []:
|
1128
|
+
arg_value = state.expr_values[arg_node]
|
1129
|
+
if arg_value is None:
|
1130
|
+
state.err(
|
1131
|
+
f"Only constant arguments to commands are allowed", arg_node
|
1132
|
+
)
|
1133
|
+
return
|
1134
|
+
arg_bytes += arg_value.serialize()
|
1135
|
+
state.directives[node] = [CmdDirective(func.cmd.get_op_code(), arg_bytes)]
|
1136
|
+
elif isinstance(func, FpyMacro):
|
1137
|
+
arg_values = []
|
1138
|
+
for arg_node in node.args if node.args is not None else []:
|
1139
|
+
arg_value = state.expr_values[arg_node]
|
1140
|
+
if arg_value is None:
|
1141
|
+
state.err(
|
1142
|
+
f"Only constant arguments to macros are allowed", arg_node
|
1143
|
+
)
|
1144
|
+
return
|
1145
|
+
arg_values.append(arg_value)
|
1146
|
+
|
1147
|
+
state.directives[node] = func.instantiate_macro(arg_values)
|
1148
|
+
else:
|
1149
|
+
state.directives[node] = None
|
1150
|
+
|
1151
|
+
|
1152
|
+
def put_sreg_in_nreg(
|
1153
|
+
sreg_idx: int, sreg_offset: int, nreg_idx: int, size: int
|
1154
|
+
) -> list[Directive]:
|
1155
|
+
if size > 4:
|
1156
|
+
return [DeserSerReg8Directive(sreg_idx, sreg_offset, nreg_idx)]
|
1157
|
+
elif size > 2:
|
1158
|
+
return [DeserSerReg4Directive(sreg_idx, sreg_offset, nreg_idx)]
|
1159
|
+
elif size > 1:
|
1160
|
+
return [DeserSerReg2Directive(sreg_idx, sreg_offset, nreg_idx)]
|
1161
|
+
elif size == 1:
|
1162
|
+
return [DeserSerReg1Directive(sreg_idx, sreg_offset, nreg_idx)]
|
1163
|
+
else:
|
1164
|
+
assert False, size
|
1165
|
+
|
1166
|
+
|
1167
|
+
class AssignExprRegisters(Visitor):
|
1168
|
+
"""assign each expr a unique register"""
|
1169
|
+
def visit_AstExpr(self, node: AstExpr, state: CompileState):
|
1170
|
+
state.expr_registers[node] = state.next_register
|
1171
|
+
state.next_register += 1
|
1172
|
+
|
1173
|
+
|
1174
|
+
class GenerateConstExprDirectives(Visitor):
|
1175
|
+
"""for each expr with a constant compile time value, generate
|
1176
|
+
directives for how to put it in its register"""
|
1177
|
+
def visit_AstExpr(self, node: AstExpr, state: CompileState):
|
1178
|
+
expr_type = state.expr_types[node]
|
1179
|
+
|
1180
|
+
if node in state.directives:
|
1181
|
+
# already have directives associated with this node
|
1182
|
+
return
|
1183
|
+
|
1184
|
+
if expr_type == NothingType:
|
1185
|
+
# impossible. nothing type has no value
|
1186
|
+
state.directives[node] = None
|
1187
|
+
return
|
1188
|
+
|
1189
|
+
if expr_type.getMaxSize() > 8:
|
1190
|
+
# bigger than 8 bytes
|
1191
|
+
# impossible. can't fit in a register
|
1192
|
+
state.directives[node] = None
|
1193
|
+
return
|
1194
|
+
|
1195
|
+
# okay, it is not nothing and it is smaller than 8 bytes.
|
1196
|
+
# should be able to put it in a reg
|
1197
|
+
|
1198
|
+
register = state.expr_registers[node]
|
1199
|
+
|
1200
|
+
expr_value = state.expr_values[node]
|
1201
|
+
|
1202
|
+
if expr_value is None:
|
1203
|
+
# no const value
|
1204
|
+
return
|
1205
|
+
|
1206
|
+
# it has a constant value at compile time
|
1207
|
+
serialized_expr_value = expr_value.serialize()
|
1208
|
+
assert len(serialized_expr_value) <= 8, len(serialized_expr_value)
|
1209
|
+
val_as_i64_bytes = bytes(8 - len(serialized_expr_value))
|
1210
|
+
val_as_i64_bytes += serialized_expr_value
|
1211
|
+
|
1212
|
+
# reinterpret as an I64
|
1213
|
+
val_as_i64 = I64Type()
|
1214
|
+
val_as_i64.deserialize(val_as_i64_bytes, 0)
|
1215
|
+
|
1216
|
+
state.directives[node] = [SetRegDirective(register, val_as_i64.val)]
|
1217
|
+
|
1218
|
+
|
1219
|
+
class GenerateNonConstExprDirectives(Visitor):
|
1220
|
+
"""for each expr whose value is not known at compile time, but can be calculated at run time,
|
1221
|
+
generate directives to calculate the value and put it in its register"""
|
1222
|
+
|
1223
|
+
def visit_AstReference(self, node: AstReference, state: CompileState):
|
1224
|
+
if node in state.directives:
|
1225
|
+
# already know how to put it in reg, or it is impossible
|
1226
|
+
return
|
1227
|
+
|
1228
|
+
expr_type = state.expr_types[node]
|
1229
|
+
ref = state.resolved_references[node]
|
1230
|
+
|
1231
|
+
directives = []
|
1232
|
+
|
1233
|
+
# does not have a constant compile time value
|
1234
|
+
|
1235
|
+
# all references that don't have a compile time value have to go into an sreg first
|
1236
|
+
# and then into an nreg
|
1237
|
+
|
1238
|
+
sreg_idx = None
|
1239
|
+
|
1240
|
+
offset = 0
|
1241
|
+
|
1242
|
+
base_ref = ref
|
1243
|
+
|
1244
|
+
# if it's a field ref, find the parent and the offset in the parent
|
1245
|
+
while isinstance(base_ref, FieldReference):
|
1246
|
+
offset += base_ref.offset
|
1247
|
+
base_ref = base_ref.parent
|
1248
|
+
|
1249
|
+
if isinstance(base_ref, FpyVariable):
|
1250
|
+
# already in an sreg
|
1251
|
+
sreg_idx = base_ref.sreg_idx
|
1252
|
+
else:
|
1253
|
+
sreg_idx = state.next_sreg
|
1254
|
+
state.next_sreg += 1
|
1255
|
+
|
1256
|
+
if isinstance(base_ref, ChTemplate):
|
1257
|
+
tlm_time_sreg_idx = state.next_sreg
|
1258
|
+
state.next_sreg += 1
|
1259
|
+
directives.append(
|
1260
|
+
GetTlmDirective(sreg_idx, tlm_time_sreg_idx, base_ref.get_id())
|
1261
|
+
)
|
1262
|
+
|
1263
|
+
elif isinstance(base_ref, PrmTemplate):
|
1264
|
+
directives.append(GetPrmDirective(sreg_idx, base_ref.get_id()))
|
1265
|
+
|
1266
|
+
else:
|
1267
|
+
assert (
|
1268
|
+
False
|
1269
|
+
), base_ref # ref should either be impossible to put in a reg or should have a compile time val
|
1270
|
+
|
1271
|
+
# pull from sreg into nreg
|
1272
|
+
directives.extend(
|
1273
|
+
put_sreg_in_nreg(
|
1274
|
+
sreg_idx, offset, state.expr_registers[node], expr_type.getMaxSize()
|
1275
|
+
)
|
1276
|
+
)
|
1277
|
+
|
1278
|
+
state.directives[node] = directives
|
1279
|
+
|
1280
|
+
def visit_AstAnd_AstOr(self, node: AstAnd | AstOr, state: CompileState):
|
1281
|
+
if node in state.directives:
|
1282
|
+
# already know how to put it in reg, or know that it's impossible
|
1283
|
+
return
|
1284
|
+
|
1285
|
+
expr_reg = state.expr_registers[node]
|
1286
|
+
directives = []
|
1287
|
+
|
1288
|
+
registers_to_compare = []
|
1289
|
+
for arg_value_expr in node.values:
|
1290
|
+
arg_value_dirs = state.directives[arg_value_expr]
|
1291
|
+
assert arg_value_dirs is not None
|
1292
|
+
directives.extend(arg_value_dirs)
|
1293
|
+
registers_to_compare.append(state.expr_registers[arg_value_expr])
|
1294
|
+
|
1295
|
+
assert len(registers_to_compare) >= 2, len(registers_to_compare)
|
1296
|
+
|
1297
|
+
# okay, now we have to "or" or "and" together all of the registers
|
1298
|
+
# "or/and" the first two together, put in res.
|
1299
|
+
# from then on, "or/and" the next with res
|
1300
|
+
|
1301
|
+
dir_type = OrDirective if isinstance(node, AstOr) else AndDirective
|
1302
|
+
|
1303
|
+
directives.append(
|
1304
|
+
dir_type(registers_to_compare[0], registers_to_compare[1], expr_reg)
|
1305
|
+
)
|
1306
|
+
|
1307
|
+
for i in range(2, len(registers_to_compare)):
|
1308
|
+
directives.append(dir_type(expr_reg, registers_to_compare[i], expr_reg))
|
1309
|
+
|
1310
|
+
state.directives[node] = directives
|
1311
|
+
|
1312
|
+
def visit_AstNot(self, node: AstNot, state: CompileState):
|
1313
|
+
if node in state.directives:
|
1314
|
+
# already know how to put it in reg
|
1315
|
+
return
|
1316
|
+
|
1317
|
+
expr_reg = state.expr_registers[node]
|
1318
|
+
directives = []
|
1319
|
+
arg_value_dirs = state.directives[node.value]
|
1320
|
+
assert arg_value_dirs is not None
|
1321
|
+
directives.extend(arg_value_dirs)
|
1322
|
+
directives.append(NotDirective(state.expr_registers[node.value], expr_reg))
|
1323
|
+
|
1324
|
+
state.directives[node] = directives
|
1325
|
+
|
1326
|
+
def visit_AstComparison(self, node: AstComparison, state: CompileState):
|
1327
|
+
if node in state.directives:
|
1328
|
+
# already know how to put it in reg
|
1329
|
+
return
|
1330
|
+
|
1331
|
+
directives = []
|
1332
|
+
|
1333
|
+
lhs_type = state.expr_types[node.lhs]
|
1334
|
+
rhs_type = state.expr_types[node.rhs]
|
1335
|
+
|
1336
|
+
lhs_reg = state.expr_registers[node.lhs]
|
1337
|
+
rhs_reg = state.expr_registers[node.rhs]
|
1338
|
+
res_reg = state.expr_registers[node]
|
1339
|
+
|
1340
|
+
directives.extend(state.directives[node.lhs])
|
1341
|
+
directives.extend(state.directives[node.rhs])
|
1342
|
+
|
1343
|
+
fp = False
|
1344
|
+
if issubclass(lhs_type, FloatType) or issubclass(rhs_type, FloatType):
|
1345
|
+
fp = True
|
1346
|
+
|
1347
|
+
if fp:
|
1348
|
+
# need to convert both lhs and rhs into F64
|
1349
|
+
# modify them in place
|
1350
|
+
|
1351
|
+
# convert int to float
|
1352
|
+
if issubclass(lhs_type, IntegerType):
|
1353
|
+
if lhs_type in UNSIGNED_INTEGER_TYPES:
|
1354
|
+
directives.append(UnsignedIntToFloatDirective(lhs_reg, lhs_reg))
|
1355
|
+
else:
|
1356
|
+
directives.append(SignedIntToFloatDirective(lhs_reg, lhs_reg))
|
1357
|
+
if issubclass(rhs_type, IntegerType):
|
1358
|
+
if rhs_type in UNSIGNED_INTEGER_TYPES:
|
1359
|
+
directives.append(UnsignedIntToFloatDirective(rhs_reg, rhs_reg))
|
1360
|
+
else:
|
1361
|
+
directives.append(SignedIntToFloatDirective(rhs_reg, rhs_reg))
|
1362
|
+
|
1363
|
+
# convert F32 to F64
|
1364
|
+
if lhs_type == F32Type:
|
1365
|
+
directives.append(FloatExtendDirective(lhs_reg, lhs_reg))
|
1366
|
+
if rhs_type == F32Type:
|
1367
|
+
directives.append(FloatExtendDirective(rhs_reg, rhs_reg))
|
1368
|
+
|
1369
|
+
if node.op.value == "==":
|
1370
|
+
if fp:
|
1371
|
+
directives.append(FloatEqualDirective(lhs_reg, rhs_reg, res_reg))
|
1372
|
+
else:
|
1373
|
+
directives.append(IntEqualDirective(lhs_reg, rhs_reg, res_reg))
|
1374
|
+
elif node.op.value == "!=":
|
1375
|
+
if fp:
|
1376
|
+
directives.append(FloatNotEqualDirective(lhs_reg, rhs_reg, res_reg))
|
1377
|
+
else:
|
1378
|
+
directives.append(IntNotEqualDirective(lhs_reg, rhs_reg, res_reg))
|
1379
|
+
else:
|
1380
|
+
|
1381
|
+
if fp:
|
1382
|
+
dir_type = FLOAT_INEQUALITY_DIRECTIVES[node.op.value]
|
1383
|
+
else:
|
1384
|
+
# if either is signed, consider both as signed
|
1385
|
+
signed = (
|
1386
|
+
lhs_type in SIGNED_INTEGER_TYPES or rhs_type in SIGNED_INTEGER_TYPES
|
1387
|
+
)
|
1388
|
+
|
1389
|
+
if signed:
|
1390
|
+
dir_type = INT_SIGNED_INEQUALITY_DIRECTIVES[node.op.value]
|
1391
|
+
else:
|
1392
|
+
dir_type = INT_UNSIGNED_INEQUALITY_DIRECTIVES[node.op.value]
|
1393
|
+
|
1394
|
+
directives.append(dir_type(lhs_reg, rhs_reg, res_reg))
|
1395
|
+
|
1396
|
+
state.directives[node] = directives
|
1397
|
+
|
1398
|
+
|
1399
|
+
class CountNodeDirectives(Visitor):
|
1400
|
+
"""count the number of directives that will be generated by each node"""
|
1401
|
+
|
1402
|
+
def visit_AstIf(self, node: AstIf, state: CompileState):
|
1403
|
+
count = 0
|
1404
|
+
# include the condition
|
1405
|
+
count += state.node_dir_counts[node.condition]
|
1406
|
+
# include if stmt
|
1407
|
+
count += 1
|
1408
|
+
# include body
|
1409
|
+
count += state.node_dir_counts[node.body]
|
1410
|
+
# include a goto end of if
|
1411
|
+
count += 1
|
1412
|
+
|
1413
|
+
if node.elifs is not None:
|
1414
|
+
count += state.node_dir_counts[node.elifs]
|
1415
|
+
if node.els is not None:
|
1416
|
+
count += state.node_dir_counts[node.els]
|
1417
|
+
|
1418
|
+
state.node_dir_counts[node] = count
|
1419
|
+
|
1420
|
+
def visit_AstElifs(self, node: AstElifs, state: CompileState):
|
1421
|
+
count = 0
|
1422
|
+
for case in node.cases:
|
1423
|
+
count += state.node_dir_counts[case]
|
1424
|
+
|
1425
|
+
state.node_dir_counts[node] = count
|
1426
|
+
|
1427
|
+
def visit_AstElif(self, node: AstElif, state: CompileState):
|
1428
|
+
count = 0
|
1429
|
+
# include the condition
|
1430
|
+
count += state.node_dir_counts[node.condition]
|
1431
|
+
# include if stmt
|
1432
|
+
count += 1
|
1433
|
+
# include body
|
1434
|
+
count += state.node_dir_counts[node.body]
|
1435
|
+
# include a goto end of if
|
1436
|
+
count += 1
|
1437
|
+
|
1438
|
+
state.node_dir_counts[node] = count
|
1439
|
+
|
1440
|
+
def visit_AstBody(self, node: AstBody, state: CompileState):
|
1441
|
+
count = 0
|
1442
|
+
for stmt in node.stmts:
|
1443
|
+
count += state.node_dir_counts[stmt]
|
1444
|
+
|
1445
|
+
state.node_dir_counts[node] = count
|
1446
|
+
|
1447
|
+
def visit_default(self, node, state):
|
1448
|
+
state.node_dir_counts[node] = (
|
1449
|
+
len(state.directives[node]) if state.directives.get(node) is not None else 0
|
1450
|
+
)
|
1451
|
+
|
1452
|
+
|
1453
|
+
class CalculateStartLineIdx(TopDownVisitor):
|
1454
|
+
"""based on the number of directives generated by each node, calculate the start line idx
|
1455
|
+
of each node's directives"""
|
1456
|
+
def visit_AstBody(self, node: AstBody, state: CompileState):
|
1457
|
+
if node not in state.start_line_idx:
|
1458
|
+
state.start_line_idx[node] = 0
|
1459
|
+
|
1460
|
+
start_idx = state.start_line_idx[node]
|
1461
|
+
|
1462
|
+
line_idx = start_idx
|
1463
|
+
for stmt in node.stmts:
|
1464
|
+
state.start_line_idx[stmt] = line_idx
|
1465
|
+
line_idx += state.node_dir_counts[stmt]
|
1466
|
+
|
1467
|
+
def visit_AstIf(self, node: AstIf, state: CompileState):
|
1468
|
+
line_idx = state.start_line_idx[node]
|
1469
|
+
state.start_line_idx[node.condition] = line_idx
|
1470
|
+
line_idx += state.node_dir_counts[node.condition]
|
1471
|
+
# include if stmt
|
1472
|
+
line_idx += 1
|
1473
|
+
state.start_line_idx[node.body] = line_idx
|
1474
|
+
line_idx += state.node_dir_counts[node.body]
|
1475
|
+
# include goto stmt
|
1476
|
+
line_idx += 1
|
1477
|
+
if node.elifs is not None:
|
1478
|
+
state.start_line_idx[node.elifs] = line_idx
|
1479
|
+
line_idx += state.node_dir_counts[node.elifs]
|
1480
|
+
if node.els is not None:
|
1481
|
+
state.start_line_idx[node.els] = line_idx
|
1482
|
+
line_idx += state.node_dir_counts[node.els]
|
1483
|
+
|
1484
|
+
def visit_AstElifs(self, node: AstElifs, state: CompileState):
|
1485
|
+
line_idx = state.start_line_idx[node]
|
1486
|
+
for case in node.cases:
|
1487
|
+
state.start_line_idx[case] = line_idx
|
1488
|
+
line_idx += state.node_dir_counts[case]
|
1489
|
+
|
1490
|
+
def visit_AstElif(self, node: AstElif, state: CompileState):
|
1491
|
+
line_idx = state.start_line_idx[node]
|
1492
|
+
state.start_line_idx[node.condition] = line_idx
|
1493
|
+
line_idx += state.node_dir_counts[node.condition]
|
1494
|
+
# include if dir
|
1495
|
+
line_idx += 1
|
1496
|
+
state.start_line_idx[node.body] = line_idx
|
1497
|
+
line_idx += state.node_dir_counts[node.body]
|
1498
|
+
# include a goto end of if
|
1499
|
+
line_idx += 1
|
1500
|
+
|
1501
|
+
|
1502
|
+
class GenerateBodyDirectives(Visitor):
|
1503
|
+
"""concatenate all directives together for each AstBody"""
|
1504
|
+
|
1505
|
+
def visit_AstIf(self, node: AstIf, state: CompileState):
|
1506
|
+
start_line_idx = state.start_line_idx[node]
|
1507
|
+
|
1508
|
+
all_dirs = []
|
1509
|
+
|
1510
|
+
cases: list[tuple[AstExpr, AstBody]] = []
|
1511
|
+
goto_ends: list[GotoDirective] = []
|
1512
|
+
|
1513
|
+
cases.append((node.condition, node.body))
|
1514
|
+
|
1515
|
+
if node.elifs is not None:
|
1516
|
+
for case in node.elifs.cases:
|
1517
|
+
cases.append((case.condition, case.body))
|
1518
|
+
|
1519
|
+
for case in cases:
|
1520
|
+
case_dirs = []
|
1521
|
+
# include the condition
|
1522
|
+
case_dirs.extend(state.directives[case[0]])
|
1523
|
+
# include if stmt (update the end idx later)
|
1524
|
+
if_dir = IfDirective(state.expr_registers[case[0]], -1)
|
1525
|
+
|
1526
|
+
case_dirs.append(if_dir)
|
1527
|
+
# include body
|
1528
|
+
case_dirs.extend(state.directives[case[1]])
|
1529
|
+
# include a temporary goto end of if, will be refined later
|
1530
|
+
goto_dir = GotoDirective(-1)
|
1531
|
+
case_dirs.append(goto_dir)
|
1532
|
+
goto_ends.append(goto_dir)
|
1533
|
+
|
1534
|
+
# if false, skip the body and goto
|
1535
|
+
if_dir.false_goto_stmt_index = (
|
1536
|
+
start_line_idx + len(all_dirs) + len(case_dirs)
|
1537
|
+
)
|
1538
|
+
|
1539
|
+
all_dirs.extend(case_dirs)
|
1540
|
+
|
1541
|
+
if node.els is not None:
|
1542
|
+
all_dirs.extend(state.directives[node.els])
|
1543
|
+
|
1544
|
+
for goto in goto_ends:
|
1545
|
+
goto.statement_index = start_line_idx + len(all_dirs)
|
1546
|
+
|
1547
|
+
state.directives[node] = all_dirs
|
1548
|
+
|
1549
|
+
def visit_AstBody(self, node: AstBody, state: CompileState):
|
1550
|
+
dirs = []
|
1551
|
+
for stmt in node.stmts:
|
1552
|
+
stmt_dirs = state.directives.get(stmt, None)
|
1553
|
+
if stmt_dirs is not None:
|
1554
|
+
dirs.extend(stmt_dirs)
|
1555
|
+
|
1556
|
+
state.directives[node] = dirs
|
1557
|
+
|
1558
|
+
|
1559
|
+
def get_base_compile_state(dictionary: str) -> CompileState:
|
1560
|
+
"""return the initial state of the compiler, based on the given dict path"""
|
1561
|
+
cmd_json_dict_loader = CmdJsonLoader(dictionary)
|
1562
|
+
(cmd_id_dict, cmd_name_dict, versions) = cmd_json_dict_loader.construct_dicts(
|
1563
|
+
dictionary
|
1564
|
+
)
|
1565
|
+
|
1566
|
+
ch_json_dict_loader = ChJsonLoader(dictionary)
|
1567
|
+
(ch_id_dict, ch_name_dict, versions) = ch_json_dict_loader.construct_dicts(
|
1568
|
+
dictionary
|
1569
|
+
)
|
1570
|
+
prm_json_dict_loader = PrmJsonLoader(dictionary)
|
1571
|
+
(prm_id_dict, prm_name_dict, versions) = prm_json_dict_loader.construct_dicts(
|
1572
|
+
dictionary
|
1573
|
+
)
|
1574
|
+
# the type name dict is a mapping of a fully qualified name to an fprime type
|
1575
|
+
# here we put into it all types found while parsing all cmds, params and tlm channels
|
1576
|
+
type_name_dict: dict[str, FppTypeClass] = cmd_json_dict_loader.parsed_types
|
1577
|
+
type_name_dict.update(ch_json_dict_loader.parsed_types)
|
1578
|
+
type_name_dict.update(prm_json_dict_loader.parsed_types)
|
1579
|
+
|
1580
|
+
# enum const dict is a dict of fully qualified enum const name (like Ref.Choice.ONE) to its fprime value
|
1581
|
+
enum_const_name_dict: dict[str, FppType] = {}
|
1582
|
+
|
1583
|
+
# find each enum type, and put each of its values in the enum const dict
|
1584
|
+
for name, typ in type_name_dict.items():
|
1585
|
+
if issubclass(typ, EnumType):
|
1586
|
+
for enum_const_name, val in typ.ENUM_DICT.items():
|
1587
|
+
enum_const_name_dict[name + "." + enum_const_name] = typ(
|
1588
|
+
enum_const_name
|
1589
|
+
)
|
1590
|
+
|
1591
|
+
# insert the implicit types into the dict
|
1592
|
+
type_name_dict["Fw.Time"] = TimeType
|
1593
|
+
for typ in NUMERIC_TYPES:
|
1594
|
+
type_name_dict[typ.get_canonical_name()] = typ
|
1595
|
+
type_name_dict["bool"] = BoolType
|
1596
|
+
# note no string type at the moment
|
1597
|
+
|
1598
|
+
callable_name_dict: dict[str, FpyCallable] = {}
|
1599
|
+
# add all cmds to the callable dict
|
1600
|
+
for name, cmd in cmd_name_dict.items():
|
1601
|
+
cmd: CmdTemplate
|
1602
|
+
args = []
|
1603
|
+
for arg_name, _, arg_type in cmd.arguments:
|
1604
|
+
args.append((arg_name, arg_type))
|
1605
|
+
# cmds are thought of as callables with a "NothingType" return value
|
1606
|
+
callable_name_dict[name] = FpyCmd(NothingType, args, cmd)
|
1607
|
+
|
1608
|
+
# for each type in the dict, if it has a constructor, create an FpyTypeCtor
|
1609
|
+
# object to track the constructor and put it in the callable name dict
|
1610
|
+
for name, typ in type_name_dict.items():
|
1611
|
+
args = []
|
1612
|
+
if issubclass(typ, SerializableType):
|
1613
|
+
for arg_name, arg_type, _, _ in typ.MEMBER_LIST:
|
1614
|
+
args.append((arg_name, arg_type))
|
1615
|
+
elif issubclass(typ, ArrayType):
|
1616
|
+
for i in range(0, typ.LENGTH):
|
1617
|
+
args.append(("e" + str(i), typ.MEMBER_TYPE))
|
1618
|
+
elif issubclass(typ, TimeType):
|
1619
|
+
args.append(("time_base", U16Type))
|
1620
|
+
args.append(("time_context", U8Type))
|
1621
|
+
args.append(("seconds", U32Type))
|
1622
|
+
args.append(("useconds", U32Type))
|
1623
|
+
else:
|
1624
|
+
# bool, enum, string or numeric type
|
1625
|
+
# none of these have callable ctors
|
1626
|
+
continue
|
1627
|
+
|
1628
|
+
callable_name_dict[name] = FpyTypeCtor(typ, args, typ)
|
1629
|
+
|
1630
|
+
# for each macro function, add it to the callable dict
|
1631
|
+
for macro_name, macro in MACROS.items():
|
1632
|
+
callable_name_dict[macro_name] = macro
|
1633
|
+
|
1634
|
+
state = CompileState(
|
1635
|
+
tlms=create_scope(ch_name_dict),
|
1636
|
+
prms=create_scope(prm_name_dict),
|
1637
|
+
types=create_scope(type_name_dict),
|
1638
|
+
callables=create_scope(callable_name_dict),
|
1639
|
+
consts=create_scope(enum_const_name_dict),
|
1640
|
+
)
|
1641
|
+
return state
|
1642
|
+
|
1643
|
+
|
1644
|
+
def compile(body: AstBody, dictionary: str) -> list[Directive]:
|
1645
|
+
state = get_base_compile_state(dictionary)
|
1646
|
+
passes: list[Visitor] = [
|
1647
|
+
AssignIds(),
|
1648
|
+
# based on assignment syntax nodes, we know which variables exist where
|
1649
|
+
CreateVariables(),
|
1650
|
+
# now that variables have been defined, all names/attributes/indices (references)
|
1651
|
+
# should be defined
|
1652
|
+
ResolveReferences(),
|
1653
|
+
# now that we know what all refs point to, we should be able to figure out the type
|
1654
|
+
# of every expression
|
1655
|
+
CalculateExprTypes(),
|
1656
|
+
# now that we know the type of each expr, we can type check all function calls
|
1657
|
+
# and also narrow down ambiguous argument types
|
1658
|
+
CheckAndResolveArgumentTypes(),
|
1659
|
+
# okay, now that we're sure we're passing in all the right args to each func,
|
1660
|
+
# we can calculate values of type ctors etc etc
|
1661
|
+
CalculateExprValues(),
|
1662
|
+
# now that we know variable values, we can generate directives for vars
|
1663
|
+
GenerateVariableDirectives(),
|
1664
|
+
# give each expr its own register
|
1665
|
+
AssignExprRegisters(),
|
1666
|
+
# for cmds, which have constant arguments, generate the corresponding directives
|
1667
|
+
GenerateConstCmdDirectives(),
|
1668
|
+
# for expressions which have constant values, generate corresponding directives
|
1669
|
+
# to put the expr in its register
|
1670
|
+
GenerateConstExprDirectives(),
|
1671
|
+
# for expressions which don't have constant values, generate directives to
|
1672
|
+
# calculate the expr at runtime and put it in its register
|
1673
|
+
GenerateNonConstExprDirectives(),
|
1674
|
+
# count the number of directives generated by each node
|
1675
|
+
CountNodeDirectives(),
|
1676
|
+
# calculate the index that the node will correspond to in the output file
|
1677
|
+
CalculateStartLineIdx(),
|
1678
|
+
# generate directives for each body node, including the root
|
1679
|
+
GenerateBodyDirectives(),
|
1680
|
+
]
|
1681
|
+
|
1682
|
+
for compile_pass in passes:
|
1683
|
+
compile_pass.run(body, state)
|
1684
|
+
for error in state.errors:
|
1685
|
+
raise error
|
1686
|
+
|
1687
|
+
return state.directives[body]
|