@h2loop/autosar 1.1.0 → 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 +7 -1
- package/agents/autosar-integrator.md +11 -0
- package/commands/autosar-trace.md +2 -2
- package/package.json +1 -1
- package/plugins/index.js +46 -44
- package/python/ecuc_engine/__init__.py +1 -1
- package/python/ecuc_engine/audit.py +24 -2
- package/python/ecuc_engine/cli.py +12 -1
- package/python/ecuc_engine/codegen/__init__.py +51 -3
- package/python/ecuc_engine/codegen/binding.py +108 -13
- package/python/ecuc_engine/codegen/bindings/sip_standin.json +304 -37
- package/python/ecuc_engine/codegen/emit.py +73 -5
- package/python/ecuc_engine/codegen/handles.py +95 -8
- package/python/ecuc_engine/codegen/model.py +6 -1
- package/python/ecuc_engine/codegen/modules/com.py +145 -0
- package/python/ecuc_engine/codegen/registry.py +92 -17
- package/python/ecuc_engine/ecucjson.py +75 -22
- package/python/ecuc_engine/find.py +6 -4
- package/python/ecuc_engine/specids.py +163 -0
- package/python/ecuc_engine/values.py +26 -0
- package/python/pyproject.toml +1 -1
- package/skills/autosar/SKILL.md +31 -5
- package/skills/autosar/references/codegen.md +28 -0
- package/skills/autosar/references/ecuc-model.md +4 -1
- package/skills/autosar/references/project-layout.md +4 -1
|
@@ -10,6 +10,13 @@ Algorithm (stable by construction):
|
|
|
10
10
|
* containers without a value get the lowest free integers >= max(def MIN, 0), in document order;
|
|
11
11
|
* the scope is one container def within one module (the symbol namespace of TPS_ECUC_02108).
|
|
12
12
|
Only INTEGER parameters are assigned. A missing STRING symbolic value is reported.
|
|
13
|
+
|
|
14
|
+
Static-code layout (layout()): a static code that indexes its tables by handle (e.g. a Com whose
|
|
15
|
+
signal table holds the Tx signals at 0..nTx-1 and the Rx signals after them) declares that order in
|
|
16
|
+
its binding ("handleLayout"). When the configured values break it (a Tx signal added after the Rx
|
|
17
|
+
ones), the generator renumbers that handle param, keeping the relative order inside each row set,
|
|
18
|
+
but only for withAuto params (the def says the configuration tool computes the value); otherwise the
|
|
19
|
+
mismatch is an error for the integrator.
|
|
13
20
|
"""
|
|
14
21
|
from __future__ import annotations
|
|
15
22
|
|
|
@@ -74,16 +81,96 @@ def _plan_module(cg: CgModel, mc: ModuleCfg, out: dict):
|
|
|
74
81
|
nxt += 1
|
|
75
82
|
|
|
76
83
|
|
|
84
|
+
def layout(cg: CgModel, modules: list, selected: dict) -> dict:
|
|
85
|
+
"""-> {"assign": [...], "errors": [...], "notes": [...]} renumbering handles to each binding's handleLayout."""
|
|
86
|
+
from .binding import Eval
|
|
87
|
+
out: dict = {"assign": [], "errors": [], "notes": []}
|
|
88
|
+
for name in modules:
|
|
89
|
+
mb, mc = selected.get(name), cg.module(name)
|
|
90
|
+
if not mb or mc is None:
|
|
91
|
+
continue
|
|
92
|
+
ev = Eval(cg, mc)
|
|
93
|
+
for hl in mb.get("handleLayout", []):
|
|
94
|
+
rows: list = []
|
|
95
|
+
for st in hl["sets"]:
|
|
96
|
+
rows += [r for r in ev.rows("@" + st, mb.get("sets", {})) if r not in rows]
|
|
97
|
+
insts = cg.instances(mc, mb["sets"][hl["sets"][0]]["rows"])
|
|
98
|
+
rows += [r for r in cg.sorted_instances(mc, insts[0].def_name) if r not in rows] if insts else []
|
|
99
|
+
if not rows:
|
|
100
|
+
continue
|
|
101
|
+
d = cg.symbolic_param(rows[0])
|
|
102
|
+
changes = [(r, i) for i, r in enumerate(rows) if parse_int(r.text(d.name) or "") != i]
|
|
103
|
+
if not changes:
|
|
104
|
+
continue
|
|
105
|
+
what = f"{d.name}: the {mb.get('_binding')} static code indexes its tables by handle in the order " \
|
|
106
|
+
f"{' then '.join(hl['sets'])}"
|
|
107
|
+
if not d.with_auto:
|
|
108
|
+
out["errors"].append(f"{what}; {len(changes)} value(s) break that order and the def has no withAuto, "
|
|
109
|
+
f"so they are not renumbered (first: {changes[0][0].path}={changes[0][0].text(d.name)})")
|
|
110
|
+
continue
|
|
111
|
+
for r, i in changes:
|
|
112
|
+
out["assign"].append({"path": r.path, "param": d.name, "value": i, "def": d.path})
|
|
113
|
+
out["notes"].append(f"{what}; renumbered {len(changes)} {d.name} value(s) (withAuto, TPS_ECUC_02107), "
|
|
114
|
+
f"e.g. {changes[0][0].name}: {changes[0][0].text(d.name)} -> {changes[0][1]}")
|
|
115
|
+
return out
|
|
116
|
+
|
|
117
|
+
|
|
77
118
|
def apply(project, assignments: list, write: bool) -> dict:
|
|
78
|
-
"""Write the assigned values into the ECUC ARXML in place (TPS_ECUC_02107)
|
|
119
|
+
"""Write the assigned values into the ECUC ARXML in place (TPS_ECUC_02107).
|
|
79
120
|
|
|
80
|
-
|
|
121
|
+
Values that replace an existing VALUE are patched in one pass per file (only the VALUE text bytes change,
|
|
122
|
+
so a renumbering of thousands of handles stays linear); new values go through the engine's editor, which
|
|
123
|
+
places the element per the schema. write=False patches only the in-memory model (dry run), so the
|
|
124
|
+
generated code still uses them.
|
|
81
125
|
"""
|
|
82
126
|
if not assignments:
|
|
83
127
|
return {"written": False, "changes": []}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
128
|
+
import difflib
|
|
129
|
+
import os
|
|
130
|
+
from ..xmltree import escape
|
|
131
|
+
by_path = {}
|
|
132
|
+
for e in _elems(project):
|
|
133
|
+
by_path.setdefault(e.path, []).append(e)
|
|
134
|
+
patches: dict = {}
|
|
135
|
+
rest = []
|
|
136
|
+
for a in assignments:
|
|
137
|
+
hits = [(e, pv) for e in by_path.get(a["path"], []) for pv in e.params if pv.name == a["param"]]
|
|
138
|
+
vn = hits[0][1].value_node if len(hits) == 1 else None
|
|
139
|
+
if vn is None or vn.cstart is None or vn.tag != "VALUE":
|
|
140
|
+
rest.append(a)
|
|
141
|
+
continue
|
|
142
|
+
patches.setdefault(hits[0][0].file, []).append((vn.cstart, vn.cend, escape(str(a["value"])).encode()))
|
|
143
|
+
changes = []
|
|
144
|
+
for f in sorted(patches):
|
|
145
|
+
before = project.files[f].data
|
|
146
|
+
after = bytearray(before)
|
|
147
|
+
for start, end, repl in sorted(patches[f], reverse=True):
|
|
148
|
+
after[start:end] = repl
|
|
149
|
+
after = bytes(after)
|
|
150
|
+
project.reload(f, after)
|
|
151
|
+
rel = project.rel(f)
|
|
152
|
+
a_, b_ = before.decode("utf-8", "replace").splitlines(True), after.decode("utf-8", "replace").splitlines(True)
|
|
153
|
+
changes.append({"file": rel, "diff": "".join(difflib.unified_diff(a_, b_, f"a/{rel}", f"b/{rel}", n=1))})
|
|
154
|
+
if write:
|
|
155
|
+
tmp = f + ".ecuc-tmp"
|
|
156
|
+
with open(tmp, "wb") as fh:
|
|
157
|
+
fh.write(after)
|
|
158
|
+
os.replace(tmp, f)
|
|
159
|
+
written = bool(patches) and write
|
|
160
|
+
if rest:
|
|
161
|
+
from ..edit import edit
|
|
162
|
+
ops = [{"op": "set", "path": a["path"], "param": a["param"], "value": a["value"]} for a in rest]
|
|
163
|
+
res = edit(project, ops, dry_run=not write, run_validate=False)
|
|
164
|
+
if not res.get("ok"):
|
|
165
|
+
raise RuntimeError(f"handle-ID write-back failed: {res.get('error')}")
|
|
166
|
+
written = written or bool(res.get("written"))
|
|
167
|
+
changes += res.get("changes", [])
|
|
168
|
+
return {"written": written, "changes": changes}
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _elems(project):
|
|
172
|
+
stack = list(project.modules)
|
|
173
|
+
while stack:
|
|
174
|
+
e = stack.pop()
|
|
175
|
+
yield e
|
|
176
|
+
stack.extend(e.children)
|
|
@@ -28,8 +28,13 @@ SCHEMA_RELEASE = {
|
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def snake(name: str) -> str:
|
|
31
|
-
"""CamelCase -> UPPER_SNAKE (NvMDevErrorDetect -> NVM_DEV_ERROR_DETECT).
|
|
31
|
+
"""CamelCase -> UPPER_SNAKE (NvMDevErrorDetect -> NVM_DEV_ERROR_DETECT).
|
|
32
|
+
|
|
33
|
+
The AUTOSAR glossary terms I-PDU / N-PDU / L-PDU stay one word (ComSupportedIPduGroups ->
|
|
34
|
+
COM_SUPPORTED_IPDU_GROUPS), as the specifications spell them in identifiers (IPDUM, ..._IPDU_...).
|
|
35
|
+
"""
|
|
32
36
|
s = re.sub(r"(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", "_", name)
|
|
37
|
+
s = re.sub(r"(?<![A-Za-z])([INL])_(Pdu)", r"\1\2", s)
|
|
33
38
|
return re.sub(r"_+", "_", s).upper()
|
|
34
39
|
|
|
35
40
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Com: Com_Cfg.h / Com_PBcfg.c.
|
|
2
|
+
|
|
3
|
+
Def-driven (generic emitter): every ComGeneral switch, the TPS_ECUC_02108 symbols of ComSignal,
|
|
4
|
+
ComSignalGroup, ComGroupSignal, ComIPdu and ComIPduGroup (handle IDs), and, without a static-code
|
|
5
|
+
binding, one table per container def (I-PDUs, signals, signal groups, group signals with bit
|
|
6
|
+
position / size / endianness / type / transfer property / timeouts / update bits).
|
|
7
|
+
|
|
8
|
+
The static Com's own table layout (signal and I-PDU tables, buffers, Tx-mode ticks, deadline
|
|
9
|
+
ticks) comes from its binding (bindings/*.json); this module only adds what neither the defs nor
|
|
10
|
+
the binding path language can express:
|
|
11
|
+
* COM_DEV_ERROR_DETECT from ComConfigurationUseDet (ECUC_Com_00141 names that output macro);
|
|
12
|
+
* notification callouts, resolved for the binding through `call:` steps:
|
|
13
|
+
- `comUserCallback`: R25-11 Com users (SWS_Com 8.6.3.1, ECUC_Com_10034/10036): the ComUserModuleCnf
|
|
14
|
+
referenced by ComUserModule/ComUserModuleCnfRef, its ComUserSignal / ComUserSignalGroup with the
|
|
15
|
+
same system signal (group) as the ComSignal (Group), and the ComUserCallback of type COM_RX_ACK
|
|
16
|
+
(receiver) or COM_TX_ACK (sender). These callbacks take a ComUserCbkHandleId.
|
|
17
|
+
- `rteSignalCallback`: the per-signal RTE callback `Rte_COMCbk_<sn>()` (receiver) /
|
|
18
|
+
`Rte_COMCbkTAck_<sn>()` (sender), <sn> = ComSignal short name (SWS_Rte Figure 4.42), bound only
|
|
19
|
+
when the RTE's callback header (Rte_Cbk.h, from the project or the output dir) declares it with
|
|
20
|
+
a void(void) prototype; Com_PBcfg.c then includes that header;
|
|
21
|
+
* the headers named by ComGeneral/ComUserCbkHeaderFile (callback prototypes) in Com_PBcfg.c;
|
|
22
|
+
* notes for configuration a static Com may silently not implement.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import re
|
|
27
|
+
|
|
28
|
+
from ..registry import Generator, register
|
|
29
|
+
|
|
30
|
+
RX_ACK, TX_ACK = "COM_RX_ACK", "COM_TX_ACK"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@register("Com")
|
|
34
|
+
class Com(Generator):
|
|
35
|
+
std_header = "ComStack_Types.h"
|
|
36
|
+
|
|
37
|
+
def specific(self, mo):
|
|
38
|
+
gen = [c for c in self.mc.containers if c.def_name == "ComGeneral"]
|
|
39
|
+
g = gen[0] if gen else None
|
|
40
|
+
det = g.text("ComConfigurationUseDet") if g is not None else None
|
|
41
|
+
mo.group("Development error detection (ECUC_Com_00141: ComConfigurationUseDet -> COM_DEV_ERROR_DETECT)",
|
|
42
|
+
[("COM_DEV_ERROR_DETECT", "STD_ON" if (det or "").strip().lower() in ("true", "1") else "STD_OFF")])
|
|
43
|
+
if g is not None:
|
|
44
|
+
for pv in g.values("ComUserCbkHeaderFile"):
|
|
45
|
+
h = (pv.value or "").strip().strip('"<>')
|
|
46
|
+
if h:
|
|
47
|
+
mo.include(h)
|
|
48
|
+
self._dir = self._directions()
|
|
49
|
+
self._users = self._com_user_callbacks()
|
|
50
|
+
self._rte_text = self.ctx.header_text("Rte_Cbk.h")
|
|
51
|
+
self._limits(mo)
|
|
52
|
+
|
|
53
|
+
# ---------- call: resolvers for the binding ----------
|
|
54
|
+
|
|
55
|
+
def path_steps(self, mo) -> dict:
|
|
56
|
+
def com_user(c, ev):
|
|
57
|
+
hit = self._users.get((c.path, self._ack(c)))
|
|
58
|
+
if hit is None:
|
|
59
|
+
return None
|
|
60
|
+
name, header = hit
|
|
61
|
+
if header:
|
|
62
|
+
mo.include(header)
|
|
63
|
+
return name
|
|
64
|
+
|
|
65
|
+
def rte_signal(c, ev):
|
|
66
|
+
if c.def_name not in ("ComSignal", "ComSignalGroup") or not self._rte_text:
|
|
67
|
+
return None
|
|
68
|
+
name = ("Rte_COMCbk_" if self._ack(c) == RX_ACK else "Rte_COMCbkTAck_") + c.name
|
|
69
|
+
if not re.search(rf"\b{re.escape(name)}\s*\(\s*void\s*\)", self._rte_text):
|
|
70
|
+
if (c.path, self._ack(c)) in self._users:
|
|
71
|
+
mo.notes.append(f"{c.path}: has a Com user callback ({self._users[(c.path, self._ack(c))][0]}), "
|
|
72
|
+
"which takes a ComUserCbkHandleId; the static code's notification type is "
|
|
73
|
+
"void(void), so it is not bound")
|
|
74
|
+
return None
|
|
75
|
+
mo.include("Rte_Cbk.h")
|
|
76
|
+
return name
|
|
77
|
+
|
|
78
|
+
return {"comUserCallback": com_user, "rteSignalCallback": rte_signal}
|
|
79
|
+
|
|
80
|
+
# ---------- helpers ----------
|
|
81
|
+
|
|
82
|
+
def _directions(self) -> dict:
|
|
83
|
+
"""ComSignal / ComSignalGroup path -> ComIPduDirection of the I-PDU that carries it."""
|
|
84
|
+
out = {}
|
|
85
|
+
for ip in self.cg.instances(self.mc, "ComIPdu"):
|
|
86
|
+
d = (ip.text("ComIPduDirection") or "").strip()
|
|
87
|
+
for r in ("ComIPduSignalRef", "ComIPduSignalGroupRef"):
|
|
88
|
+
for pv in ip.values(r):
|
|
89
|
+
if pv.value:
|
|
90
|
+
out.setdefault(pv.value.strip(), d)
|
|
91
|
+
return out
|
|
92
|
+
|
|
93
|
+
def _ack(self, c) -> str:
|
|
94
|
+
return TX_ACK if self._dir.get(c.path) == "SEND" else RX_ACK
|
|
95
|
+
|
|
96
|
+
def _com_user_callbacks(self) -> dict:
|
|
97
|
+
"""(ComSignal/ComSignalGroup path, callback type) -> (ComUserCallbackName, ComUserHeaderInclude)."""
|
|
98
|
+
by_sys: dict = {}
|
|
99
|
+
for c in self.mc.containers:
|
|
100
|
+
for p in ("ComSystemTemplateSystemSignalRef", "ComSystemTemplateSignalGroupRef"):
|
|
101
|
+
v = c.text(p)
|
|
102
|
+
if v and c.def_name in ("ComSignal", "ComSignalGroup"):
|
|
103
|
+
by_sys.setdefault(v.strip(), c)
|
|
104
|
+
out: dict = {}
|
|
105
|
+
for um in self.cg.instances(self.mc, "ComUserModule"):
|
|
106
|
+
cnf = self.cg.target(um.value("ComUserModuleCnfRef"))
|
|
107
|
+
if cnf is None:
|
|
108
|
+
continue
|
|
109
|
+
header = (cnf.text("ComUserHeaderInclude") or "").strip().strip('"<>') or None
|
|
110
|
+
for u in cnf.children:
|
|
111
|
+
sysref = u.text("ComUserSystemTemplateSystemSignalRef") or u.text("ComUserSystemTemplateSignalGroupRef")
|
|
112
|
+
sig = by_sys.get((sysref or "").strip())
|
|
113
|
+
if sig is None:
|
|
114
|
+
continue
|
|
115
|
+
for pv in u.values("ComUserCallbackRef"):
|
|
116
|
+
cb = self.cg.target(pv)
|
|
117
|
+
if cb is None:
|
|
118
|
+
continue
|
|
119
|
+
kind = (cb.text("ComUserCallbackType") or "").strip()
|
|
120
|
+
name = (cb.text("ComUserCallbackName") or "").strip()
|
|
121
|
+
if name:
|
|
122
|
+
out.setdefault((sig.path, kind), (name, header))
|
|
123
|
+
return out
|
|
124
|
+
|
|
125
|
+
def _limits(self, mo):
|
|
126
|
+
"""Configuration that is valid ECUC but that a static Com may not implement: say so, never drop it silently."""
|
|
127
|
+
if mo.binding is None:
|
|
128
|
+
return
|
|
129
|
+
cnt = lambda pred: sum(1 for c in self.mc.containers if pred(c)) # noqa: E731
|
|
130
|
+
notes = []
|
|
131
|
+
n = cnt(lambda c: c.def_name == "ComIPdu" and (c.text("ComIPduSignalProcessing") or "").strip() == "DEFERRED")
|
|
132
|
+
if n:
|
|
133
|
+
notes.append(f"{n} I-PDU(s) use ComIPduSignalProcessing=DEFERRED; the binding has no field for it "
|
|
134
|
+
"(the static code decides when Rx notifications run)")
|
|
135
|
+
n = cnt(lambda c: c.def_name == "ComSignal" and (c.text("ComRxDataTimeoutAction") or "NONE").strip() != "NONE")
|
|
136
|
+
if n:
|
|
137
|
+
notes.append(f"{n} signal(s) configure ComRxDataTimeoutAction; the binding carries only the I-PDU "
|
|
138
|
+
"deadline (TimeoutTicks), the action is up to the static code")
|
|
139
|
+
n = cnt(lambda c: c.def_name == "ComTxMode" and int(float(c.text("ComTxModeNumberOfRepetitions") or 0)) > 0)
|
|
140
|
+
if n:
|
|
141
|
+
notes.append(f"{n} Tx mode(s) configure ComTxModeNumberOfRepetitions > 0; the binding has no repetition field")
|
|
142
|
+
n = cnt(lambda c: c.def_name == "ComTxIPdu" and float(c.text("ComMinimumDelayTime") or 0) > 0)
|
|
143
|
+
if n:
|
|
144
|
+
notes.append(f"{n} Tx I-PDU(s) configure ComMinimumDelayTime > 0; the binding has no MDT field")
|
|
145
|
+
mo.notes += [f"{(mo.binding or {}).get('_binding')}: {x}" for x in notes]
|
|
@@ -57,9 +57,16 @@ class Generator:
|
|
|
57
57
|
if mb is None or mb.get("genericTables", False):
|
|
58
58
|
E.generic_tables(self.cg, mo, set(self.skip_tables(mo)))
|
|
59
59
|
if mb is not None:
|
|
60
|
-
|
|
60
|
+
try:
|
|
61
|
+
apply_binding(self.cg, mo, mb, self.path_steps(mo))
|
|
62
|
+
except B.BindingError as ex:
|
|
63
|
+
mo.errors.append(str(ex))
|
|
61
64
|
return mo
|
|
62
65
|
|
|
66
|
+
def path_steps(self, mo) -> dict:
|
|
67
|
+
"""call:<name> resolvers for the binding path language: {name: fn(container, Eval) -> token | None}."""
|
|
68
|
+
return {}
|
|
69
|
+
|
|
63
70
|
def default_std_header(self) -> str:
|
|
64
71
|
"""ComStack_Types.h for modules that reference EcuC PDUs (communication stack), else Std_Types.h."""
|
|
65
72
|
md = self.mc.defn
|
|
@@ -77,24 +84,53 @@ class Generator:
|
|
|
77
84
|
"""Module-specific emission (only what the param defs cannot express)."""
|
|
78
85
|
|
|
79
86
|
|
|
80
|
-
def apply_binding(cg, mo: E.ModuleOutput, mb: dict):
|
|
87
|
+
def apply_binding(cg, mo: E.ModuleOutput, mb: dict, ext: Optional[dict] = None):
|
|
81
88
|
ev = B.Eval(cg, mo.mc, mo.ctx)
|
|
89
|
+
ev.ext = ext or {}
|
|
90
|
+
sets = mb.get("sets", {})
|
|
91
|
+
for req in mb.get("require", []):
|
|
92
|
+
bad = [r for r in ev.rows(req["rows"], sets) if not ev.matches(r, [req])]
|
|
93
|
+
if bad:
|
|
94
|
+
vals = sorted({str(ev.raw(r, req["path"])) for r in bad})
|
|
95
|
+
mo.errors.append(f"{len(bad)} {req['rows']} container(s) have {req['path']} = {', '.join(vals)} "
|
|
96
|
+
f"({', '.join(r.name for r in bad[:5])}{', ...' if len(bad) > 5 else ''}); the "
|
|
97
|
+
f"{mb['_binding']} static code supports only "
|
|
98
|
+
f"{', '.join('(not configured)' if x is None else str(x) for x in req['in'])}: {req.get('why', '')}")
|
|
99
|
+
for un in mb.get("unsupported", []):
|
|
100
|
+
rows = cg.instances(mo.mc, un["rows"])
|
|
101
|
+
if rows:
|
|
102
|
+
mo.errors.append(f"{len(rows)} {un['rows']} container(s) configured ({', '.join(r.name for r in rows[:5])}"
|
|
103
|
+
f"{', ...' if len(rows) > 5 else ''}), which the {mb['_binding']} static code cannot "
|
|
104
|
+
f"represent: {un.get('why', '')}")
|
|
82
105
|
for inc in mb.get("sourceIncludes", []):
|
|
83
106
|
mo.include(inc)
|
|
84
107
|
macros = []
|
|
108
|
+
values: dict = {}
|
|
85
109
|
for m in mb.get("macros", []):
|
|
86
110
|
val = m["value"]
|
|
87
|
-
|
|
111
|
+
v = ev.macro(val, sets) if re.match(r"^(count|max):", val) else val
|
|
112
|
+
macros.append((m["name"], v))
|
|
113
|
+
values[m["name"]] = v
|
|
88
114
|
mo.group(f"Static-code interface ({mb['_binding']} binding)", macros)
|
|
89
|
-
ranks: dict = {}
|
|
90
115
|
pending: list = []
|
|
116
|
+
for vs in mb.get("vars", []):
|
|
117
|
+
ev.rank = 1
|
|
118
|
+
lines = []
|
|
119
|
+
for r in ev.rows(vs["rows"], sets):
|
|
120
|
+
size, _ = ev.field(r, {"name": vs["name"], "path": vs["size"]})
|
|
121
|
+
name = vs["name"].replace("{}", r.name)
|
|
122
|
+
lines.append(f"{'static ' if vs.get('static') else ''}VAR({vs['type']}, {vs['qualifier']}) {name}[{size}];")
|
|
123
|
+
pending.append(("var", ev.rank, vs.get("section", "VAR_NOINIT_UNSPECIFIED"), "\n".join(lines)))
|
|
91
124
|
for t in mb.get("tables", []):
|
|
92
125
|
ev.rank = 1
|
|
93
|
-
rows =
|
|
126
|
+
rows = ev.rows(t["rows"], sets)
|
|
94
127
|
if t.get("order"):
|
|
95
128
|
from ..values import parse_int
|
|
96
129
|
rows.sort(key=lambda r: (parse_int(r.text(t["order"]) or "") is None, parse_int(r.text(t["order"]) or "") or 0))
|
|
97
130
|
lines, externs = [], []
|
|
131
|
+
des = t.get("designated")
|
|
132
|
+
if des:
|
|
133
|
+
_check_index(cg, mo, t, rows, des, values)
|
|
98
134
|
for r in rows:
|
|
99
135
|
vals = []
|
|
100
136
|
for f in t["fields"]:
|
|
@@ -103,10 +139,12 @@ def apply_binding(cg, mo: E.ModuleOutput, mb: dict):
|
|
|
103
139
|
if f.get("extern") and v not in ("NULL_PTR", "0", "0u") and not v.startswith('"'):
|
|
104
140
|
m = re.search(r"[A-Za-z_]\w*", v)
|
|
105
141
|
if m:
|
|
106
|
-
|
|
107
|
-
if
|
|
108
|
-
externs.append(
|
|
109
|
-
lead =
|
|
142
|
+
ext_ = "extern " + f["extern"].replace("{}", m.group(0)) + ";"
|
|
143
|
+
if ext_ not in externs:
|
|
144
|
+
externs.append(ext_)
|
|
145
|
+
lead = ""
|
|
146
|
+
if des:
|
|
147
|
+
lead = f"[{des.replace('{sym}', cg.symbol(r)) if isinstance(des, str) else cg.symbol(r)}] = "
|
|
110
148
|
comment = t.get("comment", "name")
|
|
111
149
|
cm = {"name": r.name, "sym": cg.symbol(r) or r.name}.get(comment, r.name)
|
|
112
150
|
if t.get("scalar"):
|
|
@@ -117,17 +155,54 @@ def apply_binding(cg, mo: E.ModuleOutput, mb: dict):
|
|
|
117
155
|
size = t.get("size", "")
|
|
118
156
|
body = (f"/* {t.get('doc', t['rows'])} */\n{st}CONST({t['type']}, {t['qualifier']}) {t['name']}[{size}] = {{\n" +
|
|
119
157
|
",\n".join(lines) + "\n};")
|
|
120
|
-
|
|
121
|
-
pending.append((ev.rank, body, externs))
|
|
158
|
+
pending.append(("table", ev.rank, body, externs))
|
|
122
159
|
objects = mb.get("objects", [])
|
|
123
160
|
if objects:
|
|
124
|
-
# a root config object points at the (static) tables, so they all live in its file:
|
|
161
|
+
# a root config object points at the (static) tables and buffers, so they all live in its file:
|
|
125
162
|
# the most dynamic config class among them (e.g. <Mip>_PBcfg.c for a post-build PduR_Config)
|
|
126
|
-
top = max([
|
|
127
|
-
pending = [(
|
|
163
|
+
top = max([p[1] for p in pending] + [1])
|
|
164
|
+
pending = [(p[0], top) + p[2:] for p in pending]
|
|
128
165
|
for o in objects:
|
|
129
166
|
body = (f"CONST({o['type']}, {o['qualifier']}) {o['name']} = {{\n " + ", ".join(o["init"]) + "\n};")
|
|
130
|
-
pending.append((top, body, []))
|
|
131
|
-
for
|
|
132
|
-
|
|
167
|
+
pending.append(("table", top, body, []))
|
|
168
|
+
for p in pending:
|
|
169
|
+
if p[0] == "var":
|
|
170
|
+
mo.var(p[1], p[2], p[3])
|
|
171
|
+
else:
|
|
172
|
+
mo.table(p[1], p[2], p[3])
|
|
133
173
|
mo.used_mips |= ev.used_mips
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _check_index(cg, mo, t: dict, rows: list, des, values: dict):
|
|
177
|
+
"""A designated table is indexed by handle (minus an offset): every row must land in [0, size) exactly once,
|
|
178
|
+
or the static code's table lookup would be wrong. Reported as an error, never renumbered here."""
|
|
179
|
+
from ..values import parse_int
|
|
180
|
+
off_name = None
|
|
181
|
+
if isinstance(des, str):
|
|
182
|
+
m = re.fullmatch(r"\{sym\}\s*-\s*(\w+)", des.strip())
|
|
183
|
+
if not m:
|
|
184
|
+
return
|
|
185
|
+
off_name = m.group(1)
|
|
186
|
+
num = lambda v: parse_int(re.sub(r"[uUlL]+$", "", str(v))) # noqa: E731
|
|
187
|
+
off = num(values.get(off_name, "0")) if off_name else 0
|
|
188
|
+
size = num(values[t["size"]]) if t.get("size") in values else len(rows)
|
|
189
|
+
seen: dict = {}
|
|
190
|
+
bad = 0
|
|
191
|
+
for r in rows:
|
|
192
|
+
h = parse_int(cg.symbol_value(r) or "")
|
|
193
|
+
if h is None:
|
|
194
|
+
continue
|
|
195
|
+
i = h - (off or 0)
|
|
196
|
+
if not 0 <= i < (size or 0) or i in seen:
|
|
197
|
+
bad += 1
|
|
198
|
+
if bad > 5:
|
|
199
|
+
continue
|
|
200
|
+
pm = cg.symbolic_param(r)
|
|
201
|
+
mo.errors.append(f"{r.path}: {pm.name if pm else 'handle'}={h} does not fit the static code's "
|
|
202
|
+
f"{t['name']}[{t.get('size')}] indexed by {des if isinstance(des, str) else '{sym}'} "
|
|
203
|
+
f"({'collides with ' + seen[i] if i in seen else f'index {i} outside 0..{(size or 0) - 1}'}); "
|
|
204
|
+
f"the {t['rows']} handles must be contiguous in the order the static code expects")
|
|
205
|
+
continue
|
|
206
|
+
seen[i] = r.name
|
|
207
|
+
if bad > 5:
|
|
208
|
+
mo.errors.append(f"... {bad - 5} more {t['rows']} handle(s) outside {t['name']}")
|
|
@@ -8,6 +8,10 @@ Keyed by def names, not XML structure:
|
|
|
8
8
|
"ComSignal:DoorLatch": {...}}}}}
|
|
9
9
|
A container key is "<DefName>:<ShortName>", shortened to "<ShortName>" when the two are equal.
|
|
10
10
|
Multi-valued parameters are arrays. References are "->/AUTOSAR/path".
|
|
11
|
+
Every container carries "@at" (file:line of the container element). Every value is followed by
|
|
12
|
+
"@at:<Param>" = file:line of its VALUE / VALUE-REF element, and integer-typed values by "@hex:<Param>"
|
|
13
|
+
(and "@dec:<Param>" when the ARXML text is not decimal), so citations and base conversions are never guessed.
|
|
14
|
+
Keys starting with "@" are metadata; ecuc_edit ignores them.
|
|
11
15
|
"""
|
|
12
16
|
from __future__ import annotations
|
|
13
17
|
|
|
@@ -16,7 +20,7 @@ from typing import Optional
|
|
|
16
20
|
from . import defs as D
|
|
17
21
|
from .model import Elem, Project
|
|
18
22
|
from .validate import _resolve_def
|
|
19
|
-
from .values import to_json
|
|
23
|
+
from .values import int_forms, to_json
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
def key_of(e: Elem) -> str:
|
|
@@ -24,27 +28,55 @@ def key_of(e: Elem) -> str:
|
|
|
24
28
|
return e.name if dn == e.name else f"{dn}:{e.name}"
|
|
25
29
|
|
|
26
30
|
|
|
31
|
+
def value_at(project: Project, e: Elem, pv) -> str:
|
|
32
|
+
"""file:line of a parameter's VALUE / VALUE-REF element (not its container)."""
|
|
33
|
+
n = pv.value_node if pv.value_node is not None else pv.node
|
|
34
|
+
return f"{project.rel(e.file)}:{n.line}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def value_info(project: Project, e: Elem, pv) -> dict:
|
|
38
|
+
"""One configured value with its exact location and, for integers, both bases."""
|
|
39
|
+
pd = _resolve_def(project.defs, pv.def_ref)
|
|
40
|
+
numerical = pv.tag == "ECUC-NUMERICAL-PARAM-VALUE"
|
|
41
|
+
out = {"value": to_json(pd, pv.value, pv.is_ref, numerical), "at": value_at(project, e, pv)}
|
|
42
|
+
nums = int_forms(pd, pv.value, pv.is_ref, numerical, pv.def_dest)
|
|
43
|
+
if nums:
|
|
44
|
+
out.update(nums)
|
|
45
|
+
return out
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _put(out: dict, name: str, items: list, multi: bool):
|
|
49
|
+
one = lambda xs: xs if multi else xs[0] # noqa: E731
|
|
50
|
+
out[name] = one([x["value"] for x in items])
|
|
51
|
+
out[f"@at:{name}"] = one([x["at"] for x in items])
|
|
52
|
+
if any("hex" in x for x in items):
|
|
53
|
+
out[f"@hex:{name}"] = one([x.get("hex") for x in items])
|
|
54
|
+
if any("dec" in x and x["value"] != x["dec"] for x in items):
|
|
55
|
+
out[f"@dec:{name}"] = one([x.get("dec") for x in items])
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def annotated_values(project: Project, group: list) -> dict:
|
|
59
|
+
"""{param: value, "@at:param": file:line, "@hex:param": ...} for the params of one element (all its files)."""
|
|
60
|
+
items: dict = {}
|
|
61
|
+
multi: dict = {}
|
|
62
|
+
for e in group:
|
|
63
|
+
for pv in e.params:
|
|
64
|
+
pd = _resolve_def(project.defs, pv.def_ref)
|
|
65
|
+
items.setdefault(pv.name, []).append(value_info(project, e, pv))
|
|
66
|
+
multi[pv.name] = multi.get(pv.name, False) or (pd is not None and (pd.upper is None or pd.upper > 1))
|
|
67
|
+
out: dict = {}
|
|
68
|
+
for name, xs in items.items():
|
|
69
|
+
_put(out, name, xs, multi[name] or len(xs) > 1)
|
|
70
|
+
return out
|
|
71
|
+
|
|
72
|
+
|
|
27
73
|
def render(project: Project, group: list, depth: int, top: bool = True) -> dict:
|
|
28
|
-
defs = project.defs
|
|
29
74
|
e0 = group[0]
|
|
30
75
|
out: dict = {"@path": e0.path}
|
|
31
76
|
if top:
|
|
32
77
|
out["@def"] = e0.def_ref
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
for e in group:
|
|
36
|
-
for pv in e.params:
|
|
37
|
-
pd = _resolve_def(defs, pv.def_ref)
|
|
38
|
-
v = to_json(pd, pv.value, pv.is_ref, pv.tag == "ECUC-NUMERICAL-PARAM-VALUE")
|
|
39
|
-
name = pv.name
|
|
40
|
-
multi = pd is not None and (pd.upper is None or pd.upper > 1)
|
|
41
|
-
if name in vals:
|
|
42
|
-
if not isinstance(vals[name], list) or not multi:
|
|
43
|
-
vals[name] = vals[name] if isinstance(vals[name], list) else [vals[name]]
|
|
44
|
-
vals[name].append(v)
|
|
45
|
-
else:
|
|
46
|
-
vals[name] = [v] if multi else v
|
|
47
|
-
out.update(vals)
|
|
78
|
+
out["@at"] = ", ".join(f"{project.rel(e.file)}:{e.line}" for e in group)
|
|
79
|
+
out.update(annotated_values(project, group))
|
|
48
80
|
seen = set()
|
|
49
81
|
for e in group:
|
|
50
82
|
for c in e.children:
|
|
@@ -61,6 +93,24 @@ def render(project: Project, group: list, depth: int, top: bool = True) -> dict:
|
|
|
61
93
|
return out
|
|
62
94
|
|
|
63
95
|
|
|
96
|
+
def value_sites(project: Project, paths: list) -> dict:
|
|
97
|
+
"""AUTOSAR path -> {"at": container file:line, "values": {param: {value, at, dec?, hex?} | [..]}} from the
|
|
98
|
+
local ECUC, or None when the element is not in the local checkout."""
|
|
99
|
+
out: dict = {}
|
|
100
|
+
for p in paths:
|
|
101
|
+
g = project.find(p)
|
|
102
|
+
if not g:
|
|
103
|
+
out[p] = None
|
|
104
|
+
continue
|
|
105
|
+
vals: dict = {}
|
|
106
|
+
for e in g:
|
|
107
|
+
for pv in e.params:
|
|
108
|
+
vals.setdefault(pv.name, []).append(value_info(project, e, pv))
|
|
109
|
+
out[p] = {"at": ", ".join(f"{project.rel(e.file)}:{e.line}" for e in g),
|
|
110
|
+
"values": {k: (v[0] if len(v) == 1 else v) for k, v in vals.items()}}
|
|
111
|
+
return out
|
|
112
|
+
|
|
113
|
+
|
|
64
114
|
def read(project: Project, path: Optional[str], depth: int = 2, schema: bool = False) -> dict:
|
|
65
115
|
res: dict = {"defsLoaded": len(project.defs.modules)}
|
|
66
116
|
if project.warnings:
|
|
@@ -101,12 +151,15 @@ def read(project: Project, path: Optional[str], depth: int = 2, schema: bool = F
|
|
|
101
151
|
parent, _, last = path.rstrip("/").rpartition("/")
|
|
102
152
|
pg = project.find(parent)
|
|
103
153
|
if pg:
|
|
104
|
-
hits = [pv for e in pg for pv in e.params if pv.name == last]
|
|
154
|
+
hits = [(e, pv) for e in pg for pv in e.params if pv.name == last]
|
|
105
155
|
if hits:
|
|
106
|
-
pd = _resolve_def(project.defs, hits[0].def_ref)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
"
|
|
156
|
+
pd = _resolve_def(project.defs, hits[0][1].def_ref)
|
|
157
|
+
info = [value_info(project, e, pv) for e, pv in hits]
|
|
158
|
+
res["param"] = {"@path": path, "@def": hits[0][1].def_ref,
|
|
159
|
+
"@at": [x["at"] for x in info], "value": [x["value"] for x in info]}
|
|
160
|
+
if any("hex" in x for x in info):
|
|
161
|
+
res["param"]["dec"] = [x.get("dec") for x in info]
|
|
162
|
+
res["param"]["hex"] = [x.get("hex") for x in info]
|
|
110
163
|
if pd is not None:
|
|
111
164
|
res["schema"] = D.describe(pd, project.defs)
|
|
112
165
|
return res
|
|
@@ -73,7 +73,7 @@ def _refs(project: Project, e, depth: int) -> set:
|
|
|
73
73
|
|
|
74
74
|
def find(project: Project, module: Optional[str] = None, container_def: Optional[str] = None, where=None,
|
|
75
75
|
ref_to: Optional[str] = None, referenced_by: Optional[str] = None, limit: int = 50, depth: int = 2) -> dict:
|
|
76
|
-
from .ecucjson import resolve
|
|
76
|
+
from .ecucjson import annotated_values, resolve
|
|
77
77
|
conds = _where(where)
|
|
78
78
|
targets = None
|
|
79
79
|
if ref_to:
|
|
@@ -116,9 +116,11 @@ def find(project: Project, module: Optional[str] = None, container_def: Optional
|
|
|
116
116
|
continue
|
|
117
117
|
total += 1
|
|
118
118
|
if len(hits) < limit:
|
|
119
|
-
hits.append({"path": path, "def": dname, "at": f"{project.rel(
|
|
120
|
-
"values":
|
|
121
|
-
return {"count": total, "returned": len(hits), "matches": hits
|
|
119
|
+
hits.append({"path": path, "def": dname, "at": ", ".join(f"{project.rel(x.file)}:{x.line}" for x in group),
|
|
120
|
+
"values": annotated_values(project, group)})
|
|
121
|
+
return {"count": total, "returned": len(hits), "matches": hits,
|
|
122
|
+
"legend": "at = container element; values[\"@at:P\"] = file:line of P's VALUE element; "
|
|
123
|
+
"@hex:/@dec: = the same integer in the other base"}
|
|
122
124
|
|
|
123
125
|
|
|
124
126
|
ELEMENTS = {
|