@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,152 @@
1
+ """Index of the non-ECUC ARXML (System / ECU extract, SWCs, BSWMD) for cross-checks.
2
+
3
+ One expat pass per file. Every identifiable (element with a SHORT-NAME) becomes an Ident with its tag,
4
+ file, line, the identifiable children, and the leaf values *of its own* (descendants that are not
5
+ inside a nested identifiable): e.g. an I-SIGNAL-TO-I-PDU-MAPPING gets {"I-SIGNAL-REF": [...],
6
+ "START-POSITION": ["16"], "PACKING-BYTE-ORDER": [...]}. References keep their DEST type.
7
+ Nothing is keyed by project-specific names; rules navigate by AUTOSAR metaclass and reference.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import xml.parsers.expat as expat
12
+ from dataclasses import dataclass, field
13
+ from typing import Optional
14
+
15
+
16
+ @dataclass
17
+ class Ident:
18
+ path: str
19
+ tag: str
20
+ file: str
21
+ line: int
22
+ values: dict = field(default_factory=dict) # leaf tag -> [text]
23
+ dests: dict = field(default_factory=dict) # ref tag -> [DEST]
24
+ children: list = field(default_factory=list) # child identifiable paths
25
+ parent: Optional[str] = None
26
+
27
+ def one(self, tag: str) -> Optional[str]:
28
+ v = self.values.get(tag)
29
+ return v[0] if v else None
30
+
31
+
32
+ class SystemIndex:
33
+ def __init__(self):
34
+ self.by_path: dict = {}
35
+ self.by_tag: dict = {}
36
+ self._refs_to: Optional[dict] = None
37
+
38
+ def add_file(self, path: str):
39
+ try:
40
+ with open(path, "rb") as fh:
41
+ data = fh.read()
42
+ except OSError:
43
+ return
44
+ p = expat.ParserCreate()
45
+ p.buffer_text = True
46
+ stack: list = [] # [tag, Ident|None, text parts, attrs, line]
47
+ idents: list = [] # current chain of Idents
48
+
49
+ def start(name, attrs):
50
+ stack.append([name.rsplit(":", 1)[-1], None, [], attrs, p.CurrentLineNumber])
51
+
52
+ def chars(t):
53
+ if stack:
54
+ stack[-1][2].append(t)
55
+
56
+ def end(name):
57
+ tag, ident, text, attrs, line = stack.pop()
58
+ if tag == "SHORT-NAME" and stack:
59
+ owner = stack[-1]
60
+ if owner[1] is None:
61
+ parent = idents[-1] if idents else None
62
+ sn = "".join(text).strip()
63
+ ip = f"{parent.path if parent else ''}/{sn}"
64
+ owner[1] = Ident(ip, owner[0], path, owner[4], parent=parent.path if parent else None)
65
+ if parent is not None:
66
+ parent.children.append(ip)
67
+ idents.append(owner[1])
68
+ self.by_path.setdefault(ip, owner[1])
69
+ self.by_tag.setdefault(owner[0], []).append(owner[1])
70
+ return
71
+ if ident is not None:
72
+ idents.pop()
73
+ return
74
+ if idents: # a leaf value of the innermost enclosing identifiable
75
+ txt = "".join(text).strip()
76
+ if txt:
77
+ cur = idents[-1]
78
+ cur.values.setdefault(tag, []).append(txt)
79
+ if "DEST" in attrs:
80
+ cur.dests.setdefault(tag, []).append(attrs["DEST"])
81
+
82
+ p.StartElementHandler = start
83
+ p.EndElementHandler = end
84
+ p.CharacterDataHandler = chars
85
+ try:
86
+ p.Parse(data, True)
87
+ except expat.ExpatError:
88
+ pass
89
+ self._refs_to = None
90
+
91
+ def get(self, path: Optional[str]) -> Optional[Ident]:
92
+ return self.by_path.get(path.strip()) if path else None
93
+
94
+ def tags(self, tag: str) -> list:
95
+ return self.by_tag.get(tag, [])
96
+
97
+ def refs_to(self, path: str) -> list:
98
+ """Idents that reference ``path`` (any *-REF leaf)."""
99
+ if self._refs_to is None:
100
+ idx: dict = {}
101
+ for i in self.by_path.values():
102
+ for t, vals in i.values.items():
103
+ if t.endswith("-REF") or t.endswith("-TREF"):
104
+ for v in vals:
105
+ idx.setdefault(v, []).append(i)
106
+ self._refs_to = idx
107
+ return self._refs_to.get(path, [])
108
+
109
+ def foreign(self) -> dict:
110
+ return {p: (i.tag, i.line, i.file) for p, i in self.by_path.items()}
111
+
112
+ # ---- navigation helpers (by metaclass, not by name) ----
113
+ def ipdus_of_triggering(self, path: Optional[str]) -> list:
114
+ """A PDU-TRIGGERING or FRAME-TRIGGERING (any bus) -> system I-PDU paths it carries."""
115
+ t = self.get(path)
116
+ if t is None:
117
+ return []
118
+ out = []
119
+ for ref in t.values.get("I-PDU-REF", []):
120
+ out.append(ref)
121
+ for pt in t.values.get("PDU-TRIGGERING-REF", []):
122
+ out.extend(self.ipdus_of_triggering(pt))
123
+ if not out:
124
+ fr = self.get(t.one("FRAME-REF"))
125
+ if fr is not None:
126
+ for m in fr.children:
127
+ mi = self.get(m)
128
+ if mi is not None:
129
+ out.extend(mi.values.get("PDU-REF", []))
130
+ seen, res = set(), []
131
+ for x in out:
132
+ if x not in seen:
133
+ seen.add(x)
134
+ res.append(x)
135
+ return res
136
+
137
+ def channel_of(self, path: Optional[str]) -> Optional[str]:
138
+ """Physical channel (the identifiable holding the triggering) of a PDU/FRAME-TRIGGERING."""
139
+ t = self.get(path)
140
+ return t.parent if t is not None else None
141
+
142
+ def signals_of_ipdu(self, ipdu: str) -> list:
143
+ """I-SIGNAL paths mapped into a system I-PDU (via its I-SIGNAL-TO-I-PDU-MAPPING children)."""
144
+ i = self.get(ipdu)
145
+ if i is None:
146
+ return []
147
+ out = []
148
+ for c in i.children:
149
+ ci = self.get(c)
150
+ if ci is not None and ci.values.get("I-SIGNAL-REF"):
151
+ out.extend(ci.values["I-SIGNAL-REF"])
152
+ return sorted(set(out))
@@ -64,7 +64,8 @@ def _resolve_def(defs: D.DefIndex, ref: Optional[str]) -> Optional[D.DefNode]:
64
64
  return None
65
65
 
66
66
 
67
- def validate(project: Project, files: Optional[list] = None, module: Optional[str] = None) -> dict:
67
+ def validate(project: Project, files: Optional[list] = None, module: Optional[str] = None,
68
+ check_generated: Optional[bool] = None, code: bool = True) -> dict:
68
69
  defs = project.defs
69
70
  dg = Diags(project)
70
71
  warned_modules = set()
@@ -211,6 +212,18 @@ def validate(project: Project, files: Optional[list] = None, module: Optional[st
211
212
 
212
213
  from .rules import run_rules
213
214
  dg.items.extend(run_rules(project))
215
+ from .probe import load_cfg
216
+ cfg = load_cfg(project.root)
217
+ if code: # configuration completeness seen from the C code: handles used but not configured
218
+ from .audit import audit
219
+ for f in audit(project).get("findings", []):
220
+ if f["rule"] == "H2L-API-002":
221
+ dg.items.append({"file": f["file"], "line": f["line"], "path": "", "rule": f["rule"],
222
+ "severity": f["severity"], "message": f"{f['call']}: {f['message']} [{f['cite']}]",
223
+ "defRef": None})
224
+ from . import gen
225
+ if check_generated if check_generated is not None else gen.default_on(project, cfg):
226
+ dg.items.extend(gen.check(project, cfg))
214
227
 
215
228
  items = dg.items
216
229
  loaded = sorted(project.files)
@@ -228,7 +241,7 @@ def validate(project: Project, files: Optional[list] = None, module: Optional[st
228
241
  f"file(s) [{sample or 'none'}]; NOTHING was validated for it. Pass a file, "
229
242
  f"directory or glob relative to {project.root}, or omit 'files'."))
230
243
  want = {project.rel(f) for f in matched}
231
- items = [i for i in items if i["file"] in want]
244
+ items = [i for i in items if i["file"] in want or not i["file"].lower().endswith(".arxml")]
232
245
  scope["filter"] = {"files": list(files), "matched": sorted(want)[:200], "matchedCount": len(want)}
233
246
  if module:
234
247
  names = {m.name for m in project.modules} | {(m.def_ref or "").rsplit("/", 1)[-1] for m in project.modules}
@@ -144,3 +144,29 @@ def from_json(d: Optional[DefNode], v) -> str:
144
144
  if d is not None and d.kind == "BOOLEAN" and s.lower() in ("true", "false"):
145
145
  s = s.lower()
146
146
  return s
147
+
148
+
149
+ def hex_str(v: int) -> str:
150
+ return f"-0x{-v:X}" if v < 0 else f"0x{v:X}"
151
+
152
+
153
+ DEST_KIND = {"ECUC-INTEGER-PARAM-DEF": "INTEGER", "ECUC-FLOAT-PARAM-DEF": "FLOAT", "ECUC-BOOLEAN-PARAM-DEF": "BOOLEAN"}
154
+
155
+
156
+ def int_forms(d: Optional[DefNode], raw: Optional[str], is_ref: bool, numerical: bool,
157
+ dest: Optional[str] = None) -> Optional[dict]:
158
+ """{"dec": int, "hex": "0x..."} for an integer-typed value (def kind INTEGER; without loaded defs the
159
+ DEFINITION-REF DEST, else a numerical value whose text is an integer), else None. Computed here so answers
160
+ never convert bases by hand."""
161
+ if raw is None or is_ref:
162
+ return None
163
+ k = d.kind if d is not None else DEST_KIND.get(dest or "") or ("NUMBER" if numerical else None)
164
+ if k not in ("INTEGER", "NUMBER"):
165
+ return None
166
+ t = raw.strip()
167
+ if t.lower() in ("true", "false"):
168
+ return None
169
+ v = parse_int(t)
170
+ if v is None:
171
+ return None
172
+ return {"dec": v, "hex": hex_str(v)}