@h2loop/autosar 1.0.1 → 1.1.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 +4 -0
- package/agents/autosar-integrator.md +34 -2
- package/package.json +4 -1
- package/plugins/index.js +53 -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 +328 -48
- package/python/ecuc_engine/cli.py +114 -5
- package/python/ecuc_engine/codegen/__init__.py +185 -0
- package/python/ecuc_engine/codegen/binding.py +210 -0
- package/python/ecuc_engine/codegen/bindings/sip_standin.json +111 -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 +395 -0
- package/python/ecuc_engine/codegen/handles.py +89 -0
- package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
- package/python/ecuc_engine/codegen/model.py +259 -0
- package/python/ecuc_engine/codegen/modules/__init__.py +7 -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 +133 -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/find.py +162 -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/sysmodel.py +152 -0
- package/python/ecuc_engine/validate.py +15 -2
- package/python/ecuc_engine/verify_rte.py +445 -0
- package/python/pyproject.toml +1 -1
- package/skills/autosar/SKILL.md +47 -1
- package/skills/autosar/references/codegen.md +30 -0
- package/skills/autosar/references/project-layout.md +7 -1
- package/skills/autosar/references/runnable-code.md +50 -0
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"""autosar_add_signal: add one Com signal end to end, consistently, or refuse.
|
|
2
|
+
|
|
3
|
+
Chain (all derived by following references from a sibling signal already in the target I-PDU, so it works
|
|
4
|
+
for any project that follows its own conventions):
|
|
5
|
+
ECUC ComSignal (parameter elements templated from the sibling: DEFINITION-REF/DEST/value-element types),
|
|
6
|
+
ComIPduSignalRef on the I-PDU, ComHandleId = lowest unused (existing handles never move)
|
|
7
|
+
System SYSTEM-SIGNAL, I-SIGNAL, I-SIGNAL-TO-I-PDU-MAPPING and every element in the sibling's chain that
|
|
8
|
+
carries the sibling's name (I-SIGNAL-TRIGGERING, I-SIGNAL-PORT, ...), plus the reference entries that
|
|
9
|
+
shared elements (PDU-TRIGGERING, ECU extract FIBEX-ELEMENTS, ...) hold for the sibling
|
|
10
|
+
DBC an SG_ line in the frame with the I-PDU's CAN ID, if a .dbc in the project has it
|
|
11
|
+
Layout is checked first (inside the I-PDU, no overlap); ecuc_validate runs after the write and any new
|
|
12
|
+
finding rolls everything back.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import re
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
from . import paths
|
|
21
|
+
from .arxml_edit import ArxmlEditError, ArxmlEditor
|
|
22
|
+
from .model import Project
|
|
23
|
+
from .rules import RuleContext
|
|
24
|
+
from .rules.com import _layout, bits
|
|
25
|
+
from .values import parse_int
|
|
26
|
+
from .xmltree import escape, line_indent
|
|
27
|
+
|
|
28
|
+
TYPES = {"BOOLEAN": 1, "UINT8": 8, "SINT8": 8, "UINT16": 16, "SINT16": 16, "UINT32": 32, "SINT32": 32,
|
|
29
|
+
"UINT64": 64, "SINT64": 64, "FLOAT32": 32, "FLOAT64": 64}
|
|
30
|
+
ORDER = {"LITTLE_ENDIAN": "MOST-SIGNIFICANT-BYTE-LAST", "BIG_ENDIAN": "MOST-SIGNIFICANT-BYTE-FIRST", "OPAQUE": "OPAQUE"}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AddSignalError(Exception):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _resolve_ipdu(project: Project, ipdu: str):
|
|
38
|
+
from .ecucjson import resolve
|
|
39
|
+
r = resolve(project, ipdu)
|
|
40
|
+
cands = [r] if isinstance(r, str) else r.get("didYouMean", [])
|
|
41
|
+
cands = [c for c in cands if project.find(c) and (project.find(c)[0].def_ref or "").endswith("/ComIPdu")]
|
|
42
|
+
if len(cands) != 1:
|
|
43
|
+
raise AddSignalError(f"I-PDU {ipdu!r} does not name exactly one ComIPdu ({cands or 'none found'})")
|
|
44
|
+
return project.find(cands[0])[0]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def plan_layout(ctx: RuleContext, ip, pos: Optional[int], size: int, endian: str):
|
|
48
|
+
pdu = ctx.target(ctx.value(ip, "ComPduIdRef"))
|
|
49
|
+
length = parse_int(ctx.text(pdu, "PduLength") or "") if pdu is not None else None
|
|
50
|
+
if length is None:
|
|
51
|
+
raise AddSignalError(f"{ip.name}: PduLength unknown (ComPduIdRef -> EcuC Pdu)")
|
|
52
|
+
used = set()
|
|
53
|
+
members = [ctx.target(pv) for pv in ctx.values(ip, "ComIPduSignalRef")]
|
|
54
|
+
for s in (m for m in members if m is not None):
|
|
55
|
+
for _label, bs, *_rest in _layout(ctx, s):
|
|
56
|
+
used |= set(bs)
|
|
57
|
+
limit = length * 8
|
|
58
|
+
if pos is None: # first free byte-aligned position
|
|
59
|
+
for start in range(0, limit, 8):
|
|
60
|
+
b = bits(start, size, endian)
|
|
61
|
+
if min(b) >= 0 and max(b) < limit and not used & set(b):
|
|
62
|
+
pos = start
|
|
63
|
+
break
|
|
64
|
+
if pos is None:
|
|
65
|
+
raise AddSignalError(f"{ip.name}: no free byte-aligned position for {size} bits (PduLength {length})")
|
|
66
|
+
b = bits(pos, size, endian)
|
|
67
|
+
if min(b) < 0 or max(b) >= limit:
|
|
68
|
+
raise AddSignalError(f"bits {min(b)}..{max(b)} lie outside {ip.name} (PduLength {length} bytes)")
|
|
69
|
+
if used & set(b):
|
|
70
|
+
raise AddSignalError(f"bits {sorted(used & set(b))[:8]} of the new signal overlap existing signals in {ip.name}")
|
|
71
|
+
return pos, members, pdu
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _container_text(data: bytes, node) -> bytes:
|
|
75
|
+
return data[node.start:node.end]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def add_signal(project: Project, name: str, ipdu: str, bit_size: Optional[int] = None, signal_type: str = "UINT8",
|
|
79
|
+
bit_position: Optional[int] = None, endianness: str = "LITTLE_ENDIAN", init=0,
|
|
80
|
+
transfer_property: Optional[str] = None, dry_run: bool = False) -> dict:
|
|
81
|
+
from .edit import Editor
|
|
82
|
+
from .validate import validate
|
|
83
|
+
signal_type = signal_type.upper()
|
|
84
|
+
endianness = endianness.upper()
|
|
85
|
+
size = bit_size or TYPES.get(signal_type)
|
|
86
|
+
if not size:
|
|
87
|
+
raise AddSignalError(f"bit_size needed for type {signal_type}")
|
|
88
|
+
if not re.fullmatch(r"[A-Za-z_]\w*", name):
|
|
89
|
+
raise AddSignalError(f"{name!r} is not a valid SHORT-NAME / C identifier")
|
|
90
|
+
ctx = RuleContext(project)
|
|
91
|
+
ip = _resolve_ipdu(project, ipdu)
|
|
92
|
+
if project.find(f"{ip.parent.path}/{name}"):
|
|
93
|
+
raise AddSignalError(f"{ip.parent.path}/{name} already exists")
|
|
94
|
+
pos, members, pdu = plan_layout(ctx, ip, bit_position, size, endianness)
|
|
95
|
+
sib = next((m for m in members if m is not None and (m.def_ref or "").endswith("/ComSignal")), None)
|
|
96
|
+
if sib is None:
|
|
97
|
+
raise AddSignalError(f"{ip.name} has no ComSignal to use as template")
|
|
98
|
+
before = {(d["rule"], d["path"], d["message"]) for d in validate(project, check_generated=False)["diagnostics"]
|
|
99
|
+
if d["severity"] in ("error", "warning")}
|
|
100
|
+
notes: list = []
|
|
101
|
+
|
|
102
|
+
# ---------- system extract ----------
|
|
103
|
+
sysi = project.system
|
|
104
|
+
ref_pv = next((pv for g in project.find(sib.path) for pv in g.params if pv.is_ref and sysi.get(pv.value or "")), None)
|
|
105
|
+
sys_writes, new_mapping = {}, None
|
|
106
|
+
if ref_pv is not None and sysi.by_path:
|
|
107
|
+
ed = ArxmlEditor(project)
|
|
108
|
+
try:
|
|
109
|
+
new_mapping = _clone_system_chain(project, ed, sysi.get(ref_pv.value), sib.name, name)
|
|
110
|
+
m_path = new_mapping
|
|
111
|
+
ed.op_set({"path": m_path, "element": "START-POSITION", "value": pos})
|
|
112
|
+
ed.op_set({"path": m_path, "element": "PACKING-BYTE-ORDER", "value": ORDER.get(endianness, endianness)})
|
|
113
|
+
if transfer_property:
|
|
114
|
+
ed.op_set({"path": m_path, "element": "TRANSFER-PROPERTY", "value": transfer_property.upper().replace("_", "-")})
|
|
115
|
+
isig = _renamed(sysi.get(ref_pv.value).one("I-SIGNAL-REF"), sib.name, name)
|
|
116
|
+
if isig:
|
|
117
|
+
ed.op_set({"path": isig, "element": "LENGTH", "value": size})
|
|
118
|
+
ft, node = ed.tree_for(isig)
|
|
119
|
+
if ed.own_child(node, "VALUE") is not None:
|
|
120
|
+
ed.op_set({"path": isig, "element": "VALUE", "value": init})
|
|
121
|
+
else:
|
|
122
|
+
ed.op_add({"parent_path": isig, "element": "INIT-VALUE",
|
|
123
|
+
"values": {"NUMERICAL-VALUE-SPECIFICATION": {"VALUE": init}}})
|
|
124
|
+
except ArxmlEditError as e:
|
|
125
|
+
raise AddSignalError(f"system extract: {e}") from None
|
|
126
|
+
probs = ed.check()
|
|
127
|
+
if probs:
|
|
128
|
+
raise AddSignalError("system extract edit would leave: " + "; ".join(probs[:5]))
|
|
129
|
+
sys_writes = {f: ft.data for f, ft in ed.files.items() if ft.data != ed.original[f]}
|
|
130
|
+
else:
|
|
131
|
+
notes.append("no system-extract link on the template signal; extract not changed")
|
|
132
|
+
|
|
133
|
+
# ---------- ECUC ----------
|
|
134
|
+
ced = Editor(project)
|
|
135
|
+
data = project.files[sib.file].data
|
|
136
|
+
unit = ced.unit(sib.file, sib.node)
|
|
137
|
+
ind = line_indent(data, sib.node.start) or b""
|
|
138
|
+
tmpl = {pv.name: pv for g in project.find(sib.path) for pv in g.params}
|
|
139
|
+
used_ids = {parse_int(pv.value or "") for c in ctx.containers(ctx.rel(sib.def_ref)) for pv in ctx.values(c, "ComHandleId")}
|
|
140
|
+
hid = next(i for i in range(0, 1 << 16) if i not in used_ids)
|
|
141
|
+
want = {"ComBitPosition": pos, "ComBitSize": size, "ComSignalType": signal_type, "ComSignalEndianness": endianness,
|
|
142
|
+
"ComSignalInitValue": init, "ComHandleId": hid}
|
|
143
|
+
if transfer_property:
|
|
144
|
+
want["ComTransferProperty"] = transfer_property.upper()
|
|
145
|
+
elif "ComTransferProperty" in tmpl:
|
|
146
|
+
want["ComTransferProperty"] = tmpl["ComTransferProperty"].value
|
|
147
|
+
missing = [k for k in ("ComBitPosition", "ComBitSize", "ComHandleId") if k not in tmpl]
|
|
148
|
+
if missing:
|
|
149
|
+
raise AddSignalError(f"template signal {sib.name} lacks {missing}; add the signal with ecuc_edit instead")
|
|
150
|
+
skipped = [k for k in want if k not in tmpl]
|
|
151
|
+
if skipped:
|
|
152
|
+
notes.append(f"not set (template {sib.name} has no such parameter element): {', '.join(skipped)} — "
|
|
153
|
+
"set them with ecuc_edit if the module requires them")
|
|
154
|
+
nl = project.files[sib.file].newline
|
|
155
|
+
params, refs = [], []
|
|
156
|
+
for k, pv in tmpl.items():
|
|
157
|
+
if pv.is_ref:
|
|
158
|
+
if new_mapping and sysi.get(pv.value or ""):
|
|
159
|
+
refs.append(_retarget(data, pv, new_mapping))
|
|
160
|
+
continue
|
|
161
|
+
if k in want:
|
|
162
|
+
params.append(_revalue(data, pv, want[k]))
|
|
163
|
+
head = re.sub(r'\s+UUID="[^"]*"', "", data[sib.node.start:sib.node.stag_end].decode())
|
|
164
|
+
dr = sib.node.child("DEFINITION-REF")
|
|
165
|
+
u = unit.decode()
|
|
166
|
+
lines = [(0, head), (1, f"<SHORT-NAME>{escape(name)}</SHORT-NAME>"), (1, data[dr.start:dr.end].decode()),
|
|
167
|
+
(1, "<PARAMETER-VALUES>")]
|
|
168
|
+
for p in params:
|
|
169
|
+
lines += [(2, p[0])] + [(2, ln) for ln in p[1:]]
|
|
170
|
+
lines.append((1, "</PARAMETER-VALUES>"))
|
|
171
|
+
if refs:
|
|
172
|
+
lines.append((1, "<REFERENCE-VALUES>"))
|
|
173
|
+
for r in refs:
|
|
174
|
+
lines += [(2, r[0])] + [(2, ln) for ln in r[1:]]
|
|
175
|
+
lines.append((1, "</REFERENCE-VALUES>"))
|
|
176
|
+
lines.append((0, f"</{sib.node.tag}>"))
|
|
177
|
+
block = nl.join((ind if i else b"") + (u * d + t).encode() for i, (d, t) in enumerate(lines))
|
|
178
|
+
last = max((c for c in sib.parent.children if (c.def_ref or "").endswith("/ComSignal") and c.file == sib.file),
|
|
179
|
+
key=lambda c: c.node.end)
|
|
180
|
+
ced.patch(sib.file, last.node.end, last.node.end, nl + ind + block)
|
|
181
|
+
ip = project.find(ip.path)[0]
|
|
182
|
+
sref = [pv for pv in ip.params if pv.name == "ComIPduSignalRef"]
|
|
183
|
+
tpl = sref[-1].node
|
|
184
|
+
d2 = project.files[ip.file].data
|
|
185
|
+
rtext = d2[tpl.start:tpl.end].decode().replace(f">{sref[-1].value}<", f">{ip.parent.path}/{name}<")
|
|
186
|
+
rind = line_indent(d2, tpl.start) or b""
|
|
187
|
+
ced.patch(ip.file, tpl.end, tpl.end, project.files[ip.file].newline + rind + rtext.encode())
|
|
188
|
+
ecuc_writes = {f: project.files[f].data for f in ced.original if project.files[f].data != ced.original[f]}
|
|
189
|
+
|
|
190
|
+
# ---------- DBC ----------
|
|
191
|
+
dbc_writes = _dbc_line(project, pdu, name, pos, size, endianness, signal_type, notes)
|
|
192
|
+
|
|
193
|
+
writes = {**sys_writes, **ecuc_writes, **dbc_writes}
|
|
194
|
+
originals = {f: open(f, "rb").read() for f in writes}
|
|
195
|
+
res = {"signal": f"{ip.parent.path}/{name}", "ipdu": ip.path, "ComBitPosition": pos, "ComBitSize": size,
|
|
196
|
+
"ComHandleId": hid, "systemMapping": new_mapping, "files": sorted(project.rel(f) for f in writes),
|
|
197
|
+
"notes": notes}
|
|
198
|
+
if dry_run:
|
|
199
|
+
return {**res, "ok": True, "written": False}
|
|
200
|
+
for f, b in writes.items():
|
|
201
|
+
with open(f, "wb") as fh:
|
|
202
|
+
fh.write(b)
|
|
203
|
+
after = validate(Project(project.root), check_generated=False)
|
|
204
|
+
new = [d for d in after["diagnostics"] if d["severity"] in ("error", "warning")
|
|
205
|
+
and (d["rule"], d["path"], d["message"]) not in before]
|
|
206
|
+
if new:
|
|
207
|
+
for f, b in originals.items():
|
|
208
|
+
with open(f, "wb") as fh:
|
|
209
|
+
fh.write(b)
|
|
210
|
+
return {**res, "ok": False, "written": False, "error": "validation found new problems; all changes rolled back",
|
|
211
|
+
"diagnostics": new[:20]}
|
|
212
|
+
return {**res, "ok": True, "written": True, "validation": after["summary"],
|
|
213
|
+
"next": "regenerate (autosar_generate), use the generated symbol "
|
|
214
|
+
f"ComConf_ComSignal_{name} in the C code, build (autosar_build)"}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _dedent(text: str) -> list:
|
|
218
|
+
lines = text.split("\n")
|
|
219
|
+
rest = [ln for ln in lines[1:] if ln.strip()]
|
|
220
|
+
cut = min((len(ln) - len(ln.lstrip()) for ln in rest), default=0)
|
|
221
|
+
first_ind = 0
|
|
222
|
+
if rest:
|
|
223
|
+
# the first line has no leading whitespace (it starts at the element); later lines are deeper by one unit
|
|
224
|
+
first_ind = cut - min((len(ln) - len(ln.lstrip()) for ln in rest[-1:]), default=cut)
|
|
225
|
+
return [lines[0].strip()] + [ln[cut + first_ind:] if len(ln) >= cut else ln.strip() for ln in lines[1:]]
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _revalue(data: bytes, pv, value) -> list:
|
|
229
|
+
vn = pv.value_node
|
|
230
|
+
if vn is None or vn.cstart is None:
|
|
231
|
+
raise AddSignalError(f"template parameter {pv.name} has no VALUE")
|
|
232
|
+
v = "true" if value is True else "false" if value is False else str(value)
|
|
233
|
+
text = (data[pv.node.start:vn.cstart] + escape(v).encode() + data[vn.cend:pv.node.end]).decode()
|
|
234
|
+
return _dedent(text)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _retarget(data: bytes, pv, target: str) -> list:
|
|
238
|
+
vn = pv.value_node
|
|
239
|
+
text = (data[pv.node.start:vn.cstart] + escape(target).encode() + data[vn.cend:pv.node.end]).decode()
|
|
240
|
+
return _dedent(text)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _renamed(path, old: str, new: str) -> Optional[str]:
|
|
244
|
+
if not path or not isinstance(path, str):
|
|
245
|
+
return None
|
|
246
|
+
head, _, last = path.rpartition("/")
|
|
247
|
+
return f"{head}/{last.replace(old, new)}" if old in last else None
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _clone_system_chain(project: Project, ed: ArxmlEditor, mapping, old: str, new: str) -> str:
|
|
251
|
+
"""Clone the sibling's extract chain under the new name; return the new mapping path."""
|
|
252
|
+
sysi = project.system
|
|
253
|
+
if mapping is None or mapping.tag != "I-SIGNAL-TO-I-PDU-MAPPING":
|
|
254
|
+
raise ArxmlEditError("template signal's system reference is not an I-SIGNAL-TO-I-PDU-MAPPING")
|
|
255
|
+
seed = {mapping.path}
|
|
256
|
+
isig = sysi.get(mapping.one("I-SIGNAL-REF"))
|
|
257
|
+
if isig is not None:
|
|
258
|
+
seed.add(isig.path)
|
|
259
|
+
ss = sysi.get(isig.one("SYSTEM-SIGNAL-REF"))
|
|
260
|
+
if ss is not None:
|
|
261
|
+
seed.add(ss.path)
|
|
262
|
+
chain = set(seed)
|
|
263
|
+
changed = True
|
|
264
|
+
while changed: # referencing idents named after the sibling, and named idents they reference
|
|
265
|
+
changed = False
|
|
266
|
+
for p in list(chain):
|
|
267
|
+
for r in sysi.refs_to(p):
|
|
268
|
+
if r.path not in chain and old in r.path.rsplit("/", 1)[-1]:
|
|
269
|
+
chain.add(r.path)
|
|
270
|
+
changed = True
|
|
271
|
+
i = sysi.get(p)
|
|
272
|
+
for t, vals in (i.values.items() if i else []):
|
|
273
|
+
for v in vals if (t.endswith("-REF") or t.endswith("-TREF")) else []:
|
|
274
|
+
vi = sysi.get(v)
|
|
275
|
+
if vi is not None and v not in chain and old in v.rsplit("/", 1)[-1]:
|
|
276
|
+
chain.add(v)
|
|
277
|
+
changed = True
|
|
278
|
+
mapping_new = None
|
|
279
|
+
subst = {p: _renamed(p, old, new) for p in chain}
|
|
280
|
+
if any(v is None for v in subst.values()):
|
|
281
|
+
subst = {k: v for k, v in subst.items() if v}
|
|
282
|
+
for p in sorted(chain, key=lambda x: (x != mapping.path, x)):
|
|
283
|
+
ft, node = ed.tree_for(p)
|
|
284
|
+
text = ft.data[node.start:node.end].decode()
|
|
285
|
+
for a, b in subst.items():
|
|
286
|
+
text = text.replace(f">{a}<", f">{b}<")
|
|
287
|
+
text = text.replace(f"<SHORT-NAME>{p.rsplit('/', 1)[-1]}</SHORT-NAME>",
|
|
288
|
+
f"<SHORT-NAME>{subst[p].rsplit('/', 1)[-1]}</SHORT-NAME>", 1)
|
|
289
|
+
if subst[p] in ft.by_path or project.identifiables.get(subst[p]):
|
|
290
|
+
raise ArxmlEditError(f"{subst[p]} already exists")
|
|
291
|
+
ind = line_indent(ft.data, node.start) or b""
|
|
292
|
+
ed.added_tags[subst[p]] = node.tag
|
|
293
|
+
ed.patch(ft, node.end, node.end, ft.nl + ind + text.encode())
|
|
294
|
+
if p == mapping.path:
|
|
295
|
+
mapping_new = subst[p]
|
|
296
|
+
# reference entries held by shared elements (not renamed): clone the wrapper next to the original
|
|
297
|
+
for p in chain:
|
|
298
|
+
for r in sysi.refs_to(p):
|
|
299
|
+
if r.path in chain:
|
|
300
|
+
continue
|
|
301
|
+
ft, node = ed.tree_for(r.path)
|
|
302
|
+
for leaf in node.iter():
|
|
303
|
+
if leaf.text.strip() != p or not (leaf.tag.endswith("-REF") or leaf.tag.endswith("-TREF")):
|
|
304
|
+
continue
|
|
305
|
+
wrap = leaf
|
|
306
|
+
while wrap.parent is not None and wrap.parent is not node and len(wrap.parent.children) == 1 \
|
|
307
|
+
and not wrap.parent.tag.endswith("S"):
|
|
308
|
+
wrap = wrap.parent
|
|
309
|
+
text = ft.data[wrap.start:wrap.end].decode().replace(f">{p}<", f">{subst[p]}<")
|
|
310
|
+
ind = line_indent(ft.data, wrap.start) or b""
|
|
311
|
+
ed.patch(ft, wrap.end, wrap.end, ft.nl + ind + text.encode())
|
|
312
|
+
ft, node = ed.tree_for(r.path)
|
|
313
|
+
break
|
|
314
|
+
return mapping_new
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _dbc_line(project: Project, pdu, name, pos, size, endian, typ, notes) -> dict:
|
|
318
|
+
from .audit import can_info
|
|
319
|
+
infos = can_info(project, pdu.path if pdu is not None else None)
|
|
320
|
+
dbcs = paths.walk(project.root, lambda n: n.lower().endswith(".dbc")).files
|
|
321
|
+
if not dbcs or not infos:
|
|
322
|
+
return {}
|
|
323
|
+
out = {}
|
|
324
|
+
for info in infos:
|
|
325
|
+
cid = info.get("canId")
|
|
326
|
+
if cid is None:
|
|
327
|
+
continue
|
|
328
|
+
for f in dbcs:
|
|
329
|
+
text = open(f, encoding="utf-8", errors="replace").read()
|
|
330
|
+
m = re.search(rf"^BO_ ({cid}|{cid | 0x80000000}) (\w+)\s*:.*$", text, re.M)
|
|
331
|
+
if not m:
|
|
332
|
+
continue
|
|
333
|
+
block_end = m.end()
|
|
334
|
+
sg = list(re.finditer(r"^\s*SG_ .*$", text[m.end():].split("\n\n", 1)[0], re.M))
|
|
335
|
+
recv = "Vector__XXX"
|
|
336
|
+
if sg:
|
|
337
|
+
block_end = m.end() + sg[-1].end()
|
|
338
|
+
rm = re.search(r'"[^"]*"\s*(.*)$', sg[-1].group(0))
|
|
339
|
+
recv = (rm.group(1).strip() if rm else "") or recv
|
|
340
|
+
start = pos
|
|
341
|
+
if endian == "BIG_ENDIAN":
|
|
342
|
+
from .rules.system import _motorola_msb
|
|
343
|
+
start = _motorola_msb(pos, size)
|
|
344
|
+
sign = "-" if typ.startswith("S") else "+"
|
|
345
|
+
mx = (1 << size) - 1 if sign == "+" else (1 << (size - 1)) - 1
|
|
346
|
+
line = f'\n SG_ {name} : {start}|{size}@{0 if endian == "BIG_ENDIAN" else 1}{sign} (1,0) [0|{mx}] "" {recv}'
|
|
347
|
+
if re.search(rf"^\s*SG_ {re.escape(name)}\b", text, re.M):
|
|
348
|
+
continue
|
|
349
|
+
out[f] = (text[:block_end] + line + text[block_end:]).encode()
|
|
350
|
+
notes.append(f"added SG_ {name} to BO_ {m.group(1)} {m.group(2)} in {project.rel(f)}")
|
|
351
|
+
return out
|