fprime-gds 4.0.2a2__py3-none-any.whl → 4.0.2a4__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/distributor/distributor.py +9 -6
- fprime_gds/common/fpy/README.md +190 -42
- fprime_gds/common/fpy/SPEC.md +153 -39
- fprime_gds/common/fpy/bytecode/assembler.py +62 -0
- fprime_gds/common/fpy/bytecode/directives.py +620 -294
- fprime_gds/common/fpy/codegen.py +687 -910
- fprime_gds/common/fpy/grammar.lark +44 -9
- fprime_gds/common/fpy/main.py +27 -4
- fprime_gds/common/fpy/model.py +799 -0
- fprime_gds/common/fpy/parser.py +29 -34
- fprime_gds/common/fpy/test_helpers.py +119 -0
- fprime_gds/common/fpy/types.py +563 -0
- fprime_gds/common/models/dictionaries.py +23 -0
- fprime_gds/common/pipeline/standard.py +5 -0
- fprime_gds/common/testing_fw/api.py +8 -1
- fprime_gds/executables/cli.py +9 -0
- fprime_gds/executables/run_deployment.py +2 -0
- fprime_gds/flask/app.py +17 -2
- fprime_gds/flask/default_settings.py +1 -0
- fprime_gds/flask/static/index.html +7 -4
- fprime_gds/flask/static/js/config.js +9 -1
- fprime_gds/flask/static/js/vue-support/channel.js +16 -0
- fprime_gds/flask/static/js/vue-support/event.js +1 -0
- fprime_gds/flask/static/js/vue-support/fp-row.js +25 -1
- {fprime_gds-4.0.2a2.dist-info → fprime_gds-4.0.2a4.dist-info}/METADATA +1 -1
- {fprime_gds-4.0.2a2.dist-info → fprime_gds-4.0.2a4.dist-info}/RECORD +31 -27
- {fprime_gds-4.0.2a2.dist-info → fprime_gds-4.0.2a4.dist-info}/entry_points.txt +2 -1
- {fprime_gds-4.0.2a2.dist-info → fprime_gds-4.0.2a4.dist-info}/WHEEL +0 -0
- {fprime_gds-4.0.2a2.dist-info → fprime_gds-4.0.2a4.dist-info}/licenses/LICENSE.txt +0 -0
- {fprime_gds-4.0.2a2.dist-info → fprime_gds-4.0.2a4.dist-info}/licenses/NOTICE.txt +0 -0
- {fprime_gds-4.0.2a2.dist-info → fprime_gds-4.0.2a4.dist-info}/top_level.txt +0 -0
fprime_gds/common/fpy/codegen.py
CHANGED
@@ -1,44 +1,98 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
from abc import ABC
|
3
3
|
import inspect
|
4
|
-
from dataclasses import dataclass, field, fields
|
4
|
+
from dataclasses import astuple, dataclass, field, fields
|
5
|
+
from pathlib import Path
|
6
|
+
import struct
|
5
7
|
import traceback
|
6
|
-
from typing import Callable
|
7
8
|
import typing
|
9
|
+
from typing import Union, get_origin, get_args
|
10
|
+
import zlib
|
11
|
+
|
12
|
+
from fprime_gds.common.fpy.types import (
|
13
|
+
SPECIFIC_FLOAT_TYPES,
|
14
|
+
SPECIFIC_INTEGER_TYPES,
|
15
|
+
MACROS,
|
16
|
+
MAX_DIRECTIVE_SIZE,
|
17
|
+
MAX_DIRECTIVES_COUNT,
|
18
|
+
SPECIFIC_NUMERIC_TYPES,
|
19
|
+
SIGNED_INTEGER_TYPES,
|
20
|
+
UNSIGNED_INTEGER_TYPES,
|
21
|
+
CompileException,
|
22
|
+
CompileState,
|
23
|
+
FieldReference,
|
24
|
+
FppTypeClass,
|
25
|
+
FpyCallable,
|
26
|
+
FpyCmd,
|
27
|
+
FpyMacro,
|
28
|
+
FpyReference,
|
29
|
+
FpyScope,
|
30
|
+
FpyTypeCtor,
|
31
|
+
FpyVariable,
|
32
|
+
InternalIntType,
|
33
|
+
InternalStringType,
|
34
|
+
NothingType,
|
35
|
+
TopDownVisitor,
|
36
|
+
Visitor,
|
37
|
+
create_scope,
|
38
|
+
get_ref_fpp_type_class,
|
39
|
+
is_instance_compat,
|
40
|
+
)
|
41
|
+
|
42
|
+
# In Python 3.10+, the `|` operator creates a `types.UnionType`.
|
43
|
+
# We need to handle this for forward compatibility, but it won't exist in 3.9.
|
44
|
+
try:
|
45
|
+
from types import UnionType
|
46
|
+
|
47
|
+
UNION_TYPES = (Union, UnionType)
|
48
|
+
except ImportError:
|
49
|
+
UNION_TYPES = (Union,)
|
8
50
|
|
9
51
|
from fprime_gds.common.fpy.bytecode.directives import (
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
52
|
+
BINARY_STACK_OPS,
|
53
|
+
BOOLEAN_OPERATORS,
|
54
|
+
NUMERIC_OPERATORS,
|
55
|
+
UNARY_STACK_OPS,
|
56
|
+
AllocateDirective,
|
57
|
+
BinaryStackOp,
|
58
|
+
ConstCmdDirective,
|
59
|
+
FloatMultiplyDirective,
|
60
|
+
FloatTruncateDirective,
|
61
|
+
IntMultiplyDirective,
|
62
|
+
MemCompareDirective,
|
63
|
+
NoOpDirective,
|
64
|
+
StackOpDirective,
|
65
|
+
IntegerTruncate64To16Directive,
|
66
|
+
IntegerTruncate64To32Directive,
|
67
|
+
IntegerTruncate64To8Directive,
|
68
|
+
FloatLogDirective,
|
69
|
+
IntegerSignedExtend16To64Directive,
|
70
|
+
IntegerSignedExtend32To64Directive,
|
71
|
+
IntegerSignedExtend8To64Directive,
|
72
|
+
StackCmdDirective,
|
73
|
+
StorePrmDirective,
|
74
|
+
IntegerZeroExtend16To64Directive,
|
75
|
+
IntegerZeroExtend32To64Directive,
|
76
|
+
IntegerZeroExtend8To64Directive,
|
20
77
|
Directive,
|
21
78
|
FloatExtendDirective,
|
22
|
-
IntEqualDirective,
|
23
79
|
ExitDirective,
|
24
|
-
|
25
|
-
|
26
|
-
GetPrmDirective,
|
27
|
-
GetTlmDirective,
|
80
|
+
LoadDirective,
|
81
|
+
StoreTlmValDirective,
|
28
82
|
GotoDirective,
|
29
83
|
IfDirective,
|
30
84
|
NotDirective,
|
31
|
-
|
32
|
-
OrDirective,
|
33
|
-
SetSerRegDirective,
|
34
|
-
SetRegDirective,
|
85
|
+
PushValDirective,
|
35
86
|
SignedIntToFloatDirective,
|
87
|
+
StoreDirective,
|
88
|
+
UnaryStackOp,
|
36
89
|
UnsignedIntToFloatDirective,
|
37
90
|
WaitAbsDirective,
|
38
91
|
WaitRelDirective,
|
39
92
|
)
|
40
93
|
from fprime_gds.common.loaders.ch_json_loader import ChJsonLoader
|
41
94
|
from fprime_gds.common.loaders.cmd_json_loader import CmdJsonLoader
|
95
|
+
from fprime_gds.common.loaders.event_json_loader import EventJsonLoader
|
42
96
|
from fprime_gds.common.loaders.prm_json_loader import PrmJsonLoader
|
43
97
|
from fprime_gds.common.templates.ch_template import ChTemplate
|
44
98
|
from fprime_gds.common.templates.cmd_template import CmdTemplate
|
@@ -47,15 +101,13 @@ from fprime.common.models.serialize.time_type import TimeType
|
|
47
101
|
from fprime.common.models.serialize.enum_type import EnumType
|
48
102
|
from fprime.common.models.serialize.serializable_type import SerializableType
|
49
103
|
from fprime.common.models.serialize.array_type import ArrayType
|
104
|
+
from fprime.common.models.serialize.type_exceptions import TypeException
|
50
105
|
from fprime.common.models.serialize.numerical_types import (
|
51
106
|
U32Type,
|
52
107
|
U16Type,
|
53
108
|
U64Type,
|
54
109
|
U8Type,
|
55
|
-
I16Type,
|
56
|
-
I32Type,
|
57
110
|
I64Type,
|
58
|
-
I8Type,
|
59
111
|
F32Type,
|
60
112
|
F64Type,
|
61
113
|
FloatType,
|
@@ -65,469 +117,29 @@ from fprime.common.models.serialize.numerical_types import (
|
|
65
117
|
from fprime.common.models.serialize.string_type import StringType
|
66
118
|
from fprime.common.models.serialize.bool_type import BoolType
|
67
119
|
from fprime_gds.common.fpy.parser import (
|
68
|
-
|
120
|
+
AstBinaryOp,
|
69
121
|
AstBoolean,
|
70
|
-
AstComparison,
|
71
122
|
AstElif,
|
72
123
|
AstElifs,
|
73
124
|
AstExpr,
|
74
125
|
AstGetAttr,
|
75
126
|
AstGetItem,
|
76
|
-
AstNot,
|
77
127
|
AstNumber,
|
78
|
-
|
128
|
+
AstOp,
|
79
129
|
AstReference,
|
130
|
+
AstScopedBody,
|
80
131
|
AstString,
|
81
132
|
Ast,
|
82
|
-
AstTest,
|
83
133
|
AstBody,
|
84
134
|
AstLiteral,
|
85
135
|
AstIf,
|
86
136
|
AstAssign,
|
87
137
|
AstFuncCall,
|
138
|
+
AstUnaryOp,
|
88
139
|
AstVar,
|
89
140
|
)
|
90
141
|
from fprime.common.models.serialize.type_base import BaseType as FppType
|
91
142
|
|
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
143
|
|
532
144
|
class AssignIds(TopDownVisitor):
|
533
145
|
"""assigns a unique id to each node to allow it to be indexed in a dict"""
|
@@ -554,7 +166,7 @@ class CreateVariables(Visitor):
|
|
554
166
|
)
|
555
167
|
return
|
556
168
|
|
557
|
-
var = FpyVariable(node.var_type,
|
169
|
+
var = FpyVariable(node.var_type, node)
|
558
170
|
# new var. put it in the table under this scope
|
559
171
|
state.variables[node.variable.var] = var
|
560
172
|
state.runtime_values[node.variable.var] = var
|
@@ -565,7 +177,7 @@ class CreateVariables(Visitor):
|
|
565
177
|
return
|
566
178
|
|
567
179
|
|
568
|
-
class ResolveReferences(
|
180
|
+
class ResolveReferences(TopDownVisitor):
|
569
181
|
"""for each reference, resolve it in a specific scope based on its
|
570
182
|
syntactic position, or fail if could not resolve"""
|
571
183
|
|
@@ -604,7 +216,7 @@ class ResolveReferences(Visitor):
|
|
604
216
|
return None
|
605
217
|
return attr
|
606
218
|
|
607
|
-
# parent is a ch, prm, const,
|
219
|
+
# parent is a ch, prm, const, or field
|
608
220
|
|
609
221
|
value_type = get_ref_fpp_type_class(parent)
|
610
222
|
|
@@ -656,7 +268,7 @@ class ResolveReferences(Visitor):
|
|
656
268
|
state.err("Invalid syntax", node)
|
657
269
|
return None
|
658
270
|
|
659
|
-
# parent is a ch, prm, const,
|
271
|
+
# parent is a ch, prm, const, or field
|
660
272
|
|
661
273
|
value_type = get_ref_fpp_type_class(parent)
|
662
274
|
|
@@ -672,7 +284,7 @@ class ResolveReferences(Visitor):
|
|
672
284
|
|
673
285
|
if not self.is_type_constant_size(value_type):
|
674
286
|
state.err(
|
675
|
-
f"{value_type} has non-constant sized members, cannot access members",
|
287
|
+
f"{value_type.__name__} has non-constant sized members, cannot access members",
|
676
288
|
node,
|
677
289
|
)
|
678
290
|
return None
|
@@ -689,18 +301,17 @@ class ResolveReferences(Visitor):
|
|
689
301
|
)
|
690
302
|
return None
|
691
303
|
|
692
|
-
def resolve_if_ref(
|
693
|
-
self, node: AstExpr, ns: FpyScope, state: CompileState
|
694
|
-
) -> bool:
|
304
|
+
def resolve_if_ref(self, node: AstExpr, ns: FpyScope, state: CompileState) -> bool:
|
695
305
|
"""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
|
-
|
306
|
+
otherwise, if it is not a reference, return true as it doesn't need to be resolved
|
307
|
+
"""
|
308
|
+
if not is_instance_compat(node, AstReference):
|
698
309
|
return True
|
699
310
|
|
700
311
|
return self.resolve_ref_in_ns(node, ns, state) is not None
|
701
312
|
|
702
313
|
def resolve_ref_in_ns(
|
703
|
-
self, node:
|
314
|
+
self, node: AstReference, ns: FpyScope, state: CompileState
|
704
315
|
) -> FpyReference | None:
|
705
316
|
"""recursively resolves a reference in a scope, returning the resolved ref
|
706
317
|
or none if none could be found."""
|
@@ -735,19 +346,19 @@ class ResolveReferences(Visitor):
|
|
735
346
|
return
|
736
347
|
|
737
348
|
for arg in node.args if node.args is not None else []:
|
738
|
-
# arg value refs must
|
739
|
-
if not self.resolve_if_ref(arg, state.
|
740
|
-
state.err("Unknown
|
349
|
+
# arg value refs must have values at runtime
|
350
|
+
if not self.resolve_if_ref(arg, state.runtime_values, state):
|
351
|
+
state.err("Unknown runtime value", arg)
|
741
352
|
return
|
742
353
|
|
743
|
-
def visit_AstIf_AstElif(self, node: AstIf
|
354
|
+
def visit_AstIf_AstElif(self, node: Union[AstIf, AstElif], state: CompileState):
|
744
355
|
# if condition expr refs must be "runtime values" (tlm/prm/const/etc)
|
745
356
|
if not self.resolve_if_ref(node.condition, state.runtime_values, state):
|
746
357
|
state.err("Unknown runtime value", node.condition)
|
747
358
|
return
|
748
359
|
|
749
|
-
def
|
750
|
-
# lhs/rhs side of
|
360
|
+
def visit_AstBinaryOp(self, node: AstBinaryOp, state: CompileState):
|
361
|
+
# lhs/rhs side of stack op, if they are refs, must be refs to "runtime vals"
|
751
362
|
if not self.resolve_if_ref(node.lhs, state.runtime_values, state):
|
752
363
|
state.err("Unknown runtime value", node.lhs)
|
753
364
|
return
|
@@ -755,15 +366,9 @@ class ResolveReferences(Visitor):
|
|
755
366
|
state.err("Unknown runtime value", node.rhs)
|
756
367
|
return
|
757
368
|
|
758
|
-
def
|
759
|
-
|
760
|
-
|
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)
|
369
|
+
def visit_AstUnaryOp(self, node: AstUnaryOp, state: CompileState):
|
370
|
+
if not self.resolve_if_ref(node.val, state.runtime_values, state):
|
371
|
+
state.err("Unknown runtime value", node.val)
|
767
372
|
return
|
768
373
|
|
769
374
|
def visit_AstAssign(self, node: AstAssign, state: CompileState):
|
@@ -779,118 +384,201 @@ class ResolveReferences(Visitor):
|
|
779
384
|
return
|
780
385
|
var.type = type
|
781
386
|
|
782
|
-
if not self.resolve_if_ref(node.value, state.
|
783
|
-
state.err("Unknown
|
387
|
+
if not self.resolve_if_ref(node.value, state.runtime_values, state):
|
388
|
+
state.err("Unknown runtime value", node.value)
|
784
389
|
return
|
785
390
|
|
391
|
+
def visit_AstReference(self, node: AstReference, state: CompileState):
|
392
|
+
# make sure that all refs are resolved when we get to them
|
393
|
+
if node not in state.resolved_references:
|
394
|
+
state.err("Unknown variable", node)
|
395
|
+
return
|
786
396
|
|
787
|
-
class CalculateExprTypes(Visitor):
|
788
|
-
"""stores in state the fprime type of each expression, or NothingType if the expr had no type"""
|
789
397
|
|
790
|
-
|
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
|
398
|
+
class CheckUseBeforeDeclare(Visitor):
|
798
399
|
|
799
|
-
def
|
800
|
-
|
400
|
+
def __init__(self):
|
401
|
+
self.currently_declared_vars: list[FpyVariable] = []
|
801
402
|
|
802
|
-
def
|
803
|
-
state.
|
403
|
+
def visit_AstAssign(self, node: AstAssign, state: CompileState):
|
404
|
+
var = state.resolved_references[node.variable]
|
405
|
+
|
406
|
+
if var.declaration != node:
|
407
|
+
# this is not the node that declares this variable
|
408
|
+
return
|
409
|
+
|
410
|
+
# this node declares this variable
|
411
|
+
|
412
|
+
self.currently_declared_vars.append(var)
|
804
413
|
|
805
414
|
def visit_AstReference(self, node: AstReference, state: CompileState):
|
806
415
|
ref = state.resolved_references[node]
|
807
|
-
|
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
|
416
|
+
if not isinstance(ref, FpyVariable):
|
417
|
+
return
|
813
418
|
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
state.expr_types[node] = BoolType
|
419
|
+
if ref.declaration.variable == node:
|
420
|
+
# this is the initial name of the variable. don't crash
|
421
|
+
return
|
818
422
|
|
819
|
-
|
820
|
-
|
821
|
-
|
423
|
+
if ref not in self.currently_declared_vars:
|
424
|
+
state.err("Variable used before declared", node)
|
425
|
+
return
|
822
426
|
|
823
427
|
|
824
|
-
class
|
825
|
-
"""
|
826
|
-
types are right"""
|
428
|
+
class PickAndConvertTypes(Visitor):
|
429
|
+
"""stores in state the fprime type of each expression, or NothingType if the expr had no type"""
|
827
430
|
|
828
|
-
def
|
829
|
-
self, node:
|
431
|
+
def coerce_expr_type(
|
432
|
+
self, node: AstExpr, type: FppTypeClass, state: CompileState
|
830
433
|
) -> bool:
|
434
|
+
node_type = state.expr_types[node]
|
435
|
+
if self.can_coerce_type(node_type, type):
|
436
|
+
state.type_coercions[node] = type
|
437
|
+
return True
|
438
|
+
state.err(f"Expected {type.__name__}, found {node_type.__name__}", node)
|
439
|
+
return False
|
440
|
+
|
441
|
+
def can_coerce_type(self, type: FppTypeClass, to_type: FppTypeClass) -> bool:
|
442
|
+
if type == to_type:
|
443
|
+
return True
|
444
|
+
if issubclass(type, IntegerType) and issubclass(to_type, NumericalType):
|
445
|
+
# we can coerce any integer into any other number
|
446
|
+
return True
|
447
|
+
if issubclass(type, FloatType) and issubclass(to_type, FloatType):
|
448
|
+
# we can convert any float into any float
|
449
|
+
return True
|
450
|
+
if type == InternalStringType and issubclass(to_type, StringType):
|
451
|
+
# we can convert the internal String type to any string type
|
452
|
+
return True
|
831
453
|
|
832
|
-
|
833
|
-
|
454
|
+
return False
|
455
|
+
|
456
|
+
def pick_intermediate_type(
|
457
|
+
self, arg_types: list[FppTypeClass], op: BinaryStackOp | UnaryStackOp
|
458
|
+
) -> FppTypeClass:
|
459
|
+
|
460
|
+
if op in BOOLEAN_OPERATORS:
|
461
|
+
return BoolType
|
834
462
|
|
835
|
-
|
836
|
-
|
463
|
+
non_numeric = any(not issubclass(t, NumericalType) for t in arg_types)
|
464
|
+
|
465
|
+
if op == BinaryStackOp.EQUAL or op == BinaryStackOp.NOT_EQUAL:
|
466
|
+
if non_numeric:
|
467
|
+
if len(set(arg_types)) != 1:
|
468
|
+
# can only compare equality between the same types
|
469
|
+
return None
|
470
|
+
return arg_types[0]
|
471
|
+
|
472
|
+
# all arguments should be numeric
|
473
|
+
if non_numeric:
|
474
|
+
# cannot find intermediate type
|
475
|
+
return None
|
837
476
|
|
838
|
-
if
|
839
|
-
|
840
|
-
|
841
|
-
if isinstance(node.value, int):
|
842
|
-
# int literal can be converted into float or int
|
843
|
-
return issubclass(to_type, (FloatType, IntegerType))
|
477
|
+
if op == BinaryStackOp.DIVIDE or op == BinaryStackOp.EXPONENT:
|
478
|
+
# always do true division over floats, python style
|
479
|
+
return F64Type
|
844
480
|
|
845
|
-
|
481
|
+
float = any(issubclass(t, FloatType) for t in arg_types)
|
482
|
+
unsigned = any(t in UNSIGNED_INTEGER_TYPES for t in arg_types)
|
846
483
|
|
847
|
-
|
484
|
+
if float:
|
485
|
+
# at least one arg is a float
|
486
|
+
return F64Type
|
848
487
|
|
849
|
-
|
488
|
+
if unsigned:
|
489
|
+
# at least one arg is unsigned
|
490
|
+
return U64Type
|
491
|
+
|
492
|
+
return I64Type
|
493
|
+
|
494
|
+
def visit_AstNumber(self, node: AstNumber, state: CompileState):
|
495
|
+
# give a best guess as to the final type of this node. we don't actually know
|
496
|
+
# its bitwidth or signedness yet
|
497
|
+
if isinstance(node.value, float):
|
498
|
+
result_type = F64Type
|
499
|
+
else:
|
500
|
+
result_type = InternalIntType
|
501
|
+
state.expr_types[node] = result_type
|
850
502
|
|
503
|
+
def visit_AstBinaryOp(self, node: AstBinaryOp, state: CompileState):
|
851
504
|
lhs_type = state.expr_types[node.lhs]
|
852
505
|
rhs_type = state.expr_types[node.rhs]
|
853
506
|
|
854
|
-
|
855
|
-
|
507
|
+
intermediate_type = self.pick_intermediate_type([lhs_type, rhs_type], node.op)
|
508
|
+
if intermediate_type is None:
|
509
|
+
state.err(
|
510
|
+
f"Op {node.op} undefined for {lhs_type.__name__}, {rhs_type.__name__}",
|
511
|
+
node,
|
512
|
+
)
|
513
|
+
return
|
514
|
+
|
515
|
+
if not self.coerce_expr_type(node.lhs, intermediate_type, state):
|
856
516
|
return
|
857
|
-
if not
|
858
|
-
state.err(f"Cannot compare non-numeric type {rhs_type}", node.rhs)
|
517
|
+
if not self.coerce_expr_type(node.rhs, intermediate_type, state):
|
859
518
|
return
|
860
519
|
|
861
|
-
#
|
520
|
+
# okay now find which actual directive we're going to use based on this intermediate
|
521
|
+
# type, and save it
|
522
|
+
|
523
|
+
dir = None
|
524
|
+
if (
|
525
|
+
node.op == BinaryStackOp.EQUAL or node.op == BinaryStackOp.NOT_EQUAL
|
526
|
+
) and intermediate_type not in SPECIFIC_NUMERIC_TYPES:
|
527
|
+
dir = MemCompareDirective
|
528
|
+
else:
|
529
|
+
dir = BINARY_STACK_OPS[node.op][intermediate_type]
|
530
|
+
|
531
|
+
state.stack_op_directives[node] = dir
|
532
|
+
|
533
|
+
result_type = None
|
534
|
+
if node.op in NUMERIC_OPERATORS:
|
535
|
+
result_type = intermediate_type
|
536
|
+
else:
|
537
|
+
result_type = BoolType
|
538
|
+
state.expr_types[node] = result_type
|
539
|
+
|
540
|
+
def visit_AstUnaryOp(self, node: AstUnaryOp, state: CompileState):
|
541
|
+
val_type = state.expr_types[node.val]
|
862
542
|
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
if rhs_type == FloatType:
|
868
|
-
state.expr_types[node.rhs] = F64Type
|
543
|
+
intermediate_type = self.pick_intermediate_type([val_type], node.op)
|
544
|
+
if intermediate_type is None:
|
545
|
+
state.err(f"Op {node.op} undefined for {val_type.__name__}", node)
|
546
|
+
return
|
869
547
|
|
870
|
-
if
|
871
|
-
# use i64
|
872
|
-
state.expr_types[node.lhs] = I64Type
|
873
|
-
state.expr_types[node.rhs] = I64Type
|
548
|
+
if not self.coerce_expr_type(node.val, intermediate_type, state):
|
874
549
|
return
|
875
550
|
|
876
|
-
|
877
|
-
|
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
|
551
|
+
# okay now find which actual directive we're going to use based on this intermediate
|
552
|
+
# type, and save it
|
883
553
|
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
554
|
+
chosen_dir = UNARY_STACK_OPS[node.op][intermediate_type]
|
555
|
+
|
556
|
+
result_type = None
|
557
|
+
if node.op in NUMERIC_OPERATORS:
|
558
|
+
result_type = intermediate_type
|
559
|
+
else:
|
560
|
+
result_type = BoolType
|
561
|
+
|
562
|
+
state.stack_op_directives[node] = chosen_dir
|
563
|
+
state.expr_types[node] = result_type
|
564
|
+
|
565
|
+
def visit_AstString(self, node: AstString, state: CompileState):
|
566
|
+
state.expr_types[node] = InternalStringType
|
567
|
+
|
568
|
+
def visit_AstBoolean(self, node: AstBoolean, state: CompileState):
|
569
|
+
state.expr_types[node] = BoolType
|
570
|
+
|
571
|
+
def visit_AstReference(self, node: AstReference, state: CompileState):
|
572
|
+
ref = state.resolved_references[node]
|
573
|
+
state.expr_types[node] = get_ref_fpp_type_class(ref)
|
574
|
+
if isinstance(node, AstGetItem):
|
575
|
+
# the node of the index number has no expression value, it's an arg
|
576
|
+
# but only at syntax level
|
577
|
+
state.expr_types[node.item] = NothingType
|
891
578
|
|
892
579
|
def visit_AstFuncCall(self, node: AstFuncCall, state: CompileState):
|
893
580
|
func = state.resolved_references[node.func]
|
581
|
+
assert isinstance(func, FpyCallable)
|
894
582
|
func_args = func.args
|
895
583
|
node_args = node.args if node.args else []
|
896
584
|
|
@@ -914,84 +602,81 @@ class CheckAndResolveArgumentTypes(Visitor):
|
|
914
602
|
for value_expr, arg in zip(node_args, func_args):
|
915
603
|
arg_name, arg_type = arg
|
916
604
|
|
917
|
-
|
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
|
605
|
+
if not self.coerce_expr_type(value_expr, arg_type, state):
|
606
|
+
return
|
935
607
|
|
936
608
|
# got thru all args successfully
|
609
|
+
state.expr_types[node] = func.return_type
|
937
610
|
|
938
|
-
def
|
939
|
-
|
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
|
611
|
+
def visit_AstAssign(self, node: AstAssign, state: CompileState):
|
612
|
+
var_type = state.resolved_references[node.variable].type
|
946
613
|
|
947
|
-
|
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)
|
614
|
+
if not self.coerce_expr_type(node.value, var_type, state):
|
951
615
|
return
|
952
|
-
state.expr_types[node.value] = BoolType
|
953
616
|
|
617
|
+
def visit_default(self, node, state):
|
618
|
+
# coding error, missed an expr
|
619
|
+
assert not is_instance_compat(node, AstExpr), node
|
620
|
+
|
621
|
+
|
622
|
+
class AllocateVariables(Visitor):
|
954
623
|
def visit_AstAssign(self, node: AstAssign, state: CompileState):
|
955
|
-
|
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
|
624
|
+
existing_var = state.resolved_references[node.variable]
|
964
625
|
|
965
|
-
|
626
|
+
assert existing_var is not None
|
627
|
+
assert existing_var.type is not None
|
966
628
|
|
967
|
-
|
968
|
-
state.err(f"{var_type} is too big to fit in a variable", node)
|
969
|
-
return
|
629
|
+
value_size = existing_var.type.getMaxSize()
|
970
630
|
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
631
|
+
if existing_var.lvar_offset is None:
|
632
|
+
# doesn't have an lvar idx, allocate one
|
633
|
+
lvar_offset = state.lvar_array_size_bytes
|
634
|
+
state.lvar_array_size_bytes += value_size
|
635
|
+
existing_var.lvar_offset = lvar_offset
|
975
636
|
|
976
637
|
|
977
|
-
class
|
638
|
+
class CalculateConstExprValues(Visitor):
|
978
639
|
"""for each expr, try to calculate its constant value and store it in a map. stores None if no value could be
|
979
640
|
calculated at compile time, and NothingType if the expr had no value"""
|
980
641
|
|
642
|
+
def const_coerce_type(self, from_val: FppType, to_type: FppTypeClass) -> FppType:
|
643
|
+
if type(from_val) == to_type:
|
644
|
+
return from_val
|
645
|
+
if issubclass(to_type, StringType):
|
646
|
+
assert type(from_val) == InternalStringType, type(from_val)
|
647
|
+
return to_type(from_val.val)
|
648
|
+
if issubclass(to_type, FloatType):
|
649
|
+
assert issubclass(type(from_val), NumericalType), type(from_val)
|
650
|
+
return to_type(float(from_val.val))
|
651
|
+
if issubclass(to_type, IntegerType):
|
652
|
+
assert issubclass(type(from_val), IntegerType), type(from_val)
|
653
|
+
return to_type(int(from_val.val))
|
654
|
+
assert False, (from_val, type(from_val), to_type)
|
655
|
+
|
981
656
|
def visit_AstLiteral(self, node: AstLiteral, state: CompileState):
|
982
657
|
literal_type = state.expr_types[node]
|
983
|
-
|
984
|
-
|
985
|
-
|
986
|
-
or issubclass(literal_type, StringType)
|
987
|
-
or literal_type == BoolType
|
988
|
-
), literal_type
|
989
|
-
state.expr_values[node] = literal_type(node.value)
|
658
|
+
|
659
|
+
if literal_type == NothingType:
|
660
|
+
value = NothingType()
|
990
661
|
else:
|
991
|
-
|
662
|
+
try:
|
663
|
+
value = literal_type(node.value)
|
664
|
+
except TypeException as e:
|
665
|
+
state.err(f"For type {literal_type.__name__}: {e}", node)
|
666
|
+
return
|
667
|
+
|
668
|
+
coerced_type = state.type_coercions.get(node, None)
|
669
|
+
if coerced_type is not None:
|
670
|
+
try:
|
671
|
+
value = self.const_coerce_type(value, coerced_type)
|
672
|
+
except TypeException as e:
|
673
|
+
state.err(f"For type {coerced_type.__name__}: {e}", node)
|
674
|
+
return
|
675
|
+
state.expr_values[node] = value
|
992
676
|
|
993
677
|
def visit_AstReference(self, node: AstReference, state: CompileState):
|
994
678
|
ref = state.resolved_references[node]
|
679
|
+
expr_type = state.expr_types[node]
|
995
680
|
|
996
681
|
if isinstance(ref, (ChTemplate, PrmTemplate, FpyVariable)):
|
997
682
|
# we do not try to calculate or predict these values at compile time
|
@@ -1022,10 +707,20 @@ class CalculateExprValues(Visitor):
|
|
1022
707
|
else:
|
1023
708
|
assert False, ref
|
1024
709
|
|
1025
|
-
|
1026
|
-
|
1027
|
-
state.
|
1028
|
-
|
710
|
+
if expr_value is None:
|
711
|
+
# cannot calculate at compile time
|
712
|
+
state.expr_values[node] = None
|
713
|
+
return
|
714
|
+
|
715
|
+
assert isinstance(expr_value, expr_type), (expr_value, expr_type)
|
716
|
+
|
717
|
+
coerced_type = state.type_coercions.get(node, None)
|
718
|
+
if coerced_type is not None:
|
719
|
+
try:
|
720
|
+
expr_value = self.const_coerce_type(expr_value, coerced_type)
|
721
|
+
except TypeException as e:
|
722
|
+
state.err(f"For type {expr_type.__name__}: {e}", node)
|
723
|
+
return
|
1029
724
|
state.expr_values[node] = expr_value
|
1030
725
|
|
1031
726
|
def visit_AstFuncCall(self, node: AstFuncCall, state: CompileState):
|
@@ -1037,9 +732,12 @@ class CalculateExprValues(Visitor):
|
|
1037
732
|
]
|
1038
733
|
unknown_value = any(v for v in arg_values if v is None)
|
1039
734
|
if unknown_value:
|
735
|
+
# we will have to calculate this at runtime
|
1040
736
|
state.expr_values[node] = None
|
1041
737
|
return
|
1042
738
|
|
739
|
+
expr_value = None
|
740
|
+
|
1043
741
|
if isinstance(func, FpyTypeCtor):
|
1044
742
|
# actually construct the type
|
1045
743
|
if issubclass(func.type, SerializableType):
|
@@ -1048,15 +746,15 @@ class CalculateExprValues(Visitor):
|
|
1048
746
|
# t[0] is the arg name
|
1049
747
|
arg_dict = {t[0]: v for t, v in zip(func.type.MEMBER_LIST, arg_values)}
|
1050
748
|
instance._val = arg_dict
|
1051
|
-
|
749
|
+
expr_value = instance
|
1052
750
|
|
1053
751
|
elif issubclass(func.type, ArrayType):
|
1054
752
|
instance = func.type()
|
1055
753
|
instance._val = arg_values
|
1056
|
-
|
754
|
+
expr_value = instance
|
1057
755
|
|
1058
756
|
elif func.type == TimeType:
|
1059
|
-
|
757
|
+
expr_value = TimeType(*arg_values)
|
1060
758
|
|
1061
759
|
else:
|
1062
760
|
# no other FppTypeClasses have ctors
|
@@ -1065,164 +763,170 @@ class CalculateExprValues(Visitor):
|
|
1065
763
|
# don't try to calculate the value of this function call
|
1066
764
|
# it's something like a cmd or macro
|
1067
765
|
state.expr_values[node] = None
|
766
|
+
return
|
767
|
+
|
768
|
+
assert isinstance(expr_value, func.return_type), (expr_value, func.return_type)
|
769
|
+
|
770
|
+
coerced_type = state.type_coercions.get(node, None)
|
771
|
+
if coerced_type is not None:
|
772
|
+
try:
|
773
|
+
expr_value = self.const_coerce_type(expr_value, coerced_type)
|
774
|
+
except TypeException as e:
|
775
|
+
state.err(f"For type {func.return_type.__name__}: {e}", node)
|
776
|
+
return
|
777
|
+
state.expr_values[node] = expr_value
|
1068
778
|
|
1069
|
-
def
|
1070
|
-
# we do not calculate compile time value of
|
779
|
+
def visit_AstOp(self, node: AstOp, state: CompileState):
|
780
|
+
# we do not calculate compile time value of operators at the moment
|
1071
781
|
state.expr_values[node] = None
|
1072
782
|
|
1073
783
|
def visit_default(self, node, state):
|
1074
784
|
# coding error, missed an expr
|
1075
|
-
assert not
|
785
|
+
assert not is_instance_compat(node, AstExpr), node
|
1076
786
|
|
1077
787
|
|
1078
|
-
class
|
1079
|
-
"""for each
|
1080
|
-
|
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
|
788
|
+
class GenerateConstExprDirectives(Visitor):
|
789
|
+
"""for each expr with a constant compile time value, generate
|
790
|
+
directives for how to put it in its register"""
|
1089
791
|
|
1090
|
-
|
1091
|
-
|
792
|
+
def visit_AstExpr(self, node: AstExpr, state: CompileState):
|
793
|
+
if node in state.directives:
|
794
|
+
# already have directives associated with this node
|
795
|
+
return
|
1092
796
|
|
1093
|
-
|
1094
|
-
assert value_type == type(value), (value_type, type(value))
|
797
|
+
expr_value = state.expr_values[node]
|
1095
798
|
|
1096
|
-
if
|
1097
|
-
#
|
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
|
-
)
|
799
|
+
if expr_value is None:
|
800
|
+
# no const value
|
1102
801
|
return
|
1103
802
|
|
1104
|
-
|
1105
|
-
|
1106
|
-
|
1107
|
-
|
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)]
|
803
|
+
if isinstance(expr_value, NothingType):
|
804
|
+
# nothing type has no value
|
805
|
+
state.directives[node] = []
|
806
|
+
return
|
1118
807
|
|
808
|
+
# it has a constant value at compile time
|
809
|
+
serialized_expr_value = expr_value.serialize()
|
1119
810
|
|
1120
|
-
|
1121
|
-
|
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
|
811
|
+
# push it to the stack
|
812
|
+
state.directives[node] = [PushValDirective(serialized_expr_value)]
|
1172
813
|
|
1173
814
|
|
1174
|
-
class
|
1175
|
-
"""for each expr
|
1176
|
-
directives
|
1177
|
-
|
1178
|
-
|
815
|
+
class GenerateExprMacrosAndCmds(Visitor):
|
816
|
+
"""for each expr whose value is not known at compile time, but can be calculated at run time,
|
817
|
+
generate directives to calculate the value and put it in its register. for each command
|
818
|
+
or macro, generate directives for calling them with appropriate arg values"""
|
819
|
+
|
820
|
+
def get_64_bit_type(self, type: FppTypeClass) -> FppTypeClass:
|
821
|
+
assert type in SPECIFIC_NUMERIC_TYPES, type
|
822
|
+
return (
|
823
|
+
I64Type
|
824
|
+
if type in SIGNED_INTEGER_TYPES
|
825
|
+
else U64Type if type in UNSIGNED_INTEGER_TYPES else F64Type
|
826
|
+
)
|
1179
827
|
|
1180
|
-
|
1181
|
-
|
1182
|
-
|
828
|
+
def truncate_from_64_bits(
|
829
|
+
self, from_type: FppTypeClass, new_size: int
|
830
|
+
) -> list[Directive]:
|
1183
831
|
|
1184
|
-
|
1185
|
-
|
1186
|
-
state.directives[node] = None
|
1187
|
-
return
|
832
|
+
assert new_size in (1, 2, 4, 8), new_size
|
833
|
+
assert from_type.getMaxSize() == 8, from_type.getMaxSize()
|
1188
834
|
|
1189
|
-
if
|
1190
|
-
#
|
1191
|
-
|
1192
|
-
state.directives[node] = None
|
1193
|
-
return
|
835
|
+
if new_size == 8:
|
836
|
+
# already correct size
|
837
|
+
return []
|
1194
838
|
|
1195
|
-
|
1196
|
-
|
839
|
+
if from_type == F64Type:
|
840
|
+
# only one option for float trunc
|
841
|
+
assert new_size == 4, new_size
|
842
|
+
return [FloatTruncateDirective()]
|
1197
843
|
|
1198
|
-
|
844
|
+
# must be an int
|
845
|
+
assert issubclass(from_type, IntegerType), from_type
|
1199
846
|
|
1200
|
-
|
847
|
+
if new_size == 1:
|
848
|
+
return [IntegerTruncate64To8Directive()]
|
849
|
+
elif new_size == 2:
|
850
|
+
return [IntegerTruncate64To16Directive()]
|
1201
851
|
|
1202
|
-
|
1203
|
-
# no const value
|
1204
|
-
return
|
852
|
+
return [IntegerTruncate64To32Directive()]
|
1205
853
|
|
1206
|
-
|
1207
|
-
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
854
|
+
def extend_to_64_bits(self, type: FppTypeClass) -> list[Directive]:
|
855
|
+
if type.getMaxSize() == 8:
|
856
|
+
# already 8 bytes
|
857
|
+
return []
|
858
|
+
if type == F32Type:
|
859
|
+
return [FloatExtendDirective()]
|
1211
860
|
|
1212
|
-
#
|
1213
|
-
|
1214
|
-
val_as_i64.deserialize(val_as_i64_bytes, 0)
|
861
|
+
# must be an int
|
862
|
+
assert issubclass(type, IntegerType), type
|
1215
863
|
|
1216
|
-
|
864
|
+
from_size = type.getMaxSize()
|
865
|
+
assert from_size in (1, 2, 4, 8), from_size
|
1217
866
|
|
867
|
+
if type in SIGNED_INTEGER_TYPES:
|
868
|
+
if from_size == 1:
|
869
|
+
return [IntegerSignedExtend8To64Directive()]
|
870
|
+
elif from_size == 2:
|
871
|
+
return [IntegerSignedExtend16To64Directive()]
|
872
|
+
else:
|
873
|
+
return [IntegerSignedExtend32To64Directive()]
|
874
|
+
else:
|
875
|
+
if from_size == 1:
|
876
|
+
return [IntegerZeroExtend8To64Directive()]
|
877
|
+
elif from_size == 2:
|
878
|
+
return [IntegerZeroExtend16To64Directive()]
|
879
|
+
else:
|
880
|
+
return [IntegerZeroExtend32To64Directive()]
|
881
|
+
|
882
|
+
def convert_type(
|
883
|
+
self, from_type: FppTypeClass, to_type: FppTypeClass
|
884
|
+
) -> list[Directive]:
|
885
|
+
if from_type == to_type:
|
886
|
+
return []
|
887
|
+
|
888
|
+
# only valid runtime type conversion is between two numeric types
|
889
|
+
assert (
|
890
|
+
from_type in SPECIFIC_NUMERIC_TYPES and to_type in SPECIFIC_NUMERIC_TYPES
|
891
|
+
), (
|
892
|
+
from_type,
|
893
|
+
to_type,
|
894
|
+
)
|
895
|
+
# also invalid to convert from a float to an integer at runtime due to loss of precision
|
896
|
+
assert not (
|
897
|
+
from_type in SPECIFIC_FLOAT_TYPES and to_type in SPECIFIC_INTEGER_TYPES
|
898
|
+
), (
|
899
|
+
from_type,
|
900
|
+
to_type,
|
901
|
+
)
|
1218
902
|
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
903
|
+
dirs = []
|
904
|
+
# first go to 64 bit width
|
905
|
+
dirs.extend(self.extend_to_64_bits(from_type))
|
906
|
+
from_64_bit = self.get_64_bit_type(from_type)
|
907
|
+
to_64_bit = self.get_64_bit_type(to_type)
|
908
|
+
|
909
|
+
# now convert from int to float if necessary
|
910
|
+
if from_64_bit == U64Type and to_64_bit == F64Type:
|
911
|
+
dirs.append(UnsignedIntToFloatDirective())
|
912
|
+
from_64_bit = F64Type
|
913
|
+
elif from_64_bit == I64Type and to_64_bit == F64Type:
|
914
|
+
dirs.append(SignedIntToFloatDirective())
|
915
|
+
from_64_bit = F64Type
|
916
|
+
elif from_64_bit == U64Type or from_64_bit == I64Type:
|
917
|
+
assert to_64_bit == U64Type or to_64_bit == I64Type
|
918
|
+
# conversion from signed to unsigned int is implicit, doesn't need code gen
|
919
|
+
from_64_bit = to_64_bit
|
920
|
+
|
921
|
+
assert from_64_bit == to_64_bit, (from_64_bit, to_64_bit)
|
922
|
+
|
923
|
+
# now truncate back down to desired size
|
924
|
+
dirs.extend(self.truncate_from_64_bits(to_64_bit, to_type.getMaxSize()))
|
925
|
+
return dirs
|
1222
926
|
|
1223
927
|
def visit_AstReference(self, node: AstReference, state: CompileState):
|
1224
928
|
if node in state.directives:
|
1225
|
-
# already know how to put it
|
929
|
+
# already know how to put it on stack, or it is impossible
|
1226
930
|
return
|
1227
931
|
|
1228
932
|
expr_type = state.expr_types[node]
|
@@ -1232,168 +936,178 @@ class GenerateNonConstExprDirectives(Visitor):
|
|
1232
936
|
|
1233
937
|
# does not have a constant compile time value
|
1234
938
|
|
1235
|
-
#
|
1236
|
-
# and then into an nreg
|
1237
|
-
|
1238
|
-
sreg_idx = None
|
939
|
+
# first, put it in an lvar. then load it from the lvar onto stack
|
1239
940
|
|
1240
|
-
offset
|
941
|
+
# the offset of the field in the parent type
|
942
|
+
offset_in_parent_val = 0
|
943
|
+
# the offset of the lvar the parent type is stored in
|
944
|
+
offset_in_lvar_array = 0
|
1241
945
|
|
1242
946
|
base_ref = ref
|
1243
947
|
|
1244
948
|
# if it's a field ref, find the parent and the offset in the parent
|
1245
949
|
while isinstance(base_ref, FieldReference):
|
1246
|
-
|
950
|
+
offset_in_parent_val += base_ref.offset
|
1247
951
|
base_ref = base_ref.parent
|
1248
952
|
|
1249
|
-
if isinstance(base_ref,
|
1250
|
-
#
|
1251
|
-
|
953
|
+
if isinstance(base_ref, ChTemplate):
|
954
|
+
# put it in an lvar
|
955
|
+
offset_in_lvar_array = state.lvar_array_size_bytes
|
956
|
+
state.lvar_array_size_bytes += base_ref.get_type_obj().getMaxSize()
|
957
|
+
directives.append(
|
958
|
+
StoreTlmValDirective(base_ref.get_id(), offset_in_lvar_array)
|
959
|
+
)
|
960
|
+
elif isinstance(base_ref, PrmTemplate):
|
961
|
+
# put it in an lvar
|
962
|
+
offset_in_lvar_array = state.lvar_array_size_bytes
|
963
|
+
state.lvar_array_size_bytes += base_ref.get_type_obj().getMaxSize()
|
964
|
+
directives.append(
|
965
|
+
StorePrmDirective(base_ref.get_id(), offset_in_lvar_array)
|
966
|
+
)
|
967
|
+
elif isinstance(base_ref, FpyVariable):
|
968
|
+
# already should be in an lvar
|
969
|
+
offset_in_lvar_array = base_ref.lvar_offset
|
1252
970
|
else:
|
1253
|
-
|
1254
|
-
|
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()))
|
971
|
+
assert (
|
972
|
+
False
|
973
|
+
), base_ref # ref should either be impossible to put on stack or should have a compile time val
|
1265
974
|
|
1266
|
-
|
1267
|
-
|
1268
|
-
|
1269
|
-
|
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()
|
975
|
+
# load from the lvar
|
976
|
+
directives.append(
|
977
|
+
LoadDirective(
|
978
|
+
offset_in_lvar_array + offset_in_parent_val, expr_type.getMaxSize()
|
1275
979
|
)
|
1276
980
|
)
|
981
|
+
converted_type = state.type_coercions.get(node, None)
|
982
|
+
if converted_type is not None:
|
983
|
+
directives.extend(self.convert_type(expr_type, converted_type))
|
1277
984
|
|
1278
985
|
state.directives[node] = directives
|
1279
986
|
|
1280
|
-
def
|
987
|
+
def visit_AstBinaryOp(self, node: AstBinaryOp, state: CompileState):
|
1281
988
|
if node in state.directives:
|
1282
|
-
# already know how to put it
|
989
|
+
# already know how to put it on stack
|
1283
990
|
return
|
1284
991
|
|
1285
|
-
expr_reg = state.expr_registers[node]
|
1286
992
|
directives = []
|
1287
993
|
|
1288
|
-
|
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
|
994
|
+
expr_type = state.expr_types[node]
|
1302
995
|
|
1303
|
-
directives.
|
1304
|
-
|
1305
|
-
|
996
|
+
lhs_dirs = state.directives[node.lhs]
|
997
|
+
rhs_dirs = state.directives[node.rhs]
|
998
|
+
|
999
|
+
# which variant of the op did we pick?
|
1000
|
+
dir = state.stack_op_directives[node]
|
1001
|
+
|
1002
|
+
# generate the actual op itself
|
1003
|
+
directives: list[Directive] = lhs_dirs + rhs_dirs
|
1004
|
+
if dir == MemCompareDirective:
|
1005
|
+
lhs_type = state.expr_types[node.lhs]
|
1006
|
+
rhs_type = state.expr_types[node.rhs]
|
1007
|
+
assert lhs_type == rhs_type, (lhs_type, rhs_type)
|
1008
|
+
directives.append(dir(lhs_type.getMaxSize()))
|
1009
|
+
if node.op == BinaryStackOp.NOT_EQUAL:
|
1010
|
+
directives.append(NotDirective())
|
1011
|
+
elif dir == NoOpDirective:
|
1012
|
+
# don't include no op
|
1013
|
+
pass
|
1014
|
+
else:
|
1015
|
+
directives.append(dir())
|
1306
1016
|
|
1307
|
-
|
1308
|
-
|
1017
|
+
# and convert the result of the op into the desired result of this expr
|
1018
|
+
converted_type = state.type_coercions.get(node, None)
|
1019
|
+
if converted_type is not None:
|
1020
|
+
directives.extend(self.convert_type(expr_type, converted_type))
|
1309
1021
|
|
1310
1022
|
state.directives[node] = directives
|
1311
1023
|
|
1312
|
-
def
|
1024
|
+
def visit_AstUnaryOp(self, node: AstUnaryOp, state: CompileState):
|
1313
1025
|
if node in state.directives:
|
1314
|
-
# already know how to put it
|
1026
|
+
# already know how to put it on stack
|
1315
1027
|
return
|
1316
1028
|
|
1317
|
-
expr_reg = state.expr_registers[node]
|
1318
1029
|
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
1030
|
|
1324
|
-
state.
|
1031
|
+
expr_type = state.expr_types[node]
|
1325
1032
|
|
1326
|
-
|
1327
|
-
if node in state.directives:
|
1328
|
-
# already know how to put it in reg
|
1329
|
-
return
|
1033
|
+
val_dirs = state.directives[node.val]
|
1330
1034
|
|
1331
|
-
|
1035
|
+
# which variant of the op did we pick?
|
1036
|
+
dir = state.stack_op_directives[node]
|
1037
|
+
# generate the actual op itself
|
1038
|
+
directives: list[Directive] = val_dirs
|
1332
1039
|
|
1333
|
-
|
1334
|
-
|
1040
|
+
if node.op == UnaryStackOp.NEGATE:
|
1041
|
+
# in this case, we also need to push -1
|
1042
|
+
if dir == FloatMultiplyDirective:
|
1043
|
+
directives.append(PushValDirective(F64Type(-1).serialize()))
|
1044
|
+
elif dir == IntMultiplyDirective:
|
1045
|
+
directives.append(PushValDirective(I64Type(-1).serialize()))
|
1335
1046
|
|
1336
|
-
|
1337
|
-
|
1338
|
-
|
1339
|
-
|
1340
|
-
|
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:
|
1047
|
+
directives.append(dir())
|
1048
|
+
# and convert the result of the op into the desired result of this expr
|
1049
|
+
converted_type = state.type_coercions.get(node, None)
|
1050
|
+
if converted_type is not None:
|
1051
|
+
directives.extend(self.convert_type(expr_type, converted_type))
|
1380
1052
|
|
1381
|
-
|
1382
|
-
|
1053
|
+
state.directives[node] = directives
|
1054
|
+
|
1055
|
+
def visit_AstFuncCall(self, node: AstFuncCall, state: CompileState):
|
1056
|
+
node_args = node.args if node.args is not None else []
|
1057
|
+
func = state.resolved_references[node.func]
|
1058
|
+
dirs = state.directives.get(node, [])
|
1059
|
+
if len(dirs) > 0:
|
1060
|
+
# already know how to put this on the stack
|
1061
|
+
return
|
1062
|
+
if isinstance(func, FpyCmd):
|
1063
|
+
const_args = not any(
|
1064
|
+
state.expr_values[arg_node] is None for arg_node in node_args
|
1065
|
+
)
|
1066
|
+
if const_args:
|
1067
|
+
# can just hardcode this cmd
|
1068
|
+
arg_bytes = bytes()
|
1069
|
+
for arg_node in node_args:
|
1070
|
+
arg_value = state.expr_values[arg_node]
|
1071
|
+
arg_bytes += arg_value.serialize()
|
1072
|
+
dirs = [ConstCmdDirective(func.cmd.get_op_code(), arg_bytes)]
|
1383
1073
|
else:
|
1384
|
-
|
1385
|
-
|
1386
|
-
|
1074
|
+
arg_byte_count = 0
|
1075
|
+
# push all args to the stack
|
1076
|
+
# keep track of how many bytes total we have pushed
|
1077
|
+
for arg_node in node_args:
|
1078
|
+
node_dirs = state.directives[arg_node]
|
1079
|
+
assert len(node_dirs) >= 1
|
1080
|
+
dirs.extend(node_dirs)
|
1081
|
+
arg_byte_count = state.expr_types[arg_node].getMaxSize()
|
1082
|
+
# then push cmd opcode to stack as u32
|
1083
|
+
dirs.append(
|
1084
|
+
PushValDirective(U32Type(func.cmd.get_op_code()).serialize())
|
1387
1085
|
)
|
1086
|
+
# now that all args are pushed to the stack, pop them and opcode off the stack
|
1087
|
+
# as a command
|
1088
|
+
dirs.append(StackCmdDirective(arg_byte_count))
|
1089
|
+
elif isinstance(func, FpyMacro):
|
1090
|
+
# put all arg values on stack
|
1091
|
+
for arg_node in node_args:
|
1092
|
+
node_dirs = state.directives[arg_node]
|
1093
|
+
assert len(node_dirs) >= 1
|
1094
|
+
dirs.extend(node_dirs)
|
1388
1095
|
|
1389
|
-
|
1390
|
-
|
1391
|
-
|
1392
|
-
dir_type = INT_UNSIGNED_INEQUALITY_DIRECTIVES[node.op.value]
|
1096
|
+
dirs.append(func.dir())
|
1097
|
+
else:
|
1098
|
+
dirs = None
|
1393
1099
|
|
1394
|
-
|
1100
|
+
# perform type conversion if called for
|
1101
|
+
coerced_type = state.type_coercions.get(node, None)
|
1102
|
+
if coerced_type is not None:
|
1103
|
+
dirs.extend(self.convert_type(func.return_type, coerced_type))
|
1104
|
+
state.directives[node] = dirs
|
1395
1105
|
|
1396
|
-
|
1106
|
+
def visit_AstAssign(self, node: AstAssign, state: CompileState):
|
1107
|
+
var = state.resolved_references[node.variable]
|
1108
|
+
state.directives[node] = state.directives[node.value] + [
|
1109
|
+
StoreDirective(var.lvar_offset, var.type.getMaxSize())
|
1110
|
+
]
|
1397
1111
|
|
1398
1112
|
|
1399
1113
|
class CountNodeDirectives(Visitor):
|
@@ -1437,8 +1151,11 @@ class CountNodeDirectives(Visitor):
|
|
1437
1151
|
|
1438
1152
|
state.node_dir_counts[node] = count
|
1439
1153
|
|
1440
|
-
def visit_AstBody(self, node: AstBody, state: CompileState):
|
1154
|
+
def visit_AstBody(self, node: Union[AstBody, AstScopedBody], state: CompileState):
|
1441
1155
|
count = 0
|
1156
|
+
if isinstance(node, AstScopedBody):
|
1157
|
+
# add one for lvar array alloc
|
1158
|
+
count += 1
|
1442
1159
|
for stmt in node.stmts:
|
1443
1160
|
count += state.node_dir_counts[stmt]
|
1444
1161
|
|
@@ -1453,13 +1170,18 @@ class CountNodeDirectives(Visitor):
|
|
1453
1170
|
class CalculateStartLineIdx(TopDownVisitor):
|
1454
1171
|
"""based on the number of directives generated by each node, calculate the start line idx
|
1455
1172
|
of each node's directives"""
|
1456
|
-
|
1173
|
+
|
1174
|
+
def visit_AstBody(self, node: Union[AstBody, AstScopedBody], state: CompileState):
|
1457
1175
|
if node not in state.start_line_idx:
|
1458
1176
|
state.start_line_idx[node] = 0
|
1459
1177
|
|
1460
1178
|
start_idx = state.start_line_idx[node]
|
1461
1179
|
|
1462
1180
|
line_idx = start_idx
|
1181
|
+
if isinstance(node, AstScopedBody):
|
1182
|
+
# include lvar alloc
|
1183
|
+
line_idx += 1
|
1184
|
+
|
1463
1185
|
for stmt in node.stmts:
|
1464
1186
|
state.start_line_idx[stmt] = line_idx
|
1465
1187
|
line_idx += state.node_dir_counts[stmt]
|
@@ -1518,10 +1240,10 @@ class GenerateBodyDirectives(Visitor):
|
|
1518
1240
|
|
1519
1241
|
for case in cases:
|
1520
1242
|
case_dirs = []
|
1521
|
-
#
|
1243
|
+
# put the conditional on top of stack
|
1522
1244
|
case_dirs.extend(state.directives[case[0]])
|
1523
1245
|
# include if stmt (update the end idx later)
|
1524
|
-
if_dir = IfDirective(
|
1246
|
+
if_dir = IfDirective(-1)
|
1525
1247
|
|
1526
1248
|
case_dirs.append(if_dir)
|
1527
1249
|
# include body
|
@@ -1532,7 +1254,7 @@ class GenerateBodyDirectives(Visitor):
|
|
1532
1254
|
goto_ends.append(goto_dir)
|
1533
1255
|
|
1534
1256
|
# if false, skip the body and goto
|
1535
|
-
if_dir.
|
1257
|
+
if_dir.false_goto_dir_index = (
|
1536
1258
|
start_line_idx + len(all_dirs) + len(case_dirs)
|
1537
1259
|
)
|
1538
1260
|
|
@@ -1542,12 +1264,14 @@ class GenerateBodyDirectives(Visitor):
|
|
1542
1264
|
all_dirs.extend(state.directives[node.els])
|
1543
1265
|
|
1544
1266
|
for goto in goto_ends:
|
1545
|
-
goto.
|
1267
|
+
goto.dir_idx = start_line_idx + len(all_dirs)
|
1546
1268
|
|
1547
1269
|
state.directives[node] = all_dirs
|
1548
1270
|
|
1549
|
-
def visit_AstBody(self, node: AstBody, state: CompileState):
|
1271
|
+
def visit_AstBody(self, node: Union[AstBody, AstScopedBody], state: CompileState):
|
1550
1272
|
dirs = []
|
1273
|
+
if isinstance(node, AstScopedBody):
|
1274
|
+
dirs.append(AllocateDirective(state.lvar_array_size_bytes))
|
1551
1275
|
for stmt in node.stmts:
|
1552
1276
|
stmt_dirs = state.directives.get(stmt, None)
|
1553
1277
|
if stmt_dirs is not None:
|
@@ -1556,6 +1280,52 @@ class GenerateBodyDirectives(Visitor):
|
|
1556
1280
|
state.directives[node] = dirs
|
1557
1281
|
|
1558
1282
|
|
1283
|
+
HEADER_FORMAT = "!BBBBBHI"
|
1284
|
+
HEADER_SIZE = struct.calcsize(HEADER_FORMAT)
|
1285
|
+
|
1286
|
+
|
1287
|
+
@dataclass
|
1288
|
+
class Header:
|
1289
|
+
majorVersion: int
|
1290
|
+
minorVersion: int
|
1291
|
+
patchVersion: int
|
1292
|
+
schemaVersion: int
|
1293
|
+
argumentCount: int
|
1294
|
+
statementCount: int
|
1295
|
+
bodySize: int
|
1296
|
+
|
1297
|
+
|
1298
|
+
FOOTER_FORMAT = "!I"
|
1299
|
+
FOOTER_SIZE = struct.calcsize(FOOTER_FORMAT)
|
1300
|
+
|
1301
|
+
|
1302
|
+
@dataclass
|
1303
|
+
class Footer:
|
1304
|
+
crc: int
|
1305
|
+
|
1306
|
+
|
1307
|
+
def serialize_directives(dirs: list[Directive], output: Path):
|
1308
|
+
output_bytes = bytes()
|
1309
|
+
|
1310
|
+
for dir in dirs:
|
1311
|
+
dir_bytes = dir.serialize()
|
1312
|
+
if len(dir_bytes) > MAX_DIRECTIVE_SIZE:
|
1313
|
+
raise CompileException(
|
1314
|
+
f"Directive {dir} in sequence too large (expected less than {MAX_DIRECTIVE_SIZE}, was {len(dir_bytes)})",
|
1315
|
+
None,
|
1316
|
+
)
|
1317
|
+
output_bytes += dir_bytes
|
1318
|
+
|
1319
|
+
header = Header(0, 0, 0, 1, 0, len(dirs), len(output_bytes))
|
1320
|
+
output_bytes = struct.pack(HEADER_FORMAT, *astuple(header)) + output_bytes
|
1321
|
+
|
1322
|
+
crc = zlib.crc32(output_bytes) % (1 << 32)
|
1323
|
+
footer = Footer(crc)
|
1324
|
+
output_bytes += struct.pack(FOOTER_FORMAT, *astuple(footer))
|
1325
|
+
|
1326
|
+
output.write_bytes(output_bytes)
|
1327
|
+
|
1328
|
+
|
1559
1329
|
def get_base_compile_state(dictionary: str) -> CompileState:
|
1560
1330
|
"""return the initial state of the compiler, based on the given dict path"""
|
1561
1331
|
cmd_json_dict_loader = CmdJsonLoader(dictionary)
|
@@ -1571,11 +1341,16 @@ def get_base_compile_state(dictionary: str) -> CompileState:
|
|
1571
1341
|
(prm_id_dict, prm_name_dict, versions) = prm_json_dict_loader.construct_dicts(
|
1572
1342
|
dictionary
|
1573
1343
|
)
|
1344
|
+
event_json_dict_loader = EventJsonLoader(dictionary)
|
1345
|
+
(event_id_dict, event_name_dict, versions) = event_json_dict_loader.construct_dicts(
|
1346
|
+
dictionary
|
1347
|
+
)
|
1574
1348
|
# the type name dict is a mapping of a fully qualified name to an fprime type
|
1575
1349
|
# here we put into it all types found while parsing all cmds, params and tlm channels
|
1576
1350
|
type_name_dict: dict[str, FppTypeClass] = cmd_json_dict_loader.parsed_types
|
1577
1351
|
type_name_dict.update(ch_json_dict_loader.parsed_types)
|
1578
1352
|
type_name_dict.update(prm_json_dict_loader.parsed_types)
|
1353
|
+
type_name_dict.update(event_json_dict_loader.parsed_types)
|
1579
1354
|
|
1580
1355
|
# enum const dict is a dict of fully qualified enum const name (like Ref.Choice.ONE) to its fprime value
|
1581
1356
|
enum_const_name_dict: dict[str, FppType] = {}
|
@@ -1590,11 +1365,12 @@ def get_base_compile_state(dictionary: str) -> CompileState:
|
|
1590
1365
|
|
1591
1366
|
# insert the implicit types into the dict
|
1592
1367
|
type_name_dict["Fw.Time"] = TimeType
|
1593
|
-
for typ in
|
1368
|
+
for typ in SPECIFIC_NUMERIC_TYPES:
|
1594
1369
|
type_name_dict[typ.get_canonical_name()] = typ
|
1595
1370
|
type_name_dict["bool"] = BoolType
|
1596
1371
|
# note no string type at the moment
|
1597
1372
|
|
1373
|
+
cmd_response_type = type_name_dict["Fw.CmdResponse"]
|
1598
1374
|
callable_name_dict: dict[str, FpyCallable] = {}
|
1599
1375
|
# add all cmds to the callable dict
|
1600
1376
|
for name, cmd in cmd_name_dict.items():
|
@@ -1602,8 +1378,8 @@ def get_base_compile_state(dictionary: str) -> CompileState:
|
|
1602
1378
|
args = []
|
1603
1379
|
for arg_name, _, arg_type in cmd.arguments:
|
1604
1380
|
args.append((arg_name, arg_type))
|
1605
|
-
# cmds are thought of as callables with a
|
1606
|
-
callable_name_dict[name] = FpyCmd(
|
1381
|
+
# cmds are thought of as callables with a Fw.CmdResponse return value
|
1382
|
+
callable_name_dict[name] = FpyCmd(cmd_response_type, args, cmd)
|
1607
1383
|
|
1608
1384
|
# for each type in the dict, if it has a constructor, create an FpyTypeCtor
|
1609
1385
|
# object to track the constructor and put it in the callable name dict
|
@@ -1641,7 +1417,7 @@ def get_base_compile_state(dictionary: str) -> CompileState:
|
|
1641
1417
|
return state
|
1642
1418
|
|
1643
1419
|
|
1644
|
-
def compile(body:
|
1420
|
+
def compile(body: AstScopedBody, dictionary: str) -> list[Directive]:
|
1645
1421
|
state = get_base_compile_state(dictionary)
|
1646
1422
|
passes: list[Visitor] = [
|
1647
1423
|
AssignIds(),
|
@@ -1650,27 +1426,21 @@ def compile(body: AstBody, dictionary: str) -> list[Directive]:
|
|
1650
1426
|
# now that variables have been defined, all names/attributes/indices (references)
|
1651
1427
|
# should be defined
|
1652
1428
|
ResolveReferences(),
|
1429
|
+
CheckUseBeforeDeclare(),
|
1653
1430
|
# now that we know what all refs point to, we should be able to figure out the type
|
1654
1431
|
# of every expression
|
1655
|
-
|
1656
|
-
# now that
|
1657
|
-
|
1658
|
-
CheckAndResolveArgumentTypes(),
|
1432
|
+
PickAndConvertTypes(),
|
1433
|
+
# now that expr types have been narrowed down, we can allocate lvar space for variables
|
1434
|
+
AllocateVariables(),
|
1659
1435
|
# okay, now that we're sure we're passing in all the right args to each func,
|
1660
1436
|
# we can calculate values of type ctors etc etc
|
1661
|
-
|
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(),
|
1437
|
+
CalculateConstExprValues(),
|
1668
1438
|
# for expressions which have constant values, generate corresponding directives
|
1669
|
-
# to put the expr
|
1439
|
+
# to put the expr on the stack
|
1670
1440
|
GenerateConstExprDirectives(),
|
1671
|
-
#
|
1672
|
-
#
|
1673
|
-
|
1441
|
+
# generate directives to calculate exprs, macros and cmds at runtime and put them
|
1442
|
+
# on the stack
|
1443
|
+
GenerateExprMacrosAndCmds(),
|
1674
1444
|
# count the number of directives generated by each node
|
1675
1445
|
CountNodeDirectives(),
|
1676
1446
|
# calculate the index that the node will correspond to in the output file
|
@@ -1684,4 +1454,11 @@ def compile(body: AstBody, dictionary: str) -> list[Directive]:
|
|
1684
1454
|
for error in state.errors:
|
1685
1455
|
raise error
|
1686
1456
|
|
1687
|
-
|
1457
|
+
dirs = state.directives[body]
|
1458
|
+
if len(dirs) > MAX_DIRECTIVES_COUNT:
|
1459
|
+
raise CompileException(
|
1460
|
+
f"Too many directives in sequence (expected less than {MAX_DIRECTIVES_COUNT}, had {len(dirs)})",
|
1461
|
+
None,
|
1462
|
+
)
|
1463
|
+
|
1464
|
+
return dirs
|