Oberon RTK

ECS for Control: The ECS File

Reference for the ECS file, the program's definition extracted from its sources: the kind declaration every module carries, the ROLENAMES and MODULES sections and their kinds, the DECLARATIONS, the HEADER of a file written for generation, and the ECS map file beside it.

Overview

An ECS file is a text file beside a program's sources that states the program's definition in the terms of the architecture: which modules exist, of which kind, under which names they play their kind's roles, and where they belong; which tokens, surfaces, manifests, library imports, adapter procedures, periods, resources, schedules and crossings the sources establish. The file is extracted from the sources: every module declares its kind in its own text, and the extractor reads the declarations and the Oberon text and writes the file. Nothing in it is inferred.

One term recurs throughout and is worth having in hand from the start: a schedule is one run of the kernel's loop over its own set of partitions, and a program has one or more of them. Each schedule runs on its own core: a program with several schedules is a multi-core program, one binary with one schedule per core. Where two schedules exchange data, the file declares the crossing, and the program with one schedule is simply the case where nothing crosses.

Three things to know before the terms:

  • The file is not required to implement the architecture. A program written to the architecture's discipline is complete without it; what the discipline requires is the kinds' constructs, and what the tools require is the kind declaration in each module. The file is what makes the program's structure statable in one place and comparable between versions: the extractor writes it from the sources, the checker verifies the program against what its modules declare, and the generator writes a skeleton from a file written for that purpose.

  • The kinds are the architecture's module taxonomy. Module names are the programmer's free choice; the kind names are fixed, and each names exactly one kind of module with one placement. The documents of this set use the kind names as their vocabulary for that reason: "a SYSTEM module of a PARTITION" says what no module name can.

  • The file has two roles under one grammar. The extracted file is the record of a program: its head and allow-lists lines, ROLENAMES, MODULES and DECLARATIONS, written by extract-ecs, regenerated whenever the sources change, never edited. A file written for generation is authored: ROLENAMES, MODULES and the HEADER the generated modules carry, and no DECLARATIONS, since there are no sources yet. A file whose content disagrees with the sources is a finding, not a decision.

  • The program is a built program. The extractor reads a program that compiles and links, verifies that by the build's own products, the code, symbol and binary files and their dates, and never invokes the compiler. The linker's map file names every linked module with its location, and that list is the program: a module in the tree that did not link is not in it, and a library transport an adapter names is found there by name, not along a search path. Every finding the extractor reports is therefore a finding about the architecture, never about the Oberon.

The grammar has one writer, extract-ecs (its manual page), and one reader, the generator that writes a program skeleton from an authored file; check-ecs reads the sources only and never the file. The grammar is strict: an unknown section, kind or fact, or a reference that resolves to nothing, is an error, never skipped. Beside the ECS file the extractor writes the ECS map file, the program's definition in the forms a reviewer reads (The ECS Map File, below).

The Kind Declaration

Every module of a program declares its kind for the tools in a comment of its own before the MODULE line, among any other comments there; a program reviewed by hand alone needs no declaration:

(* ECSKIND: SYSTEM Manifest Name Run *)
MODULE BlinkSystem;

The comment holds the tag ECSKIND:, the kind, and the kind's parameters: the module's names for the kind's roles, in the order the parameters table gives (The Kinds, below), variables before procedures. A kind's roles are fixed; the names are the module's own, so S and Build play the STATE kind's two roles in one program and Store and Create may play them in another. A parameter names a role's item if the module has one: a LOCKED module without an interface record still declares LOCKED L I, and the declaration says what the record would be called, not that it exists. An ADAPTER has no roles and one parameter, the transport it serves. A kind without roles is declared bare, (* ECSKIND: PERIODS *).

The comment is the declaration, and its formatting inside is free, as whitespace is free in Oberon: the comment's text, with whitespace and asterisks stripped from both ends, is the tag, an optional space, the colon, the kind and the parameters, separated by whitespace of any kind, newlines included, and nothing else. (**** ECSKIND:STATE S Build ****) and a declaration spread over three lines are the same declaration. A comment whose stripped text does not start with the tag is not a declaration, whatever it mentions; one that starts with the tag and does not match the form is a finding, never skipped.

Exactly one kind declaration per module, read before anything else: a module without one, with two, with an unknown kind, or with the wrong number of parameters is a finding that stops the extraction and the check. The declaration is the module's statement of what it is, and every check reads the module in that light: as each variable has a type, each module has a kind.

