@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.
@@ -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
- proj_out = resolve_out(root, "project", opts)
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>] follow R to target T, then the container that references T from another
18
- module (or from <Module>): how a module finds its neighbour's PDU handle
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']} literal {raw} not in the binding's enum map")
193
- return (str(m[raw.strip()]), raw.strip())
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 macro(self, expr: str) -> str:
197
- m = re.match(r"^(count|max):([\w]+)(?:/(\w+))?\s*([+-]\s*\d+)?$", expr.strip())
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: