@h2loop/autosar 1.0.1 → 1.2.0
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.
- package/README.md +11 -1
- package/agents/autosar-integrator.md +45 -2
- package/commands/autosar-trace.md +2 -2
- package/package.json +4 -1
- package/plugins/index.js +55 -23
- package/python/ecuc_engine/__init__.py +1 -1
- package/python/ecuc_engine/add_signal.py +351 -0
- package/python/ecuc_engine/arxml_edit.py +387 -0
- package/python/ecuc_engine/audit.py +352 -50
- package/python/ecuc_engine/cli.py +125 -5
- package/python/ecuc_engine/codegen/__init__.py +233 -0
- package/python/ecuc_engine/codegen/binding.py +305 -0
- package/python/ecuc_engine/codegen/bindings/sip_standin.json +378 -0
- package/python/ecuc_engine/codegen/cdiff.py +395 -0
- package/python/ecuc_engine/codegen/cwriter.py +150 -0
- package/python/ecuc_engine/codegen/emit.py +463 -0
- package/python/ecuc_engine/codegen/handles.py +176 -0
- package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
- package/python/ecuc_engine/codegen/model.py +264 -0
- package/python/ecuc_engine/codegen/modules/__init__.py +7 -0
- package/python/ecuc_engine/codegen/modules/com.py +145 -0
- package/python/ecuc_engine/codegen/modules/comstack.py +14 -0
- package/python/ecuc_engine/codegen/modules/dem.py +22 -0
- package/python/ecuc_engine/codegen/modules/mcal.py +170 -0
- package/python/ecuc_engine/codegen/modules/memstack.py +24 -0
- package/python/ecuc_engine/codegen/registry.py +208 -0
- package/python/ecuc_engine/codegen/schm_memmap.py +159 -0
- package/python/ecuc_engine/context.py +481 -0
- package/python/ecuc_engine/csrc.py +189 -0
- package/python/ecuc_engine/defs.py +30 -0
- package/python/ecuc_engine/ecucjson.py +75 -22
- package/python/ecuc_engine/find.py +164 -0
- package/python/ecuc_engine/gen.py +105 -0
- package/python/ecuc_engine/history.py +146 -0
- package/python/ecuc_engine/model.py +4 -1
- package/python/ecuc_engine/probe.py +229 -0
- package/python/ecuc_engine/rte_api.py +935 -0
- package/python/ecuc_engine/rules/__init__.py +5 -3
- package/python/ecuc_engine/rules/mem.py +84 -0
- package/python/ecuc_engine/rules/nvm.py +131 -0
- package/python/ecuc_engine/rules/pdur.py +87 -0
- package/python/ecuc_engine/rules/system.py +162 -0
- package/python/ecuc_engine/specids.py +163 -0
- package/python/ecuc_engine/sysmodel.py +152 -0
- package/python/ecuc_engine/validate.py +15 -2
- package/python/ecuc_engine/values.py +26 -0
- package/python/ecuc_engine/verify_rte.py +445 -0
- package/python/pyproject.toml +1 -1
- package/skills/autosar/SKILL.md +78 -6
- package/skills/autosar/references/codegen.md +58 -0
- package/skills/autosar/references/ecuc-model.md +4 -1
- package/skills/autosar/references/project-layout.md +10 -1
- package/skills/autosar/references/runnable-code.md +50 -0
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
"""Semantic diff of generated C configuration code (reference dir vs generated dir).
|
|
2
|
+
|
|
3
|
+
Compared: `#define` names and values, and file-scope initialised objects (struct/array tables)
|
|
4
|
+
element by element, including table ordering. Ignored: formatting, comments, banners, include
|
|
5
|
+
order, MemMap/section boilerplate, storage qualifiers (static/CONST()/const).
|
|
6
|
+
|
|
7
|
+
Values are normalised before comparison: integer literals by value (0x2A0u == 672), casts and
|
|
8
|
+
outer parentheses dropped, macros resolved through the defines of the same side (falling back to
|
|
9
|
+
the other side's defines for symbols a side does not define, e.g. ComConf_* from a Com_Cfg.h that
|
|
10
|
+
was not regenerated), constant integer expressions folded. Designated initialisers
|
|
11
|
+
(`[NvMConf_X] = {...}`) are placed at their resolved index.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import ast
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
BUILTINS = {"TRUE": "1", "FALSE": "0", "STD_ON": "1", "STD_OFF": "0", "STD_HIGH": "1", "STD_LOW": "0",
|
|
21
|
+
"E_OK": "0", "E_NOT_OK": "1"}
|
|
22
|
+
|
|
23
|
+
TOKEN = re.compile(r"""
|
|
24
|
+
(?P<ws>\s+)
|
|
25
|
+
| (?P<str>"(?:\\.|[^"\\])*")
|
|
26
|
+
| (?P<chr>'(?:\\.|[^'\\])*')
|
|
27
|
+
| (?P<num>(?:0[xX][0-9A-Fa-f]+|\d+\.\d*(?:[eE][+-]?\d+)?|\.\d+(?:[eE][+-]?\d+)?|\d+(?:[eE][+-]?\d+)?)[uUlLfF]*)
|
|
28
|
+
| (?P<id>[A-Za-z_]\w*)
|
|
29
|
+
| (?P<op>->|<<|>>|<=|>=|==|!=|&&|\|\||[-+*/%&|^~!<>=?:;,.\[\](){}#])
|
|
30
|
+
""", re.X)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def strip_comments(src: str) -> str:
|
|
34
|
+
out, i, n = [], 0, len(src)
|
|
35
|
+
while i < n:
|
|
36
|
+
c = src[i]
|
|
37
|
+
if c in "\"'":
|
|
38
|
+
j = i + 1
|
|
39
|
+
while j < n and src[j] != c:
|
|
40
|
+
j += 2 if src[j] == "\\" else 1
|
|
41
|
+
out.append(src[i:j + 1])
|
|
42
|
+
i = j + 1
|
|
43
|
+
elif src.startswith("/*", i):
|
|
44
|
+
j = src.find("*/", i + 2)
|
|
45
|
+
j = n if j < 0 else j + 2
|
|
46
|
+
out.append(" " + "\n" * src.count("\n", i, j))
|
|
47
|
+
i = j
|
|
48
|
+
elif src.startswith("//", i):
|
|
49
|
+
j = src.find("\n", i)
|
|
50
|
+
i = n if j < 0 else j
|
|
51
|
+
else:
|
|
52
|
+
out.append(c)
|
|
53
|
+
i += 1
|
|
54
|
+
return "".join(out)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def tokenize(src: str) -> list:
|
|
58
|
+
toks, pos = [], 0
|
|
59
|
+
while pos < len(src):
|
|
60
|
+
m = TOKEN.match(src, pos)
|
|
61
|
+
if not m:
|
|
62
|
+
toks.append(src[pos]) # stray character (e.g. '@'): keep, never crash
|
|
63
|
+
pos += 1
|
|
64
|
+
continue
|
|
65
|
+
pos = m.end()
|
|
66
|
+
if m.lastgroup != "ws":
|
|
67
|
+
toks.append(m.group(m.lastgroup))
|
|
68
|
+
return toks
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def split_source(src: str) -> tuple:
|
|
72
|
+
"""-> (defines {name: value-text}, code text without preprocessor lines)."""
|
|
73
|
+
src = strip_comments(src).replace("\\\r\n", " ").replace("\\\n", " ")
|
|
74
|
+
defines: dict = {}
|
|
75
|
+
code = []
|
|
76
|
+
for line in src.splitlines():
|
|
77
|
+
s = line.strip()
|
|
78
|
+
if s.startswith("#"):
|
|
79
|
+
m = re.match(r"#\s*define\s+([A-Za-z_]\w*)(\([^)]*\))?\s*(.*)$", s)
|
|
80
|
+
if m:
|
|
81
|
+
name = m.group(1) + (m.group(2) or "")
|
|
82
|
+
if m.group(1) not in defines and name not in defines:
|
|
83
|
+
defines[name if m.group(2) else m.group(1)] = m.group(3).strip()
|
|
84
|
+
code.append("")
|
|
85
|
+
continue
|
|
86
|
+
code.append(line)
|
|
87
|
+
return defines, "\n".join(code)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class Parser:
|
|
91
|
+
def __init__(self, toks: list):
|
|
92
|
+
self.t = toks
|
|
93
|
+
self.i = 0
|
|
94
|
+
|
|
95
|
+
def objects(self) -> dict:
|
|
96
|
+
"""File-scope `<decl> name[...] = <init>;` -> {name: init-tree}."""
|
|
97
|
+
out: dict = {}
|
|
98
|
+
stmt_start = 0
|
|
99
|
+
depth = 0
|
|
100
|
+
t = self.t
|
|
101
|
+
i = 0
|
|
102
|
+
while i < len(t):
|
|
103
|
+
tok = t[i]
|
|
104
|
+
if tok in "({[":
|
|
105
|
+
depth += 1
|
|
106
|
+
elif tok in ")}]":
|
|
107
|
+
depth -= 1
|
|
108
|
+
elif tok == ";" and depth == 0:
|
|
109
|
+
stmt_start = i + 1
|
|
110
|
+
elif tok == "=" and depth == 0:
|
|
111
|
+
name = self._declarator(stmt_start, i)
|
|
112
|
+
self.i = i + 1
|
|
113
|
+
init = self._init()
|
|
114
|
+
if name:
|
|
115
|
+
out[name] = init
|
|
116
|
+
while self.i < len(t) and t[self.i] != ";":
|
|
117
|
+
self.i += 1
|
|
118
|
+
i = self.i
|
|
119
|
+
stmt_start = i + 1
|
|
120
|
+
depth = 0
|
|
121
|
+
i += 1
|
|
122
|
+
return out
|
|
123
|
+
|
|
124
|
+
def _declarator(self, start: int, eq: int) -> Optional[str]:
|
|
125
|
+
j = eq - 1
|
|
126
|
+
while j >= start and self.t[j] == "]": # skip [size] groups
|
|
127
|
+
d = 0
|
|
128
|
+
while j >= start:
|
|
129
|
+
if self.t[j] == "]":
|
|
130
|
+
d += 1
|
|
131
|
+
elif self.t[j] == "[":
|
|
132
|
+
d -= 1
|
|
133
|
+
if d == 0:
|
|
134
|
+
break
|
|
135
|
+
j -= 1
|
|
136
|
+
j -= 1
|
|
137
|
+
if j >= start and re.match(r"^[A-Za-z_]\w*$", self.t[j]):
|
|
138
|
+
return self.t[j]
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
def _init(self):
|
|
142
|
+
t = self.t
|
|
143
|
+
if self.i < len(t) and t[self.i] == "{":
|
|
144
|
+
self.i += 1
|
|
145
|
+
items = []
|
|
146
|
+
while self.i < len(t) and t[self.i] != "}":
|
|
147
|
+
desig = None
|
|
148
|
+
if t[self.i] == "[":
|
|
149
|
+
j = self.i + 1
|
|
150
|
+
d = 1
|
|
151
|
+
while j < len(t) and d:
|
|
152
|
+
d += {"[": 1, "]": -1}.get(t[j], 0)
|
|
153
|
+
j += 1
|
|
154
|
+
if j < len(t) and t[j] == "=":
|
|
155
|
+
desig = ("index", t[self.i + 1:j - 1])
|
|
156
|
+
self.i = j + 1
|
|
157
|
+
elif t[self.i] == "." and self.i + 2 < len(t) and t[self.i + 2] == "=":
|
|
158
|
+
desig = ("field", t[self.i + 1])
|
|
159
|
+
self.i += 3
|
|
160
|
+
val = self._init()
|
|
161
|
+
items.append((desig, val))
|
|
162
|
+
if self.i < len(t) and t[self.i] == ",":
|
|
163
|
+
self.i += 1
|
|
164
|
+
self.i += 1 # '}'
|
|
165
|
+
return ("list", items)
|
|
166
|
+
start = self.i
|
|
167
|
+
d = 0
|
|
168
|
+
while self.i < len(t):
|
|
169
|
+
tok = t[self.i]
|
|
170
|
+
if tok in "([{":
|
|
171
|
+
d += 1
|
|
172
|
+
elif tok in ")]}":
|
|
173
|
+
if d == 0:
|
|
174
|
+
break
|
|
175
|
+
d -= 1
|
|
176
|
+
elif tok in ",;" and d == 0:
|
|
177
|
+
break
|
|
178
|
+
self.i += 1
|
|
179
|
+
return ("expr", t[start:self.i])
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def parse_c(src: str) -> tuple:
|
|
183
|
+
defines, code = split_source(src)
|
|
184
|
+
return defines, Parser(tokenize(code)).objects()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ---------- normalisation ----------
|
|
188
|
+
|
|
189
|
+
INT = re.compile(r"^(0[xX][0-9A-Fa-f]+|\d+)[uUlL]*$")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class Resolver:
|
|
193
|
+
def __init__(self, own: dict, fallback: dict):
|
|
194
|
+
self.own = own
|
|
195
|
+
self.fallback = fallback
|
|
196
|
+
|
|
197
|
+
def lookup(self, name: str) -> Optional[str]:
|
|
198
|
+
if name in self.own:
|
|
199
|
+
return self.own[name]
|
|
200
|
+
if name in self.fallback:
|
|
201
|
+
return self.fallback[name]
|
|
202
|
+
return BUILTINS.get(name)
|
|
203
|
+
|
|
204
|
+
def norm_tokens(self, toks: list, depth: int = 0) -> str:
|
|
205
|
+
toks = list(toks)
|
|
206
|
+
out = []
|
|
207
|
+
for tk in toks:
|
|
208
|
+
if re.match(r"^[A-Za-z_]\w*$", tk) and depth < 24:
|
|
209
|
+
v = self.lookup(tk)
|
|
210
|
+
if v is not None and v != tk:
|
|
211
|
+
out.append("(" + self.norm_tokens(tokenize(v), depth + 1) + ")")
|
|
212
|
+
continue
|
|
213
|
+
m = INT.match(tk)
|
|
214
|
+
out.append(str(int(m.group(1), 0) if not m.group(1).startswith("0") or m.group(1) == "0"
|
|
215
|
+
or m.group(1)[:2] in ("0x", "0X") else int(m.group(1), 8)) if m else tk)
|
|
216
|
+
return fold(" ".join(out))
|
|
217
|
+
|
|
218
|
+
def norm(self, toks: list) -> str:
|
|
219
|
+
return self.norm_tokens(toks)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
CAST = re.compile(r"\(\s*(?:const\s+)?[A-Za-z_]\w*(?:\s*\*)*\s*\)\s*(?=[\w(&-])")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def fold(expr: str) -> str:
|
|
226
|
+
e = CAST.sub("", expr)
|
|
227
|
+
e = re.sub(r"\s+", " ", e).strip()
|
|
228
|
+
while e.startswith("(") and e.endswith(")") and _balanced(e[1:-1]):
|
|
229
|
+
e = e[1:-1].strip()
|
|
230
|
+
if re.fullmatch(r"[\d\s()+\-*/%<>&|^~]+", e or "x"):
|
|
231
|
+
try:
|
|
232
|
+
v = _eval(ast.parse(e, mode="eval").body)
|
|
233
|
+
if isinstance(v, int):
|
|
234
|
+
return str(v)
|
|
235
|
+
except Exception: # noqa: BLE001
|
|
236
|
+
pass
|
|
237
|
+
return re.sub(r"\s*([()\[\],&*.])\s*", r"\1", e).replace("->", "->")
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _balanced(s: str) -> bool:
|
|
241
|
+
d = 0
|
|
242
|
+
for ch in s:
|
|
243
|
+
d += {"(": 1, ")": -1}.get(ch, 0)
|
|
244
|
+
if d < 0:
|
|
245
|
+
return False
|
|
246
|
+
return d == 0
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _eval(n):
|
|
250
|
+
if isinstance(n, ast.Constant) and isinstance(n.value, int):
|
|
251
|
+
return n.value
|
|
252
|
+
if isinstance(n, ast.UnaryOp):
|
|
253
|
+
v = _eval(n.operand)
|
|
254
|
+
return {ast.USub: lambda: -v, ast.UAdd: lambda: v, ast.Invert: lambda: ~v}[type(n.op)]()
|
|
255
|
+
if isinstance(n, ast.BinOp):
|
|
256
|
+
a, b = _eval(n.left), _eval(n.right)
|
|
257
|
+
ops = {ast.Add: lambda: a + b, ast.Sub: lambda: a - b, ast.Mult: lambda: a * b,
|
|
258
|
+
ast.FloorDiv: lambda: a // b, ast.Div: lambda: a // b, ast.Mod: lambda: a % b,
|
|
259
|
+
ast.LShift: lambda: a << b, ast.RShift: lambda: a >> b, ast.BitOr: lambda: a | b,
|
|
260
|
+
ast.BitAnd: lambda: a & b, ast.BitXor: lambda: a ^ b}
|
|
261
|
+
return ops[type(n.op)]()
|
|
262
|
+
raise ValueError("not constant")
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def flatten(tree, res: Resolver, prefix: str = "") -> dict:
|
|
266
|
+
"""Init tree -> {path: normalised leaf}; designators resolved to indices / field names."""
|
|
267
|
+
kind, val = tree
|
|
268
|
+
if kind == "expr":
|
|
269
|
+
return {prefix or "=": res.norm(val)}
|
|
270
|
+
out: dict = {}
|
|
271
|
+
pos = 0
|
|
272
|
+
for desig, item in val:
|
|
273
|
+
if desig is None:
|
|
274
|
+
key = f"[{pos}]"
|
|
275
|
+
pos += 1
|
|
276
|
+
elif desig[0] == "index":
|
|
277
|
+
idx = res.norm(desig[1])
|
|
278
|
+
key = f"[{idx}]"
|
|
279
|
+
pos = int(idx) + 1 if idx.lstrip("-").isdigit() else pos + 1
|
|
280
|
+
else:
|
|
281
|
+
key = f".{desig[1]}"
|
|
282
|
+
out.update(flatten(item, res, prefix + key))
|
|
283
|
+
return out
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def rows(tree, res: Resolver) -> Optional[list]:
|
|
287
|
+
"""Top-level elements of an array initialiser as (index key, normalised row) for order checks."""
|
|
288
|
+
kind, val = tree
|
|
289
|
+
if kind != "list":
|
|
290
|
+
return None
|
|
291
|
+
out = []
|
|
292
|
+
pos = 0
|
|
293
|
+
for desig, item in val:
|
|
294
|
+
if desig is not None and desig[0] == "index":
|
|
295
|
+
idx = res.norm(desig[1])
|
|
296
|
+
key = f"[{idx}]"
|
|
297
|
+
pos = int(idx) + 1 if idx.isdigit() else pos + 1
|
|
298
|
+
elif desig is None:
|
|
299
|
+
key = f"[{pos}]"
|
|
300
|
+
pos += 1
|
|
301
|
+
else:
|
|
302
|
+
return None
|
|
303
|
+
out.append((key, tuple(sorted(flatten(item, res).items()))))
|
|
304
|
+
return out
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
# ---------- module grouping ----------
|
|
308
|
+
|
|
309
|
+
def module_of(fname: str) -> str:
|
|
310
|
+
base = os.path.basename(fname)
|
|
311
|
+
if base.startswith("SchM_"):
|
|
312
|
+
return base[5:].rsplit(".", 1)[0]
|
|
313
|
+
stem = base.rsplit(".", 1)[0]
|
|
314
|
+
return stem.split("_", 1)[0]
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def load_dir(d: str) -> dict:
|
|
318
|
+
"""{file basename: (defines, objects)} for *.h / *.c directly in d (or the single file d)."""
|
|
319
|
+
files = [d] if os.path.isfile(d) else [os.path.join(d, f) for f in sorted(os.listdir(d))]
|
|
320
|
+
out = {}
|
|
321
|
+
for f in files:
|
|
322
|
+
if not f.endswith((".h", ".c")) or not os.path.isfile(f):
|
|
323
|
+
continue
|
|
324
|
+
with open(f, errors="replace") as fh:
|
|
325
|
+
out[os.path.basename(f)] = parse_c(fh.read())
|
|
326
|
+
return out
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
def diff_dirs(reference: str, generated: str, modules: Optional[list] = None, max_items: int = 200) -> dict:
|
|
330
|
+
ref, gen = load_dir(reference), load_dir(generated)
|
|
331
|
+
ref_defs = {k: v for _, (d, _) in sorted(ref.items()) for k, v in d.items()}
|
|
332
|
+
gen_defs = {k: v for _, (d, _) in sorted(gen.items()) for k, v in d.items()}
|
|
333
|
+
rr, gr = Resolver(ref_defs, gen_defs), Resolver(gen_defs, ref_defs)
|
|
334
|
+
mods_ref: dict = {}
|
|
335
|
+
mods_gen: dict = {}
|
|
336
|
+
for f in ref:
|
|
337
|
+
mods_ref.setdefault(module_of(f), []).append(f)
|
|
338
|
+
for f in gen:
|
|
339
|
+
mods_gen.setdefault(module_of(f), []).append(f)
|
|
340
|
+
names = sorted(set(mods_ref) & set(mods_gen))
|
|
341
|
+
if modules:
|
|
342
|
+
names = [m for m in names if m in modules]
|
|
343
|
+
report: dict = {"modules": {}, "notGenerated": sorted(set(mods_ref) - set(mods_gen)),
|
|
344
|
+
"notInReference": sorted(set(mods_gen) - set(mods_ref))}
|
|
345
|
+
for m in names:
|
|
346
|
+
report["modules"][m] = _diff_module(m, mods_ref[m], mods_gen[m], ref, gen, rr, gr, max_items)
|
|
347
|
+
tot = {"matching": 0, "missing": 0, "extra": 0, "mismatches": 0}
|
|
348
|
+
for r in report["modules"].values():
|
|
349
|
+
for k in tot:
|
|
350
|
+
tot[k] += r["summary"][k]
|
|
351
|
+
report["total"] = tot
|
|
352
|
+
return report
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def _diff_module(m, rfiles, gfiles, ref, gen, rr: Resolver, gr: Resolver, cap: int) -> dict:
|
|
356
|
+
rd = {k: v for f in sorted(rfiles) for k, v in ref[f][0].items()}
|
|
357
|
+
gd = {k: v for f in sorted(gfiles) for k, v in gen[f][0].items()}
|
|
358
|
+
ro = {k: v for f in sorted(rfiles) for k, v in ref[f][1].items()}
|
|
359
|
+
go = {k: v for f in sorted(gfiles) for k, v in gen[f][1].items()}
|
|
360
|
+
skip = re.compile(r"(_H|_START_SEC_\w+|_STOP_SEC_\w+|MEMMAP_ERROR|\w*_MEMMAP_SEQUENCE_OPEN\w*)$")
|
|
361
|
+
rd = {k: v for k, v in rd.items() if not skip.search(k)}
|
|
362
|
+
gd = {k: v for k, v in gd.items() if not skip.search(k)}
|
|
363
|
+
dm = {"match": [], "missing": sorted(set(rd) - set(gd)), "extra": sorted(set(gd) - set(rd)), "mismatch": []}
|
|
364
|
+
for k in sorted(set(rd) & set(gd)):
|
|
365
|
+
a, b = rr.norm(tokenize(rd[k])), gr.norm(tokenize(gd[k]))
|
|
366
|
+
if a == b:
|
|
367
|
+
dm["match"].append(k)
|
|
368
|
+
else:
|
|
369
|
+
dm["mismatch"].append({"name": k, "reference": rd[k], "generated": gd[k], "normalised": [a, b]})
|
|
370
|
+
om = {"match": [], "missing": sorted(set(ro) - set(go)), "extra": sorted(set(go) - set(ro)), "mismatch": [],
|
|
371
|
+
"order": []}
|
|
372
|
+
elements = {"match": 0, "mismatch": 0}
|
|
373
|
+
for k in sorted(set(ro) & set(go)):
|
|
374
|
+
fa, fb = flatten(ro[k], rr), flatten(go[k], gr)
|
|
375
|
+
bad = [{"element": f"{k}{p}", "reference": fa.get(p), "generated": fb.get(p)}
|
|
376
|
+
for p in sorted(set(fa) | set(fb)) if fa.get(p) != fb.get(p)]
|
|
377
|
+
elements["match"] += sum(1 for p in fa if fb.get(p) == fa[p])
|
|
378
|
+
if not bad:
|
|
379
|
+
om["match"].append(k)
|
|
380
|
+
continue
|
|
381
|
+
ra, rb = rows(ro[k], rr), rows(go[k], gr)
|
|
382
|
+
if ra is not None and rb is not None and sorted(x[1] for x in ra) == sorted(x[1] for x in rb):
|
|
383
|
+
om["order"].append({"name": k, "reference": [x[0] for x in ra], "generated": [x[0] for x in rb],
|
|
384
|
+
"note": "same rows, different table order"})
|
|
385
|
+
continue
|
|
386
|
+
elements["mismatch"] += len(bad)
|
|
387
|
+
om["mismatch"].append({"name": k, "elements": bad[:cap], "count": len(bad)})
|
|
388
|
+
summary = {"matching": len(dm["match"]) + len(om["match"]),
|
|
389
|
+
"missing": len(dm["missing"]) + len(om["missing"]),
|
|
390
|
+
"extra": len(dm["extra"]) + len(om["extra"]),
|
|
391
|
+
"mismatches": len(dm["mismatch"]) + len(om["mismatch"]) + len(om["order"])}
|
|
392
|
+
dm["match"] = len(dm["match"])
|
|
393
|
+
om["match"] = om["match"]
|
|
394
|
+
return {"files": {"reference": sorted(rfiles), "generated": sorted(gfiles)}, "defines": dm, "objects": om,
|
|
395
|
+
"elements": elements, "summary": summary}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""C text rendering shared by every module generator (no module knowledge here).
|
|
2
|
+
|
|
3
|
+
Rules implemented (cited where the standard sets them):
|
|
4
|
+
* generated-file banner (no timestamps, so output is byte-stable) and include guards;
|
|
5
|
+
* `#define` alignment, STD_ON/STD_OFF for booleans (SWS_Std_00010, SWS_BSW_00029);
|
|
6
|
+
* MemMap wrapping `<MIP>_START_SEC_<SECTION>` / `<MIP>_STOP_SEC_<SECTION>` + `#include "<Mip>_MemMap.h"`
|
|
7
|
+
(SWS_MemMap_00032, SWS_BSW_00006);
|
|
8
|
+
* C type of a parameter from its def (Platform_Types names, SWS_PlatformTypes).
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
from .. import ENGINE_VERSION
|
|
16
|
+
from .. import defs as D
|
|
17
|
+
from ..values import parse_float, parse_int
|
|
18
|
+
|
|
19
|
+
GENERATOR = f"Hydron AUTOSAR codegen {ENGINE_VERSION}"
|
|
20
|
+
CONFIG_SECTION = "CONFIG_DATA_UNSPECIFIED"
|
|
21
|
+
C_IDENT = re.compile(r"^[A-Za-z_]\w*$")
|
|
22
|
+
C_EXPR = re.compile(r"^[&*(]*[A-Za-z_][\w.\[\]>-]*\)*$") # &x, x[2], x.y, p->q, (x)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def banner(name: str, mc_name: str, variant: str, release: tuple, inputs: list, digest: str) -> str:
|
|
26
|
+
rel = ".".join(str(x) for x in release)
|
|
27
|
+
lines = [
|
|
28
|
+
"/" + "*" * 78,
|
|
29
|
+
f" * File: {name}",
|
|
30
|
+
f" * Module: {mc_name} ({variant}, AUTOSAR R{rel})",
|
|
31
|
+
f" * Generator: {GENERATOR} (deterministic: same ECUC + param defs -> same file)",
|
|
32
|
+
]
|
|
33
|
+
for i, f in enumerate(inputs[:8]):
|
|
34
|
+
lines.append(f" * {'Inputs:' if i == 0 else '':11} {f}")
|
|
35
|
+
if len(inputs) > 8:
|
|
36
|
+
lines.append(f" * {'':11} ... {len(inputs) - 8} more")
|
|
37
|
+
lines += [f" * Input hash: {digest}", " * DO NOT EDIT. Change the ECUC configuration and regenerate (autosar_codegen).",
|
|
38
|
+
" " + "*" * 78 + "/"]
|
|
39
|
+
return "\n".join(lines)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def guard(name: str) -> str:
|
|
43
|
+
return re.sub(r"\W", "_", name).upper()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def define(name: str, value: str, comment: str = "") -> str:
|
|
47
|
+
pad = max(1, 56 - len(name))
|
|
48
|
+
line = f"#define {name}{' ' * pad}{value}"
|
|
49
|
+
return f"{line} /* {comment} */" if comment else line
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def memmap(mip: str, body: str, section: str = CONFIG_SECTION) -> str:
|
|
53
|
+
MIP = mip.upper()
|
|
54
|
+
return (f"#define {MIP}_START_SEC_{section}\n#include \"{mip}_MemMap.h\"\n\n{body.rstrip()}\n\n"
|
|
55
|
+
f"#define {MIP}_STOP_SEC_{section}\n#include \"{mip}_MemMap.h\"")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def c_uint(v: int, hexa: bool = False) -> str:
|
|
59
|
+
if v < 0:
|
|
60
|
+
return f"({v})"
|
|
61
|
+
return f"0x{v:X}u" if hexa else f"{v}u"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def c_float(raw: str) -> str:
|
|
65
|
+
f = parse_float(raw)
|
|
66
|
+
if f is None:
|
|
67
|
+
return raw
|
|
68
|
+
if f != f or f in (float("inf"), float("-inf")):
|
|
69
|
+
return "0.0" # not representable in a C initialiser; the validator flags such values
|
|
70
|
+
s = repr(float(f))
|
|
71
|
+
return f"({s})" if s.startswith("-") else s
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def c_text(raw: str) -> str:
|
|
75
|
+
"""STRING / FUNCTION-NAME / LINKER-SYMBOL value: a C identifier or expression verbatim, else a string literal."""
|
|
76
|
+
t = raw.strip()
|
|
77
|
+
if C_EXPR.match(t):
|
|
78
|
+
return t
|
|
79
|
+
return '"' + t.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def int_ctype(lo: Optional[int], hi: Optional[int]) -> str:
|
|
83
|
+
if lo is not None and lo < 0:
|
|
84
|
+
for bits in (8, 16, 32):
|
|
85
|
+
if (hi is None or hi < 2 ** (bits - 1)) and lo >= -(2 ** (bits - 1)):
|
|
86
|
+
return f"sint{bits}"
|
|
87
|
+
return "sint64"
|
|
88
|
+
if hi is None:
|
|
89
|
+
return "uint32"
|
|
90
|
+
for bits in (8, 16, 32):
|
|
91
|
+
if hi < 2 ** bits:
|
|
92
|
+
return f"uint{bits}"
|
|
93
|
+
return "uint64"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def param_ctype(d: D.DefNode) -> Optional[str]:
|
|
97
|
+
"""Platform type for a param def in a generated table, or None if it has no table representation."""
|
|
98
|
+
k = d.kind
|
|
99
|
+
if k == "INTEGER":
|
|
100
|
+
return int_ctype(parse_int(d.min) if d.min else None, parse_int(d.max) if d.max else None)
|
|
101
|
+
if k == "BOOLEAN":
|
|
102
|
+
return "boolean"
|
|
103
|
+
if k == "FLOAT":
|
|
104
|
+
return "float64"
|
|
105
|
+
if k == "ENUMERATION":
|
|
106
|
+
return "uint8" if len(d.literals) <= 256 else "uint16"
|
|
107
|
+
return None # STRING / FUNCTION-NAME / LINKER-SYMBOL / MULTILINE-STRING bind to static-code prototypes
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def macro_value(d: Optional[D.DefNode], raw: str) -> str:
|
|
111
|
+
"""Value of a pre-compile scalar #define."""
|
|
112
|
+
k = d.kind if d is not None else None
|
|
113
|
+
if k == "BOOLEAN":
|
|
114
|
+
return "STD_ON" if raw.strip().lower() in ("true", "1") else "STD_OFF"
|
|
115
|
+
if k == "INTEGER":
|
|
116
|
+
v = parse_int(raw)
|
|
117
|
+
return c_uint(v) if v is not None else raw
|
|
118
|
+
if k == "FLOAT":
|
|
119
|
+
return c_float(raw)
|
|
120
|
+
if k == "ENUMERATION":
|
|
121
|
+
return raw.strip() if C_IDENT.match(raw.strip()) else c_text(raw)
|
|
122
|
+
t = c_text(raw)
|
|
123
|
+
return t if C_IDENT.match(t) or t.startswith('"') or (t.startswith("(") and t.endswith(")")) else f"({t})"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def table_value(d: Optional[D.DefNode], raw: Optional[str], hexa: bool = False) -> tuple:
|
|
127
|
+
"""(C initialiser, comment) of a param value in a generated table."""
|
|
128
|
+
k = d.kind if d is not None else None
|
|
129
|
+
if raw is None:
|
|
130
|
+
raw = d.default if d is not None and d.default is not None else None
|
|
131
|
+
if raw is None:
|
|
132
|
+
return ({"BOOLEAN": "FALSE", "FLOAT": "0.0"}.get(k or "", "0u"), "not configured")
|
|
133
|
+
if k == "BOOLEAN":
|
|
134
|
+
return ("TRUE" if raw.strip().lower() in ("true", "1") else "FALSE", "")
|
|
135
|
+
if k == "INTEGER":
|
|
136
|
+
v = parse_int(raw)
|
|
137
|
+
return (c_uint(v, hexa) if v is not None else raw, "")
|
|
138
|
+
if k == "FLOAT":
|
|
139
|
+
return (c_float(raw), "")
|
|
140
|
+
if k == "ENUMERATION":
|
|
141
|
+
lit = raw.strip()
|
|
142
|
+
if lit in d.literals:
|
|
143
|
+
return (c_uint(d.literals.index(lit)), lit)
|
|
144
|
+
return ("0u", f"unknown literal {lit}")
|
|
145
|
+
return (c_text(raw), "")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def initializer(values: list, comment: str = "", indent: str = " ") -> str:
|
|
149
|
+
body = ", ".join(values)
|
|
150
|
+
return f"{indent}{{ {body} }}" + (f" /* {comment} */" if comment else "")
|