The Sections

An ECS file has a head line, an allow-lists line, and up to four sections, in this order:

Section In the extracted file In a file for generation Content
head line written optional the label and the extraction's date and time
allow-lists line written absent the allow-lists file the extraction used, by that file's head line
ROLENAMES written authored the program's names for the kinds' roles, once per kind
MODULES written authored the module inventory: groups, entries, kinds and parameters
DECLARATIONS written absent the facts the sources establish, per module, inside the frame of their group
HEADER absent authored the header block the generated modules carry, verbatim until the end of the file

The head line is the label, --, the word extracted, the date and time to the minute, and the extractor's name and version: EcsControlSchedule-v29 -- extracted 2026-10-12 14:05 extract-ecs 1.0. It is the first line of the file when present, and it takes no part in the comparison of two files.

The allow-lists line is the keyword ALLOWLISTS and the head line of the allow-lists file the extraction read, verbatim. The allow-lists file is the tools' data for the kinds' import lists, versioned with the library; a project may run the tools on a copy of it, and the line records under which file the facts were extracted. It follows the head line, and it takes part in the comparison.

A section starts with its keyword on a line of its own. Comments are Oberon comments, (* ... *), and may appear anywhere outside the HEADER section. No separators are written: one item per line is the house style, and the next token decides every boundary.

ROLENAMES

The ROLENAMES section states the program's names for the roles, one line per kind, in the kind table's order:

ROLENAMES
  PARTITION Create AddSystems
  SYSTEM Manifest Name Run
  STATE S Build
  LOCKED L I
  TOKENS L I Names
  CONS Assert
  BOUNDARY Crossings

A kind's line is the default for its entries: an entry carries no parameters of its own unless its module deviates, and then it carries all of them, OddSystem: SYSTEM Manifest Name Step. A kind absent from ROLENAMES has its parameters on every entry of that kind. The extractor writes as a kind's line the names most modules of the kind use, and writes no line, every entry explicit, when no names have a majority; a file for generation may use the section as it likes. Every spelling of the same names means the same, and the comparison of two files reads the meaning, not the text.

MODULES

The MODULES section is the program's inventory. It consists of groups, and every group lists entries.

Groups

A group is written as a name, =, a group kind, its entries, and END. There are four group kinds:

Group kind Names Lists
PROGRAM the program, by its label the program-level modules: one SCHEDULE entry per schedule, the program-level definition modules, the boundary, and the OTHER and transport modules of the program level
SCHEDULE a schedule's module the partitions the schedule creates, one PARTITION entry each, in the order of their creation
PARTITION a partition the partition's modules
LIBRARY a library, by a free name the library transports the program's adapters serve

A file has exactly one PROGRAM group, one SCHEDULE group per schedule, one PARTITION group per partition, and any number of LIBRARY groups. A partition belongs to exactly one schedule. The PROGRAM group is a group, never a module: no module carries the program's label.

The PROGRAM group of a one-schedule program, from EcsControlSchedule-v26:

  EcsControlSchedule-v26 = PROGRAM
    EcsControlScheduleV26: SCHEDULE
    P: PERIODS
    R: RESOURCES
    T: TOKENBASES
    N: TOKENNAMES
    C: CONS
    MemMap: OTHER
  END

The entry's parameter, the gate, comes from the ROLENAMES line, SCHEDULE run here: schedule 0's gate is a local procedure, called from the module body and from nowhere else, while every other schedule's gate is exported and started by a deployment call. A two-schedule program has two SCHEDULE entries in its PROGRAM group and a BOUNDARY entry beside them; each schedule group then lists its own partitions:

  EcsControlScheduleS0v29 = SCHEDULE
    BlinkPartition: PARTITION
    CoordPartition: PARTITION
    MonPartition: PARTITION
    PrintPartition: PARTITION
  END

  EcsControlScheduleS1v29 = SCHEDULE
    AdaptPartition: PARTITION
  END

The one-schedule program is the case of one SCHEDULE group: there is one notation, not two.

Entries

An entry is written as a module name, :, a kind, and the kind's parameters when the entry carries its own (ROLENAMES, above): BlinkSystem: SYSTEM under a ROLENAMES line for SYSTEM, OddSystem: SYSTEM Manifest Name Step when the module's names deviate. The kind and the parameters are those of the module's kind declaration, verbatim. Several modules of one kind may share a line, comma-separated, when none carries parameters. The = of a group defines an entity; the : of an entry places a module under its kind.

