Regenerating code
archec gen writes the code again each time the model changes. This page is
about what happens to the code you wrote around it, how a line of generated
code leads back to the model, and how to see a regeneration before it happens.
Your code: USER CODE regions#
Generated C, Rust and Go mark the places where hand-written code goes:
/* @arche Brake.calibrate main.arche:42 fn */
void brake_calibrate(brake_t *self)
{
/* USER CODE BEGIN Brake.calibrate.body */
self->offset = adc_read(3);
/* USER CODE END Brake.calibrate.body */
}
(// USER CODE BEGIN … in Rust and Go.) When archec writes the file again, it
reads the file on disk first and puts back what is between the markers of each
id. The ids are model paths (Brake.calibrate.body, Brake.init,
car.brake_fw.start), not line numbers, so the code around a region can move,
grow or shrink and your code still lands in its place.
Where the regions are:
| Generator | Regions |
|---|---|
c, c/<part>.c |
<Part>.includes after the includes; <Part>.init at the end of <part>_init; <Part>.<fn>.body for a function whose body the model does not give (none, or Python the action language cannot say); <Part>.<port>.on-receive, <Part>.<port>.on-deadline likewise |
c, c/<part>_ports.c |
<Part>.ports.includes; <Part>.<port>.send.<MESSAGE>, the body of each send function |
c, c/<binary>/wiring.c |
<deployment>.<binary>.includes; <deployment>.<binary>.start, in <binary>_start once the parts are initialised and before the threads start |
ros2, ros2_nodes/<binary>/src/<exe>.cpp |
<deployment>.<binary>.<exe>.includes after the includes; <part>.node at the end of each node's constructor; <part>.members in its class; <deployment>.<binary>.<exe>.start in main, once the nodes are made and before the threads start |
ros2, ros2_nodes/<binary>/CMakeLists.txt |
<deployment>.<binary>.cmake before ament_package() |
ros2, ros2_nodes/<binary>/c/<part>.c |
the c target's: <Part>.includes, <Part>.init, … |
go, parts.go |
<Part>.init in New<Part>; <Part>.<fn>.body for a function the model gives no body |
embassy, src/app.rs |
<part path>.<fn>.body for a function with no body; each guard to implement; each input handler (<part path>.on_<port>) |
A body the model gives (in the action language, or Python it can translate) is generated, not a region: change it in the model.
When a region disappears. If the model no longer gives a region — the
function was removed or renamed — the code that was in it is not dropped: it
is added to <file>.orphans beside the file (for example
generated/c/brake.c.orphans), under the region's id, the date and the line
it was at. Move what you still need into a region of the new code and delete
the file; it is not compiled. A region that still holds only what archec put
there (its stub) is not an orphan.
A file of yours with regions. Some files are yours, written once and
never again (keep): c/<part>_ports.c, src/app.rs. When such a file has
regions and nothing outside them changed since archec wrote it, archec
writes it again around your regions, so a new port or a new function reaches
it. As soon as you edit outside the regions, the file is yours as before and
left as it is (archec gen says so; delete it for a fresh one). An older file
with no regions is never touched.
Your own generators get the same: in a template,
{{#region "{{ id }}.glue"}}
todo!()
{{/region}}
writes the markers around the default code, in the comment style of the
file's name (/* */, // for .rs and .go, # for scripts and Makefiles), or
the one a second argument names ("c", "line", "hash"). A script calls
ctx.region(id, defaultCode, style, indent). See generators.
Regions in another generator's markers. The style "user-code" writes
the markers // -- start of user code: <name> … // -- end of user code, so
that code written in files another generator wrote that way is carried into
the new ones:
// -- start of user code: additional members
int m_count = 0;
// -- end of user code
# -- in a .txt or CMake file, <!-- start of user code: name--> in XML (by
the file's name; "user-code-hash" and "user-code-xml" say it). The region's
name may hold spaces and the end marker names nothing; archec reads them on
regeneration like its own, by name: write {{#region "additional members" "user-code"}} in a template, or ctx.region("additional members", "", "user-code", "", "Core.h") in a script, with the name the other generator
gave it, and the code in the file on disk comes back there.
From the code to the model#
Each generated function or type that comes from a model element has a tag:
/* @arche Brake.calibrate main.arche:42 fn */
the element's path, its file (relative to the project) and line, and its kind
(component, fn, init, machine, on-receive, send, thread,
binary, part). From the tags, archec gen writes
generated/.archec/genmap.json: for each file, the line ranges of each
element (from its tag to where its braces close), and what it needs to carry
the regions (a hash of the stubs and of the file outside its regions).
archec where reads it to put a compiler's error back in the model:
$ make -C generated/c/brake_fw 2>&1 | archec where main.arche --from generated/c/brake_fw -
../brake.c:17:5: error: use of undeclared identifier 'adc_read'
↳ main.arche:42: Brake.calibrate (fn), in your USER CODE region Brake.calibrate.body
$ archec where main.arche "generated/c/brake.c:17:5: error: …"
main.arche:42 Brake.calibrate (fn) ← generated/c/brake.c:17, in your USER CODE region Brake.calibrate.body
It reads gcc, clang and Go (file:line:col:), rustc (--> file:line:col)
and MSVC (file(line)). A path is resolved from where the compiler ran
(--from, else here), then by its end against the map in generated/ (or
--gen <dir>).
In VS Code, a generated file with tags has a Go to model element lens above each tagged function (and the command of that name, which uses the cursor); on an element of a .arche file, Show generated code opens the code generated from it.
Before writing: --diff and the preview#
archec gen main.arche --target c --diff
prints a unified diff of what a write would change — with your regions
carried, exactly as the write would — writes nothing, and exits 1 when
something would change (0 when nothing would). --check does the same and
lists the files instead of the diff; both fit CI. A region that would become
an orphan is said too.
In VS Code, Arche: Preview Generated Changes… opens VS Code's diff editor for each file that would change, then asks whether to write them.
Limits#
- A tag carries the element's line, so a model edit above it changes the generated file (its tags), even when the code is the same.
- The ranges in the map follow braces: an element written without braces
(a declaration) ends at its
;; a brace inside a string with an escaped quote can throw the count off. embassy'ssrc/arche.rsand the Gomain.gohave no regions: they are the generator's alone. Files written once that predate regions (main.c,drivers.c,user.go) stay yours and are never rewritten.
Renaming an element#
A region is named after its element, so a rename changes its name:
BrakeEcu.on_cmd.body becomes BrakeEcu.on_command.body. Your code still
follows the element.
- With the rename tools, the code is carried exactly. These are the
editor's Rename (F2) and
archec refactor rename main.arche BrakeEcu.on_cmd on_command.- They record the rename in
arche.renames.jsonbeside the model. Commit it, as you commitarche.lock. - The next
archec genapplies each entry once per output folder. The genmap remembers the last one it applied. - A component renamed writes another file (
brake.c→brake_unit.c), and the code follows it there. The old file is said to be no longer generated: delete it.
- They record the rename in
- By hand, a rename is recognised when it is clear: exactly one region
with code is gone and exactly one new region takes its place, with the same
name but for one part and the same stub from the generator.
archec gensays so:Brake.hold.body → Brake.clamp.body (your code carried: looked like a rename). - Anything else stops
archec gen. That covers two functions of the same signature renamed at once, and an element removed.- Nothing is written, and the exit code is 1.
--checkand--diffstop the same way, so CI sees it. - The output names each stranded region and the new regions it may belong
to. Then either:
--move Brake.apply.body=Brake.press.body(repeatable) puts the code where it belongs;--orphans oklets it go to<file>.orphans, as before.
- In VS Code, Preview Generated Changes asks the same question for each stranded region.
archec checkwarns before that (gen-stranded) while you edit the model: it readsgenerated/beside it.
- Nothing is written, and the exit code is 1.