@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
|
@@ -103,25 +103,44 @@ def generate(project: Project, modules: Optional[list] = None, out_dir: Optional
|
|
|
103
103
|
wb = H.apply(project, pl["assign"], write=write_back)
|
|
104
104
|
if pl["assign"]:
|
|
105
105
|
cg = CgModel(project, opts.get("mip"))
|
|
106
|
+
# handle layout the static code indexes by (binding "handleLayout"), only for withAuto handle params
|
|
107
|
+
proj_out = resolve_out(root, "project", opts)
|
|
108
|
+
headers, sources = _scan_c(root, [out, proj_out, os.path.join(root, ".hydron")])
|
|
109
|
+
bindings = B.load_all(root)
|
|
110
|
+
lay = H.layout(cg, want, {m: B.select(bindings, m, headers, binding) for m in want})
|
|
111
|
+
if lay["errors"]:
|
|
112
|
+
return {"ok": False, "error": "handle IDs do not fit the static code's table layout", "handleErrors": lay["errors"]}
|
|
113
|
+
if lay["assign"]:
|
|
114
|
+
wb2 = H.apply(project, lay["assign"], write=write_back)
|
|
115
|
+
wb = {"written": wb["written"] or wb2["written"], "changes": wb["changes"] + wb2["changes"]}
|
|
116
|
+
pl["assign"] += lay["assign"]
|
|
117
|
+
cg = CgModel(project, opts.get("mip"))
|
|
118
|
+
pl["notes"] += lay["notes"]
|
|
106
119
|
timings["handles_ms"] = int((time.time() - t1) * 1000)
|
|
107
120
|
|
|
108
121
|
# 2. emit
|
|
109
122
|
t2 = time.time()
|
|
110
|
-
|
|
111
|
-
headers, sources = _scan_c(root, [out, proj_out, os.path.join(root, ".hydron")])
|
|
112
|
-
ctx = Ctx(cg=cg, headers=headers, bindings=B.load_all(root), binding_name=binding, release=cg.release(),
|
|
123
|
+
ctx = Ctx(cg=cg, headers=headers, bindings=bindings, binding_name=binding, release=cg.release(),
|
|
113
124
|
sources=sources)
|
|
114
125
|
ctx.options = opts
|
|
115
126
|
ctx.generating = set(want)
|
|
116
127
|
ctx.available = set(headers) | (set(os.listdir(proj_out)) if os.path.isdir(proj_out) else set())
|
|
128
|
+
ctx.out_headers = {f: os.path.join(proj_out, f) for f in sorted(os.listdir(proj_out))
|
|
129
|
+
if f.endswith(".h")} if os.path.isdir(proj_out) else {}
|
|
117
130
|
files: dict = {}
|
|
118
131
|
report: dict = {}
|
|
119
132
|
memmaps = []
|
|
120
133
|
notes = list(pl["notes"])
|
|
134
|
+
errors: dict = {}
|
|
135
|
+
outputs = []
|
|
121
136
|
for name in want:
|
|
122
137
|
mc = cg.module(name)
|
|
123
138
|
gen = generator_for(name)(ctx, mc)
|
|
124
139
|
mo = gen.run()
|
|
140
|
+
if mo.errors:
|
|
141
|
+
errors[name] = mo.errors
|
|
142
|
+
continue
|
|
143
|
+
outputs.append(mo)
|
|
125
144
|
rendered = mo.render()
|
|
126
145
|
n, text = S.schm_header(ctx, mo)
|
|
127
146
|
rendered[n] = text
|
|
@@ -146,6 +165,11 @@ def generate(project: Project, modules: Optional[list] = None, out_dir: Optional
|
|
|
146
165
|
notes.append(f"project MemMap.h ({P_rel(root, headers['MemMap.h'])}) kept; generated code includes "
|
|
147
166
|
"<Mip>_MemMap.h (SWS_MemMap_00032)")
|
|
148
167
|
timings["emit_ms"] = int((time.time() - t2) * 1000)
|
|
168
|
+
if errors:
|
|
169
|
+
return {"ok": False, "error": "the configuration cannot be represented by the static code's binding; "
|
|
170
|
+
"nothing was written", "moduleErrors": errors, "notes": notes,
|
|
171
|
+
"handles": {"assigned": pl["assign"], "writtenBack": wb["written"], "changes": wb["changes"]}}
|
|
172
|
+
notes += dangling_symbols(ctx, outputs)
|
|
149
173
|
|
|
150
174
|
# 3. write
|
|
151
175
|
os.makedirs(out, exist_ok=True)
|
|
@@ -166,6 +190,30 @@ def generate(project: Project, modules: Optional[list] = None, out_dir: Optional
|
|
|
166
190
|
"notes": notes, "timings": timings, "release": ".".join(map(str, ctx.release))}
|
|
167
191
|
|
|
168
192
|
|
|
193
|
+
def dangling_symbols(ctx, outputs: list) -> list:
|
|
194
|
+
"""Symbolic names (<Mip>Conf_<Def>_<Name>) that the project's C uses but the configuration no longer has.
|
|
195
|
+
The old generated header may still define them, so the stale use only shows once this output replaces it."""
|
|
196
|
+
defined = {s for mo in outputs for s, _, _ in mo.symbols}
|
|
197
|
+
rx = re.compile(r"\b(" + "|".join(sorted(re.escape(mo.mip) for mo in outputs)) + r")Conf_\w+") if outputs else None
|
|
198
|
+
out = []
|
|
199
|
+
if rx is None:
|
|
200
|
+
return out
|
|
201
|
+
seen: dict = {}
|
|
202
|
+
for f in ctx.sources:
|
|
203
|
+
try:
|
|
204
|
+
with open(f, errors="replace") as fh:
|
|
205
|
+
text = fh.read()
|
|
206
|
+
except OSError:
|
|
207
|
+
continue
|
|
208
|
+
for m in rx.finditer(text):
|
|
209
|
+
if m.group(0) not in defined:
|
|
210
|
+
seen.setdefault(m.group(0), ctx.cg.p.rel(f))
|
|
211
|
+
for sym in sorted(seen):
|
|
212
|
+
out.append(f"{seen[sym]} uses {sym}, which the configuration does not define (container deleted or "
|
|
213
|
+
"renamed?); the build will fail on it")
|
|
214
|
+
return out
|
|
215
|
+
|
|
216
|
+
|
|
169
217
|
def P_rel(root: str, f: str) -> str:
|
|
170
218
|
r = os.path.relpath(f, root)
|
|
171
219
|
return f if r.startswith("..") else r
|
|
@@ -14,12 +14,24 @@ Path language (a list of steps, evaluated left to right from the row container):
|
|
|
14
14
|
sub:<Def> first sub-container of def <Def>
|
|
15
15
|
ref:<R> follow ECUC reference R to its target container
|
|
16
16
|
back:<Def>/<R> the container of def <Def> (any module) whose reference R points here
|
|
17
|
-
peer:<R>[@<Module>]
|
|
18
|
-
module (or from <Module>): how a module
|
|
19
|
-
(TPS_ECUC 3.4.1.3, via EcucPduCollection/Pdu)
|
|
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)
|
|
20
22
|
sym TPS_ECUC_02108 symbol of the current container (terminal)
|
|
21
23
|
module | name | index the current container's module name / SHORT-NAME / table index (terminal)
|
|
22
|
-
Macro values: count:<Def> | max:<Def>/<Param>, optionally followed by +N or -N.
|
|
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>}).
|
|
23
35
|
"""
|
|
24
36
|
from __future__ import annotations
|
|
25
37
|
|
|
@@ -91,6 +103,7 @@ class Eval:
|
|
|
91
103
|
self.mc = mc
|
|
92
104
|
self.rank = 1
|
|
93
105
|
self.used_mips: set = set()
|
|
106
|
+
self.ext: dict = {} # call:<name> resolvers contributed by the module generator
|
|
94
107
|
|
|
95
108
|
def steps(self, start: Container, steps) -> tuple:
|
|
96
109
|
"""-> ("container", Container) | ("value", (raw, def, owner)) | ("token", str) | (None, None)"""
|
|
@@ -124,12 +137,14 @@ class Eval:
|
|
|
124
137
|
self._touch(cur, self.cg.param_def(cur, rname))
|
|
125
138
|
elif op == "peer":
|
|
126
139
|
rname, _, want = arg.partition("@")
|
|
140
|
+
want, _, wdef = want.partition("/")
|
|
127
141
|
self._touch(cur, self.cg.param_def(cur, rname))
|
|
128
142
|
t = self.cg.target(cur.value(rname))
|
|
129
143
|
if t is None:
|
|
130
144
|
return (None, None)
|
|
131
145
|
hits = [c for c in self.cg.referrers(t) if c is not cur and
|
|
132
|
-
(c.module.name == want if want else c.module is not cur.module)
|
|
146
|
+
(c.module.name == want if want else c.module is not cur.module) and
|
|
147
|
+
(not wdef or c.def_name == wdef)]
|
|
133
148
|
hits.sort(key=lambda c: (c.module.name, self.cg.by_path_index(c)))
|
|
134
149
|
cur = hits[0] if hits else None
|
|
135
150
|
elif op == "sym":
|
|
@@ -137,6 +152,12 @@ class Eval:
|
|
|
137
152
|
if s and self.ctx is not None:
|
|
138
153
|
s = self.ctx.xsym(cur, self.mc, self.used_mips)
|
|
139
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)
|
|
140
161
|
elif op == "module":
|
|
141
162
|
return ("token", cur.module.mip)
|
|
142
163
|
elif op == "name":
|
|
@@ -149,6 +170,32 @@ class Eval:
|
|
|
149
170
|
return (None, None)
|
|
150
171
|
return ("container", cur)
|
|
151
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
|
+
|
|
152
199
|
def _touch(self, c: Container, d):
|
|
153
200
|
if c.module is self.mc:
|
|
154
201
|
self.rank = max(self.rank, self.cg.eff_class(self.mc, d))
|
|
@@ -161,6 +208,8 @@ class Eval:
|
|
|
161
208
|
ok = kind == "value" and (v[0] or "").strip().lower() in ("true", "1")
|
|
162
209
|
if not ok:
|
|
163
210
|
return (spec.get("default", "0u"), "")
|
|
211
|
+
if "agg" in spec:
|
|
212
|
+
return self._agg(row, spec)
|
|
164
213
|
paths = spec["path"]
|
|
165
214
|
alts = paths if isinstance(paths, list) and paths and isinstance(paths[0], list) else [paths]
|
|
166
215
|
kind, v = None, None
|
|
@@ -182,23 +231,69 @@ class Eval:
|
|
|
182
231
|
s = self.ctx.xsym(v, self.mc, self.used_mips)
|
|
183
232
|
return (s, "")
|
|
184
233
|
if kind == "token":
|
|
185
|
-
if fmt:
|
|
186
|
-
return (fmt.replace("{MIP}", v.upper()).replace("{Mip}", v), "")
|
|
234
|
+
if fmt and fmt not in ("hex", "int"):
|
|
235
|
+
return (fmt.replace("{MIP}", v.upper()).replace("{Mip}", v).replace("{}", v), "")
|
|
187
236
|
return (v, "")
|
|
188
237
|
raw, d, owner = v
|
|
189
238
|
if "enum" in spec and raw is not None:
|
|
190
239
|
m = spec["enum"]
|
|
191
240
|
if raw.strip() not in m:
|
|
192
|
-
raise BindingError(f"{row.path}: {spec['path']}
|
|
193
|
-
|
|
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), "")
|
|
194
253
|
return W.table_value(d, raw, hexa=fmt == "hex")
|
|
195
254
|
|
|
196
|
-
def
|
|
197
|
-
|
|
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())
|
|
198
293
|
if not m:
|
|
199
|
-
raise BindingError(f"bad macro expression {expr!r} (count:<Def> | max:<Def>/<Param> [+N])")
|
|
294
|
+
raise BindingError(f"bad macro expression {expr!r} (count:<Def> | count:@<Set> | max:<Def>/<Param> [+N])")
|
|
200
295
|
op, dname, pname, off = m.groups()
|
|
201
|
-
insts = self.cg.instances(self.mc, dname)
|
|
296
|
+
insts = self.rows(dname, sets or {}) if dname.startswith("@") else self.cg.instances(self.mc, dname)
|
|
202
297
|
if op == "count":
|
|
203
298
|
v = len(insts)
|
|
204
299
|
else:
|