-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgrammar.ebnf
More file actions
585 lines (465 loc) · 28.7 KB
/
Copy pathgrammar.ebnf
File metadata and controls
585 lines (465 loc) · 28.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
(*
Silicon — Formal Grammar (EBNF) — ADR-0020 surface
Notation follows ISO/IEC 14977 with these extensions:
[ … ] optional
{ … } zero or more repetitions
( … ) grouping
| alternation
" … " terminal string
(* … *) comment
This EBNF is the human-readable grammar specification for Silicon as defined
by ADR-0020 (Odin-inspired, keyword-light surface). It is implemented by the
hand-written, dependency-free recursive-descent parser in
src/parser/handwritten/ (lexer.ts + parser.ts); there is no separate grammar
file. This EBNF is the canonical contract the parser implements.
ADR-0020 in one breath:
- A bare `name := value` is an IMMUTABLE value binding (the common case).
At the top level it is an immutable global; inside a block it is an
immutable local (enforced — reassignment is a type error, E0007).
- `@mut name := value` is a MUTABLE value binding.
- `@fn name params? := body` defines a function (params are bare; their
types come from a preceding `\\` signature line).
- `@type Name := …` defines a type: `$A | $B` is a sum, `{ field Type, … }`
is a struct, a bare TypeExpr is an alias.
- Calls are ALWAYS parenthesised: user `f(a, b)` and intrinsics
`@if(c, {t}, {e})`, `@loop(cond, {b})`, `@match(x, $A v => v, …)`,
`@toInt(x)`, etc. There is no call sigil.
- Types are SPACE-separated / positional, never colon-annotated:
`\\ add (Int, Int) -> Int`, struct field `x Int`, payload `$Some value T`.
":" is reserved for ":=" and "::" only.
TRANSITIONAL NOTE (migration only — NOT part of this canonical grammar):
The parser is currently an ADDITIVE SUPERSET. Alongside the ADR-0020 forms
above it still ACCEPTS the legacy pre-ADR-0020 surface (the "&"-call sigil,
explicit `@fn`/`@global`/`@local`/`@var`, `@struct`/`@enum`, and paren-free
calls) so existing src/strata/*.si and the test corpus keep parsing during
migration. Those forms are DEPRECATED and slated for removal; they are
documented for reference in §Legacy below but are NOT the language.
See §LL(1) below for the parser-shaped (left-factored) presentation of the
productions that look non-LL(1) in their natural form.
See docs/stability.md for the stability contract on this grammar.
See docs/strata.md for how strata extend the keyword/operator surface
without grammar changes.
*)
(* ── Top level ──────────────────────────────────────────────────────────── *)
Program = { Element } ;
Element = SignatureLine (* a `\\` type signature for the next def *)
| Item Terminator
;
(* `#`/`##` comments are stripped as trivia by the lexer and are not
Elements — see §Whitespace and comments. *)
Terminator = ";"
| (* virtual semicolon: newline or EOF accepted only when the
parser is already at an Element terminator position; see
§Automatic semicolon insertion. *)
;
Item = Definition
| Assignment
| Expression
;
(* ── Definitions (unified, keyword-light) ───────────────────────────────── *)
(*
Kinds of definition, distinguished by their leading token:
- bare `name := value` — immutable VALUE binding
- "@mut" `@mut name := value` — mutable VALUE binding
- "@fn" `@fn name params := body` — FUNCTION definition
- "@type" `@type Name := TypeRhs` — TYPE definition
A `\\` SignatureLine on the preceding element carries the value/function type;
see SignatureLine below. A bare definition (no keyword) may itself follow a
signature line — params present ⇒ a function, no params ⇒ an immutable binding.
KIND / scope rules (semantic, post-parse):
- A bare `name := value` is IMMUTABLE. At the top level it is an immutable
global; inside a block it is an immutable local. The typechecker rejects
reassignment to it (E0007).
- "@mut" is legal only on a VALUE binding (no params) and makes it mutable.
- "@fn" marks a function explicitly; bare params imply a function.
- "@type" puts its RHS in TYPE context (StructType / VariantSum / alias).
*)
Definition = BareDef (* name := value — immutable binding *)
| "@mut" BareDef (* mutable value binding *)
| "@fn" FnDef (* function definition *)
| "@type" TypeDef (* type definition *)
;
BareDef = Namespace [ GenericParams ] [ Params ] Binding ;
FnDef = Namespace [ GenericParams ] [ Params ] Binding ;
TypeDef = Namespace [ GenericParams ] ":=" TypeRhs ;
Binding = ":=" Expression ;
(*
The RHS of a `@type` is read in TYPE context:
@type Maybe := $Some value Int | $None (* sum *)
@type Point := { x Int, y Int } (* struct *)
@type Id := Int (* alias *)
Parser reality: only the `:= {` struct form is recognised syntactically as a
struct. A sum RHS is parsed as an ordinary expression — a `$`-variant Primary
chained by the `|` BinOp — and an alias RHS as a bare Namespace / type
expression; the TYPE meaning is assigned at elaboration.
*)
TypeRhs = StructType
| VariantSum
| TypeExpr
;
StructType = "{" [ TypedField { "," TypedField } ] "}" ;
VariantSum = VariantCase { "|" VariantCase } ; (* "|" reuses the BinOp token *)
VariantCase = "$" identifier [ TypedField { "," TypedField } ] ;
(* A struct field or variant payload: "name Type" — the type is OPTIONAL (fields
reuse the bare-parameter rule, so `x` and `x Int` both parse). No colon. *)
TypedField = identifier [ TypeExpr ] ;
GenericParams = "[" identifier { "," identifier } "]" ;
(*
Function parameters are BARE identifiers — their types come from the `\\`
signature line. There is no parenthesised parameter form: parentheses now
mean a call or a grouping. A parameter may optionally carry an inline type.
@fn add a, b := { a + b } \\ add (Int, Int) -> Int
*)
Params = Param { "," Param } ;
Param = identifier [ TypeExpr ] ;
(* ── Signature lines ────────────────────────────────────────────────────── *)
(*
A `\\` SignatureLine attaches a type to the definition that follows it. The
definition may be keyword-led (`@fn`/`@type`/…) or BARE — a bare definition
with params is a function, a bare definition without params is an immutable
binding.
\\ add (Int, Int) -> Int
add a, b := { a + b };
SIGNATURE-LINE MODIFIERS (ADR-0020 decision 8, amended by ADR-0024): the `\\`
line may carry leading modifiers. IMPLEMENTED: `@extern` (body-less external),
`@export` (host/WIT-world export — the signature-line form of the `@export name;`
statement), and `@pub` (ADR-0024 module visibility: callable across the module
boundary as `mod::name`). They appear in any order and are a pure prefix-token
loop (LL(1)). `@platform(…)` is still designed-only.
DEPENDENCY IMPORT (ADR-0024): `\\ @use name [as alias];` binds a dependency
component for the file (then call `alias::fn` / `name::fn`). Implemented as a
pre-parse directive (like `@use 'path';`), not a SignatureLine production.
*)
SignatureLine = "\\" { Modifier } Namespace [ GenericParams ] TypeExpr ;
Modifier = "@extern" | "@export" | "@pub" | "@platform" "(" identifier ")" ;
(* ── Assignments (reassignment) ─────────────────────────────────────────── *)
(*
Reassignment is "=" (not ":="). It is legal only against a MUTABLE binding
(`@mut`); reassigning an immutable bare binding is a type error (E0007).
@mut n := 0; n = n + 1;
There is no element/index assignment form — collection mutation goes through
calls, e.g. `vec_set_i32(v, i, x)`.
*)
Assignment = Namespace "=" Expression ;
(* ── Expressions ────────────────────────────────────────────────────────── *)
(*
Expressions are left-associative binary chains over a postfix-called primary.
There is no precedence table: all operators associate left-to-right.
Precedence is expressed via parentheses or nested calls.
*)
Expression = ExprEnd { BinOp ExprEnd } ;
ExprEnd = Primary { CallSuffix } ; (* postfix call: f(...) *)
Primary = Literal
| VariantCtor (* $Name declarator / pattern (type RHS, @match) *)
| Keyword (* @if/@loop/@match/… ; args via CallSuffix *)
| Namespace (* a name, or a call `name(args)` via CallSuffix *)
| Block
| "(" Expression ")"
;
(* Calls are ALWAYS parenthesised: user `f(a, b)` and intrinsic `@if(c, …)`. *)
CallSuffix = "(" [ Expression { "," Expression } ] ")" ;
Keyword = "@" identifier ;
(* In expression context a Keyword MUST be followed by a CallSuffix
— `@if(...)`, `@loop(...)`, `@match(...)`. A bare @kw with no
"(" is an error (except the @true/@false literals below). *)
BinOp = operator-char { operator-char }
| ".." (* half-open range — ADR 0016 *)
;
operator-char
= "=" | "<" | ">" | "!" | "+" | "-" | "*" | "/" | "%"
| "^" | "|" | "~" | "?"
;
(* Note: "&", "@", "$", "." are NOT valid operator characters — "@"
is the keyword sigil, "$" the variant/literal sigil, "." a
namespace separator, and "&" is now a free (non-operator)
character (the call sigil it once denoted is retired). ":"
appears only in ":=" (binding) and "::" (namespace), never as a
type-annotation sigil. "->" (an operator string) denotes a
function type inside a TypeExpr. "=>" (an operator run) is the
match-arm arrow. "|" doubles as the variant separator inside a
`@type` VariantSum.
".." is a distinct two-character token the lexer recognises
ahead of the single "." namespace separator (ADR 0016). It is a
half-open range `lo..hi` (hi excluded) and is SYNTACTIC-ONLY:
valid only as the subject of an iterate `@loop`, not a
first-class value. A `..` anywhere else is rejected at
elaboration; `1...5`, `1..`, `..5`, `a..b..c` are errors. *)
(* ── Variants ───────────────────────────────────────────────────────────── *)
(*
`$Name` is the variant DECLARATOR / PATTERN sigil. It appears in a `@type`
sum RHS and in `@match` patterns, optionally carrying comma-separated payload
fields (`$Some value T`, `$None`). It does NOT take a CallSuffix.
VALUE-position construction does NOT use `$`: it calls the generated
constructor function by plain name — `Some(x)`, `None()` — an ordinary
Namespace + CallSuffix. So `Some(42)`, not `$Some(42)`, builds a value.
*)
VariantCtor = "$" identifier [ Param { "," Param } ] ;
(* ── Blocks ─────────────────────────────────────────────────────────────── *)
(*
A block is a sequence of ";"-terminated items with an optional trailing
expression that becomes the block's value (no ";"). A bare `name := value`
inside a block is an immutable LOCAL (scope inferred from block depth).
*)
Block = "{" { Item ";" } [ Expression ] "}" ;
(* ── Namespace paths ─────────────────────────────────────────────────────── *)
Namespace = identifier { ("::" | ".") identifier } ;
(* e.g. foo module::bar std.io *)
(* ── Literals ────────────────────────────────────────────────────────────── *)
Literal = ArrayLiteral
| ObjectLiteral
| TupleLiteral
| StringLiteral
| FloatLiteral
| IntLiteral
| BooleanLiteral
;
ArrayLiteral = "$[" [ Expression { "," Expression } ] "]" ;
ObjectLiteral = "${" [ KeyValuePair { "," KeyValuePair } ] "}" ;
TupleLiteral = "$(" [ Expression { "," Expression } ] ")" ;
KeyValuePair = identifier "=" Expression ;
StringLiteral = "'" { string-char } "'" ;
string-char = (* any character except "'", "\", and line terminators *) ;
IntLiteral = HexLiteral | BinLiteral | OctLiteral | DecLiteral ;
DecLiteral = digit { digit } { "_" digit { digit } } ;
HexLiteral = ("0x" | "0X") hexdigit { hexdigit | "_" hexdigit } ;
BinLiteral = ("0b" | "0B") bindigit { bindigit | "_" bindigit } ;
OctLiteral = ("0o" | "0O") octdigit { octdigit | "_" octdigit } ;
FloatLiteral = DecLiteral "." digit { digit } { "_" digit { digit } } ;
(* both the integer and fractional parts accept "_" digit
separators, e.g. 123_456.789_012; they are stripped from
the value. *)
BooleanLiteral = "@true" | "@false" ;
(* ── Types ───────────────────────────────────────────────────────────────── *)
(*
Types appear in `\\` signature lines, in struct fields / variant payloads
("name Type"), as type arguments, and as a `@type` alias RHS. There is NO
leading ":" — type syntax is space-separated / positional. A function type
is parenthesised parameter types and "->": (Int, Int) -> Int. A value type
is bare: Int, Option[Int].
Examples:
(Int, Int) -> Int — binary function returning Int
() -> Bool — nullary function returning Bool
Int — a plain value type
Option[Int] — a generic value type
*)
TypeExpr = TypeAtom [ "->" TypeExpr ] ; (* "->" = function type *)
TypeAtom = "(" [ TypeExpr { "," TypeExpr } ] ")" (* param list / grouping *)
| identifier [ TypeArgs ] (* Int, Option[Int] *)
;
TypeArgs = "[" TypeExpr { "," TypeExpr } "]" ;
(* ── Identifiers ─────────────────────────────────────────────────────────── *)
identifier = "_" (* discard — no binding *)
| letter { letter | digit | "_" }
| "_" (letter | digit | "_") { letter | digit | "_" }
;
letter = "a".."z" | "A".."Z" ;
digit = "0".."9" ;
hexdigit = digit | "a".."f" | "A".."F" ;
bindigit = "0" | "1" ;
octdigit = "0".."7" ;
(* ── Whitespace and comments ─────────────────────────────────────────────── *)
(*
Whitespace and comments are ignored everywhere between tokens.
They are not listed in production rules above for clarity, except that the
hand-written lexer preserves whether skipped trivia before a token contained a
line-end. The parser uses that metadata for ADR-0026 automatic semicolon
insertion.
*)
whitespace = " " | "\t" | "\x0B" | "\x0C" | " " | ""
| (* Unicode space separators U+2000–U+200B, U+3000 *)
;
line-end = "\n" | "\r" | "" | "" ;
line-comment = "#" { (* any char except line-end *) } line-end ;
(*
Automatic semicolon insertion (ADR-0026, accepted):
- Explicit ";" remains valid forever.
- At the top level, newline or EOF may terminate a complete Element.
- Inside blocks, newline may terminate a complete item only when the next
token starts another item.
- Newline before "}" does not insert a terminator by default; the preceding
item remains the block's trailing value.
- Newline is not a terminator before continuation tokens such as binary
operators, comma, "."/"::", "(", ")" or "]".
- A call's "(" must stay on the same logical line as the callee.
- Normal "\\" signature lines attach to the following definition; bodyless
"\\ @extern ..." signatures may terminate at line end.
This is parser-position ASI, not lexer-global semicolon rewriting.
*)
(*
Notes:
- "#" starts a line comment; "##" is also consumed as a line comment. Both
are stripped as trivia during lexing — no DocComment node is produced.
- `@use '<path>';` is NOT a grammar construct — it is a pre-parse module
directive resolved (and stripped) by the use-resolver before parsing, like
the comments above. It never reaches this grammar.
- String literals use "'" (single quote). There are no escape sequences
in 0.1 — use raw bytes or unicode code points via WASM intrinsics.
*)
(* ── LL(1) form ──────────────────────────────────────────────────────────── *)
(*
Silicon's grammar targets LL(1): top-down, leftmost derivation, single token
of lookahead. The Item three-way (definition / assignment / expression) and
the Block trailing-expression rule are written above in their natural,
human-readable form rather than the left-factored form the parser consumes.
Both encode the same language under either presentation — left-factoring is a
parser-side concern, not a surface change. This section gives the
left-factored equivalents for reference.
──────────────────────────────────────────────────────────────────────
0. Element / first-token classifier
At the top of an Element the parser peeks ONE token (and at most one more):
\\ -> SignatureLine, then the next def
def-kw NOT followed by "(" -> Definition (@mut / @fn / @type)
def-kw followed by "(" -> a keyword CALL, e.g. @if(...) —
routed to Item as an Expression
otherwise -> Item
A leading @kw is therefore a DEFINITION only when it is NOT immediately
followed by "(". `@if(c, {t}, {e})` is an expression-statement, not a def.
──────────────────────────────────────────────────────────────────────
1. Item — definition vs. reassignment vs. expression-as-statement
Natural form (above):
Item = Definition | Assignment | Expression ;
Definition = BareDef | "@mut" BareDef | "@fn" FnDef | "@type" TypeDef ;
Assignment = Namespace "=" Expression ;
A bare definition, a reassignment, and an expression-via-Namespace all start
with `identifier`, so the parser cannot pick the production from the leading
token alone. It first strips an optional "@mut", or takes the "@type" path,
then parses the leading Namespace and decides on the NEXT token.
Left-factored (LL(1)):
Item = [ "@mut" ] Namespace ItemTail
| "@fn" FnDef
| "@type" TypeDef
| NonNamespacePrimary { CallSuffix } { BinOp ExprEnd }
;
ItemTail = [ GenericParams ] [ Params ] ":=" Expression (* definition *)
| "=" Expression (* reassignment *)
| { CallSuffix } { BinOp ExprEnd } (* expression-stmt *)
;
After the leading Namespace, ONE token decides ItemTail:
- a bare `identifier` / "[" (generics) / ":=" -> definition;
- "=" -> reassignment;
- "(" (a CallSuffix) or a BinOp -> expression-statement.
Single token of lookahead past the LHS, no backtracking — statement-local,
as the line-independence architecture requires.
──────────────────────────────────────────────────────────────────────
2. Block — trailing expression vs. another statement
Natural form (above):
Block = "{" { Item ";" } [ Expression ] "}" ;
After parsing what looks like an Item, the parser cannot tell whether it was
a statement (followed by ";") or the trailing expression (followed by "}")
until it has consumed the whole thing.
Left-factored (LL(1)):
Block = "{" BlockBody "}" ;
BlockBody = (* empty block *)
| Item BlockTail
;
BlockTail = ";" BlockBody (* was a statement; loop *)
| (* was the trailing expr *)
;
After parsing one Item, ONE token of lookahead decides BlockTail:
";" -> statement, continue; "}" -> trailing expression, done. Block entry
increments a block-depth counter; a bare `name := value` parsed at depth > 0
is an immutable LOCAL, at depth 0 an immutable GLOBAL.
──────────────────────────────────────────────────────────────────────
Tokenization assumptions
The LL(1) classification assumes a tokenizer that treats the following as
single multi-character tokens (not character sequences needing multi-char
lookahead in the parser):
"$[" "${" "$(" (* array / object / tuple literal openers *)
"::" (* namespace separator *)
":=" (* definition binding *)
"\\" (* attached-signature sigil (two backslashes) *)
"->" (* function-type arrow inside a TypeExpr *)
".." (* half-open range — distinct from "." (ADR 0016) *)
"@true" "@false" (* boolean literals — distinct from @kw *)
Operator runs ("=", "==", "=>", "<=", "->", "++", …) are lexed by maximal
munch into a single `op` token; "=>" (match-arm arrow) and "->" (function-type
arrow) are ordinary operator runs, not dedicated tokens.
Under these tokenization rules:
- VariantCtor ("$" identifier) vs. literal openers ($[ / ${ / $()
is LL(1): they are distinct tokens at the lexer level.
- Boolean literals (@true / @false) vs. keyword calls (@kw "(")
is LL(1): the reserved boolean tokens are recognised by text, and a @kw
in expression position requires a following "(".
The hand-written LL(1) recursive-descent parser in src/parser/handwritten/
disambiguates these spots directly, following the left-factored forms
documented above — there are no PEG-style ordered alternatives.
──────────────────────────────────────────────────────────────────────
What this is NOT
This appendix does NOT change the language — every program that parses under
the natural form parses identically under the left-factored form. It only
documents the parser shape a strict LL(1) implementation must follow.
*)
(* ── Strata boundary ─────────────────────────────────────────────────────── *)
(*
This grammar defines the fixed syntactic skeleton of Silicon. It is
intentionally minimal and stable.
ADR-0020 fixes a small set of keywords into the grammar skeleton itself —
the binding/type/mutability surface:
@mut @fn @type @true @false
@extern (* brace form `@extern { \\ … }` today;
`\\ @extern`/@export/@platform modifiers
are designed but NOT YET parsed *)
All OTHER keywords are resolved at elaboration time via the strata registry.
This means:
- @if, @loop, @match, @return, @defer, @try, @as, @toInt, … — all are
elaborated keywords, not grammar reserved words. In expression position
each appears as `@kw(args…)` (a Keyword Primary with a CallSuffix).
- @use is NOT a grammar construct: `@use '<path>';` is a pre-parse module
directive resolved (and stripped) by the use-resolver before parsing.
- @loop is one keyword whose meaning is chosen at elaboration by the count
of comma-operands before the trailing { body } block (ADR 0016):
@loop({ body }) infinite (≡ @loop(1, { body }))
@loop(cond, { body }) while — cond re-checked each iteration
@loop(v, subject, { body }) iterate: v ← each element
@loop(i, v, subject, { body }) iterate: i ← position, v ← element
`subject` is a `lo..hi` range or an i32-element Vec; `_` discards a binder;
≥ 4 operands are rejected. Every form desugars to the while shape, so the
grammar needs no new production — it is an ordinary parenthesised call.
- The surface storage/shape keywords of the pre-ADR-0020 grammar —
@global, @local, @var, @struct, @enum — are DROPPED from the canonical
surface. Storage is inferred from scope (top level ⇒ global, in block ⇒
local), mutability is explicit via @mut, and struct/sum shapes are
written as `@type Name := { … }` / `@type Name := $A | $B`. (The parser
still accepts the old keywords transitionally — see §Legacy.)
- User-defined strata keywords (@my_kw) are syntactically identical to any
other @identifier. The grammar accepts them; the elaborator resolves
them. In expression position they too take the `@kw(args…)` call form.
- The grammar will NOT be extended to add new keyword tokens. New language
features ride the existing Definition and parenthesised-call forms via
new strata entries.
See docs/strata.md for authoring strata; docs/strata-authoring-guide.md for
the &Compiler::* API; docs/stability.md §1 for the language stability rules.
*)
(* ── Legacy (transitional — DEPRECATED, NOT canonical) ───────────────────── *)
(*
During migration to ADR-0020 the hand-written parser is an ADDITIVE SUPERSET:
in addition to everything above it STILL ACCEPTS the legacy pre-ADR-0020
surface so the existing src/strata/*.si sources and the test corpus keep
parsing. None of the forms below are part of the canonical grammar; they are
documented only so readers of legacy code are not surprised. They will be
retired.
- "&"-call sigil. Paren-free calls introduced by "&":
& add 1, 2 ≡ add(1, 2)
& @if cond, {t}, {e} ≡ @if(cond, {t}, {e})
& @as Type, expr (ascription — a dedicated AscribeExpr form)
LegacyCall = "&" ( Keyword | Namespace ) LegacyCallArgs ;
LegacyCallArgs = Expression { "," Expression } | (* empty *) ;
ADR-0020 retires "&" entirely; calls are always parenthesised.
- Explicit storage / mutability keywords. `@global name := v`,
`@local name := v`, and `@var name := v` are accepted and mapped directly
to their kinds; unlike a bare `name := v` they do NOT auto-scope by depth
and do NOT carry the immutable flag. ADR-0020 uses bare `name := v` and
`@mut name := v` instead.
- Explicit shape keywords. `@struct Point x Int, y Int` and
`@enum …` are accepted and mapped to the corresponding type definition.
ADR-0020 writes `@type Point := { x Int, y Int }` and
`@type Name := $A | $B`.
- Parenthesised parameter lists and the `@extern { \\ … }` /
`@interface { \\ … }` SignatureBlock brace form are accepted. ADR-0020
uses bare Params; the brace form remains the only extern syntax until the
`\\ @extern …` modifier form (decision 8) is implemented.
- "@fn" optional after `\\`. Legacy code may still write an explicit `@fn`
where a bare definition now suffices.
Migration commits: 6a4ad72 (model), 70269ef (immutable flag + typechecker
E0007), 07e29f4 (parser additive superset).
*)