Перейти к содержимому

Приложение A: Грамматика

Это приложение — плотный справочник. Для изучения языка читайте книгу, начиная с Предисловия.

// однострочный
/* многострочный
комментарий */
identifier = letter (letter | digit | "_")*
stable_id = "#" alphanumeric+
qualified_name = identifier ("." identifier)*
quoted_string = '"' (any_char - '"' | '\"')* '"'
| '"""' (any_char* - '"""') '"""' (* triple-quoted: no escapes *)

Зарезервированные ключевые слова (файлы .arch)

Заголовок раздела «Зарезервированные ключевые слова (файлы .arch)»
type, in, export, use, from,
aspect, aspects, required, cascade, append,
override, drop,
process, subprocess, view,
each, if, else, select, one, try, catch, parallel, branch,
join, race, out, await, reversible, unwind, on, do, go, as, dist, fail, finish,
focus, group, by, layout, include, exclude,
true, false

aspect — жёсткое ключевое слово, открывающее каждую форму членства/классификации (aspect k: v, aspect { … }, aspect k for …). aspects по-прежнему лексируется как ключевое слово, но зарезервировано-с-ошибкой: старые формы aspects { … } / aspects.x всё ещё парсятся (для восстановления после ошибки), но поднимают диагностику 'labels' was renamed to 'aspect'; ни один тип, интерфейс или модуль не может называться aspects.

focus теперь — клауза акцента проекции (§Декларации проекций) — она подсвечивает совпадающие элементы и никогда не меняет, какие узлы выбирает проекция. layout, include и exclude остаются зарезервированными, но являются устаревшими: каждое всё ещё парсится, но лишь для того, чтобы поднять целевую диагностику, указывающую на замену (layout → закрепить позицию через style; include/exclude → выбирать через show/hide).

export, use, from и as допустимы как в файле .arch (импорты use … from с файловой областью видимости — as переименовывает локально — и export type / export interface / export subprocess), так и в манифесте package.archspace.

package, name, version, widgets, repo, commit и dependencies распознаются как имена полей/блоков в грамматике манифеста, но не являются зарезервированными словами — они нигде не затеняют идентификаторы.

Типы модулей (service, database, system, …), типы поверхностей и типы интерфейсов (rest_create, rest_read, kafka, …) не являются зарезервированными ключевыми словами — это пользовательские идентификаторы, вводимые декларациями type module …, type surface …, type interface ….

Базовые типы. module, surface, interface — предзарегистрированные базовые типы; это идентификаторы, не ключевые слова. process и view — предзарегистрированные базовые типы и зарезервированные ключевые слова, потому что их тела имеют специализированные грамматические правила.

{ } ( ) [ ] : , ; . * > = | \

Стрелка шага процесса — > (одиночный «больше»); = привязывает результат шага (order = Customer > Orders.CreateOrder); * — маркер каскад-группы (cascade *); ; разделяет шаги в строку внутри блока/тела; | (веер) и \ (змейка) — ведущие строчные глифы в теле процесса: | пишет шаг против головы предыдущего соседа, \ — против его хвоста. Фрагменты грамматики ниже это отражают.

