@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.
Files changed (53) hide show
  1. package/README.md +11 -1
  2. package/agents/autosar-integrator.md +45 -2
  3. package/commands/autosar-trace.md +2 -2
  4. package/package.json +4 -1
  5. package/plugins/index.js +55 -23
  6. package/python/ecuc_engine/__init__.py +1 -1
  7. package/python/ecuc_engine/add_signal.py +351 -0
  8. package/python/ecuc_engine/arxml_edit.py +387 -0
  9. package/python/ecuc_engine/audit.py +352 -50
  10. package/python/ecuc_engine/cli.py +125 -5
  11. package/python/ecuc_engine/codegen/__init__.py +233 -0
  12. package/python/ecuc_engine/codegen/binding.py +305 -0
  13. package/python/ecuc_engine/codegen/bindings/sip_standin.json +378 -0
  14. package/python/ecuc_engine/codegen/cdiff.py +395 -0
  15. package/python/ecuc_engine/codegen/cwriter.py +150 -0
  16. package/python/ecuc_engine/codegen/emit.py +463 -0
  17. package/python/ecuc_engine/codegen/handles.py +176 -0
  18. package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
  19. package/python/ecuc_engine/codegen/model.py +264 -0
  20. package/python/ecuc_engine/codegen/modules/__init__.py +7 -0
  21. package/python/ecuc_engine/codegen/modules/com.py +145 -0
  22. package/python/ecuc_engine/codegen/modules/comstack.py +14 -0
  23. package/python/ecuc_engine/codegen/modules/dem.py +22 -0
  24. package/python/ecuc_engine/codegen/modules/mcal.py +170 -0
  25. package/python/ecuc_engine/codegen/modules/memstack.py +24 -0
  26. package/python/ecuc_engine/codegen/registry.py +208 -0
  27. package/python/ecuc_engine/codegen/schm_memmap.py +159 -0
  28. package/python/ecuc_engine/context.py +481 -0
  29. package/python/ecuc_engine/csrc.py +189 -0
  30. package/python/ecuc_engine/defs.py +30 -0
  31. package/python/ecuc_engine/ecucjson.py +75 -22
  32. package/python/ecuc_engine/find.py +164 -0
  33. package/python/ecuc_engine/gen.py +105 -0
  34. package/python/ecuc_engine/history.py +146 -0
  35. package/python/ecuc_engine/model.py +4 -1
  36. package/python/ecuc_engine/probe.py +229 -0
  37. package/python/ecuc_engine/rte_api.py +935 -0
  38. package/python/ecuc_engine/rules/__init__.py +5 -3
  39. package/python/ecuc_engine/rules/mem.py +84 -0
  40. package/python/ecuc_engine/rules/nvm.py +131 -0
  41. package/python/ecuc_engine/rules/pdur.py +87 -0
  42. package/python/ecuc_engine/rules/system.py +162 -0
  43. package/python/ecuc_engine/specids.py +163 -0
  44. package/python/ecuc_engine/sysmodel.py +152 -0
  45. package/python/ecuc_engine/validate.py +15 -2
  46. package/python/ecuc_engine/values.py +26 -0
  47. package/python/ecuc_engine/verify_rte.py +445 -0
  48. package/python/pyproject.toml +1 -1
  49. package/skills/autosar/SKILL.md +78 -6
  50. package/skills/autosar/references/codegen.md +58 -0
  51. package/skills/autosar/references/ecuc-model.md +4 -1
  52. package/skills/autosar/references/project-layout.md +10 -1
  53. package/skills/autosar/references/runnable-code.md +50 -0