An ADAPTER entry always carries its one parameter, the transport it serves: X: ADAPTER Y, where Y is a transport declared in a partition or in a LIBRARY group.

A PARTITION group, from the same program:

  CoordPartition = PARTITION
    PeriodSystem: SYSTEM
    CScoord: STATE
    CLcoord: LOCKED
    Tcoord: TOKENS
    Wcoord: WIRING
    Vcoord: VALUES
    PeriodPort: SPORT
    PeriodPortAdapt: ADAPTER PeriodPort
  END

A LIBRARY group declares the transports a program takes from a library rather than from its own tree; its members are transport kinds only. The extractor finds them through the adapters' parameters, locates them by name in the linker's map file, and reads their kind declarations where they lie:

  Lib = LIBRARY
    ObservationPorts: SPORT
  END

(ecs-file-kinds)=

The Kinds

Every kind names one kind of module, has one placement, and has its roles: the exported items a module of the kind provides, each a named slot, <storevar>, that the module's kind declaration fills with the module's own identifier. The placement is where the kind's entries may appear: in the PROGRAM group, in a SCHEDULE group, in a PARTITION group, or in a LIBRARY group. The roles columns below name the slots; the parameters table at the end of this section gives their order per kind and the identifiers the examples use.

Program-level kinds, members of the PROGRAM group:

Kind The module Roles
SCHEDULE a schedule's module: creates its partitions, sets the boundary, registers their Systems, plans, commits, and runs the schedule; schedule 0's module is the program's main module, its gate local and called from the module body; every other gate exported and started by a deployment call <gateproc>, the gate
PERIODS the periods: one tick period per schedule, and the Systems' periods in ticks of their schedule none
RESOURCES the resource choices: library handles, chip units and pins, exception priorities, each an authored choice in the owning module's vocabulary none
TOKENBASES the program-wide token allocation: each partition's token count, its base, and the total against the kernel's capacity none
TOKENNAMES the program-wide token name table, gathered from the partitions' TOKENS modules, for the plan report <namesvar>
CONS the consistency module: the assertions that hold across partitions' definitions, the first station of schedule 0's sequence; one per program <assertproc>
BOUNDARY the schedule boundary: the crossings between schedules, declared once for the whole program <crossingsvar>
OTHER a module the definition places but says nothing about, tracked by existence only none

The partition kind, member of a SCHEDULE group:

Kind The module Roles
PARTITION the partition module: creates the partition's world and registers its Systems <createproc> <addsystemsproc>

Partition kinds, members of a PARTITION group:

Kind The module Roles
SYSTEM one System: its manifest, its name, its run procedure <manifestvar> <namevar> <runproc>
STATE the partition's variable Component state, the store record, and its build procedure <storevar> <buildproc>
LOCKED the partition's locked Component state: written once at load, read-only thereafter, with its interface record for other partitions <lockedvar> <interfacevar>
TOKENS the partition's tokens, one per store variable, its surfaces, and its token names <tokensvar> <surfacesvar> <namesvar>
WIRING the partition's structure: cardinalities and role-binding indices none
VALUES the partition's tuning values none
ADAPTER the store access procedures over one transport, store-handle-indexed where the store path is an array, and the procedure types that carry their direction none; its parameter is <transport>, the module it serves
BINDING a device seam of the partition: operates interface hardware for the partition's Systems, configured at the partition's creation, called by its Systems none
OTHER as above none

Transport kinds, members of a PARTITION group when the transport lives in the program's tree and one partition's store holds its instances, of the PROGRAM group when the stores of several partitions do, or of a LIBRARY group when it comes from a library:

Kind The transport
SPORT a sampling port: the latest value, one writer, one reader, wait-free; a closed API of put, get and init
QPORT a queueing port: messages in sending order, one producer, one consumer; send and receive under preconditions
RBUFFER a ring buffer: elements in order, one producer, one consumer; put and get, single and multiple, under preconditions
BUFFER a buffer whose organisation the definition leaves unstated; its adapter is hand-written

SPORT, QPORT and RBUFFER are canonical kinds: a module of that kind is identical in its interface to the framework's reference implementation of that kind. That is what lets the generator write a working adapter from the kind alone, and what a LIBRARY entry promises about a library module. A module whose interface differs is a BUFFER. Transport kinds have no roles: the canonical API is the kind.

