Tags: interlang, dx, policy, non-protocol
Interlang lets a policy say which records belong together using their fields and relationships. For example, this selects messages in the lobby:
converge:
include Message:
Message['#Chat'] == 'message'
Message.Room == 'lobby'
Both field conditions must hold. converge selects
matching stored records, makes them inspectable by peer-authored rules,
and generates a request policy for matching peer advertisements.
Transfer still requires both sides’ policies to agree under
Lace-040.
Interlang compiles to ordinary 030/040 Datalog. Canonical Datalog bytes, not Interlang text, identify the finalized operand and exchange plan. This document defines the self-contained contribution language, standard Interlang. Lace-130 defines compilation and structural composition of complete policy constraints.
Use expose when peer-authored rules may inspect records
without selecting them for transfer:
expose:
include Member:
Member['#Chat'] == 'member'
converge:
include Notice:
Notice['#Chat'] == 'notice'
Notice.Visibility == 'public'
Exposure is always whole-record exposure under 020. Neither block
implicitly includes supporting records: an include lists
its outputs explicitly.
Effect blocks occur only at contribution top level. They contain selections, not nested effect blocks. Separate selections union within a contribution’s effect. Across independent complete policy constraints, contributed selection and exposure facets intersect and directional vetoes union under 130/040. The two finalized exchange operands then intersect.
A plain include discovers candidates through their
advertised fields. include ... by hash instead follows a
hash bound by checked local support, such as a manifest link, or names
one record’s hash directly. The discovery rules below define both
forms.
A contribution contains helper definitions, effect blocks, and
datalog: blocks. It may be empty or helper-only; producing
an exchange operand requires facets, as described under Compilation and
finality.
Source MUST be UTF-8 NFC with LF line endings. Each nesting level MUST add exactly two ASCII spaces; top-level items have no indentation. Implementations MUST reject tabs in indentation, skipped levels, and dedents to a non-enclosing level. After blank lines and whole-line comments are removed, every block body MUST be nonempty.
Each header, clause, and inline Datalog rule MUST occupy one physical
line. Headers have no inline body, and expressions do not continue
across lines, even inside parentheses or brackets. Horizontal token
spacing does not change meaning. # starts a comment only
when ASCII spaces alone precede it on the line; otherwise it is
expression data. Comments are removed before lowering. Blank lines and
comments do not open, close, or satisfy a block body.
Clauses are conjunction, not sequential execution. Each selection and
helper definition has its own variable scope. Multiple definitions of
one helper are Datalog union. Pipe-separated bodies, brace blocks,
trailing => include, and implicit top-level selections
MUST be rejected. These syntax restrictions do not apply to punctuation
inside quoted constants. The complete grammar appears at the end of this
document.
Interlang uses 030 variable kinds: uppercase variables are
record-reference shaped, lowercase variables are text, and
_ is anonymous and non-binding. Helper names use ordinary
030 lowercase helper syntax. Capitalized profile and runtime calls
belong in datalog: blocks; the only friendly runtime call
is peer_confidential(peer_key).
A field equality or inequality contains at least one field access.
Its uppercase receiver is bound through checked Have and
positive Field facts:
task(Task) :=
Task['#Tasks'] == 'task'
Task['Content-Type'] == 'text/plain'
Task.Tag[0] == 'urgent'
Task.Tag[_] == 'urgent'
After ., the token is the exact field name; brackets
spell names that are not identifiers.
An omitted occurrence index means exactly one
occurrence. The first clause lowers to occurrence
0 plus absence of occurrence 1:
Have(Task), Field(Task,'#Tasks','0','task'), not Field(Task,'#Tasks','1',_)
Canonical occurrence indexes are contiguous, so this proves singleton
presence. [0] selects occurrence zero without requiring a
singleton. [_] selects any occurrence; a decimal index
selects that exact canonical occurrence. Dot and bracket field names use
the same rules. The compiler MUST preserve these positive field bindings
and occurrence checks. With _ as the value, a clause tests
presence: Task['#Tasks'] == _ holds for exactly one
#Tasks occurrence of any value and
Task.Tag[_] == _ for at least one Tag;
!= rejects _.
Field-to-variable clauses bind the value as well as the record:
message_parent(Message, room, Parent) :=
Message.Room == room
Message.Parent == Parent
A lowercase variable receives arbitrary text. An uppercase value
variable adds 030’s record-reference shape constraint. Field-to-field
equality reads both positive occurrences and joins their values.
Inequality binds the selected occurrence into a private value, then
applies !=. A missing field does not satisfy
inequality.
Field names are application data except for the structural names in
010 and the synthetic :Type and :Length in
020. Bracket syntax spells a synthetic name:
Note[':Length'] <= 4096 holds for a record of at most
4096 canonical bytes, field lines included. Authors choose required
occurrences, value formats, identities, scope, and ordering constraints.
For an unconstrained local record universe, use explicit
Have in a datalog: block.
A helper gives a clause body a name:
message(Message, room) :=
Message['#Chat'] == 'message'
Message.Room == room
Helpers lower to ordinary lowercase Datalog predicates. Argument
kinds are inferred across definitions and calls; _ MUST NOT
appear in a helper head. Friendly definitions and inline Datalog share
one local helper namespace and may call each other by authored names.
Rules for the same helper union when their signatures agree.
A positive helper call binds through its lowered relation. Negation, comparisons, shapes, matches, cardinality, and admission-order tests do not create the bindings required for safety. A field access inside an operator test, or among a negated call’s arguments, is still a positive field clause: only the call itself is negated. A helper graph supplying a selected record must ground it through positive field clauses, positive helper calls, link targets from checked support, or TopK candidates. Advanced helpers have additional discovery restrictions described below.
A record-link clause follows a parsed link:
manifest_chunk(Manifest, Chunk) :=
Manifest['#Files'] == 'manifest'
Manifest['Chunk+Link'](range) == Chunk
The link clause lowers to:
RecordLink(Manifest,'Chunk+Link',_,range,Chunk)
It binds its target and any non-anonymous link-data variable, but not the owning record. A positive field or helper clause must ground that owner. This is a binding requirement, not an execution order. Field-call syntax is the only Interlang record-link syntax. Advertisements have no parsed record-link relation, so a field-discovered candidate cannot inspect its own links.
Tests use bindings supplied by positive clauses. These helpers
demonstrate 030 TextShape, TextMatch,
Compare(Lex(...)), Cardinality,
Compare(Int(...)), and
Compare(AdmitOrder(...)):
json_message(Message) :=
Message.Path == name
name starts with 'msg/'
name ends with '.json'
message_body(Message) :=
Message.Path == name
name has shape ('msg/', './', 'body')
identifier(Message) :=
Message.Name matches '[a-z][a-z0-9-]{0,31}'
newer(Message, Earlier) :=
Message.Revision == new_revision
Earlier.Revision == old_revision
Message.LowerBound == lower_bound
new_revision sort after old_revision
new_revision sort at-or-after lower_bound
endorsement(Message, Endorsement) :=
Message['#Chat'] == 'message'
Endorsement.Subject == Message
endorsed(Message) :=
Message['#Chat'] == 'message'
count endorsement(Message, _) >= 3
small_counter(Message) :=
Message.Counter <= 100
raised_counter(Message, Earlier) :=
Message.Counter == counter
counter > Earlier.Counter
admitted_later(Later, Earlier) :=
Later['#Chat'] == 'message'
Earlier['#Chat'] == 'message'
Later admit after Earlier
An integer clause compares two operands, each a field access, a text
variable, or an unquoted canonical unsigned decimal literal, and lowers
to Compare(Int(...)). A value outside the 030
Int domain makes the comparison false. In every operator
test, a field access lowers to a positive field clause that binds and
grounds its record, including the singleton check when no occurrence
index is written. The tests themselves bind nothing: a variable must be
bound by another positive clause. Integer interpretation and other field
constraints apply only where the policy states them.
term matches 'constant' lowers to
TextMatch(term,'constant'). The right operand MUST be a
quoted constant, not a variable, field access, or _. The
test binds nothing and has no capture or substring result. Pattern
grammar, escaping, whole-value semantics, static validation, and fixed
bounds are defined only by 030’s
TextMatch. Interlang string quoting is the ordinary 030
layer, separate from pattern escapes. To negate a match, use a helper
and ordinary not.
To choose one current message per room, first define the eligible candidates, then rank them:
message_candidate(Message, room, revision) :=
Message['#Chat'] == 'message'
Message.Room == room
Message.Revision == revision
Message.State != 'deleted'
current_message(Message, room) :=
greatest 1 per (room) from message_candidate(Message, room, revision) by int revision
The clause maps to 030 TopK: greatest is
'>', least is '<', and
omitted per is PartitionBy().
lex, int, and admit map to
Lex, Int, and AdmitOrder. The
unquoted limit becomes the canonical quoted Datalog bound. Lowering MUST
preserve candidate argument order, partition order, ordering form,
direction, and limit.
TopK binds variables from selected complete candidate facts. The candidate relation reaches its lower-stratum fixed point before ranking; each partition is independent. The primary key is followed by every complete candidate component in argument order, with greatest or least applying to that complete ordering. TopK selects a set, not an output order.
A dynamic int value outside 030’s canonical
unsigned-decimal domain excludes that complete candidate before
partition construction without failing evaluation. An invalid quoted
int constant is rejected during complete-module validation,
even when the limit is zero.
Restrictions that must apply before ranking belong in the candidate helper. Other body clauses join after TopK and do not constrain or repartition its candidates. Candidate and ordering terms use the restricted Datalog-shaped grammar, never field accesses: project a field into a helper variable, then rank that variable.
The candidate is a friendly or advanced local helper—not a runtime,
profile, facet, or builtin. Advanced definitions do not supply automatic
discovery or friendly by-hash grounding. A nonempty per
contains distinct candidate variables. The ordering term occurs
literally in the candidate and is not _; admit
takes a record-reference variable or supported record hash. Complete 030
validation still governs kinds, safety, stratification, limits, and
aggregate validity.
The session-authority facts describe the endpoint evaluating the
rules, not the policy’s author. Here(local_key) is that
endpoint’s own key, present when its carrier has a By-secret;
PeerConfidential(peer_key) is the key its live peer proved.
A policy is evaluated at both endpoints (040), so
at the peer Here is the peer’s key. Which key signed a
record is a record fact: write its canonical By as a
constant (records signed by one
key).
peer_confidential(peer_key) lowers to
PeerConfidential(peer_key). On a connected route it exists
only after the live peer proves control of the matching By-secret under
host-asserted confidential-channel or locally verified channel-bound
assurance defined by 050/060; the runtimes’ direct operations supply
neither fact (100).
Source cannot create or replace it:
trusted_notice(Notice) :=
Notice['#Chat'] == 'notice'
peer_confidential('V.EXAMPLE_REQUIRED_PEER_KEY_B64A.H3')
converge:
include Notice:
trusted_notice(Notice)
Direct PeerConfidential and Here calls are
invalid in friendly clauses. Advanced policy may use declared profile
predicates in inline Datalog. Applications represent times and versions
as ordinary fields and choose their shape and ordering policy
explicitly.
A datalog: block contributes ordinary 030 rules, one per
indented physical line, retaining Datalog commas, periods, and rule
semantics:
datalog:
configured_kind('notice') :- true.
SelectStoredRecord(R) :- Have(R), Field(R,'#Chat',_,kind), configured_kind(kind).
Advanced rules express effects through the 040 facets:
SelectStoredRecord/1,
SelectAdvertisedCandidate/1, ExposeRecord/1,
TxDisabled/0, and RxDisabled/0.
Generated friendly rules and inline rules are assembled before
030/040 validation and canonical serialization; the fragment is not
canonicalized separately. Every helper reference is local to this
contribution. Module declarations, qualified helper calls, and raw
@ providers are invalid.
Each policy below is complete. Every participant in an exchange
supplies one, and a record moves only when both select it and its sender
exposes it. The lace-compiler test
interlang_examples compiles every example in this document,
helper-only ones with a facet added, and exchanges sample records under
each policy in this section.
converge:
include Note:
Note['#Notes'] == 'note'
Note.Room == 'lobby'
Note.Time >= 1767225600
A #Application field names the application and record
kind, as 010
recommends. Note.Time >= 1767225600 holds for a
single canonical unsigned decimal Time of at least that
value, here Unix seconds from 2026; the application chooses that
convention.
converge:
include Note:
Note['#Notes'] == 'note'
Note.By == 'V.EXAMPLE_AUTHOR_KEY_B64A.H3'
Only a Seal has a By field (020), so an unsigned
record with the same fields is not selected. The constant is the
signer’s canonical public By text.
member(Membership, group, peer_key, joined) :=
Membership['#Chat'] == 'membership'
Membership.By == 'V.EXAMPLE_ADMIN_KEY_B64A.H3'
Membership.Group == group
Membership.Member == peer_key
Membership.Joined == joined
converge:
include Message:
Message['#Chat'] == 'message'
Message.Group == group
Message.Time > joined
member(Membership, group, peer_key, joined)
peer_confidential(peer_key)
datalog:
SelectAdvertisedCandidate(Record) :- Advertised(Record), AdvertisedField(Record,'#Chat','0','message').
The sender holds the membership records. A message is selected for a
peer that proved the key named by a membership in the message’s group,
signed by the administrator, when the message’s Time is
numerically later than the joining time; both are canonical unsigned
decimals.
At the peer, this operand’s request evaluation sees the peer’s session authority and only the peer’s
exposed records. There the converge selection finds no membership, so
the datalog: rule admits any advertised chat message and
leaves the rest of request choice to the receiver’s policy; this
sender’s stored selection still decides what it sends. The rule names
the application’s records rather than every advertised record because it
is also this participant’s own request selection when it receives, where
the only other request constraint is the peer’s operand. Lace-040
defines both evaluations.
converge:
include Snapshot:
Snapshot['#Viewer'] == 'snapshot'
datalog:
TxDisabled() :- true.
TxDisabled empties this participant’s advertisements, so
it only receives; RxDisabled instead makes it send-only (directional
vetoes).
converge:
include Note by hash:
Note == 'S.ZGEVHbEEKmCMF2aSbmbQ4~trwxANOQFmraeGg_JAp9C.H3'
include Comment:
Comment['#Notes'] == 'comment'
Comment.Note == 'S.ZGEVHbEEKmCMF2aSbmbQ4~trwxANOQFmraeGg_JAp9C.H3'
This get or list query names one note by
its hash, here 010’s example Seal, and finds the comments whose
Note field holds that hash. The note is requested by hash
without inspecting it; the comments are found through their fields. Both
require their holder’s own policy to select and expose them (040).
Knowing a hash never bypasses the holder’s selection or exposure.
Store sample records in a memory Lace and list them with
the policy; the result is the policy’s selection. To test an exchange,
run a bounded interlace between two memory Laces, one wrapped in a
LocalPeer carrying the other participant’s policy (100). Direct
operations carry no peer proof, so a policy using
peer_confidential is tested over a connected pair with
By-secrets and confidential-channel assurance (100).
Field clauses and helpers are defined above; operator tests and aggregates rest on 030 built-ins, facets and vetoes on 040, and composition of independent policies on 130. The lowering sections below matter only when a selection is rejected as an invalid discovery shape.
The compiler produces checked selection and exposure rules, plus request rules for convergence outputs. It MUST preserve 020 origin-aware fact views and whole-record exposure. Generated advertisement dependencies MUST remain request-only under 040.
An inclusion MUST name a nonempty list of distinct record-reference
variables, all safely bound by its body. Constants, _,
duplicates, omitted outputs, and unsafe outputs MUST be rejected.
Supporting variables are not implicitly selected or exposed:
converge:
include Message, Membership:
Message['#Chat'] == 'message'
Message.Room == room
Membership['#Chat'] == 'membership'
Membership.Room == room
The compiler MUST emit a separate unary rule for each output and applicable facet. Direct body conditions repeat; authored helpers share their checked definitions. The compiler MUST NOT invent an output-tuple relation to share a selection body. Authors may define that helper explicitly, subject to ordinary materialization and limits.
For successful evaluations over coherent inputs, a multi-output selection equals the union of its single-output selections. This promises neither atomic transfer, paired arrival, tuple events, result ordering, nor equal work or failure thresholds. It also promises no single body execution or shared aggregate evaluation. Each emitted TopK retains its complete-candidate, partition, ordering, and per-occurrence limits; other outputs are not partition keys or aggregate filters.
Output order is not semantic. Implementations MUST normalize it before scope allocation, rule emission, and discovery traversal so permutations produce identical operand bytes. General logical-equivalence detection is not required. There is no separate output-count cap: emitted facets are unary, and all emitted rules and specializations count toward 030/040/130 preparation bounds. Excessive expansion MUST fail, not truncate outputs.
by hash applies to the complete output list. Outputs
needing different discovery modes use separate selections, optionally
sharing an authored helper.
includeFor each plain convergence output, the compiler marks that output as
the advertised candidate. Its checked Have and
Field accesses become Advertised and
AdvertisedField; other records remain checked support. For
example, the singleton field clause from above becomes:
Advertised(Task), AdvertisedField(Task,'#Tasks','0','task'), not AdvertisedField(Task,'#Tasks','1',_)
The compiler MUST preserve occurrence indexes, singleton checks, constants, record-reference shapes, and builtin constraints. Advertising a field does not weaken its test.
Each output is specialized independently; implementations MUST NOT mark all outputs simultaneously. An invalid discovery graph rejects the complete selection, even when a TopK bound is zero. If each of two outputs needs the other as checked support, they cannot bootstrap an empty receiver.
Exposure-only selections generate no advertised specialization and require no advertised projectability. They may inspect checked links and admission order of their selected records.
Checked lowering MUST keep one ordinary predicate per source helper. Advertised lowering MUST emit only specializations reachable from field-discovered convergence outputs, identified by:
(helper name, arity, candidate argument-position set)
For example:
message(Message, room) :=
Message['#Chat'] == 'message'
Message.Room == room
visible_message(Message) :=
message(Message, room)
room starts with 'public-'
converge:
include Message:
visible_message(Message)
Compilation emits these rules:
ExposeRecord(Message) :- visible_message(Message).
SelectAdvertisedCandidate(Message) :- il_visible_message_advertised_p0(Message).
SelectStoredRecord(Message) :- visible_message(Message).
il_message_advertised_p0(Message,room) :- Advertised(Message), AdvertisedField(Message,'#Chat','0','message'), not AdvertisedField(Message,'#Chat','1',_), AdvertisedField(Message,'Room','0',room), not AdvertisedField(Message,'Room','1',_).
il_visible_message_advertised_p0(Message) :- il_message_advertised_p0(Message,room), TextShape(room,'public-','','').
message(Message,room) :- Have(Message), Field(Message,'#Chat','0','message'), not Field(Message,'#Chat','1',_), Field(Message,'Room','0',room), not Field(Message,'Room','1',_).
visible_message(Message) :- message(Message,room), TextShape(room,'public-','','').
Checked helpers keep their authored names, which inline Datalog may call. Advertised specializations receive compiler-allocated names distinct from every local symbol; the spelling is not semantic. Isolation from other policies comes from 130’s structural renaming, not from source names. Candidate marks propagate by record-variable identity through helper argument positions, marking every matching repeated or aliased position. Each reachable finite specialization MUST be emitted once. The following rules define projectability:
| Construct | Advertised specialization |
|---|---|
| positive candidate field clause | Advertised(candidate) plus matching
AdvertisedField, occurrence, shape, and singleton
constraints |
| unmarked support field clause | retain checked Have and Field facts |
| text read from a candidate field | retain text binding; it may join checked support |
inequality, TextShape, TextMatch,
Lex, or Int over bound values |
retain builtin |
| local friendly helper receiving candidate positions | call the exact candidate-position specialization |
| union, positive or mutual recursion | specialize reachable rules and preserve recursion |
| negated helper or cardinality receiving candidate positions | specialize callee and preserve operator |
RecordLink(candidate,...,Target) |
reject |
RecordLink(Support,...,candidate) |
retain checked fact |
AdmitOrder(candidate) |
reject |
| admission order over unmarked support | retain checked operation |
| TopK local helper with marked positions | call exact advertised specialization and preserve aggregate arguments |
| TopK helper with no marked position | retain checked aggregate support |
TopK AdmitOrder over a marked candidate |
reject |
| helper with any advanced Datalog defining rule receiving a marked candidate | reject automatic specialization |
| unmarked advanced local helper | retain ordinary call after complete graph validation |
A rejected path is an invalid discovery shape. The compiler MUST reject source before specialization would exceed active generated-output limits.
include by hashUse by hash when checked support already names the
target:
manifest(Manifest, Chunk) :=
Manifest['#Files'] == 'manifest'
Manifest['Chunk+Link'](range) == Chunk
converge:
include Manifest:
Manifest['#Files'] == 'manifest'
include Chunk by hash:
manifest(Manifest, Chunk)
For the chunk, compilation emits these rules:
ExposeRecord(Chunk) :- Have(Chunk), manifest(Manifest,Chunk).
SelectAdvertisedCandidate(Chunk) :- Advertised(Chunk), manifest(Manifest,Chunk).
SelectStoredRecord(Chunk) :- manifest(Manifest,Chunk).
Discovery uses the hash, not an advertised projection of the target’s
facts. Checked support may bind the target as a RecordLink
target or a record-reference-shaped field value, or the body may name it
directly as described below. The support proof cannot require the
target’s Have, Field, owning-side
RecordLink, or AdmitOrder before transfer.
Stored selection names the target by hash alone, without its
Have; exposure adds a Have guard. The sender
still transfers only what its own policy independently selects and
exposes under 040: exposing a manifest does not expose its linked
targets. The support body MUST NOT depend on advertisements or facets.
Opaque inline-Datalog helpers cannot prove its grounding.
A by-hash body may instead bind an output to one record with
Output == 'hash', where the constant is supported typed
record-hash text:
converge:
include Note by hash:
Note == 'S.ZGEVHbEEKmCMF2aSbmbQ4~trwxANOQFmraeGg_JAp9C.H3'
Since 030 has no =, compilation substitutes the constant
for the output in each emitted rule, so this emits these rules:
ExposeRecord('S.ZGEVHbEEKmCMF2aSbmbQ4~trwxANOQFmraeGg_JAp9C.H3') :- Have('S.ZGEVHbEEKmCMF2aSbmbQ4~trwxANOQFmraeGg_JAp9C.H3').
SelectAdvertisedCandidate('S.ZGEVHbEEKmCMF2aSbmbQ4~trwxANOQFmraeGg_JAp9C.H3') :- Advertised('S.ZGEVHbEEKmCMF2aSbmbQ4~trwxANOQFmraeGg_JAp9C.H3').
SelectStoredRecord('S.ZGEVHbEEKmCMF2aSbmbQ4~trwxANOQFmraeGg_JAp9C.H3') :- true.
Only an output of that selection takes a constant, at most one each, and the clause is invalid in any other body. The constant binds the output as support does, so the support proof still forbids every other clause inspecting it. Other clauses of the body remain, with the constant in place of the output. A hash binding joins after TopK like any other outer clause: it MUST NOT filter or repartition the aggregate’s candidates. Lowering may isolate that aggregate in an ordinary helper before substituting the constant in its call.
TopK may bind a by-hash target only when its contribution-local friendly candidate helper recursively proves this same contract. Aggregate safety, field discovery, and checked-support hash binding are separate proofs. Passing the target to an opaque helper cannot prove target independence and MUST be rejected. Unmarked advanced local support may filter a locally proven binding after complete dependency validation.
Every output in a multi-output by-hash selection MUST independently
satisfy the proof. Each exposure rule has only its own
Have(Output) guard and each request rule only its own
Advertised(Output) guard. The compiler MUST NOT conjoin all
output guards onto the support body. One missing target must not
suppress another admitted target’s selection or exposure.
An exposure-only by hash selection emits checked
support, its independent Have(Target) guard, and
ExposeRecord(Target). It requires independent local
admission before exposure and generates no stored-selection or request
rule.
Lace-130 defines self-contained compilation, hygienic composition of complete policies, normalization, and preparation accounting. Source has no module wrapper, imports, external relation interface, or qualified helper references. Compilation uses one parser and lowerer; applications supply complete source bytes before preparation.
The compiler retains frontend provenance for discovery and grounding proofs. Automatic specialization MUST NOT cross opaque inline-Datalog helpers. Complete authored rules and dependency graphs MUST be validated, including unused rules, positive and negated calls, cardinality, and aggregates. Empty and helper-only bodies may satisfy the grammar but compilation as an exchange policy MUST reject them. Veto-only contributions are valid policies.
Final output MUST be an ordinary self-contained 030/040 program, validated for kinds, safety, stratification, limits, and complete request-only advertisement reachability. Parsed validated rules may be retained with their canonical text to avoid reparsing unchanged local output. Public and peer raw canonical bytes still require independent boundary validation.
Independent complete policies compose structurally under 130 without sharing private helpers. Two ordered finalized operands, their plan, and a typed local slot produce a side-local executable. Compilation performs no acquisition or evaluation and adds no session authority or exposure.
Anonymous contributions MUST follow this grammar. It uses
= for definition, [...] for optional text,
{...} for repetition, and | for alternatives;
those marks are notation, not source characters.
source = { item } ;
item = helper-definition | converge-block | expose-block
| datalog-block ;
helper-definition = helper-name, "(", [parameter-list], ")", ":=", EOL,
INDENT, clause-body, DEDENT ;
parameter-list = parameter, {",", parameter} ;
parameter = record-variable | text-variable ;
converge-block = "converge:", EOL, INDENT,
selection, {selection}, DEDENT ;
expose-block = "expose:", EOL, INDENT,
selection, {selection}, DEDENT ;
selection = "include", output-list, ":", EOL,
INDENT, clause-body, DEDENT
| "include", output-list, "by", "hash", ":", EOL,
INDENT, hash-body, DEDENT ;
hash-body = hash-clause, EOL, {hash-clause, EOL} ;
hash-clause = clause | hash-binding ;
hash-binding = record-variable, "==", string ;
output-list = record-variable, {",", record-variable} ;
datalog-block = "datalog:", EOL, INDENT,
datalog-rule-line, {datalog-rule-line}, DEDENT ;
clause-body = clause, EOL, {clause, EOL} ;
clause = field-equality | field-inequality | link-equality
| helper-call | negated-call | text-shape-test | text-match-test
| lex-order-test | integer-test | cardinality-test
| admission-order-test | top-k-clause ;
field-equality = term, "==", term ;
field-inequality = term, "!=", term ;
link-equality = link-access, "==", record-term ;
helper-call = helper-atom | "peer_confidential", "(", term, ")" ;
helper-atom = helper-name, "(", [argument-list], ")" ;
negated-call = "not", helper-call ;
text-shape-test = term, "starts", "with", term
| term, "ends", "with", term
| term, "has", "shape", "(", term, ",", string,
",", term, ")" ;
text-match-test = term, "matches", string ;
lex-order-test = term, "sort", ("after" | "at-or-after"), term ;
integer-test = integer-operand, comparison, integer-operand ;
integer-operand = field-access | text-variable | unsigned-decimal ;
cardinality-test = "count", helper-atom, comparison, unsigned-decimal ;
admission-order-test = record-term, "admit", "after", record-term ;
top-k-clause = top-k-direction, unsigned-decimal,
["per", "(", partition-variable-list, ")"],
"from", top-k-candidate, "by", top-k-order ;
top-k-direction = "greatest" | "least" ;
partition-variable-list
= variable, {",", variable} ;
top-k-candidate = helper-name, "(", [top-k-argument-list], ")" ;
top-k-argument-list = top-k-argument, {",", top-k-argument} ;
top-k-argument = record-variable | text-variable | "_" | string ;
top-k-order = "lex", top-k-order-term
| "int", top-k-order-term
| "admit", record-term ;
top-k-order-term = record-variable | text-variable | string ;
comparison = "<" | "<=" | ">" | ">=" ;
variable = record-variable | text-variable ;
argument-list = term, {",", term} ;
term = record-variable | text-variable | "_" | string
| field-access ;
record-term = record-variable | string ;
field-access = record-variable, (".", field-identifier,
["[", occurrence-index, "]"]
| "[", string, "]", ["[", occurrence-index, "]"]) ;
link-access = record-variable, "[", string, "]", "(", term, ")" ;
occurrence-index = "_" | unsigned-decimal ;
helper-name = ? [a-z][a-z0-9_]* except peer_confidential, true, and not ? ;
record-variable = ? [A-Z][A-Za-z0-9_]* ? ;
text-variable = ? [a-z][a-z0-9_]* except true and not ? ;
field-identifier = ? [A-Za-z][A-Za-z0-9_]* ? ;
unsigned-decimal = ? 0 or [1-9][0-9]* ? ;
string = ? 030 single-quoted constant ? ;
datalog-rule-line = ? one ordinary 030 rule on one physical line, then EOL ? ;
EOL = ? LF, or end of source after the final content line ? ;
INDENT = ? increase indentation by exactly two ASCII spaces ? ;
DEDENT = ? return to an enclosing indentation level ? ;