Importing
Models from elsewhere, in arche-lang: an Arche project, a SysML v2 model, a CAN DBC, ASN.1 types, a robot (URDF, MJCF), a Capella project, a Melody CCM project and Polarion requirements.
An Arche project#
archec import <arche-project> -o <dir> rewrites any Arche project in arche-lang, and
archec diff <a> <b> compares two projects as data (key order, quoting and empty values do
not count; list order does). The import builds what it wrote, compares it with the project,
and fixes each difference on the declaration that made it — until the build gives the
project back exactly — then writes the short forms and copies the files the project
refers to (assets). What the language has no word for is written with attr key = value.
examples/kacao is the KaCAO project (antenna control on a Kria KR260) imported this way:
seven model files, 211 components, 206 requirements with their verification blocks, 54
scenarios with alt/loop fragments, 1280 traces, French with English translations
("Libellé" en "Label") — and archec build gives back the very project it came from.
A name in brackets in a text is a checked reference: [REQ-KAC-037], [kria.ps.app.tracking].
Arche gets it as it writes references (KRIA260.yaml#kria.ps.app.tracking), so nothing changes in the project.
A long text puts its translation on the next line, under it (fmt --simplify splits such lines):
description "Le PS doit faire tourner l'application…"
en "The PS shall run the application…"
In VS Code, a workspace with several projects (examples/ has three) shows one root per project in the Model and Files views.
A SysML v2 model#
archec import model.sysml -o model.arche reads a model in SysML v2 textual notation
(stdout without -o). The result is built at once, and its errors are listed. The parser is
tolerant: it reads each member of a body, a header up to ; or a { … } body, and only then
works out what it is. What it does not know is not an error.
| SysML v2 | arche-lang |
|---|---|
| the one package around the model | project "Name" (its doc is the description); inner packages are packages |
part def D :> B |
component D extends B; a part def only used by a part at the top (part car : Car) is system Car |
part w : Wheel[4], part x { … } |
part w: Wheel[4], component x { … } |
attribute mass = 1200 [kg], attribute g : Gear = Gear::park |
var mass: float64 = 1200 unit kg, var g: Gear = park |
port def P { out item t : Torque; }, port p : ~P |
port-type P; in p: P receives Torque (~ turns the items around) |
item def M, attribute def S { … }, attribute def T :> Real, enum def E |
message M, struct S, type T = float64, enum E |
an item def also used as data (in cmd : Cmd; in an action, attribute c : Cmd) |
struct CmdData { … } and message Cmd: CmdData: a message is not a type, so the action takes cmd: CmdData |
interface def I { end a : P; end b : ~P; } |
interface I { messages … } (the items of its ends' port defs) |
connect a.p to b.q, connection c connect …, interface i : I connect … |
a.p -> b.q (turned to go from the out end), link c a.p -> b.q |
flow of M from a.p to b.q |
a.p -> b.q { carries M } |
exhibit state s : S { entry; then A; state A; … } |
states { state A; … }, the initial state first |
transition first A accept e if g then B, accept e then B |
A -> B on e [g]; accept after 10 [s] is a trigger in text, on "after 10 [s]" |
action a : Def { in x : Real; out y : Real; }, perform action |
fn a(x: float64) -> float64 |
requirement <'R1'> massReq /* Mass */ { doc /* … */ } |
requirement R1 "Mass" { text "…" }; a nested requirement derives its parent |
requirement mass : MassReq; (a usage of a requirement def) |
requirement mass { text "…" }, the text its definition's doc when it has none of its own |
satisfy R by x, verify R |
satisfies R on part x (or on the definition), verifies R |
verification def T { objective { verify R; } } |
test-plan T { case T-1 }, and verified-by T on R |
doc /* … */, // and /* … */ comments |
description "…", comments |
Limits:
- Names are global in arche-lang, so
importand qualified names are dropped:A::B::CisC. Two things with the same name in different packages clash. - Types from the standard library (
ScalarValues,ISQ,SI…) map tofloat64,int32,uint32,boolandstring. A quantity keeps its unit from the value, not from its type. - Behaviour is Python in Arche: an entry/exit action is an empty
on-enter/on-exit, a transition'sdoeffect is a comment, and an action's steps are left out. Adoaction becomes the state'sdoonly when the part has that action. satisfy R by world.car.enginegoes on the partengineofcar's definition, so it holds for every car.- Expressions are not read: a guard is copied as it is, and a default value that is not a literal or a name is left out with a note.
- Nothing is dropped silently: what has no place is a
// sysml line N: …comment where it was (N its line in the SysML file), and a noteline N: …. Left so: use cases, calcs, constraints, analysis, allocation, views,subject/requirein requirements, connection defs, n-ary connections, a second state machine in one part. Metadata (@X,#X,metadata def) is left out with a note. - arche-lang has no multiplicity range:
[2..4]becomes[4], and[*]on a part is left out.
A CAN DBC#
archec import bus.dbc -o bus.arche reads a CAN database and writes the constructs archec gen --target dbc writes a DBC from, so the one gives the other back: importing the DBC that gen
writes for examples/autonomous-car and generating it again gives the same file, byte for byte.
| DBC | arche-lang |
|---|---|
BO_ 256 EngineData: 8 ECM |
message EngineData { byte-order …; size 8 } — a wire format; the route's id 0x100 |
SG_ EngineSpeed : 7|16@0+ (0.25,0) [0|16383.75] "rpm" TCM |
EngineSpeed: float32 unit rpm at 0 bits 16 scale 0.25 — at counts along the frame as the wire format does (a Motorola signal from its most significant bit); the order of most signals is the message's byte-order, the others say their own |
the sign (-), factor and offset |
signed, scale, offset: a scaled signal is a float32 (float64 past 24 bits); an unscaled one the smallest uint/int that holds it, a 1-bit one a bool |
[min|max] |
range min..max, when it is not what the bits carry anyway (and not [0|0]) |
SIG_VALTYPE_ … : 1 / 2 |
float32 / float64, sent as its IEEE bits |
VAL_ 512 Gear 0 "Park" 1 "Reverse" … |
enum Gear { Park = 0, Reverse = 1, … } (named after its VAL_TABLE_ when one is the same); a text that is not a name is the literal's label |
CM_ BO_, CM_ SG_, CM_ BU_ |
the label of the message, of the field, of the node's component and processor |
BA_ "GenMsgCycleTime" BO_ 256 10; |
period 10ms on the message (and gen writes it back as GenMsgCycleTime) |
BU_: ECM TCM |
component ECM with an out port per frame it sends and an in port per frame it receives (can, or can-fd when a frame is longer than 8 bytes or the bus says CAN FD); a part of the logical system; a processor of the platform on the bus; a binary on it in the deployment |
| a frame and its receivers | a link and a route over the bus for each receiver, all at the frame's id (gen writes them as one frame again) |
Limits:
- A multiplexed signal (
m0,m1…) has no place: it is a// dbc line N:comment in its message, and the multiplexor (M) is a plain field. Signal groups, environment variables, value tables no signal uses, and attributes other than the cycle time and the bus type are comments too, each with a note. - Each receiver gets the whole frame: when its signals go to different nodes,
genlists every receiver on every signal.Vector__XXX(no receiver) is a part of its own, whichgendoes not list inBU_. - A frame sent by several nodes (
BO_TX_BU_) is sent by the first. - A value table on a scaled or signed signal, or past 8 bits, stays a comment on its field. An IEEE float with a factor or an offset loses them (the language sends a float as its bits).
- Two frames with one name are one message when their signals are the same, else the second is
renamed (
Name_2). An extended id is kept as it is, its bit 31 set, in the route'sid. - The file's own comment (
CM_ "…") is a comment at the top, whichgendoes not write back.
ASN.1#
archec import types.asn [more.asn…] -o types.arche reads ASN.1 type definitions (X.680, the
part that says data). Each module is a package; a module's IMPORTS … FROM M is found among
the modules of the files given. Names are global in arche-lang: a name two modules define is
suffixed in the second (Speed_2), a hyphen is an underscore (Common-Types → Common_Types).
| ASN.1 | arche-lang |
|---|---|
BOOLEAN, REAL |
bool, float64 |
INTEGER (0..30000), INTEGER |
the smallest uint/int that holds it, with range 0..30000; int64 unbounded |
ENUMERATED { ok, degraded(5), failed } |
enum { ok = 0, degraded = 5, failed = 1 } — unnumbered items take the smallest number free, as X.680 says |
OCTET STRING (SIZE (4)), OCTET STRING |
byte[4], bytes |
BIT STRING (SIZE (8)) |
bool[8] (bool[] when the size varies) |
IA5String, UTF8String, VisibleString, PrintableString… |
string, its kind and SIZE in a comment |
SEQUENCE { a T, b U OPTIONAL, c V DEFAULT x }, SET { … } |
struct { a: T; b: optional U; c: V = x } |
CHOICE { stop NULL, go Speed } |
union { stop; go: Speed } |
SEQUENCE (SIZE (4)) OF T, SEQUENCE SIZE (1..16) OF T |
T[4], T[] (the bounds in a comment) |
| a SEQUENCE no other type uses | message (what is sent), its fields inline; a CHOICE no other type uses: its union and message <Name>_MSG: <Name> |
| an inline SEQUENCE, CHOICE or ENUMERATED in a field | a type of its own named after its place (Report_mode) |
maxSpeed INTEGER ::= 30000 |
const maxSpeed = 30000 (INTEGER, BOOLEAN, REAL and string values) |
X ::= Y, X ::= INTEGER (…) |
type X = Y, type X = uint16 range … |
Not supported, each with a note naming the line (and, for a whole assignment, a // asn1 line N:
comment where it was): information object classes and objects (CLASS, &field), parameterised
types (Pair { T } ::= …), value sets, COMPONENTS OF, the values of anything but an INTEGER,
BOOLEAN, REAL or string, named numbers on an INTEGER (kept as a comment), OBJECT IDENTIFIER and
the time types (written as string), and every constraint but a value range, a list of single
values (read as their range) and SIZE — FROM, PATTERN, CONTAINING, WITH COMPONENTS are
left out. Tags ([3] IMPLICIT) and extension markers (..., [[ … ]]) say the encoding and how
the type may grow: they are left out, the extension additions read as ordinary components. A type
named in IMPORTS but in none of the files is bytes. The encoding (BER, PER, ACN) is not read:
for a bit-exact layout, give the message a wire format.
A robot: URDF and MJCF#
archec import robot.urdf -o robot.arche reads a URDF robot, archec import scene.xml a
MuJoCo model (MJCF; a .xml is read by its <robot> or <mujoco> root). The result is a
component for the robot, its bodies nested inside it as the kinematic tree goes, each with the
language's bodies — mass, shape, joint — so archec gen --target mujoco writes it back; a part commands that sends to its actuators; and a system with both.
It is written as archec fmt writes and built at once.
component Pendulum "pendulum" {
solver 1ms // <option timestep>
component pole {
prop hinge = {damping: 0.05} // what `joint` does not say
physical {
mass 0.3309144262kg // from its geoms, as MuJoCo computes it
shape capsule 0.02m, 0.5m at 0, 0, -0.25m
joint hinge y at 0, 0, 1m range -1.570796327..1.570796327 = 0.5
}
component servo {
kind actuator
prop actuator = {type: position, joint: "hinge"}
prop kp = 10
in command: data receives SERVO_CMD // value: float64 unit rad range -1.5..1.5
}
component angle "angle (jointpos)" {
kind sensor
prop sensor = jointpos
prop joint = "hinge"
out value: data sends ANGLE // value: float64 unit rad
}
}
}
| Source | arche-lang |
|---|---|
a URDF <link>, an MJCF <body> |
a component in its parent's, mass |
| its place in its parent, its joint (URDF revolute, continuous, prismatic, floating, fixed; MJCF hinge, slide, ball, free) | joint hinge y at x, y, z rot r, p, y range lo..hi = start (URDF's origin rpy, MJCF's quat, euler, axisangle, xyaxes or zaxis as rot); the joint's damping, URDF's effort and velocity limits in prop <joint> = {…} |
<inertial> |
mass, prop inertial = {pos, quat, diaginertia | fullinertia}; without one (MJCF), the mass of the geoms, as MuJoCo computes it |
<collision>, <geom> |
the first the language can say is the body's shape (box, sphere, capsule, cylinder; rot when it is turned in its body); the others prop <name> = {geom: …} with MuJoCo's sizes, a URDF <visual> with visual: true |
<site> |
prop <name> = {site: sphere, pos, size} |
<transmission>, a <ros2_control> command interface, an MJCF actuator |
a part kind actuator in the body of its joint, in command receiving a message whose field has its unit (rad: position, rad/s: velocity) and its ctrlrange as range; prop kp, prop kv, prop actuator = {type, joint, gear…}; commands sends to it |
a Gazebo or URDF <sensor>, an MJCF sensor or camera |
a part kind sensor in its body, prop sensor = imu | jointpos | accelerometer…, out value sending a message with its units, at its rate (period); width, height, channels, range as the gazebo target reads them |
MJCF <include>, <default> classes, <frame> |
read in, resolved, folded into what they hold |
MJCF <option timestep gravity>, the first <key> |
solver, prop gravity; each joint's start (= value) |
Lengths are in m, angles in rad (MJCF degrees are turned into radians, Euler angles and axes into
quaternions), masses in kg. A body's frame is the source's: its joint's rot turns it in its
parent's (a turn about one axis as rot z 90deg, any other as roll, pitch, yaw), and its axis,
its shape, its prop records and the bodies in it are in that frame, as the source says them. The rest — lights, materials, tendons, equalities, contact pairs,
Gazebo plugins, mimic joints, the keyframes after the first — is a // urdf: or // mjcf:
comment where it was, and a note; an attribute archec does not read is a note.
Limits: a xacro file must be expanded first (xacro robot.urdf.xacro > robot.urdf). A body has
one shape and one joint: its other geoms and joints stay as records, which gen --target mujoco does not read. A mesh is its file name: its volume is not computed, so a body with only
a mesh and no <inertial> has no mass. Actuator defaults are read from the element's own class
entry, then general.
Capella#
archec capella exchanges a model with Capella 6.1, both ways. It runs the Arche app's own
Capella converter (bundled in vendor/arche.js), so archec and the app's Capella dialog give
the same files.
archec capella main.arche -o project.zip # → a Capella project, as the archive Capella imports
archec capella main.arche -o project/ # → the same project as a folder
archec capella model.capella -o model/ # → arche-lang (the .capella's fragments and .aird beside it are read)
archec capella project.zip -o model/ # → arche-lang, from a Capella project exported as an archive
Export. The model is built, then each component goes to the Arcadia level its
layernames (Operational, System, Logical, Physical, EPBS). Data, messages and interfaces go to the most abstract level that uses them. The.capellais checked against the Capella 6.1 metamodel before anything is written; a problem is listed and the exit code is 1. What Capella has no place for (aderivesbetween two requirements, a scenario step on no exported exchange…) is listed with the reason. In Capella: File › Import › General › Existing Projects into Workspace.Import. The model becomes the Arche project the app's import makes: one model per Arcadia level, laid out as the
.airddraws it (--no-diagramsto skip the layout), with--prefixfor the file names and--layers oa,sa,la,pa,epbsto keep only some levels. That project is then rewritten in arche-lang asarchec importdoes, and built. Requirements and scenarios becomerequirementandscenariodeclarations; the rest of the model too:system-function fly "Fly" { // a function, its parts inside it system-function steer "Steer" { kind route allocated dd-sa.drone // allocated to a component, by path in order "Order" receives command // its ports and what they carry } } function-flow read-stick.stick -> steer.order as orders { name "Orders"; carries command } function-chain branching "Branching" { step or { kind or }; edge or -> steer { when "armed" } } use-case fly-a-mission "Fly a mission" { // a capability involves dd-sa.pilot, dd-sa.drone, steer, pilot-chain, command } property colour { type string; kinds system; group physical } // a property value's definition component drone "Drone" { mass 1.5; prop certified = true; prop physical = {colour: "red"} }The functions and capabilities of the one level that has some go to
functional.yamlandusecases.yaml; when several levels have functions, each keeps its file (file "dd-la-functions.yaml"on its functions, flows and chains), and a name both levels use is written as its address where it would be ambiguous. Missions stayattrof the capabilities file.--project dirkeeps the intermediate Arche project.Round trip. The import keeps where each element came from (the
<prefix>-capella.yamldata block), so exporting it again gives the elements their Capella ids back: components, ports, exchanges, functions and their ports, functional exchanges, chains, capabilities, missions and property values. What the app's export makes anew (data type literals, state machine regions, chain involvements, scenario lifelines) gets new ids: about 70 to 80% of a model's ids survive.
Melody CCM#
A Melody CCM project (.ccmproject, PYCOGEN's input) is read by the
built-in McCOFFEE plugin (plugins). It reads
the projects the file refers to too: Melody puts them side by side
(../../<P>/model/<P>.ccmproject). A project found elsewhere is named with
--option include=<dir> and --allow-read <dir>, as PYCOGEN's -I. The
library models (predefined types, the interaction patterns) come with the
plugin.
archec import TU_Timers.ccmproject -o timers \
--option include=../../../predefinedcomponents/timers/models --allow-read ../../../predefinedcomponents/timers/models
archec export timers/main.arche --format melody -o TU_Timers.ccmproject
It writes a folder: main.arche, one file per project (its modules as
packages), deployment.arche (the imported file's deployments) and
missing.arche (a stub per reference to a project not found).
| Melody | arche-lang |
|---|---|
ModuleDef |
package |
AliasDef, StructDef, UnionDef, EnumerationDef, ConstantDef, ExceptionDef, NativeDef |
type X = T, struct, union (discriminator and labels as attr), enum with values, const, exception, type X native |
a sequence, an array (IDLTypePackage) |
T[], T[N]; a bounded sequence attr melody_bound |
PK_LONG, PK_ULONGLONG, PK_OCTET, PK_STRING… |
int32, uint64, uint8, string…; PK_WSTRING, PK_WCHAR, PK_LONGDOUBLE the nearest, the kind in attr melody_primitive |
InterfaceDef (OperationDef, AttributeDef) |
interface with fn (in/out/inout, oneway, raises) and attribute (readonly) |
ComponentDef, its AttributeDefs |
component, its vars |
ProvidesDef, UsesDef |
in p: provides I, out p: requires I |
PublishesDef, ConsumesDef of an EventDef |
a message, ports pattern pub-sub; topic "/<Event>" |
fires/receives port of an FF ModuleRef |
message <ModuleRef>: T, out … sends / in … receives |
calls/serves port of a Service ModuleRef |
service "<ModuleRef>" |
requests/responds port of an RR ModuleRef |
<ModuleRef>Request and Response messages, pattern request-reply (deferred) |
sources/sinks port of a Dataflow ModuleRef, DataVectorDef |
dataflow { vector V; header H; threshold … }; the vector type V = T { attr melody_kind = "DataVectorDef" } |
AlgorithmDef (attributes, parameters, stereotypes) |
kind algorithm: parameters, var … { parameter configuration }, processing { stereotype … } |
SPCompositeDef, SubComponentComposition, PortDefDelegation, PrecedenceLink |
its parts, in p = part.port, after |
MetricPackage, BasicMetric; produced and consumed metrics |
package of message M { kind metric }; ports that send them, and receive them with a topic filter |
| a deployment: instances, connections (connectors joined), attribute values | system with parts, links, vars and arguments |
processes (hostName) |
a processor per host in a platform system, a binary per process, routes between hosts |
ActiveObject |
thread (priority, stack, queue) |
MemoryAllocator, hardware Memory |
allocator, the processor's memory (sizes in KiB) |
| execution, deployment memory, working memory, communication memory, buffering policies | place (execution, memory hrt/lrt/saved/tmp/comm, buffering) |
| serialization policy | attr melody_serialization on the place |
Each element keeps its Melody id (attr melody_id), and its Melody name when
arche-lang's differs (attr melody_name): the exporter writes them back, so
import → export → import gives the same model. What arche-lang has no word
for is a // melody: comment, and the notes count it: SCA resources and
applications, CCM assemblies, user-defined interaction patterns, user
allocator policies, annotations, source file descriptors. An allocator
arche-lang would count as too small (it counts a block per use, McCOFFEE takes
blocks as transfers need them) keeps its uses as attr melody_memory_<use> /
attr melody_buffering_allocator.
The exporter writes PYCOGEN's anonymised namespaces by default
(com.dummygroup.mde, sizes as attributes): PYCOGEN's MelodyParser reads the
file. --option vendor=com.thalesgroup.mde --option sizes=elements writes
Melody's.
Polarion#
archec polarion writes a Polarion project's work items as requirements, from a JSON export
or straight from the server. It works for any Polarion project: the standard fields are known,
a custom field is recognised by its name or said in polarion.yaml, and what has no place in
the language is kept as attr, so nothing is lost.
archec polarion workitems.json --project main.arche -o spec/polarion.arche # an export
POLARION_TOKEN=… archec polarion pull main.arche # the server
archec polarion pull main.arche --dry-run # the request, not sent
In VS Code: Arche: Import from Polarion… does the same — it asks the server,
the project and the query once (kept in polarion.yaml) and the token once (kept in VS Code's
secret storage; Arche: Forget the Polarion Token removes it), writes the file, offers to
include it, and builds.
The export is what the REST API answers to GET /rest/v1/projects/{p}/workitems?fields[workitems]=@all:
one page {data: [...]}, an array of pages, or archec polarion pull --save workitems.json.
archec import workitems.json does the same as archec polarion workitems.json.
polarion.yaml, beside main.arche (all optional):
url: https://polarion.example.com/polarion # pull: the server
project: MYPROJ # pull: the project id
query: "type:requirement AND NOT status:rejected"
out: spec/polarion.arche # where the requirements go (else -o, else stdout)
id: id # id (MYPROJ-123) | puid
documents: # a Polarion document → the project's deliverable
System Requirements: SRS
modules: [System] # only the documents whose id contains one of these
fields: # a field → what it is in the language
verifMethod: method # description text rationale priority status
testLevel: attr test-level # category method owner origin, attr <name>, skip
legacyType: skip
values: # a property's Polarion values → the language's
status: { inReview: proposed }
method: { R: inspection }
derives: [parent, satisfies, refines] # the link roles that are `derives`
| Polarion | arche-lang |
|---|---|
id (or puid with id: puid) |
the requirement's id |
title |
its name |
description, a …Rationale field (XHTML) |
description, rationale, as plain text (paragraphs, line breaks, list items kept) |
severity (must_have, should_have, nice_to_have, blocker…) or else priority (90 … 10) |
priority (critical, high, medium, low) |
status (draft, open, inReview, approved, rejected…) |
status (draft, proposed, approved, implemented, verified, obsolete) |
a …Category… field |
category (the first; all of them as attr categories) |
a …Method… field (T, A, I, D, test, analysis…) |
method |
an …Owner… field, else the assignee |
owner |
| the document (module) | document: the deliverable documents: names, or one written for it |
id, puid, outline number |
origin "Polarion MYPROJ-123 · REQ-0085 · SRS-4.2" |
a link in a derives: role to a work item written here, or to a requirement of the project |
derives |
| any other link | attr polarion-links = {describes: ["MYPROJ-9"], parent: ["MYPROJ-1"]} |
| any other field, a custom enum or user relationship | attr <field-name> (costCenter → attr cost-center) |
A value with no meaning in the language (a status weird) is written as a comment and named in a
note: map it under values:. With --project (or pull main.arche), a requirement the project
already declares is left out and links to it are derives; what the output file itself declared is
rewritten, so pulling again updates it.