The parameters, kind by kind. A declaration's parameters are the kind's roles in this order, one identifier per role; the right column shows the declarations the examples use:

Kind Parameters In the examples
SCHEDULE <gateproc> (* ECSKIND: SCHEDULE Run *)
PERIODS none (* ECSKIND: PERIODS *)
RESOURCES none (* ECSKIND: RESOURCES *)
TOKENBASES none (* ECSKIND: TOKENBASES *)
TOKENNAMES <namesvar> (* ECSKIND: TOKENNAMES Names *)
CONS <assertproc> (* ECSKIND: CONS Assert *)
BOUNDARY <crossingsvar> (* ECSKIND: BOUNDARY Crossings *)
OTHER none (* ECSKIND: OTHER *)
PARTITION <createproc> <addsystemsproc> (* ECSKIND: PARTITION Create AddSystems *)
SYSTEM <manifestvar> <namevar> <runproc> (* ECSKIND: SYSTEM Manifest Name Run *)
STATE <storevar> <buildproc> (* ECSKIND: STATE S Build *)
LOCKED <lockedvar> <interfacevar> (* ECSKIND: LOCKED L I *)
TOKENS <tokensvar> <surfacesvar> <namesvar> (* ECSKIND: TOKENS L I Names *)
WIRING none (* ECSKIND: WIRING *)
VALUES none (* ECSKIND: VALUES *)
BINDING none (* ECSKIND: BINDING *)
ADAPTER <transport>, the module it serves (* ECSKIND: ADAPTER ObservationPorts *)
SPORT, QPORT, RBUFFER, BUFFER none (* ECSKIND: SPORT *)

A role name is the slot's name, in angle brackets wherever a document or a tool speaks of it; the identifier in the declaration is chosen by the program. A finding names both: STATE storevar = Store: not exported.

Two kinds carry something beyond their roles: an ADAPTER names its transport as its parameter, and a SCHEDULE has its partitions in a group of its own. The transport kinds are the only kinds with more than one placement; every other kind has exactly one.

DECLARATIONS

The DECLARATIONS section holds the facts the sources establish about the declared modules. It is extracted from the sources by extract-ecs and never authored, and a file for generation has none: the inventory says which modules exist, the declarations say what they establish.

The section is closed under reference: every dotted path in it resolves to a line in the file. A fact that would name something the file does not declare is not written; the extractor reports it instead.

Frames

Facts are grouped in frames, one per PARTITION and one for the PROGRAM, written as the group's name, :, the group kind, the frame's entries, and END. Frames do not nest. Inside a frame, each entry names a module of the group with its kind, followed by its facts and END.

Every module may carry one fact, the library modules it imports; ten kinds have facts of their own beside it, and a module with no fact gets no entry:

Kind Facts
any kind the library modules the module imports
TOKENS the tokens with their partition-local ordinals; the surfaces
SYSTEM the System's name; its period; its manifest, one line per non-empty set; the VALUES constants it names
ADAPTER per exported procedure, its direction and the store variables it reaches
SCHEDULE schedule 0 its number; every other schedule its deployment call
BOUNDARY the crossings, per pair of schedules
PERIODS, RESOURCES, TOKENBASES, WIRING, VALUES the module's exported constants, each with its expression as the source writes it

Facts

A fact is written as a name, =, and a value. A value is a number, a string in double quotes, a set expression, a list of names, or Oberon text as the source writes it: a constant expression, or a deployment call.

Imports. Any entry declares the library modules its module imports, by name, in the order of the module's import list:

    UARTbinding: BINDING
      library = GPIO, UART, Clocks
    END

The list holds what the kind's import list does not cover: a program module, and a framework module the allow-lists name by name, is checked against the list and not recorded; every other import is a library the module draws on, recorded for the reviewer and nothing more. A module that imports no such module carries no library fact.

Tokens. A TOKENS entry declares each token under the module's token record and each surface under its surface record:

    Tcoord: TOKENS
      L.mission.periodStep = 0
      L.periodPort.ports = 1
      I.readPeriods = {L.periodPort.ports}
    END