program = declaration* ;
declaration = use_decl
| type_decl
| module_decl
| process_decl
| view_decl
| subprocess_decl
| process_step (* v0.7: a bare step at root — an anonymous
single-step process; caller is explicit at
root, no enclosing module *)
;
(* `use` is valid at the top of a `.arch` file (file-scoped) and in a
`package.archspace` manifest (package- or space-scoped). *)
use_decl = "export"? "use" use_targets "from" dotted_name ;
use_targets = "*" | use_import ("," use_import)* ;
use_import = identifier ("as" identifier)? ;
dotted_name = identifier ("." identifier)* ;
type_decl = "export"? "type" stable_id? parent_type identifier type_body? ;
parent_type = qualified_name ; (* validator rejects 'process' and 'view'; may be space-qualified *)
type_body = "{" type_member* "}" ;
type_member = type_field_decl
| aspect_decl (* replaces type_label_block *)
| type_aspect_blank
| type_subdecl
;
type_field_decl = field_modifier* ( "required" field_path
| field_path ":" value field_body?
) ;
field_modifier = ( "cascade" "*"? ) | "append" ;
field_body = "{" field_body_member* "}" ;
field_body_member = ( "required" | "cascade" | "append" )*
field_path ( ":" value field_body? )? ;
field_path = identifier ("." identifier)* ;
(* `required aspect <key>` — a mandatory aspect blank on a type body.
`cascade`/`append` are rejected on aspects; aspects always cascade with
override semantics (Spec §4.4). *)
type_aspect_blank = "required" "aspect" aspect_key ( ":" aspect_value_list )? ;
type_subdecl = "required"? ( surface_decl | interface_decl | module_decl ) ;
value = identifier | quoted_string | number | "true" | "false" ;
module_decl = type_name stable_id? identifier? in_clause? module_body? ;
(* v0.7: the name (identifier) may be OMITTED for a CUSTOM-typed nested module —
it auto-takes its type name, snake_case→UpperCamelCase (type `database` →
`Database`). Validator constraints not in the EBNF: base `module` does not
qualify; the applied type must be used exactly ONCE in the parent; no
stable_id until promoted; it is NOT a TODO (fully resolved). *)
type_name = identifier ; (* registered module type *)
in_clause = "in" qualified_name ;
module_body = "{" module_member* "}" ;
module_member = field_assignment
| description
| aspect_decl (* replaces labels_block *)
| surface_decl
| interface_decl
| module_decl (* nested submodules *)
| process_decl (* module-scoped process *)
| subprocess_decl (* reusable helper *)
| process_step (* v0.7: bare step — anonymous single-step
process; implicit caller = this module *)
| drop_stmt
| override_decl
;
description = quoted_string ;
field_assignment = field_path ":" value ;
(* ASPECT — the keyword takes no dot after it (`aspect deployment.zone`, not
`aspect.deployment.zone`). An inline entry's value is OPTIONAL (a bare
`aspect domain` is a valueless blank). A bare STRING value is a
classification (a shared group identity); a bare MODULE-REF value is a
membership — the ref must resolve to a real module, which becomes a
*place* on that key's plane (Spec §4.4). Block entries are separated by
`;` or a newline — NEVER a comma; `aspect { a: "x", b: "y" }` is a parse
error, write `aspect { a: "x"; b: "y" }` or one entry per line. *)
aspect_decl = "aspect" ( aspect_inline | aspect_block ) ;
aspect_inline = aspect_key ( ":" aspect_value_list )? ;
aspect_key = identifier ("." identifier)* ;
aspect_block = "{" aspect_entry* "}" ; (* SINGULAR keyword — one block, many keys;
entries separated by `;` or a newline *)
aspect_entry = "drop" aspect_key
| "required" aspect_key ( ":" aspect_value_list )?
| aspect_key ":" aspect_value_list
;
(* Aspect VALUES accept a scalar, a bracketed multi-value list (multi-
membership — `tag: ["edge", "public"]` joins both), OR a module reference
(dotted — places nest, e.g. `Euro.DC1`) with an optional `as <instance>`
naming the member's instance at that place. `as` is legal ONLY in an
aspect value — in a general field value it is a parse error. Multiple
bare values on one line are comma-separated (`Euro as active, Euro as
standby`); trailing `,` is a parse error. *)
aspect_value_list = aspect_value ( "," aspect_value )* ;
aspect_value = quoted_string | number | "true" | "false" | bracket_array
| qualified_name ( "as" identifier )? ;
bracket_array = "[" ( aspect_value ( ","? aspect_value )* ","? )? "]" ;
drop_stmt = "drop" qualified_name ;
override_decl = "override" ( surface_decl | interface_decl | module_decl ) ;
surface_decl = surface_type identifier surface_body? ;
surface_type = "surface" | identifier ; (* "surface" or user-defined *)
surface_body = "{" surface_member* "}" ;
surface_member = field_assignment
| description
| aspect_decl (* classification/required/drop forms only —
a surface confers no membership presence *)
| surface_decl (* nested surfaces *)
| interface_decl
| drop_stmt
| override_decl
;
interface_decl = "export"? type_name identifier interface_body? ;
type_name = identifier ; (* registered interface type *)
interface_body = "{" interface_member* "}" ;
interface_member = field_assignment
| description
| aspect_decl (* classification/required/drop forms only —
an interface confers no membership presence *)
| drop_stmt
;
(* v0.7 removed the `subscribes:` member. An event is an async-kind interface
(e.g. `kafka`) reached by an ordinary `>` process edge — no subscription
wiring on the interface. *)
process_decl = "process" stable_id? identifier in_clause? "{" process_body "}" ;
process_body = ( description | process_step )* ;
process_step = ( simple_step step_name? )
| note_step
| each_step
| if_step
| select_step
| try_step
| parallel_step
| reversible_step
| await_step
| do_step
| go_step
| owner_block
| fail_step
| finish_step
;
(* OWNER — who performs the node. A qualified name (module / actor / earlier-bound
name), `|` (fan = the previous sibling's head), or `dist` (distributed /
emergent — no coordinator). Omitted = inherit from the nearest enclosing owner,
else a TODO. `dist` is control-only (a call always has a caller) and does NOT
propagate. Resolution: explicit → `|` → nearest non-dist enclosing → TODO. *)
owner = qualified_name | "|" | "dist" ;
caller = qualified_name | "|" ; (* note / `do` / unwind-call owner:
never `dist`, never `\` *)
call_caller = caller | "\\" ; (* `\\` (snake) is call-only *)
(* OWNER BLOCK — set the owner (or `dist`) for a run of steps. *)
owner_block = ( qualified_name | "|" | "dist" ) block ;
(* CALL — caller (= owner) left of `>`, may be omitted (anonymous head / bare) or
`|` (fan) / `\` (snake, call-only). Hops CHAIN: `A > B > C` desugars to `A > B ; B > C`, each hop's
caller being the top module of the previous callee; binding captures the final
hop; linear only. A call_target resolving to a MODULE synthesizes an anonymous
interface (a TODO); a trailing "." marks an unspecified interface (a TODO). *)
simple_step = ( identifier "=" )? call_caller? ( ">" call_target arg_list? )+ ;
step_name = "as" identifier ; (* name a step for `go` / diff *)
(* A path. A single "." is the sole separator. A TRAILING "." marks the terminal
interface as unspecified/anonymous (a TODO). A reserved keyword is legal in
member position after "." (`Worker.process`) — never as the first segment. *)
call_target = identifier ( "." member_seg )* "."? ;
member_seg = identifier | keyword ;
arg_list = "(" ( arg ("," arg)* )? ")" ;
arg = identifier ; (* bare intent names; not type-checked *)
(* NOTE — owner-anchored free-form string, no edge. The owner is REQUIRED
(`Orders "…"`); a bare string is a description, not a note. *)
note_step = caller quoted_string ;
(* All heads / conditions / labels / await targets below are FREE-FORM: rendered,
not resolved or validated. A body is a brace block or a single `:`-step. *)
(* BRANCH — exclusive choice. A condition with no body = a draft branch. *)
if_step = owner? "if" condition? body?
( "else" "if" condition? body? )*
( "else" body? )? ;
condition = identifier | quoted_string ;
(* SELECT — multi-way, INCLUSIVE (every matching case runs). `one` = first match,
N = first N matching in order, bare = all matching. *)
select_step = owner? "select" select_count? select_head? "{" select_case+ "}" ;
select_count = "one" | number ;
select_head = identifier | quoted_string ;
select_case = case_label body ;
case_label = identifier | quoted_string ;
(* LOOP / RETRY. `each` iterates. `each <bound> try { } catch { } else { }` = the
retry form (catch = restoration + retry; success exits; bound exhausted → else).
A `try` *nested* in a plain `each` body is a per-iteration guard. *)
each_step = owner? "each" each_head? ( retry_tail | body ) ;
each_head = ( ( identifier | quoted_string ) ( "in" ( qualified_name | quoted_string ) )? )
| number ;
retry_tail = "try" block ( "catch" catch_label? body )* ( "else" body )? ;
catch_label = identifier | quoted_string ;
(* GUARD — plain try/catch: handle the failure, continue forward (no retry). *)
try_step = owner? "try" body ( "catch" catch_label? body )* ;
(* PARALLEL — concurrent branches with a PREFIX merge clause. `out` = detach (no
wait); `join N` = proceed at N, keep losers (N omitted = all); `race N` =
proceed at N, cancel losers (N omitted = 1). The merge may be a boolean over
branch names. `select` = concurrent inclusive; `each` = fan-out. *)
parallel_step = owner? "parallel" "out"? merge_clause? parallel_body ;
parallel_body = ( "each" each_head block ) (* fan-out: a loop body *)
| ( "select" select_count? "{" branch+ "}" ) (* concurrent inclusive *)
| ( "{" branch+ "}" ) ; (* plain branches *)
merge_clause = ( "join" | "race" ) merge_spec? ;
merge_spec = number | bool_join ;
bool_join = bool_and ( "or" bool_and )* ; (* `or`/`and` case-insensitive; no `not` *)
bool_and = bool_atom ( "and" bool_atom )* ;
bool_atom = identifier | "(" bool_join ")" ; (* branch names only *)
(* BRANCH — a parallel branch: optionally NAMED (the name a boolean merge refers
to), then a one-liner, a block, or a nested control node. *)
branch = "branch" branch_name? ( ( ":" process_step ) | block | control_step ) ;
branch_name = identifier ;
control_step = if_step | select_step | each_step | try_step | parallel_step
| reversible_step | await_step ;
(* AWAIT — timed / event wait, owned by the waiter; target free-form. *)
await_step = owner? "await" await_target ;
await_target = quoted_string | ( identifier | number )+ ;
(* REVERSIBLE — saga span; trigger defaults to `on error`. Each forward step or
block may carry an `unwind` (inline catch-and-rethrow). `unwind` may NOT nest
inside control — wrap the control in a block and put `unwind` on the block.
A line may be reversal-only (`unwind` with no forward action). *)
reversible_step = owner? "reversible" trigger? "{" reversible_item* "}" ;
trigger = "on" ( identifier | quoted_string ) ;
reversible_item = description
| ( ( simple_step | block ) unwind_clause? step_name? )
| ( unwind_clause step_name? ) (* reversal-only line *)
| note_step | do_step | go_step | fail_step | finish_step
| control_step ;
unwind_clause = "unwind" unwind_action ;
unwind_call = ( identifier "=" )? caller? ( ">" call_target arg_list? )+ ;
unwind_action = unwind_call | fail_step | finish_step | do_step | block ;
(* RE-ENTRY / REUSE — structural ops, OWNERLESS. `go` re-enters a step named with
`as` and resumes forward. `do` splices a subprocess; an optional caller prefix
is the TEMPLATE default-caller for the subprocess's owner-omitted steps (bound
to `subprocess X on Caller`), NOT an owner of the `do`. *)
go_step = "go" identifier ;
do_step = caller? "do" qualified_name arg_list? ;
fail_step = "fail" quoted_string? ; (* terminate: failure *)
finish_step = "finish" quoted_string? ; (* terminate: success *)
(* Steps in a block / body are separated by newline OR `;` (inline). *)
block = "{" ( process_step ( ";" process_step )* )? "}" ;
body = block | ( ":" process_step ( ";" process_step )* ) ;
subprocess_decl = "export"? "subprocess" stable_id? identifier on_caller? param_list? "{" process_body "}" ;
on_caller = "on" identifier ; (* template default-caller param, bound at the `do` site *)
param_list = "(" ( identifier ("," identifier)* )? ")" ; (* intent-only, not bound *)

Тело view — это тело УПОРЯДОЧЕННЫХ КЛАУЗ (как process/policy): зона заголовка (описание / поле / knob), за которой следует последовательность упорядоченных клауз. Проекция несёт не более одной клаузы представления (table / matrix / flow) — доска — это отсутствие такой клаузы.

view_decl = "view" stable_id? view_head "{" view_body "}" ;
(* views are never `export`ed. Declares at file top level or
in a module body; `in <Module> view … { }` is the placed
twin of the body form. A module-scoped view resolves
selector names from its module context first. *)
view_head = identifier identifier? ; (* <Name> | <ParentView> <Name> —
the two-identifier form is a VIEW INSTANCE: its clauses
append to the referenced (parent) view. *)
view_body = view_header_member* view_clause* ;
view_header_member
= description
| knob_decl (* a view parameter, four shapes below *)
| field_assignment (* base view only *)
| knob_binding ; (* instance only — binds a parent-view knob *)
knob_binding = identifier ":" signed_value ;
view_clause = show_clause | hide_clause | focus_clause | group_by_clause
| on_clause | style_clause | flow_clause
| table_clause | matrix_clause | grid_clause | lens_clause
| story_clause ;
show_clause = "show" selector ; (* union matching nodes/edges into the view *)
hide_clause = "hide" selector ; (* subtract *)
focus_clause = "focus" selector ; (* emphasis; never changes the node selection *)
group_by_clause = "group" "by" ( getter | "in" selector ) ;
(* nest by repetition; the getter is sigiled (`@@team`) and groups
by VALUE, the `in` form groups by CONTAINMENT — its selector
names the containers, one frame each *)
on_clause = "on" plane_name ("," plane_name)* ; (* one = plane board, many
= weave; an `on` in a `flow` view diagnoses — a process
carries its own plane *)
style_clause = "style" selector "{" style_member* "}" ; (* the universal
rewrite rule; the subject may be `violating <Policy>` *)
flow_clause = "flow" ( qualified_name | knob_ref ) [ "sequence" | "bpmn" ] flow_body? ;
(* the process representation; plain `flow X` is the swimlane
walkthrough, `sequence` a UML lifeline diagram, `bpmn` a
lane/pool BPMN. `flow $knob` parameterizes the subject.
A `flow_body` is `bpmn`-only. *)
flow_body = "{" ( lane_entry | pool_entry )* "}" ;
lane_entry = "lane" "by" getter (* one lane per getter value *)
| "lane" quoted_string ":" selector (* named lane merging a query *)
| "lane" selector ; (* expand one callee's steps *)
pool_entry = "pool" "by" getter (* one pool per getter value *)
| "pool" quoted_string ":" selector (* named pool merging a query *)
| "pool" ( qualified_name | knob_ref ) ; (* one pool for an element ref *)
table_clause = "table" "{" ( table_column | table_sort )* "}" ;
table_column = "column" getter quoted_string? (* getter form — editable *)
| "column" quoted_string ":" expression ; (* calc form — read-only *)
table_sort = "sort" "by" getter ;
matrix_clause = "matrix" ( "{" matrix_entry* "}" )? ;
matrix_entry = "axis" getter | "rows" getter | "cols" getter
| "order" "by" matrix_order | "cluster" ;
matrix_order = "cluster" | "layer" | getter ;
(* DSM: `axis` groups the shared SQUARE axis; `rows`/`cols` diverge only
for a rectangular matrix (no `order`/`cluster` there). `order by
cluster`/`layer` runs SCC + topological analysis; `cluster` boxes
cycles on the diagonal and implies `order by cluster`. *)
grid_clause = "grid" ( "{" ( "rows" getter | "cols" getter | "color" getter )* "}" )? ;
(* cross-tab: cells LIST the modules at each (row, col) label
intersection; `color` tints members by a third field. *)
lens_clause = "lens" "{" lens_entry* "}" ; (* computed inspector values *)
lens_entry = identifier quoted_string? ":" expression ; (* name ["Title"]: <expr> *)
story_clause = "story" "{" story_chapter+ "}" ; (* an ordered walk; at most 5 chapters *)
story_chapter = "chapter" quoted_string "{" quoted_string? story_node_list "}" ;
(* the LEADING string is the chapter's note, the same law
module and view descriptions obey; the nodes follow, and
their WRITTEN ORDER is the narration's order *)
story_node_list = selector ( ("," | logical_line_break) selector )* ;
plane_name = qualified_name ; (* dotted, open-world; no `plane` keyword *)

В story_node_list logical_line_break — это перевод строки, разделяющий два селектора: единственное место в теле проекции, где перенос строки несёт смысл, чтобы узлы главы можно было писать по одному на строку, а не через запятую.

Тело style. Тело клаузы style (общее с декларацией верхнего уровня style bundle) — это набор переопределений полей, вложенных ГРУПП полей без двоеточия, директив rename / pin и use <Bundle>.

style_member = style_rewrite | style_group | rename_directive | pin_directive | style_use ;
style_rewrite = qualified_name ":" expression ; (* RHS is the one expression grammar *)
style_group = qualified_name "{" style_member* "}" ; (* colon-less nested field group *)
rename_directive = "rename" quoted_string ; (* a display label *)
pin_directive = "pin" signed_number "," signed_number ; (* WYSIWYG position; drag writes it *)
style_use = "use" identifier ; (* apply a named style bundle *)
signed_number = "-"? number ;

Ручки (knob). knob — это параметр проекции в одной из ЧЕТЫРЁХ форм. Голова снимает неоднозначность: knob { открывает блок записей перечисления; иначе, после имени ручки, : → литеральное значение по умолчанию (число/слайдер), from → выпадающий список значений, а второй идентификатор → выбор элемента по типу.

knob_decl = "knob" ( knob_block | knob_number | knob_from | knob_element ) ;
knob_block = "{" knob_enum_entry* "}" ;
knob_enum_entry = identifier ( "one" | "any" ) "of" ":" atomic_value ("," atomic_value)* ;
(* single | multi; each element is ATOMIC — string / identifier /
number ONLY (no boolean, no array) *)
atomic_value = identifier | quoted_string | number ;
knob_number = identifier ":" signed_value knob_restrictions? ; (* literal default ⇒ slider *)
knob_restrictions = "{" ( ("min" | "max" | "step") ":" signed_number )* "}" ;
knob_from = identifier "from" getter ; (* a dropdown over a plane/aspect's values *)
knob_element = identifier identifier ; (* `knob <type> <name>` — a kind-led element picker *)
(* A kind-determined value slot (knob default / binding) is a `scalar` —
string, identifier, number, or boolean — with an optional leading `-` on a
numeric literal (drag writes negatives). It is NOT an array. *)
signed_value = "-" number | scalar ;

Селектор выбирает набор элементов модели ИЛИ набор рёбер — его сорт определяется по тексту (стрелка делает его edge-сортом, иначе node-сортом). Селекторы — общее ядро под view (show/hide/focus/style), policy и violating. Приоритет (от слабого к сильному): or < and < not < стрелка/терм.

selector = sel_or ;
sel_or = sel_and ("or" sel_and)* ;
sel_and = sel_not ("and" sel_not)* ;
sel_not = "not" sel_not | sel_term ;
sel_term = sel_arrow | sel_primary ;
(* Edge patterns. A chain (A > B > C) is a single edge-sort term — the union of
its hops. `>` = direct adjacency, `>>` = directed reachability cone,
`<>` = undirected (symmetrized). `within N` is a suffix after a hop's right
side (`A <> B within 2`); on a chain it binds — and distributes — per hop. *)
sel_arrow = sel_primary ( arrow_op sel_primary within_bound? )+ ;
arrow_op = ">" | ">>" | "<>" ;
within_bound = "within" ( NUMBER | knob_ref ) ;
sel_primary = "*" (* universal — every element *)
| "this" (* the bound subject (predicates/policies) *)
| "outside" (* elements not selected in this view *)
| knob_ref (* $name — a view knob *)
| aspect_atom (* @@key[:"value" | :$knob] *)
| "violating" qualified_name (* a policy's active findings; sort follows it *)
| extraction
| predicate
| "(" selector ")"
| qualified_name ; (* a built-in kind, a user type (incl. subtypes),
or an element reference *)
extraction = ("sources" | "targets" | "nodes" | "owners") "of" "(" selector ")" ;
predicate = "in" sel_primary (* structural containment / process participation *)
| "on" plane_name (* plane membership *)
| "where" "(" expression ")" ; (* a boolean expression per candidate *)
aspect_atom = "@@" qualified_name (":" (value | knob_ref))? ;
(* glued: @@team, @@zone:"pci", @@zone:$zone *)
knob_ref = "$" identifier ;

Грамматика выражений — это язык значений: геттеры с сигилами над элементом, операторы, агрегаты и функции. Селекторы и выражения встречаются ТОЛЬКО в скобочных ключевых порталах (where (…), exists (…), count (…), <agg> <getter> of (…)).

expression = expr_or ;
expr_or = expr_and ("or" expr_and)* ;
expr_and = expr_not ("and" expr_not)* ;
expr_not = "not" expr_not | expr_compare ;
expr_compare = expr_add ( compare_op expr_add )? ;
compare_op = "=" | "!=" | "<" | "<=" | ">" | ">=" ;
expr_add = expr_mul ( ("+" | "-") expr_mul )* ;
expr_mul = expr_unary ( ("*" | "/") expr_unary )* ;
expr_unary = "-" expr_unary | expr_primary ;
expr_primary = literal
| "this"
| getter
| aggregate
| exists
| function_call
| "(" expression ")" ;
(* Getters. `@field` reads a field, `@@aspect` reads an aspect; builtins include
@name @type @kind @connections @todos @dist @reversible @compensated. A `(…)`
suffix is a capability getter with raw argument text (e.g. @ver.changes(...)). *)
getter = ("@" | "@@") getter_path ( "(" raw_args ")" )? ;
getter_path = word ("." word)* ; (* word may be a reserved keyword after a sigil *)
aggregate = "count" "(" selector ")"
| "count" "distinct" getter "of" "(" selector ")"
| ("sum" | "min" | "max") getter "of" "(" selector ")" ;
exists = "exists" "(" selector ")" ;
function_call = identifier "(" ( expression ("," expression)* )? ")" ; (* colorize / heat / css *)
literal = quoted_string | number | "true" | "false" | identifier ;
(* a bare identifier is an identifier-VALUE (a bareword, like an
unquoted string). The reserved expression words `this` /
`exists` / `count` / `sum` / `min` / `max`, and an identifier
glued to `(` (a function_call), are matched by earlier
expr_primary alternatives — none fall through to this
identifier-as-literal case. *)

policy — это запрос над моделью, выдающий находки (узел или ребро, нарушающие правило), обеспечиваемый archlang check, всплывающий как диагностика LSP и выбираемый через violating <Policy>. Политика объявляется на верхнем уровне файла или в теле модуля; in <Module> policy … { } — размещённый двойник формы с телом. Область видимости политики, привязанной к модулю, — это поддерево этого модуля: находки по узлам — по членству в поддереве, находки по рёбрам — по их исходной конечной точке.

policy_decl = "export"? "policy" stable_id? identifier "{" policy_body "}" ;
policy_body = ( header_field | policy_rule )* ;
header_field = qualified_name ":" value ; (* e.g. severity: error | warning | advisory *)
policy_rule = forbid_rule | require_rule | except_rule | escalate_rule | when_gate ;
forbid_rule = "forbid" selector ; (* a finding per matching node/edge *)
require_rule = "require" selector ":" selector ; (* subject : obligation — a finding when unmet *)
except_rule = "except" selector quoted_string field_body? ; (* a waiver; reason mandatory *)
escalate_rule = "escalate" qualified_name "to" identifier ; (* raise-only severity override *)
(* Change gate — evaluated over a base→head diff (the accept-gate). The
`changed(<getter>)` portal narrows to elements whose getter value differs; it
is legal on `changed` ONLY — `added`/`removed` take no portal. An edge subject
(arrow / `violating`) takes `added`/`removed` only. *)
when_gate = "when" selector change_verb "{" review_req* "}" ;
change_verb = "added" | "removed" | "changed" ( "(" getter ")" )? ;
review_req = "require" "review" "from" ( NUMBER "of" )? ( getter | identifier ) ;

Декларации запросов, пакетов стилей и линз (v0.10)

Заголовок раздела «Декларации запросов, пакетов стилей и линз (v0.10)»

Четыре декларации управления/словаря, переиспользующие ядро селекторов/выражений. Все они диспетчеризуются по заголовку члена: заголовок, за которым следует : / ., — это поле, а не одна из этих деклараций. Стабильный id, если присутствует, стоит сразу после ключевого слова. view, policy, query и lens объявляются на верхнем уровне файла или внутри тела модуля; view и policy дополнительно размещаются через in <Module> …. style bundle и use policy остаются только верхнего уровня.

query_decl = "export"? "query" stable_id? identifier ":" selector ;
(* a named, reusable selector; `export` publishes it across
the package boundary *)
style_bundle_decl = "style" "bundle" stable_id? identifier "{" style_member* "}" ;
(* a reusable style body (style_member, above), applied
elsewhere via `use <Bundle>`. Not `export`able. *)
lens_decl = "lens" "{" lens_entry* "}" ;
(* a computed-inspector block at top level or in a module
body; reuses lens_entry (above). Unnamed — no name, no
stable_id (dispatched on `lens` immediately followed by `{`). *)
use_policy_stmt = "use" "policy" identifier "from" dotted_name policy_use_body? ;
policy_use_body = "{" ( except_rule | escalate_rule )* "}" ;
(* governance INSTANTIATION, not a type import: pulls a
published `policy` from a package, then may locally
waive (`except`) or raise (`escalate`) it. *)

Заголовки view/policy/селекторов ниже — это контекстные идентификаторы — распознаются по позиции (заголовок клаузы / заголовок члена / слот после сигила), а не жёсткие зарезервированные слова; каждый остаётся допустимым идентификатором везде в остальных местах. Исключение — focus, group, by, on и one: они ЯВЛЯЮТСЯ жёсткими лексерными ключевыми словами (§Зарезервированные ключевые слова), переиспользуемыми в позиции view/селектора. Обратите внимание на асимметрию one / anyone жёсткое ключевое слово, any контекстное.

show, hide, style, flow, table, matrix, lens, story, chapter, knob, column,
sort, rename, pin, rows, cols, any, of, bundle, sequence, bpmn, lane, pool,
policy, query, forbid, require, except, escalate, when, review, severity,
violating, sources, targets, nodes, owners, this, outside, where, exists,
count, sum, min, max, distinct, and, or, not, within

Манифест объявляет ровно одно из: package: (корень пакета — непрозрачная единица разрешения) или name: (пространство внутри объемлющего пакета). Полную модель видимости см. в PACKAGE-ARCHSPACE.

manifest = manifest_field* ;
manifest_field = package_field
| space_field
| version_field
| widgets_field
| description
| evidence_pin
| dependencies_block
| policy_packs_block
| use_decl
;
package_field = "package" ":" dotted_name ; (* package root — opaque unit *)
space_field = "name" ":" dotted_name ; (* a space within a package *)
version_field = "version" ":" quoted_string ;
widgets_field = "widgets" ":" quoted_string ;
description = quoted_string ;
evidence_pin = ( "repo" ":" quoted_string ) | ( "commit" ":" quoted_string ) ;
(* `package.archspace` ONLY: the code repository this
package's `sources:` bindings are checked against, and
the revision they are true of. `commit` is required
whenever `repo` is set — an unpinned path attests that a
file existed once, not that the model is true of a
commit. The bindings themselves are ordinary fields
(`sources: "src/a.ts:12-88", "src/b.ts"`), so they need
no production of their own. *)
dependencies_block = "dependencies" "{" dep_entry* "}" ;
dep_entry = dotted_name ":" quoted_string ","? ;
policy_packs_block = "policies" "{" ( dotted_name ":" quoted_string )* "}" ;
(* `package.archspace` ONLY (peer of `dependencies { }`): the
governance PACKS this package adopts, each pinned to the
version the pack declares. Adoption is what makes a pack
un-droppable — the gate fails if a pinned pack stops
resolving, ships a different version, or has one of its
exported policies no longer imported. *)
(* `use_decl` as defined under Top-level; package-scoped at the package root,
space-scoped in a space manifest. *)
dotted_name = identifier ("." identifier)* ;

.arch для исходных файлов. package.archspace для манифеста.

UTF-8.

Синтаксически не значимы вне строк в кавычках и комментариев. Отступы рекомендованы для читаемости; стандартная библиотека использует 4 пробела.

Строка в кавычках может занимать несколько строк. Лексер автоматически снимает отступ: ведущая строка из одних пробелов и минимальный общий отступ на остальных строках срезаются. Распознаются экранирования \" и \\.

Строки в тройных кавычках ("""…""") — сырые: без экранирований, без интерполяции.