Review and change
A model kept as text goes through the same process as code: branches, pull requests, continuous integration. ArcLang adds the tools that make a model review about meaning instead of lines.
The examples on this page use this model.
model BrakeAssist {
requirements safety {
req "REQ-001" "Obstacle detection" { description: "Detect an obstacle at 150 m or more" }
req "REQ-002" "Braking reaction" { description: "Command braking within 100 ms" }
}
architecture logical {
// Owned by the perception team
component "Radar" {
id: "LC-RADAR"
function "Detect obstacle"
port out objects
}
component "Brake controller" {
id: "LC-BRAKE"
function "Command braking"
port in objects
}
component_exchange "Detected objects" {
from_port: "LC-RADAR.objects"
to_port: "LC-BRAKE.objects"
}
}
}
trace "LC-RADAR" satisfies "REQ-001"
trace "LC-BRAKE" satisfies "REQ-002"Semantic diff
arclang diff compares two versions of a model by the identity of their elements, not by their lines. Moving a block or reformatting a file is no change. Renaming an element that keeps its id is a modification, not a removal and an addition.
In the second version below, the two components have swapped places, the brake controller is renamed and gains a function, a requirement is reworded, and a trace is added.
model BrakeAssist {
requirements safety {
req "REQ-001" "Obstacle detection" { description: "Detect an obstacle at 200 m or more" }
req "REQ-002" "Braking reaction" { description: "Command braking within 100 ms" }
}
architecture logical {
component "Brake control unit" {
id: "LC-BRAKE"
function "Command braking"
function "Log braking event"
port in objects
}
// Owned by the perception team
component "Radar" {
id: "LC-RADAR"
function "Detect obstacle"
port out objects
}
component_exchange "Detected objects" {
from_port: "LC-RADAR.objects"
to_port: "LC-BRAKE.objects"
}
}
}
trace "LC-RADAR" satisfies "REQ-001"
trace "LC-BRAKE" satisfies "REQ-002"
trace "LC-RADAR" satisfies "REQ-002"$ arclang diff v1.arc v2.arc
Semantic diff (by stable identity): v1.arc -> v2.arc
+ added LogicalFunction 'Log braking event' [Log braking event]
~ modified LogicalComponent 'Brake control unit' [LC-BRAKE]
name: "Brake controller" -> "Brake control unit"
~ modified Requirement 'Obstacle detection' [REQ-001]
description: "Detect an obstacle at 150 m or more" -> "Detect an obstacle at 200 m or more"
+ trace LC-RADAR satisfies REQ-002
Total: 1 added, 0 removed, 2 modified, 1 trace(s) added, 0 trace(s) removed, 0 trace(s) modified, 0 relationship change(s)
Error: semantic differences foundThe exit code is 1 when the models differ, as with diff, so a pipeline can require an empty diff for a pure refactoring. --json gives the same report for scripts, with the UUID of each element.
The diff compares everything the compiled model holds, not a list of chosen fields: every element with its attributes, and every relationship. Change a latency, the severity of a hazard and the rationale of a trace, and each is reported with its old and new value:
model Braking {
requirements safety {
req "REQ-001" "Detection range" { description: "Detect an obstacle at 150 m" }
}
architecture logical {
component "Brake controller" {
id: "LC-BRAKE"
function "Command braking" { id: "LF-CMD" latency: 40 ms }
}
}
}
safety_analysis {
hazard "Late braking" {
severity: "S2"
exposure: "E4"
controllability: "C3"
asil: "ASIL-C"
mitigated_by: ["REQ-001"]
}
}
trace "LC-BRAKE" satisfies "REQ-001" { rationale: "Commands the actuator" }$ arclang diff v3.arc v4.arc
Semantic diff (by stable identity): v3.arc -> v4.arc
~ modified LogicalFunction 'Command braking' [LF-CMD]
latency: "40 ms" -> "45 ms"
~ modified Hazard 'Late braking' [Late braking]
severity: "S2" -> "S3"
~ trace LC-BRAKE satisfies REQ-001
rationale: "Commands the actuator" -> "Commands the actuator within budget"
Total: 0 added, 0 removed, 2 modified, 0 trace(s) added, 0 trace(s) removed, 1 trace(s) modified, 0 relationship change(s)
Error: semantic differences foundRelationships other than traces (exchanges, deployments, verifications by a test case) are reported the same way, as relation lines. Versions before 4.0.1 missed attribute values, test cases, hazards and rationales; if you gate a pipeline on an empty diff, use 4.0.1 or later.
Since 4.6.0 diff also compares a file with a git revision, or two revisions, and adds to the report what the change does to the gate, the budgets and the coverage. That, history, merging and baselines are on the Semantic git page.
Impact analysis
arclang impact answers "what does this change reach, and why?" from the compiled model, never from names or guesses. It is deterministic: the same model and the same seeds give the same report, byte for byte.
$ arclang impact v2.arc REQ-001
Impact analysis for 'REQ-001':
6 impacted: 0 blockers, 3 majors, 3 minors · gate ISO26262 FAIL
Distance 1 (directly affected):
[minor] LogicalComponent 'Radar' [LC-RADAR] (LA, architecture) — REQ-001 --satisfied by--> LC-RADAR
Distance 2 (transitively affected):
[minor] LogicalFunction 'Detect obstacle' [Detect obstacle] (LA, architecture) — REQ-001 --satisfied by--> LC-RADAR --performs--> Detect obstacle
[major] ComponentExchange 'Detected objects' [Detected objects] (LA, interface) — REQ-001 --satisfied by--> LC-RADAR --sends--> Detected objects
[major] ComponentPort 'objects' [LC-RADAR.objects] (LA, interface) — REQ-001 --satisfied by--> LC-RADAR --has port--> LC-RADAR.objects
Distance 3 (transitively affected):
[minor] LogicalComponent 'Brake control unit' [LC-BRAKE] (LA, architecture) — REQ-001 --satisfied by--> LC-RADAR --sends--> Detected objects --received by--> LC-BRAKE
[major] ComponentPort 'objects' [LC-BRAKE.objects] (LA, interface) — REQ-001 --satisfied by--> LC-RADAR --sends--> Detected objects --received by--> LC-BRAKE.objects
Total affected elements: 6The examples that follow use a small model with a timing budget. Two functions make up a chain whose budget is 100 ms:
model Braking {
requirements safety {
req "REQ-001" "Braking reaction" { id: "REQ-001" description: "Brake within 100 ms of detection" }
}
system_analysis "Braking" {
function "Detect obstacle" { id: "SF-DET" latency: 40 ms }
function "Command braking" { id: "SF-BRK" latency: 50 ms }
functional_chain "Detection to braking" {
id: "FC-BRAKE"
involves: ["SF-DET", "SF-BRK"]
latency_budget: 100 ms
}
}
architecture logical {
component "Brake controller" { id: "LC-BRAKE" function "Command braking" }
}
}
trace "LC-BRAKE" satisfies "REQ-001"
test_case "TC-001" { verifies: ["REQ-001"] method: "test" }$ arclang impact budget.arc SF-DET
Impact analysis for 'SF-DET':
1 impacted: 0 blockers, 1 major, 0 minors · gate ISO26262 PASS
Distance 1 (directly affected):
[major] FunctionalChain 'Detection to braking' [FC-BRAKE] (SA, budget) — SF-DET --involved in--> FC-BRAKE
Total affected elements: 1Each impacted element carries its distance, the shortest path that explains it (SF-DET --involved in--> FC-BRAKE), a category and a severity. Fifteen kinds of link are followed, each typed and oriented: realization, allocation, deployment, exchange, interface, chain, satisfies, verifies, safety, containment, typing, constraint, view, behavior and reference. --kinds restricts them, --direction downstream|upstream|both and --depth N bound the walk, and -e/--element can be repeated.
| Severity | When |
|---|---|
| blocker | a gate blocker names it; a requirement, hazard or budget carrying an ASIL, DAL or SIL; in a what-if, a requirement newly unsatisfied or a constraint or latency budget newly broken |
| major | an interface (port, exchange, link); a test to redo; a requirement, hazard or budget to re-check; a type user; an integrity-level element |
| minor | a view, or any other element to review |
What if
--change takes an arclang edit request (or an array of them), and --delete <element> takes an element away together with the traces and list entries naming it. The edit is applied to a copy of the text, the copy is compiled, and the two models are compared: the gate before and after, the new and cleared blockers, constraints that flip, latency budgets (the sum of a chain against its bound), requirement coverage, verdicts made stale, elements added, removed or changed. Nothing is written. An edit that leaves a model that does not compile is refused with the compiler's reason.
$ arclang impact budget.arc --change '{"op":"set","element":"SF-DET","key":"latency","value":"80 ms"}'
Impact analysis for 'the proposed change (SF-DET)':
1 impacted: 1 blocker, 0 majors, 0 minors · gate ISO26262 PASS → FAIL
+ new blocker: functional_chain 'Detection to braking': sum of function latencies 130.0 ms exceeds the 100.0 ms budget
~ budget Detection to braking: 90 ms → 130 ms for 100 ms — EXCEEDED
Distance 1 (directly affected):
[blocker] FunctionalChain 'Detection to braking' [FC-BRAKE] (SA, budget) — SF-DET --involved in--> FC-BRAKE
Total affected elements: 1Since a revision
--since REV does the same between the model as committed at a revision (HEAD, main, a tag, a commit) and the file as it is now. This is the "impact of my changes" the VS Code panel shows.
$ arclang impact budget.arc --since HEAD
Impact analysis for 'the changes since HEAD':
1 impacted: 1 blocker, 0 majors, 0 minors · gate ISO26262 PASS → FAIL
+ new blocker: functional_chain 'Detection to braking': sum of function latencies 105.0 ms exceeds the 100.0 ms budget
~ budget Detection to braking: 90 ms → 105 ms for 100 ms — EXCEEDED
Distance 1 (directly affected):
[blocker] FunctionalChain 'Detection to braking' [FC-BRAKE] (SA, budget) — SF-BRK --involved in--> FC-BRAKE
Total affected elements: 1Reports
-f text|json|md|html|tsv|xlsx and -o file choose the output. md, html and xlsx are a change-request report: summary, impact (blockers first), gate, constraints, budgets, coverage, verdicts and changes. The Excel workbook has a frozen, filterable header and status colors (-o is required for it).
In VS Code
Impact analysis opens in an editor tab of its own, live: from the diagram (context menu, floating toolbar), the model tree, the text (a code action Analyze impact of ...), the Properties pane, or the palette (Impact analysis: analyze an element..., what if..., my changes). The tab shows the graph (rings by distance, colors by severity), the table with the clickable why path, filters (severity, layer, category, depth, direction, kinds of link), the what-if panel with Apply the change (one undoable edit) or Discard, the exports, Copy as table, and Ask AI when a provider is set. The impacted elements are outlined on the diagrams by severity, with a count on each layer tab.
Formatter
arclang fmt puts a model in canonical layout. It rewrites whitespace only: indentation, spacing inside a line, blank lines. Comments, the order of declarations, the spelling of literals and your line breaks are kept, because the formatter works on the token stream and never rebuilds text from the model. Each result is read again and must carry exactly the same tokens and comments as the input; otherwise nothing is written.
arclang fmt model.arc # print the formatted text
arclang fmt models/ --write # rewrite every model of a directory
arclang fmt models/ --check # exit 1 when a file is not formattedThe samples of this page are indented with two spaces; the canonical layout uses four, so the check reports the file:
$ arclang fmt v1.arc --check
v1.arc
Error: 1 file(s) not formatted; run `arclang fmt --write`Scripted edits
arclang set, unset and rename change one thing in a model file and leave every comment, the layout and all other declarations byte for byte. Without --write the result goes to standard output.
$ arclang set v1.arc LC-BRAKE owner '"Chassis team"' --write
$ arclang rename v1.arc LC-BRAKE "Brake control unit" --write$ grep -n -B1 -A5 'Brake control unit' v1.arc
13- }
14: component "Brake control unit" {
15- id: "LC-BRAKE"
16- function "Command braking"
17- port in objects
18- owner: "Chassis team"
19- }An edit is refused, and nothing is written, unless the two syntax trees differ by exactly what was asked, the edited model compiles, and the compiled element shows the requested value. A rename is refused when the element would not keep its identity. Here the component writes no id, so it is known by its name:
model Logging {
requirements safety {
req "REQ-001" "Logging" { description: "Log every braking event" }
}
architecture logical {
component "Logger" { function "Record event" }
}
}
trace "Logger" satisfies "REQ-001"$ arclang rename logger.arc Logger "Event recorder"
Error: Compilation error: logger.arc: Semantic error: 1 unresolved trace reference(s):
trace 'Logger satisfies REQ-001': unknown element 'Logger' (from) — declare it or fix the referenceLimits: one attribute at a time, no creation or deletion of elements, no edit of relationships. For those, the HTTP API accepts writes as git commits, and the playground's Properties view and palette write through the same editor.
Arcadia transitions
Capella's transition commands create, on the next level, what realizes the elements of this one. arclang transition does the same in the source: a system function for each operational activity no function realizes, a logical function (allocated to a component) for each system function, a behavior component on a node for each logical component, each with its realization. What is realized already is left alone, so a transition can be run again after the model grew.
operational_analysis "Driving" {
actor "Driver" { id: "OA-DRV" }
operational_activity "Brake" { id: "OA-BRK" performed_by: "Driver" }
operational_activity "Watch the road" { id: "OA-WTC" performed_by: "Driver" }
}
system_analysis SA {
function "Watch the road" { id: "SF-WTC" }
}
logical_architecture LA {
component "Controller" { id: "LC-CTL" }
}
physical_architecture PA {
node "ECU" { id: "PN-ECU" }
}
trace "SF-WTC" realizes "OA-WTC"--plan says, for every step, what is realized and what the step would create:
$ arclang transition levels.arc --plan
[
{
"step": "oa-to-sa",
"label": "Operational activities to system functions",
"candidates": [
{
"id": "OA-BRK",
"name": "Brake",
"kind": "operational_activity",
"realized_by": []
},
{
"id": "OA-WTC",
"name": "Watch the road",
"kind": "operational_activity",
"realized_by": [
"SF-WTC"
]
}
],
"blocked": null
},
{
"step": "sa-to-la",
"label": "System functions to logical functions",
"candidates": [
{
"id": "SF-WTC",
"name": "Watch the road",
"kind": "function",
"realized_by": []
}
],
"blocked": null
},
{
"step": "la-to-pa",
"label": "Logical components to physical components",
"candidates": [
{
"id": "LC-CTL",
"name": "Controller",
"kind": "component",
"realized_by": []
}
],
"blocked": null
}
]A step writes the declarations and prints the model; --write writes it back. --into names the component or node that receives the new elements; without it, one is created when none exists.
$ arclang transition levels.arc --step oa-to-sa --write
$ arclang transition levels.arc --step sa-to-la --into Controller --write
$ arclang transition levels.arc --step la-to-pa --into ECU --write$ grep -n 'SF-Brake\|LF-\|PC-\|realizes\|allocated_component' levels.arc
9: id: "SF-Brake"
16: id: "LF-Watch-the-road"
19: id: "LF-Brake"
27: id: "PC-Controller"
28: allocated_component: "LC-CTL"
32:trace "SF-WTC" realizes "OA-WTC"
33:trace "SF-Brake" realizes "OA-BRK" {}
34:trace "LF-Watch-the-road" realizes "SF-WTC" {}
35:trace "LF-Brake" realizes "SF-Brake" {}A realizing element takes the name of what it realizes, as in Capella. When the model already refers to that name, a second element carrying it would make the reference ambiguous: the new one is then named apart, and the command says why.
Several teams, several files
With import, each team owns a file and reviews its own changes; the compiler assembles the model and resolves traces across files. See Several files.
In a pull request
A typical pipeline for a model repository:
arclang fmt models/ --check
arclang check models/main.arc --lint
arclang gate models/main.arc --standard iso26262
arclang diagram models/main.arc -f html -o diagrams.html # attach to the reviewFor editors, arclang lsp --stdio is a language server that publishes the compiler's diagnostics at their exact source positions as you type.