A token's name is the store variable's path below the record, verbatim: mission.periodStep names the column periodStep of the table mission. The ordinal is partition-local; the program-wide value is the ordinal plus the partition's base from TOKENBASES. A surface is a named set of the partition's own tokens, published for other partitions: a manifest, an adapter or a crossing refers to a surface by name and never to a foreign partition's tokens one by one. The record names, L and I here, are the module's own; the file declares them and the checker verifies them.

Systems. A SYSTEM entry declares the System's name, its period as the registration call states it, and its manifest:

    BlinkSystem: SYSTEM
      name = "Blink"
      period = P.BlinkPeriod
      manifest.C = Tcoord.I.readPeriods
      manifest.O = {Tblink.L.blinker.tickers}
      manifest.P = {Tblink.L.ledLeader.setpoints}
    END

The manifest has six sets: C and Cp for consumption, this pass's value and the previous pass's value; O and P for owned and produced writes; Px and Cx for deposits into a shared facility and their collection. A set absent from the entry is empty. A System that names constants of a VALUES module declares them, module-qualified, each once: values = Vmon.OpsStep_None, Vmon.TestSeq_None.

Adapters. An ADAPTER entry declares, per exported procedure, its direction and the store variables it reaches:

    PeriodPortAdapt: ADAPTER
      get.GetVal = {Tcoord.L.periodPort.ports}
      put.PutVal = {Tcoord.L.periodPort.ports}
    END

The directions are put, get and read: a write into the transport, a consuming read, and a read that consumes nothing, such as a predicate. They are read from the procedure's declared parameter modes: a value parameter carries data in, a VAR parameter carries it out, a function result reads without consuming. A procedure that reaches a second store variable beside the transport, an observation kept next to it, lists both.

Schedules. Schedule 0's entry declares its number; every other schedule's entry declares the deployment call that starts its gate, as the source writes it:

    EcsControlScheduleS0v30: SCHEDULE
      schedule = 0
    END

    EcsControlScheduleS1v30: SCHEDULE
      deployment = Cores.StartCoreOne(Core1Prog.Run, NIL)
    END

Schedule 0 is the one whose module is the main module, its gate local and called from the module body. The core every other schedule runs on lives in its deployment call alone, in the deployment procedure's name or arguments, and the tools do not know the deployment procedures: the file states the call, and the reviewer reads the core off it. The call is written as the source writes it, import aliases included, on one line. The schedules are n in all, and the crossing indices of the boundary range over 0…n-1 (The boundary, below).

The boundary. A BOUNDARY entry declares the crossings as two matrices over schedule pairs, flow for dataflow and deposit for deposits, each indexed from schedule to schedule:

    B: BOUNDARY
      Crossings.flow[0, 1] = Tprint.I.clipsObs
      Crossings.flow[1, 0] = Tadapt.I.drainCmd
    END

A crossing names surfaces only: what a partition publishes is what may cross a schedule boundary, exactly as it may cross a partition boundary. The variable's name, Crossings here, is the module's own, declared as the kind's parameter; the matrix indices are the schedule numbers as the source states them, constants evaluated; 0 is schedule 0, and which index is which other schedule is the programmer's statement, read against the deployment calls.

Constants. A definition module's entry lists its exported constants with their expressions as the source writes them:

    P: PERIODS
      TickPeriodS0 = 50
      EachTickPeriod = 0
      BlinkPeriod = EachTickPeriod
      PeriodPeriod = 5000 DIV TickPeriodS0
    END

The expression is not evaluated here: the file states what the module states. The ECS map file carries the evaluated values where a reviewer needs them.

References and Set Expressions

A reference is module-qualified: Tblink.L.blinker.tickers names a token by its TOKENS module, its record and its path, Tcoord.I.readPeriods names a surface by its module, its record and its name. References never go through import aliases; the file speaks in module names.

A set expression is written as in Oberon: braced lists of references, {a, b}, and bare surface references, joined by +. A surface is a set and therefore stands bare, never inside braces; a token stands inside braces, never bare. Tadapt.I.drainCmd + Tcoord.I.readPeriods is the union of two surfaces; {Tblink.L.ledLeader.setpoints, Tblink.L.ledFollower.setpoints} is a set of two tokens.

The HEADER section belongs to a file written for generation: the comment block the generated modules carry, taken verbatim from the section's keyword to the end of the file. An extracted file has none; the modules' headers are the modules' own. Two conventions apply to its lines:

  • A line beginning with ! is mandatory: every module the generator writes carries it. The ! itself is not part of the line.

  • The line <<DESCRIPTION>> is the description slot: where the generator writes a module's description, taken from the kind's reference implementation, when it creates the module. A header without the slot gets no description.