@@ -0,0 +1,305 @@
1
+ """Static-code bindings: the data contract between generated tables and a BSW implementation.
2
+
3
+ The ECUC param defs say WHAT is configured; they do not say how a given implementation's static
4
+ code lays out its configuration structs (`NvM_BlockDescriptorType`, `PduR_RoutingPathType`, ...).
5
+ That layout belongs to the BSW vendor (SIP). A binding is a JSON file that maps those structs,
6
+ field by field, onto ECUC values with a small path language, so no layout is hard-coded in Python.
7
+
8
+ Bundled bindings live in `codegen/bindings/*.json`; a project adds or overrides them in
9
+ `.hydron/codegen/bindings/*.json`. A module binding is used when its `detect` header is found in
10
+ the project (or when the binding is named explicitly).
11
+
12
+ Path language (a list of steps, evaluated left to right from the row container):
13
+ param:<P> value of parameter/reference P in the current container (terminal)
14
+ sub:<Def> first sub-container of def <Def>
15
+ ref:<R> follow ECUC reference R to its target container
16
+ back:<Def>/<R> the container of def <Def> (any module) whose reference R points here
17
+ peer:<R>[@<Module>[/<Def>]] follow R to target T, then the container that references T from another
18
+ module (or from <Module>, optionally only containers of def <Def>): how a module
19
+ finds its neighbour's PDU handle (TPS_ECUC 3.4.1.3, via EcucPduCollection/Pdu)
20
+ call:<name> a module generator's resolver (Generator.path_steps()) for what the defs cannot
21
+ express as a path, e.g. Com's notification callouts (terminal, token)
22
+ sym TPS_ECUC_02108 symbol of the current container (terminal)
23
+ module | name | index the current container's module name / SHORT-NAME / table index (terminal)
24
+ Macro values: count:<Def> | count:@<Set> | max:<Def>/<Param>, optionally followed by +N or -N.
25
+
26
+ Module binding keys besides tables/objects/macros:
27
+ sets {"<Set>": {"rows": "<Def>", "where": [{"path": <path>, "in": [literal, ...]}]}}: named row
28
+ selections (e.g. the Tx signals: ComSignals whose I-PDU is SEND); tables use "rows": "@<Set>"
29
+ vars RAM objects per row that tables point to (e.g. I-PDU buffers), sized by a path
30
+ require [{"rows": "<Def>", "path": <path>, "in": [...], "why": "..."}]: what the static code supports;
31
+ a configuration outside it is an error, never silently mis-generated
32
+ Field keys: path, default, enum (literal -> C token), fmt ("hex" | "int" | template with {} / {MIP} / {Mip}),
33
+ divide (a path from the row whose value divides this one, rounded: seconds -> main-function ticks),
34
+ agg ({"over": <multi-ref>, "params": [...], "op": "min_nonzero" | "max", "skipIf": <param>}).
35
+ """
36
+ from __future__ import annotations
37
+
38
+ import glob
39
+ import json
40
+ import os
41
+ import re
42
+ from typing import Optional
43
+
44
+ from ..values import parse_int
45
+ from . import cwriter as W
46
+ from .model import CgModel, Container, ModuleCfg
47
+
48
+ HERE = os.path.dirname(__file__)
49
+
50
+
51
+ class BindingError(Exception):
52
+ pass
53
+
54
+
55
+ def load_all(root: str) -> dict:
56
+ """name -> binding dict; project bindings override bundled ones of the same name."""
57
+ out: dict = {}
58
+ for f in sorted(glob.glob(os.path.join(HERE, "bindings", "*.json"))) + sorted(
59
+ glob.glob(os.path.join(root, ".hydron", "codegen", "bindings", "*.json"))):
60
+ with open(f) as fh:
61
+ b = json.load(fh)
62
+ b["_file"] = f
63
+ out[b["name"]] = b
64
+ return out
65
+
66
+
67
+ def select(bindings: dict, module: str, headers: dict, explicit: Optional[str]) -> Optional[dict]:
68
+ """The module binding to use: the explicitly named binding, else the first whose detect header matches."""
69
+ if explicit == "none":
70
+ return None
71
+ names = [explicit] if explicit else sorted(bindings)
72
+ for n in names:
73
+ b = bindings.get(n)
74
+ if b is None:
75
+ raise BindingError(f"unknown binding {n!r} (known: {', '.join(sorted(bindings)) or 'none'})")
76
+ mb = b.get("modules", {}).get(module)
77
+ if mb is None:
78
+ continue
79
+ det = mb.get("detect")
80
+ if explicit or det is None or _detect(det, headers):
81
+ return dict(mb, _binding=n)
82
+ return None
83
+
84
+
85
+ def _detect(det: dict, headers: dict) -> bool:
86
+ path = headers.get(det["header"])
87
+ if not path:
88
+ return False
89
+ try:
90
+ with open(path, errors="replace") as f:
91
+ text = f.read()
92
+ except OSError:
93
+ return False
94
+ return all(t in text for t in ([det["contains"]] if isinstance(det.get("contains"), str) else det.get("contains", [])))
95
+
96
+
97
+ class Eval:
98
+ """Evaluates binding paths against one module; records touched params (config class) and used modules."""
99
+
100
+ def __init__(self, cg: CgModel, mc: ModuleCfg, ctx=None):
101
+ self.ctx = ctx
102
+ self.cg = cg
103
+ self.mc = mc
104
+ self.rank = 1
105
+ self.used_mips: set = set()
106
+ self.ext: dict = {} # call:<name> resolvers contributed by the module generator
107
+
108
+ def steps(self, start: Container, steps) -> tuple:
109
+ """-> ("container", Container) | ("value", (raw, def, owner)) | ("token", str) | (None, None)"""
110
+ if isinstance(steps, str):
111
+ steps = [steps]
112
+ cur = start
113
+ for i, st in enumerate(steps):
114
+ op, _, arg = st.partition(":")
115
+ last = i == len(steps) - 1
116
+ if op == "param":
117
+ pv = cur.value(arg)
118
+ d = self.cg.param_def(cur, arg)
119
+ self._touch(cur, d)
120
+ if pv is not None and pv.is_ref:
121
+ if last:
122
+ return ("value", (pv.value, d, cur))
123
+ cur = self.cg.target(pv)
124
+ else:
125
+ return ("value", (pv.value if pv is not None else None, d, cur)) if last else (None, None)
126
+ elif op == "sub":
127
+ subs = cur.sub(arg)
128
+ cur = subs[0] if subs else None
129
+ elif op == "ref":
130
+ self._touch(cur, self.cg.param_def(cur, arg))
131
+ cur = self.cg.target(cur.value(arg))
132
+ elif op == "back":
133
+ dname, _, rname = arg.partition("/")
134
+ hits = [c for c in self.cg.referrers(cur, ref=rname) if c.def_name == dname]
135
+ cur = hits[0] if hits else None
136
+ if cur is not None:
137
+ self._touch(cur, self.cg.param_def(cur, rname))
138
+ elif op == "peer":
139
+ rname, _, want = arg.partition("@")
140
+ want, _, wdef = want.partition("/")
141
+ self._touch(cur, self.cg.param_def(cur, rname))
142
+ t = self.cg.target(cur.value(rname))
143
+ if t is None:
144
+ return (None, None)
145
+ hits = [c for c in self.cg.referrers(t) if c is not cur and
146
+ (c.module.name == want if want else c.module is not cur.module) and
147
+ (not wdef or c.def_name == wdef)]
148
+ hits.sort(key=lambda c: (c.module.name, self.cg.by_path_index(c)))
149
+ cur = hits[0] if hits else None
150
+ elif op == "sym":
151
+ s = self.cg.symbol(cur)
152
+ if s and self.ctx is not None:
153
+ s = self.ctx.xsym(cur, self.mc, self.used_mips)
154
+ return ("token", s) if s else (None, None)
155
+ elif op == "call":
156
+ fn = self.ext.get(arg)
157
+ if fn is None:
158
+ raise BindingError(f"path step call:{arg}: the {self.mc.name} generator provides no such resolver")
159
+ v = fn(cur, self)
160
+ return ("token", v) if v else (None, None)
161
+ elif op == "module":
162
+ return ("token", cur.module.mip)
163
+ elif op == "name":
164
+ return ("token", cur.name)
165
+ elif op == "index":
166
+ return ("token", W.c_uint(self.cg.index_of(cur)))
167
+ else:
168
+ raise BindingError(f"unknown path step {st!r}")
169
+ if cur is None:
170
+ return (None, None)
171
+ return ("container", cur)
172
+
173
+ def rows(self, spec: str, sets: dict) -> list:
174
+ """Rows of a table: a container def (ordered by symbolic handle) or a named set "@<Set>"."""
175
+ if not spec.startswith("@"):
176
+ return list(self.cg.sorted_instances(self.mc, spec))
177
+ st = sets.get(spec[1:])
178
+ if st is None:
179
+ raise BindingError(f"unknown row set {spec} (sets: {', '.join(sorted(sets)) or 'none'})")
180
+ return [r for r in self.cg.sorted_instances(self.mc, st["rows"]) if self.matches(r, st.get("where", []))]
181
+
182
+ def raw(self, row: Container, path) -> Optional[str]:
183
+ kind, v = self.steps(row, path)
184
+ if kind == "value":
185
+ return (v[0] or "").strip() if v[0] is not None else None
186
+ if kind == "token":
187
+ return v
188
+ return None
189
+
190
+ def matches(self, row: Container, where: list) -> bool:
191
+ for w in where:
192
+ v = self.raw(row, w["path"])
193
+ if "in" in w and v not in w["in"]:
194
+ return False
195
+ if "notIn" in w and v in w["notIn"]:
196
+ return False
197
+ return True
198
+
199
+ def _touch(self, c: Container, d):
200
+ if c.module is self.mc:
201
+ self.rank = max(self.rank, self.cg.eff_class(self.mc, d))
202
+
203
+ def field(self, row: Container, spec: dict) -> tuple:
204
+ """(C initialiser, comment) for one struct field."""
205
+ when = spec.get("when")
206
+ if when is not None:
207
+ kind, v = self.steps(row, when)
208
+ ok = kind == "value" and (v[0] or "").strip().lower() in ("true", "1")
209
+ if not ok:
210
+ return (spec.get("default", "0u"), "")
211
+ if "agg" in spec:
212
+ return self._agg(row, spec)
213
+ paths = spec["path"]
214
+ alts = paths if isinstance(paths, list) and paths and isinstance(paths[0], list) else [paths]
215
+ kind, v = None, None
216
+ for alt in alts: # alternatives: the first path that yields a value wins
217
+ kind, v = self.steps(row, alt)
218
+ if kind is not None and not (kind == "value" and v[0] is None):
219
+ break
220
+ default = spec.get("default")
221
+ fmt = spec.get("fmt")
222
+ if kind is None or (kind == "value" and v[0] is None):
223
+ if default is None:
224
+ raise BindingError(f"{row.path}: field {spec.get('name')} ({spec['path']}) has no value and no default")
225
+ return (default, "")
226
+ if kind == "container":
227
+ s = self.cg.symbol(v)
228
+ if s is None:
229
+ return (W.c_uint(self.cg.index_of(v)), v.name)
230
+ if self.ctx is not None:
231
+ s = self.ctx.xsym(v, self.mc, self.used_mips)
232
+ return (s, "")
233
+ if kind == "token":
234
+ if fmt and fmt not in ("hex", "int"):
235
+ return (fmt.replace("{MIP}", v.upper()).replace("{Mip}", v).replace("{}", v), "")
236
+ return (v, "")
237
+ raw, d, owner = v
238
+ if "enum" in spec and raw is not None:
239
+ m = spec["enum"]
240
+ if raw.strip() not in m:
241
+ raise BindingError(f"{row.path}: {spec['path']} = {raw.strip()} is not supported by the static code "
242
+ f"(the {self.mc.name} binding maps only {', '.join(m)})")
243
+ e = m[raw.strip()]
244
+ return (W.c_uint(e) if isinstance(e, int) else str(e), raw.strip())
245
+ if "divide" in spec:
246
+ return (self._ticks(row, raw, spec), "")
247
+ if fmt == "int": # a numeric value held in a STRING param (e.g. ComSignalInitValue)
248
+ n = parse_int(raw.strip())
249
+ if n is None:
250
+ raise BindingError(f"{row.path}: {spec['path']} = {raw.strip()!r} is not an integer; the static code's "
251
+ f"{spec.get('name')} field cannot hold it")
252
+ return (W.c_uint(n), "")
253
+ return W.table_value(d, raw, hexa=fmt == "hex")
254
+
255
+ def _ticks(self, row: Container, raw, spec: dict) -> str:
256
+ from ..values import parse_float
257
+ v = parse_float(str(raw).strip())
258
+ base = self.raw(row, spec["divide"])
259
+ b = parse_float(base) if base is not None else None
260
+ if v is None or not b:
261
+ raise BindingError(f"{row.path}: cannot convert {spec.get('name')} = {raw} to ticks of {spec['divide']} = {base}")
262
+ return W.c_uint(int(round(v / b)))
263
+
264
+ def _agg(self, row: Container, spec: dict) -> tuple:
265
+ """Aggregate params over the targets of a multi-valued reference of the row (e.g. the smallest non-zero
266
+ timeout of an I-PDU's signals, SWS_Com_00291)."""
267
+ from ..values import parse_float
268
+ a = spec["agg"]
269
+ vals = []
270
+ for pv in row.values(a["over"]):
271
+ t = self.cg.target(pv)
272
+ if t is None or (a.get("skipIf") and t.value(a["skipIf"]) is not None):
273
+ continue
274
+ for p in a["params"]:
275
+ x = parse_float((t.text(p) or "").strip())
276
+ if x is not None:
277
+ vals.append(x)
278
+ if a["op"] == "min_nonzero":
279
+ vals = [x for x in vals if x > 0]
280
+ v = min(vals) if vals else None
281
+ elif a["op"] == "max":
282
+ v = max(vals) if vals else None
283
+ else:
284
+ raise BindingError(f"unknown aggregate {a['op']!r}")
285
+ if v is None:
286
+ return (spec.get("default", "0u"), "")
287
+ if "divide" in spec:
288
+ return (self._ticks(row, repr(v), spec), "")
289
+ return (W.c_float(repr(v)), "")
290
+
291
+ def macro(self, expr: str, sets: Optional[dict] = None) -> str:
292
+ m = re.match(r"^(count|max):(@?[\w]+)(?:/(\w+))?\s*([+-]\s*\d+)?$", expr.strip())
293
+ if not m:
294
+ raise BindingError(f"bad macro expression {expr!r} (count:<Def> | count:@<Set> | max:<Def>/<Param> [+N])")
295
+ op, dname, pname, off = m.groups()
296
+ insts = self.rows(dname, sets or {}) if dname.startswith("@") else self.cg.instances(self.mc, dname)
297
+ if op == "count":
298
+ v = len(insts)
299
+ else:
300
+ vals = [parse_int(c.text(pname) or "") for c in insts]
301
+ vals = [x for x in vals if x is not None]
302
+ v = max(vals) if vals else 0
303
+ if off:
304
+ v += int(off.replace(" ", ""))
305
+ return W.c_uint(v)