日本語 · English

Kairos Language Specification — 5. Grammar and Symbols

Translated from the canonical Japanese chapter spec/40-grammar.md. The source_sha above records the source revision; a consistency check flags this page when the Japanese original changes.

5.1 Symbol table

Symbol Layer / role Meaning
|> Both layers Stage connection (body layer: stream → stream / premise layer: premise → premise)
. Both layers Premise qualification (Gregorian.month)
| Both layers Combinator: union
& Both layers Combinator: intersection
\ Both layers Combinator: difference (U+005C — not the yen sign ¥)
= Both layers Binding (definition)
== != < <= > >= Value expr. Comparison
+ - * / mod div Value expr. Arithmetic (mod = remainder and div = integer division are words)
and or not Value expr. Logic (words; symbols are reserved for the combinators)
? : Value expr. Ternary conditional
in Value expr. List-membership predicate
[…] / l[i] Value expr. List literal / indexing (0-based). A list of instant elements is a time-stream constant (§3.8)
"…" Value expr. String literal (no newlines, no escapes; the values of TZs and provenance. ADR-32)
.. Value expr. Date range (sugar expanding to consecutive days)
=> Both layers Lambda binding (distinct from the type notation ->)
@name Preamble Lightweight reference to a premise bundle
# Both layers Line comment

5.2 Signatures of the body-layer core family

Generators and windowing:

In-window selection and thinning:

Moving points:

Projections and combinators:

Strides (a family separate from the selectors): the anchor is mandatory (ADR-31); no reset is defaulted.

Window-membership predicate (projection family; §4.9):

5.3 Signatures of the premise layer

Window-generating words of primitive definitions (names final; §5.4): grid, span and split produce windows; cycle produces labels.

Among the window-generating words, the one that produces labels:

Public words are the top-level bindings of a premise block (Gregorian.month). Boundaries are selector reuse (monthStart = month |> first).

Tables and external supply:

The right-hand side of a premise binding may also be a body-layer stream expression (layer crossing; the definition of week is an example. §3.6).

Derived definitions:

Sugar definitions (no dedicated syntax = the existing = binding):

5.4 Naming status

Settled (2026-07-07, as of RC2, plus ADR-31/32): first, the words settled at RC1 — no cracks in the 40-examples expressiveness verification.

Words confirmed as final names through ADR-29/30:

The body layer’s symbols and the lexis are likewise settled.

Settled (2026-07-14, ADR-46): the external-supply declaration external (the rival candidate socket was rejected because it would be the same spelling with a different sense as the “socket” metaphor of ADR-15 in the English-language documents) and the kind values dates/instants (consistent in feel with everyInstant and the date literal; a sweep confirmed no collisions with existing identifiers).

Settled (2026-07-09, the F51 batch confirmation): nonWorking (the reserved public word for entities; §3.9), coincides (the window-membership predicate; §4.9 — the rival candidates hits/anyIn/sharesWindow were rejected), rebase (re-anchoring; §4.4 — the rival candidates relabel/sameDate were rejected), and bizOpen/bizClose/isOpen (the standard derivations; §3.9.1) are confirmed as final names unchanged. The business-hours supply pair is renamed and finalized as sessionOpens/sessionCloses (formerly the placeholders opens/closes — ordinary English words with a wide collision surface against accidental same-name bindings; ADR-41 revised). All have seen live use (16–35 files) with no cracks in the names.

Final (2026-07-26, the last placeholder resolved): the window-boundary phase-shift sugar is rephase (formerly the placeholder shiftBoundary). With this, every word bears its official name — no placeholders remain (the history is CHANGELOG addendum 11).

Addendum (2026-08-17, ADR-49/50): words settled later.

Addendum (2026-08-21, ADR-51/52): words settled later.

5.5 Lexis (ADR-28)

Date literals: YYYY-MM-DD (day) and YYYY-MM-DDThh:mm(:ss(.f+)?)? (with time of day). They carry no TZ specifier — the anchor is struck by the in-scope premise’s tz: (I6).

Rules for the time part:

Standalone time literals (ADR-51): Thh:mm(:ss(.f+)?)? — a wall-clock time with no date part. The value is a time of day; it materializes into an instant only in the strideBy(1d, from:) position.

String literals: from " to the next " (no newlines inside; no escape sequences = strings containing quotes are a future extension. ADR-32). Used as the member values of tz: and source:.