The header of EcsControlSchedule-v26:

HEADER
!(**
!  Oberon RTK Framework v3.2
!  --
!  Example/test program for ECS architecture
!  EcsControlSchedule-v26
!  --
  <<DESCRIPTION>>
!  --
!  MCU: RP2350
!  Board: Pico2
!  --
  Copyright (c) 2026 Gray, gray@grayraven.org
!  https://oberon-rtk.org/licences/
!**)

The ECS Map File

The extractor writes a second file beside the ECS file, with the same stem and the extension .ecsm: the ECS map file, the program's definition in the forms a reviewer reads, named after the linker's map file, which it resembles in purpose. It is text for people, read by no tool, and it carries its own head line: the label, the extraction's date and time, the ECS file it belongs to, and the extractor's name and version. Its sections, in this order:

Section Content
SCHEDULES per schedule: its gate and the call that starts it, schedule 0 marked, its tick period, its partitions in creation order
ADAPTERS per adapter and access procedure: the direction, the store tokens reached as program-wide values, the Systems calling it
TOKENMAP per TOKENS module: its base and count as stated and as evaluated, each token's program-wide value; then the ranges, gaps and overlaps across the program
MANIFESTS per schedule, a matrix: one row per System, one column per token by program-wide value, the set's letter in the cell, the partition index over each range

Where the ECS file states an expression as written, the ECS map file states the evaluated value. The two files are written by one run and compared as a pair: the ECS file by its meaning, the ECS map file as text below its head line.

The Label

The label is the program's name in the file: the name of the PROGRAM group and of the PROGRAM frame. It is a name with letters, digits and hyphens, never dotted, and no module carries it.

In the extracted file the label is the program directory's name. In a file for generation it is authored, and the generator takes it as given.

The Directories

A program's sources lie in one directory per group: the program-level modules in the program directory, each partition's modules in a directory of their own below it, named as the partition module, and a library's transports wherever the library is. The extractor places a module by the first directory below the program directory in its linked path: the program directory itself is the PROGRAM group, a partition directory is that partition's group, and anything deeper is the programmer's own organisation and reads the same. One directory per partition, always: a one-partition program has one partition directory, as a one-schedule program has one SCHEDULE group. A partition kind in the program directory, or a program-level kind in a partition directory, is a placement error.

The generator writes by the same rule and creates nothing else. The file carries no directory data beyond the rule, and a LIBRARY entry names a module that exists.

Grammar

The grammar in one place, in the notation of the Oberon report. Keywords and the kind names are written in capitals; the kind sets are the ones of The Kinds above.

ECSFile        = [HeadLine] [AllowListsLine] [RolenamesSection] ModulesSection [DeclarationsSection] [HeaderSection].

HeadLine       = label "--" "extracted" date time ident version.
AllowListsLine = "ALLOWLISTS" line.

RolenamesSection = "ROLENAMES" {kind {ident}}.

ModulesSection = "MODULES" {Group}.
Group          = ProgramGroup | ScheduleGroup | PartitionGroup | LibraryGroup.
ProgramGroup   = label "=" "PROGRAM" {Entry} "END".
ScheduleGroup  = ident "=" "SCHEDULE" {Entry} "END".
PartitionGroup = ident "=" "PARTITION" {Entry} "END".
LibraryGroup   = ident "=" "LIBRARY" {Entry} "END".
Entry          = ident {"," ident} ":" kind
               | ident ":" kind ident {ident}.

DeclarationsSection = "DECLARATIONS" {Frame}.
Frame          = label ":" "PROGRAM" {DeclEntry} "END"
               | ident ":" "PARTITION" {DeclEntry} "END".
DeclEntry      = ident ":" kind {Fact} "END".
Fact           = factName "=" Value.
factName       = ident {"." ident} ["[" number "," number "]"].
Value          = number | string | SetExpression | NameList | SourceText | "UNREAD".
NameList       = Name {"," Name}.
Name           = ident ["." ident].
SourceText     = Oberon text as the source writes it, to the end of the line: a constant expression, or a deployment call.
SetExpression  = Term {"+" Term}.
Term           = "{" [Reference {"," Reference}] "}" | Reference.
Reference      = ident "." ident "." ident {"." ident}.

