ArcLang language tour
An ArcLang model follows the Arcadia method: it describes a system at several levels, from the need to the delivered product, and links the levels with traces. This page walks through every level with samples that compile. The normative syntax is the grammar; the attributes each kind of element accepts are in the metamodel.
Structure of a file
A file holds an optional model block and any number of top-level blocks. Blocks can sit inside the model block or after it; both forms mean the same.
model Demo {
version: "1.0.0"
description: "A model is a list of blocks"
}
requirements stakeholder {
req "STK-001" "Range" { description: "The vehicle shall cover 400 km on one charge" }
}The rules that apply everywhere:
- Names are identifiers (
Driver), dotted names (Vehicle.MonitorEnvironment) or strings ("Brake controller"). - Attributes are
key: valuepairs. A value is a string, a number, a quantity with a unit (25 ms), a bare name, a list in brackets or a nested block. - Identity.
id: "LC-001"gives an element a stable identifier. Without it, the identifier is a prefix and the name, for instanceLC-Brake_controller. Give an explicitidto anything other tools will refer to: a rename then changes nothing else. - Comments are
// to the end of the lineand/* between marks */. - Strictness. A construct outside the grammar is a compile error with a line and a column. Nothing is skipped silently.
Requirements
Requirements live in requirements blocks, optionally named by kind (stakeholder, system, safety or any identifier), or inside a system_analysis block.
requirements safety {
req "REQ-BRK-001" "Emergency braking activation" {
description: "The system shall apply emergency braking when collision risk is critical"
priority: "Critical"
safety_level: "ASIL-D"
}
requirement "REQ-BRK-002" {
title: "Driver warning"
description: "The system shall warn the driver before braking"
safety_level: "ASIL-B"
}
}safety_level is an enumerated attribute: ASIL (QM, ASIL-A to ASIL-D), DAL (DAL-A to DAL-E) or SIL values are accepted, and any other spelling is reported.
Operational analysis
The operational analysis captures what the users and the surrounding systems need to do, before any solution: actors, entities, their activities, the interactions between activities, and operational capabilities.
operational_analysis "Emergency braking: operational view" {
operational_capability "Avoid collisions" {
id: "OC-001"
description: "Avoid collisions with obstacles and vulnerable road users"
}
actor "Driver" { id: "OA-ACT-001" description: "Vehicle driver" }
entity Vehicle {
description: "The vehicle being controlled"
activity MonitorEnvironment { description: "Observe surroundings" }
activity ApplyBrakes { description: "Slow the vehicle down" }
}
interaction ThreatSeen {
from: Vehicle.MonitorEnvironment
to: Vehicle.ApplyBrakes
}
}An interaction can also be written with an arrow: interaction A -> B { }. Operational processes (operational_process, an ordered involves list of activities, with the same control nodes and sequence links as a functional chain) and communication means between entities (communication_means, with from and to) complete the level. Interactions and communication means are elements: a trace can name them by their id or label.
System analysis
The system analysis says what the system must do, as a black box: functions with typed ports, the exchanges between them, the missions and capabilities that justify them, and functional chains.
system_analysis "Emergency braking: system view" {
mission SafeBraking {
id: "MIS-001"
description: "Prevent collisions through autonomous emergency braking"
}
function "Acquire sensor data" {
id: "SF-ACQ"
latency: 20 ms
port out objects { data_type: "ObjectList" }
}
function "Assess threat" {
id: "SF-THREAT"
latency: 30 ms
port in objects { data_type: "ObjectList" }
port out threat { data_type: "ThreatLevel" }
}
function "Compute braking force" {
id: "SF-BRAKE"
latency: 25 ms
port in threat { data_type: "ThreatLevel" }
}
functional_exchange "SF-ACQ.objects" -> "SF-THREAT.objects" { label: "object list" }
functional_exchange "SF-THREAT.threat" -> "SF-BRAKE.threat" { label: "threat level" }
capability EmergencyBraking {
id: "CAP-001"
mission: "MIS-001"
involves: ["SF-ACQ", "SF-THREAT", "SF-BRAKE"]
}
functional_chain "Detection to braking" {
id: "FC-AEB"
involves: ["SF-ACQ", "SF-THREAT", "SF-BRAKE"]
latency_budget: 100 ms
}
}A function port is either in or out, as in Arcadia. An exchange names the ports it connects; an exchange that names a port its function does not declare is reported. A functional chain lists its functions in order, and its latency_budget is what the production gate compares with the sum of their latencies.
A chain can branch, as Capella's functional chain descriptions do: control nodes (and, or, iterate) and sequence links between the steps of the chain and its control nodes, each link with an optional condition. A chain can also involve another chain, by its id or name, as a step of its own. A chain that involves nothing yet is accepted with a warning.
system_analysis "Branching" {
function "Decide" { id: "SF-DECIDE" }
function "Brake" { id: "SF-BRAKE" }
function "Warn" { id: "SF-WARN" }
functional_chain "Warn the driver" { id: "FC-WARN" involves: ["SF-WARN"] }
functional_chain "React to a threat" {
id: "FC-REACT"
involves: ["SF-DECIDE", "SF-BRAKE", "FC-WARN"]
control or "Choice"
control and "Done"
sequence "SF-DECIDE" -> "Choice"
sequence "Choice" -> "SF-BRAKE" { condition: "obstacle close" }
sequence "Choice" -> "FC-WARN" { condition: "obstacle far" }
sequence "SF-BRAKE" -> "Done"
sequence "FC-WARN" -> "Done"
}
}A sequence link whose end is neither a step of the chain nor one of its control nodes is an error. Control nodes and links are elements: a control node's id defaults to <chain id>.<name>. Operational processes take the same control and sequence declarations.
Capabilities nest, and relate to each other with extends, includes and specializes. Every reference must resolve to a declared element of the same level: a dangling one is a compile error.
The system, its components and its actors carry oriented ports, and component exchanges link them, as a system architecture blank diagram shows:
system_analysis "System" {
system_component "Emergency braking" { id: "SC-AEB" port in "pedal state" port out "brake command" }
actor "Driver" { id: "SA-DRIVER" port out "pedal" }
component_exchange "Pedal" { from_port: "SA-DRIVER.pedal" to_port: "SC-AEB.pedal state" }
}Logical architecture
The logical architecture opens the box: components, the logical functions each one performs, their ports and interfaces, the exchanges between functions and between components. It is written logical_architecture "Name" { } or architecture logical { }.
As in Arcadia, each level has functions of its own. A function written inside a component is performed by it; it carries its own ports and sub-functions, and realizes: names the system function it realizes. Functional exchanges between logical functions are declared at the logical level, their ends being functions or Function.port. A function no component performs yet is declared at the level itself.
logical_architecture "Emergency braking: components" {
component "Sensor fusion" {
id: "LC-FUSION"
safety_level: "ASIL-D"
function "Fuse radar and camera" {
id: "LF-FUSE"
latency: 25 ms
port out "object list"
function "Reject ghost targets" { id: "LF-GHOSTS" }
}
port out objects { interface: "ObjectList" }
}
component "Brake controller" {
id: "LC-BRAKE"
safety_level: "ASIL-D"
multiplicity: 2
function "Compute braking command" { id: "LF-BRAKE" port in "object list" }
port in objects { interface: "ObjectList" }
provides interface IBrakeCommand { protocol: "CAN" }
component "Plausibility monitor" { id: "LC-MON" }
}
functional_exchange "Objects" { from: "LF-FUSE.object list" to: "LF-BRAKE.object list" }
component_exchange "Object flow" {
from_port: "LC-FUSION.objects"
to_port: "LC-BRAKE.objects"
}
}Components nest. A function written inside a component is performed by it, which is how the diagrams know where to draw it. multiplicity says how many of the component its owner has: a number, or a range as text ("0..1", "1..*").
A port may hold ports, as a connector holds its pins: an exchange names one as Component.port.pin, and the diagram draws it at the port of the component. An exchange may join more than two ends, a bus for instance: ends: lists them all, and the diagram draws them meeting at a junction. Both come from SysML v2 (port def with ports, connect (a, b, c)); exported to Capella, whose ports are flat and exchanges binary, a held port becomes its outer port and an exchange of n ends n-1 exchanges, each noted.
logical_architecture "Wheels" {
component "Wheel" {
id: "LC-WHEEL"
port inout "nuts" { port inout "lug nut" { multiplicity: 5 } }
}
component "Hub" {
id: "LC-HUB"
port inout "shanks" { port inout "shank" { multiplicity: 5 } }
}
component "Body ECU" { id: "LC-ECU" port inout "can" }
component "Door module" { id: "LC-DOOR" port inout "can" }
component "Seat module" { id: "LC-SEAT" port inout "can" }
component_exchange "Fastening" { from_port: "LC-WHEEL.nuts.lug nut" to_port: "LC-HUB.shanks.shank" }
component_exchange "Body CAN" { ends: ["LC-ECU.can", "LC-DOOR.can", "LC-SEAT.can"] }
}Product lines
A component variation: true is a point of variation; the components it holds are its variants, each variant: true, and it holds nothing else. A configuration is one product of the line: it selects one variant of every variation. The compiler rejects a variant outside a variation, a component inside one that is not a variant, a selection of what is not a variant, and two variants of one variation; it warns when a configuration leaves a variation open. In SysML v2 they are variation part, variant part, and a #Configuration part with a dependency to each variant it selects.
logical_architecture "EV" {
component "Vehicle" {
id: "LC-EV"
component "Battery" {
id: "LC-BATT"
variation: true
component "Standard pack" { id: "LC-PACK-STD" variant: true energy: 64 kWh }
component "Long range pack" { id: "LC-PACK-LR" variant: true energy: 82 kWh }
}
component "Charger" {
id: "LC-CHG"
variation: true
component "11 kW onboard charger" { id: "LC-OBC-11" variant: true }
component "22 kW onboard charger" { id: "LC-OBC-22" variant: true }
}
}
}
configuration "City" { id: "CFG-CITY" selects: ["LC-PACK-STD", "LC-OBC-11"] }
configuration "Touring" { id: "CFG-TOURING" selects: ["LC-PACK-LR", "LC-OBC-22"] }The diagrams draw variants dashed, and a declared diagram with configuration: draws one product.
Physical architecture
The physical architecture says what runs where: nodes (hardware), the behavior components and logical components deployed on them, the physical functions those components perform, and the links between nodes. A behavior_component carries ports and declares the functions it performs, each with its ports and realizes: the logical function it realizes; allocated_component: names the logical component it realizes. Functional exchanges between physical functions, functional chains of physical functions and capability_realization blocks realizing the logical ones are declared at the physical level. A physical_link ends on nodes, or on their ports written Node.port, and is then drawn between the ports. A node has two kinds of ports: port "eth0" is a physical port, unoriented, where links end; port in|out "orders" is a component port, oriented, where a physical_exchange may end (Node.port), as Capella gives its node components, actors above all. A node or a logical component marked actor: true is an actor of its level, as Capella has them.
logical_architecture "Components" {
component "Sensor fusion" { id: "LC-FUSION" }
component "Brake controller" { id: "LC-BRAKE" }
}
physical_architecture "Hardware" {
node "Central ADAS ECU" {
id: "PN-ADAS"
node_type: "ECU"
processor: "ARM Cortex-A53"
ram: 512 MB
deploys "LC-FUSION"
}
node "Brake ECU" {
id: "PN-BRAKE"
node_type: "ECU"
deploys "LC-BRAKE"
behavior_component "Brake software" {
id: "PC-BRAKE-SW"
function "Compute braking command" { id: "PF-BRAKE" port in "object list" }
}
}
link "Brake CAN" {
from: "PN-ADAS"
to: "PN-BRAKE"
protocol: "CAN FD"
bandwidth: 5 Mbps
}
}deploys must name a declared component: a dangling deployment is reported on every build. A physical_path is an ordered list of links, and a physical_exchange describes what travels over a link, with the fields an interface control document needs.
Product breakdown (EPBS)
The end product breakdown structure lists what is actually delivered: systems, subsystems and configuration items.
epbs "Emergency braking: products" {
system "AEB system" {
id: "EPBS-001"
subsystem "Sensing" {
id: "EPBS-100"
item "Front radar" { id: "EPBS-101" part_number: "RAD-77-A" supplier: "Internal" }
item "Front camera" { id: "EPBS-102" part_number: "CAM-12-C" }
}
}
}Traces
A trace links two elements, usually across levels. The compiler resolves both ends: a trace to an element that does not exist fails the build.
operational_analysis "Need" {
entity Vehicle { activity ApplyBrakes { description: "Slow the vehicle down" } }
}
system_analysis "System" {
requirement "REQ-001" { description: "Brake within 100 ms of detection" }
function "Compute braking force" { id: "SF-BRAKE" }
}
logical_architecture "Components" {
component "Brake controller" { id: "LC-BRAKE" }
}
trace "LC-BRAKE" satisfies "REQ-001" { rationale: "Commands the actuator" }
trace "SF-BRAKE" realizes "ApplyBrakes" { rationale: "The function realizes the operational activity" }
trace "LC-BRAKE" implements "SF-BRAKE"The kinds are satisfies, implements, validates, realizes, refines and allocates; an allocation may be typed by an allocation definition, whose kinds of ends the compiler checks. rationale is free text; it is what a reviewer reads to judge whether the link makes sense. The metamodel lists which kinds of elements each kind of trace may link.
Verification
A test_case states how requirements are verified. The gate requires every requirement to be verified by at least one.
requirements safety {
req "REQ-001" "Braking reaction" { description: "Brake within 100 ms of detection" }
}
test_case "TC-001" {
verifies: ["REQ-001"]
method: "test"
description: "HIL measurement of detection-to-command latency"
}method is one of test, analysis, inspection or demonstration.
An analysis asserts: constraints: its verdict is theirs, pass when they all hold and fail otherwise, computed on every build. A test run elsewhere says its verdict: (pass, fail, inconclusive or error, the verdicts of SysML v2). A requirement with a failing verification case is not verified, even if another passes, and the production gate names the cases that fail.
system_analysis "Needs" {
requirement "Range" { id: "REQ-RANGE" description: "Enough energy on board for the range" }
}
logical_architecture "EV" {
component "Vehicle" { id: "LC-EV" energy: 60 kWh }
component "Battery" { id: "LC-BATT" energy: 64 kWh }
}
constraint "Battery covers the range" {
id: "CST-RANGE"
assert: "LC-BATT".energy >= "LC-EV".energy
}
test_case "Range analysis" {
id: "TC-RANGE"
verifies: ["REQ-RANGE"]
method: "analysis"
asserts: ["CST-RANGE"]
}With a 50 kWh battery the build warns verification: test_case 'Range analysis' fails: constraint 'Battery covers the range' does not hold, and the requirement is no longer verified. In SysML v2 the case is a verification that asserts the constraint.
Hazards
safety_analysis holds the hazard analysis. For ISO 26262, a hazard carries severity, exposure and controllability, the ASIL that results, and the requirements that mitigate it.
requirements safety {
req "REQ-001" "Braking reaction" { description: "Brake within 100 ms of detection" }
}
safety_analysis {
hazard "Late braking" {
id: "HAZ-001"
description: "Braking is commanded too late to avoid the obstacle"
severity: "S3"
exposure: "E4"
controllability: "C3"
asil: "ASIL-D"
mitigated_by: ["REQ-001"]
}
}Threats and primary assets
security_analysis holds the cybersecurity analysis of one level (the Capella Cybersecurity viewpoint). A primary asset is what must be protected: a service (functional), a piece of information (information) or a business concern (enterprise), made of declared functions, exchange items or classes. A threat has a kind, a criticality level, the assets it applies to and the components or actors it involves. A threat realizes the threat of the level above by an ordinary realizes trace; a scenario, a functional chain or an operational process may be owned by a threat (owner:), as by a capability.
security_scales declares, once per model, the literals of each security need and the threat kinds; security_<need> attributes and threat kinds are checked against them.
security_scales {
confidentiality: ["0", "1", "2", "3"]
integrity: ["0", "1", "2", "3"]
availability: ["0", "1", "2", "3"]
traceability: ["0", "1", "2", "3"]
threat_kinds: ["Eavesdropping", "Denial of service", "Theft"]
}
operational_analysis "OA" {
entity "Ministry" { id: "OE-Ministry" activity "Monitor the crowd" { id: "OA-Monitor" } }
}
system_analysis "SA" {
function "Provide visuals" { id: "SF-Provide-visuals" }
system_component "Surveillance" { id: "SC-Surveillance" }
}
security_analysis {
level: "oa"
threat "Block the visualization of public events" {
id: "TH-OA-1"
kind: "Denial of service"
level: 2
involves: ["OE-Ministry"]
}
}
security_analysis {
level: "sa"
asset "Service of supply of visuals" {
id: "AS-SA-1"
kind: "functional"
members: ["SF-Provide-visuals"]
security_availability: "2"
}
threat "Block provision of visuals" {
id: "TH-SA-1"
kind: "Denial of service"
level: 2
rationale: "A foreign agent wants to blind the public forces"
applies_to: ["AS-SA-1"]
involves: ["SC-Surveillance"]
security_availability: "2"
}
}
trace "TH-SA-1" realizes "TH-OA-1"Numbers written without a decimal point are integers (level: 2); with one they are decimals (mass: 800.0), and each is kept as written on export.
Data model
Classes, enumerations, data types and exchange items describe what flows on the exchanges.
class RadarFrame {
id: "CL-001"
range_m: "float"
azimuth_deg: "float"
velocity_mps: "float"
}
enumeration ThreatLevel {
id: "EN-001"
values: ["None", "Low", "Critical"]
}
data_type BrakingForce {
id: "DT-001"
base: "float"
unit: "N"
}
exchange_item RadarTargets {
id: "EI-001"
mechanism: "FLOW"
elements: ["CL-001"]
}Every attribute of a class other than id, description, summary, status, constraints and extends is a field. Fields keep their declared order in every output, and a field declared twice is an error. extends: "Base" names the class this one specializes; the class diagram draws the generalization. The mechanism of an exchange item is EVENT, FLOW, OPERATION, DATA or SHARED_DATA.
An association between two classes is an element of its own, as in Capella: from is the class owning the navigable end, to the class it reaches, role the field of from typed by to, kind one of association, aggregation or composition, and multiplicity free text (1, 0..*). The class diagram draws the association in place of the field's own link. Unknown classes, or a role that is not a field of from, are errors.
class Seat { id: "CL-SEAT" passenger: "Passenger" }
class Passenger { id: "CL-PAX" name: "String" }
association "has" {
id: "AS-001"
from: "CL-SEAT"
to: "CL-PAX"
role: "passenger"
kind: "composition"
multiplicity: "0..1"
}Modes and states
A state machine declares modes (chosen behaviors) and states (undergone conditions), and the transitions between them. Both ends of a transition must be declared. In Arcadia a transition is commanded by the functional dataflow, so a trigger names a declared element, here a function; a trigger that matches nothing is reported. A transition keeps every attribute written, such as events: and guard:; a state keeps its kind: (final, choice, fork, join...) and, when it opens a region, its own initial:. A machine with several top-level regions, each with its own initial state, writes them as region "Name" { initial: "A" state "A" {} ... } blocks; their states and transitions belong to the machine as well.
system_analysis "System" {
function AssessThreat { description: "Rate the collision risk" }
function ComputeBrakingForce { description: "Decide how hard to brake" }
function MonitorVehicleState { description: "Watch speed and faults" }
}
state_machine BrakingModes {
initial: "Standby"
mode Standby { description: "Armed, monitoring" }
mode Warning { description: "Threat detected, driver warned" }
mode EmergencyBraking { description: "Autonomous braking active" }
state Failed { description: "Sensor or actuator failure" }
transition Standby -> Warning { trigger: "AssessThreat" }
transition Warning -> EmergencyBraking { trigger: "ComputeBrakingForce" }
transition EmergencyBraking -> Standby { trigger: "MonitorVehicleState" }
transition Standby -> Failed { trigger: "MonitorVehicleState" guard: "sensor_fault" }
}A state can hold a region: states and modes declared inside it, with their own transitions. The diagram draws the region as a box inside its parent, and a transition may leave a nested state for any state of the machine.
system_analysis "System" {
function MonitorVehicleState { description: "Watch speed and faults" }
}
state_machine Power {
initial: "Off"
state Off {}
state On {
state Idle {}
state Busy { entry: ["MonitorVehicleState"] }
transition Idle -> Busy { trigger: "MonitorVehicleState" }
}
transition Off -> On { trigger: "MonitorVehicleState" }
transition Busy -> Off { trigger: "MonitorVehicleState" guard: "halt" }
}Scenarios
A scenario is an ordered exchange of messages between participants, drawn as a sequence diagram. Participants must be declared elements, and a message can only link participants.
logical_architecture "Components" {
component "Radar" { id: "LC-RADAR" }
component "Fusion" { id: "LC-FUSION" }
component "Brake" { id: "LC-BRAKE" }
}
scenario EmergencyStop {
participants: ["LC-RADAR", "LC-FUSION", "LC-BRAKE"]
message "LC-RADAR" -> "LC-FUSION" "raw radar targets"
message "LC-FUSION" -> "LC-BRAKE" "critical threat" { type: "async" }
}A combined fragment groups consecutive messages under a frame: loop, opt, alt, par, seq or critical, with an optional label. alt and par cut the frame into operand blocks, each with its guard; the other kinds take their messages directly. Fragments nest, and the messages keep their place in the scenario's order.
logical_architecture "Components" {
component "Radar" { id: "LC-RADAR" }
component "Fusion" { id: "LC-FUSION" }
component "Brake" { id: "LC-BRAKE" }
}
scenario Tracking {
participants: ["LC-RADAR", "LC-FUSION", "LC-BRAKE"]
message "LC-RADAR" -> "LC-FUSION" "start tracking"
fragment loop "while a target is tracked" {
message "LC-RADAR" -> "LC-FUSION" "raw radar targets"
fragment alt {
operand "threat confirmed" { message "LC-FUSION" -> "LC-BRAKE" "critical threat" { type: "async" } }
operand "else" { message "LC-FUSION" -> "LC-RADAR" "keep scanning" }
}
}
message "LC-FUSION" -> "LC-BRAKE" "stop" { type: "async" }
}A fragment that mixes operand blocks and loose messages is refused, as is a kind the language does not know.
A message keeps every attribute written: kind: is Capella's message kind, exchange: names the functional or component exchange it carries, and an unknown exchange is reported. A scenario or a state machine can name its owner:, the element it belongs to; data types, enumerations and exchange items keep their attributes too (status:, unit_name:...).
A lifeline can assert the state or mode it is in, or the function it runs, between messages, as Capella's state fragments do: state "Lifeline" "Armed", mode ... or function "Lifeline" "Sense". The body holds the messages the assertion spans; without a body it is an instant. The lifeline must be a participant and the name a declared state, mode or function, by id or unambiguous name. These declarations are allowed inside fragments and operands too, and the sequence diagram draws them as rounded boxes on the lifeline.
logical_architecture "Components" {
component "Radar" { id: "LC-RADAR" function "Scan" { id: "LF-SCAN" } }
component "Fusion" { id: "LC-FUSION" }
}
state_machine RadarModes {
initial: "Standby"
state "Standby" { id: "ST-STANDBY" }
state "Tracking" { id: "ST-TRACKING" }
transition "Standby" -> "Tracking" { trigger: "start tracking" }
}
scenario Tracking {
participants: ["LC-RADAR", "LC-FUSION"]
state "LC-RADAR" "Standby"
message "LC-FUSION" -> "LC-RADAR" "start tracking"
state "LC-RADAR" "Tracking" {
function "LC-RADAR" "Scan" {
message "LC-RADAR" -> "LC-FUSION" "raw radar targets"
}
}
}Several files
import assembles a model from several files, so that each team owns its part. Paths are relative to the importing file, imports are recursive, a file imported along two paths is read once, and a missing file or a cycle is an error. A trace in one file can target an element declared in another. Two requirements of one id are an error that names both places, file and line.
$ head -12 multi/main.arc
// Multi-file model: each team owns its fragment, the root file assembles
// them. Paths are resolved relative to THIS file. The traces below reference
// elements from BOTH fragments — they only resolve after the merge, which is
// exactly what makes this a model assembly and not a text include.
import "requirements.arc"
import "logical.arc"
model MultiFileDemo {
version: "1.0"
}
$ arclang check multi/main.arc
Checking multi/main.arc...
✓ No compilation errorsWhat is parsed but not modeled
dataflow, data_flows, validation, traces and traceability blocks are accepted by the parser for compatibility with older models, but they are not part of the compiled model. The compiler says so with a warning each time; it does not pretend.