Width literals: a number + unit sequence. d = civil day (a conventional width); h/m/s = elapsed time.

Numbers: integers and decimals.

Identifiers and enumeration labels: Unicode letters are allowed (kanji labels such as 甲 and 子 are fine). The reserved symbols (|> . | & \ = => < > [ ] ( ) { } : , ? @ #) and whitespace cannot appear in identifiers.

Comments: from # to the end of the line.

Statement separation and continuation (ADR-44): statements (bindings, premise members, body expressions) are separated by newlines. There is no continuation symbol.

5.6 Grammar (EBNF)

The structure and the vocabulary are both final (§5.4 — no placeholders remain). Sugar (the a..b range, the point-free shorthand) disappears into core on expansion.

(* ---- Program ---- *)
program        = { statement } ;
statement      = premise-def | preamble | binding | stream-expr ;

(* ---- Premise layer ---- *)
premise-def    = "premise" , name , ( premise-block | "=" , premise-expr ) ;
premise-expr   = name , [ "with" , premise-block ] , { "|>" , stage } ;
premise-block  = "{" , { member | binding } , "}" ;
member         = member-key , ":" , value-expr ;
member-key     = "calendar-system" | "calendar" | "axis" | "roll" | "granularity"
               | "tz" | "wkst" | "asof" | "source" | "epoch" ;
                 (* epoch is exclusive to primitive-definition blocks
                    (not allowed in user-side preambles. ADR-31) *)

(* Preamble (independent statement; governs what follows until the next preamble. §3.2) *)
preamble       = ( "@" , name , { member } )            (* lightweight form + postfix folding *)
               | ( "premise" , premise-block )          (* full inline form *)
               | ( "@" , name , "{" , { statement } , "}" ) ;   (* block form (explicit extent) *)

(* ---- Bindings (public words, sugar, and value functions share one mechanism) ---- *)
binding        = name , "(" , params , ")" , "=" , rhs
               | name , "=" , rhs ,
                 [ "covering" , ":" , covering-list ] ;  (* the postfix coverage claim (ADR-37 decision 5)
                                                            attaches only to parameterless bindings (postfix
                                                            on a parameterized binding is a static error —
                                                            the grammar now splits the branches = addendum 16
                                                            patch). When rhs is a bare table literal it reads
                                                            as a table attribute (the canonical parse. ADR-45) *)
params         = param , { "," , param } ;
param          = name | named-param ;
named-param    = param-key , ":" , name ;
rhs            = lambda | stream-expr | gen-expr | value-expr ;

(* ---- Body layer ---- *)
stream-expr    = pipe-expr , { combine-op , pipe-expr } ;     (* one precedence level, left-assoc. *)
combine-op     = "|" | "&" | "\" ;
pipe-expr      = stream-atom , { "|>" , stage } ;
stream-atom    = table-literal | ( name | qualified ) , [ "(" , args , ")" ] | "(" , stream-expr , ")" ;
                 (* qualified application = head-position application of a qualification pin
                    (Gregorian.year(2020). ADR-42) *)
stage          = ( name | qualified ) , [ "(" , args , ")" ] ;
qualified      = name , "." , name ;
args           = arg , { "," , arg } ;
arg            = named-arg | lambda | stream-expr | value-expr ;
named-arg      = param-key , ":" , ( lambda | stream-expr | value-expr | cycle-labels ) ;
                                       (* cycle-labels only in the value position of segmentBy's
                                          labels: (ADR-47) *)
cycle-labels   = "cycle" , ( list-literal | name ) , "anchor" , ":" , date-literal ;
param-key      = "on" | "unit" | "of" | "from" | "edges" | "empties"
               | "by" | "anchor" | "phase" | "covering" | "label" | "labels"
               | "kind" | "source"                       (* kind:/source: are external's (ADR-46) *)
               | "to" | "until" ;                        (* to: is rebase's (ADR-40), until: is takeLast's (ADR-52) —
                                                            omissions patched in addendum 15 (review 11, item B) *)

(* ---- Premise-layer window generation (infix) ---- *)
gen-expr       = operand , gen-word , gen-arg , { named-arg } ;
operand        = name ;                                  (* chronos, day, month, year, etc. *)
gen-word       = "grid" | "span" | "split" | "cycle" ;
gen-arg        = width-literal | lambda | list-literal | name | "(" , lambda , ")" ;

(* ---- Value expressions ---- *)
value-expr     = ternary ;
ternary        = or-expr , [ "?" , value-expr , ":" , value-expr ] ;
or-expr        = and-expr , { "or" , and-expr } ;
and-expr       = unary-not , { "and" , unary-not } ;
unary-not      = [ "not" ] , comparison ;                (* the function form not(x) is also allowed *)
comparison     = additive , [ comp-op , additive ] ;
comp-op        = "==" | "!=" | "<" | "<=" | ">" | ">=" | "in" ;
additive       = multiplicative , { ( "+" | "-" ) , multiplicative } ;
multiplicative = unary , { ( "*" | "/" | "mod" | "div" ) , unary } ;
unary          = [ "-" ] , postfix ;
postfix        = atom , { "[" , value-expr , "]" | "(" , args , ")" } ;
atom           = number | date-literal | time-literal | width-literal | string-literal | name | qualified
               | list-literal | "(" , value-expr , ")" ;
lambda         = ( name | "_" | "(" , params , ")" ) , "=>" , ( value-expr | stream-expr ) ;

(* ---- Lists and tables ---- *)
list-literal   = "[" , [ list-elem , { "," , list-elem } ] , "]" ;
list-elem      = value-expr | date-range ;
date-range     = date-literal , ".." , date-literal ;    (* sugar expanded into consecutive days *)
table-literal  = list-literal , { "covering" , ":" , covering-list
                                | "labels" , ":" , list-literal } ; (* postfixes in either order, once
                                                            each (a duplicate is a static error;
                                                            RC5 addendum 9). A stream constant when the
                                                            elements are instants (§3.8). Zero elements
                                                            are a stream constant only with a covering:
                                                            postfix (the empty table. ADR-45). labels:
                                                            is a parallel
                                                            label sequence (ADR-30) *)
covering-list  = covering-range , { "," , covering-range } ;
                 (* interval list = declaring interior gaps (ADR-37) *)
covering-range = [ covering-edge ] , ".." , [ covering-edge ] ; (* omitted edge = open end (completeness
                                                                   claim). Bare ".." = complete everywhere.
                                                                   ADR-37 decision 9 *)
covering-edge  = date-literal | digit4 ;                 (* year-only shorthand allowed *)

(* ---- Lexis (§5.5) ---- *)
date-literal   = digit4 , "-" , digit2 , "-" , digit2 ,
                 [ "T" , digit2 , ":" , digit2 , [ ":" , digit2 , [ "." , digits ] ] ] ;
time-literal   = "T" , digit2 , ":" , digit2 , [ ":" , digit2 , [ "." , digits ] ] ;  (* standalone time. ADR-51 *)
width-literal  = width-seg , { width-seg } ;             (* one or more segments — the empty string is
                                                            not derivable. Mixing civil d with elapsed
                                                            h/m/s is a static error (ADR-28 = the
                                                            classification is a check, not lexis) *)
width-seg      = number , ( "d" | "h" | "m" | "s" ) ;
number         = digits , [ "." , digits ] ;
string-literal = '"' , { ? any character except " and newline ? } , '"' ;   (* no escapes. ADR-32 *)
name           = letter , { letter | digit } ;           (* letter = any Unicode letter (kanji allowed) *)
comment        = "#" , { ? any character up to end of line ? } ;

(* Basic lexis (addendum 16 patch — these were previously undefined nonterminals) *)
digit          = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;
digit2         = digit , digit ;
digit4         = digit , digit , digit , digit ;
digits         = digit , { digit } ;
letter         = ? a Unicode letter (category Letter; kanji allowed; no digits or symbols) ? ;

Notes:

Operator precedence is defined exactly by the nesting of the production rules above. The combinators share one precedence level and are left-associative; a check imposes the convention that mixing & requires parentheses (§4.5).

Rules that belong to scoping and to governance checks rather than to the grammar:

5.7 Open questions

The canonical record of open questions and homework is ../../design/90-open-questions.md (syntax-level details in ../../design/30-syntax/00-syntax-draft.md §4). Within the scope of this specification, a separate operator for the out-of-scope variable-k pairs (introduced only if needed; the word boundary is reserved for it) and anonymous-window labeling for of: remain open (naming is final for every word = the rephase ruling of 2026-07-26, §5.4; the external-supply declaration is final as external = ADR-46, §3.8). Neither changes the semantics (additive extension; the DoD classification is at the head of ../../design/90-open-questions.md).