HeaderSection  = "HEADER" {line}.

label          = letter {letter | digit | "-"}.
ident          = letter {letter | digit}.
number         = digit {digit}.
string         = '"' {character} '"'.

Comments are Oberon comments, (* ... *), admitted anywhere before the HEADER keyword; the header's lines and the allow-lists line are taken verbatim to the end of their line or of the file. There are no separators: the next token decides every boundary.

The kind a kind stands for depends on the group: the program-level kinds and the transport kinds in a PROGRAM group, PARTITION in a SCHEDULE group, the partition kinds and the transport kinds in a PARTITION group, the transport kinds in a LIBRARY group. The second form of Entry carries the entry's parameters, an adapter's transport among them; the comma form admits several modules of one kind on a line when none carries parameters. A DeclEntry has the kind of its module, and the form of a Value is decided by the fact's name, the table below: a number alone is a number, a bare name is a constant expression under a constant's name and a module under library. The value UNREAD stands where the extractor met a construct it does not read, the discipline prescribing an effect and not a form: the fact's name is written, its value is not, the reader takes the fact as absent, and the reviewer reads the source.

The facts a DeclEntry admits depend on its kind:

Kind Fact Value
any kind library a list of module names: the library modules the module imports
TOKENS <record>.<path> a number: the token's partition-local ordinal
TOKENS <record>.<name> a braced set of the module's own tokens: a surface
SYSTEM name a string: the System's name
SYSTEM period the registration call's period argument, as written
SYSTEM manifest.C, manifest.Cp, manifest.O, manifest.P, manifest.Px, manifest.Cx a set expression
SYSTEM values a list of module-qualified names: the VALUES constants the System names
ADAPTER put.<procedure>, get.<procedure>, read.<procedure> a braced set of the tokens the procedure reaches
SCHEDULE schedule the number 0, on schedule 0's entry alone
SCHEDULE deployment the deployment call as the source writes it, on every other schedule's entry
BOUNDARY <variable>.flow[i, j], <variable>.deposit[i, j] a set expression over surfaces, from schedule i to schedule j
PERIODS, RESOURCES, TOKENBASES, WIRING, VALUES <constant> the constant's expression as the source writes it

Rules the Grammar Does Not Express

The reader enforces these beyond the syntax:

  • One PROGRAM group, one SCHEDULE group per schedule, one PARTITION group per partition; every SCHEDULE entry of the PROGRAM group has its group, every PARTITION entry of a SCHEDULE group has its group, and a partition belongs to exactly one schedule.

  • Parameters match the kind. An entry's parameters, its own or its kind's ROLENAMES line, are as many as the kind has roles; an entry of a kind absent from ROLENAMES carries its own; an entry carries all of its kind's parameters or none.

  • Kinds by placement. An entry's kind must be admissible in its group; a TOKENS entry in the PROGRAM group or a TOKENBASES entry in a PARTITION group is an error.

  • An adapter's transport is declared: in a PARTITION group under a transport kind, or in a LIBRARY group.

  • Frames follow the groups. Every DeclEntry sits in the frame of the group that declares its module; frames do not nest; an entry outside a frame is an error. A LIBRARY group has no frame: a library module is read for its kind declaration alone.

  • Closed under reference. Every Reference resolves to a declared module, one of its two records, and a token or surface declared there. A token stands inside braces, a surface stands bare; a surface's members are tokens of the same module; a manifest or crossing names a foreign module's surfaces, never its tokens one by one.

  • One record name each. A TOKENS entry declares all tokens under one record name and all surfaces under another; the two differ. Ordinals are not repeated within a module.

  • No duplicate facts. A fact name appears once per entry; a reference appears once per set.

  • Schedule 0 is numbered, the others are deployed. Exactly one SCHEDULE entry carries schedule = 0, every other carries deployment; the matrix indices of a crossing lie in 0…n-1 for n schedules, and a schedule does not cross to itself.

  • UNREAD is written by the extractor alone. A file for generation has no DECLARATIONS, so it never carries the value; the reader takes such a fact as absent.

  • The HEADER block is one balanced Oberon comment, once the ! marks and the description slot line are taken as the conventions they are: it is emitted as a module's leading comment, verbatim.

A file that breaks a rule is refused as a whole; nothing is read past the error.

See Also

Last updated: 2 November 2026