1037 lines
48 KiB
Markdown
1037 lines
48 KiB
Markdown
# The ww Language Specification
|
|
|
|
Version 0.1 (2026-06-21). **Status: normative-intent, in progress.**
|
|
|
|
ww is a systems language: *Hare semantics + CSP concurrency, no garbage
|
|
collector*, with a Rob-Pike / Plan-9 sensibility and Go-style explicit
|
|
`package`/`import`. This document is the language contract. Where the two
|
|
compiler stages disagree with this text, the text is wrong and is a bug
|
|
to be filed (CLAUDE.md rule 10 requires the stages to agree with each
|
|
other; this spec requires them to agree with *it*).
|
|
|
|
Two implementations track this spec and must stay byte-identical
|
|
(rule 10): the C bootstrap frontend `cmd/wcc` (the reference) and the ww
|
|
reimplementation `lib/ww/syntax`. The token table in §2 is kept
|
|
numerically identical between them on purpose.
|
|
|
|
This is not a tutorial. It is terse by design, modelled on the Go
|
|
language specification (golang.org/ref/spec): EBNF where a grammar is
|
|
worth pinning, prose for semantics, and a normative appendix (§12) that
|
|
consolidates every sanctioned divergence from Hare. The appendix is the
|
|
highest-value part of this document — it is the single normative home
|
|
for rules currently scattered across CLAUDE.md, `.ai/`, and project
|
|
memory.
|
|
|
|
Sections marked **(reserved)** describe surface that is tokenised but not
|
|
yet given meaning; they are written when the feature lands.
|
|
|
|
---
|
|
|
|
## 1. Notation
|
|
|
|
The grammar uses Extended Backus-Naur Form, following the Go spec's
|
|
conventions:
|
|
|
|
```
|
|
Production = production_name "=" [ Expression ] "." .
|
|
Expression = Term { "|" Term } .
|
|
Term = Factor { Factor } .
|
|
Factor = production_name | token | Group | Option | Repetition .
|
|
Group = "(" Expression ")" .
|
|
Option = "[" Expression "]" .
|
|
Repetition = "{" Expression "}" .
|
|
```
|
|
|
|
`|` is alternation, `{}` is zero-or-more, `[]` is zero-or-one, `()` is
|
|
grouping. Terminal tokens are in `"quotes"` or are the named token
|
|
classes from §2 (`ident`, `int_lit`, …).
|
|
|
|
---
|
|
|
|
## 2. Lexical elements
|
|
|
|
### 2.1 Source representation
|
|
|
|
Source is UTF-8. One UTF-8-encoded byte order mark (U+FEFF, bytes
|
|
`EF BB BF`) is ignored when it is the first code point of a physical source
|
|
file. Its three encoded bytes still count in source positions, so a following
|
|
token on the first line begins at column 4. U+FEFF is invalid at
|
|
every other source position, including inside string and rune literals and
|
|
comments. Apart from that marker rule, the lexer operates on bytes and
|
|
non-ASCII bytes are legal only inside string and rune literals and comments.
|
|
|
|
Every malformed UTF-8 byte in an eligible selected physical source produces
|
|
one positioned `invalid UTF-8 encoding` error at its 1-based physical line and
|
|
raw-byte column. The byte is consumed and omitted from the lexer's logical
|
|
character stream before token recovery. A malformed multi-byte spelling is
|
|
therefore diagnosed once for each byte that decodes as U+FFFD with width one;
|
|
a correctly encoded U+FFFD is valid. Malformed bytes cannot split an
|
|
identifier, number or suffix, operator, escape, comment delimiter, package
|
|
keyword, or import spelling into different tokens. Filename and test-role
|
|
eligibility precede validation, so an excluded physical file contributes no
|
|
UTF-8 diagnostic. This rule is independent of the leading-BOM and raw-NUL
|
|
rules below and does not make source bytes package, import, graph, action,
|
|
artifact, publication, or persistence identity.
|
|
|
|
Each raw byte `00` (U+0000) is invalid at every physical source position,
|
|
including in comments and string or rune literal text. It produces one
|
|
positioned `invalid NUL character` error at that byte's source position and
|
|
is omitted from the lexer's logical character stream before token recovery.
|
|
It therefore cannot split an identifier, number, operator, escape, or comment
|
|
boundary into different tokens. This is a source-representation rule, before
|
|
package/import interpretation; it does not make U+0000 a package, import, or
|
|
artifact identity component. An escape spelling such as `"\\x00"` is not a
|
|
raw source byte and remains a legal literal value.
|
|
|
|
### 2.2 Comments
|
|
|
|
Line comments only, introduced by `//` and running to end of line. There
|
|
are no block comments (Hare lineage; `/* */` is not recognised).
|
|
|
|
Two comment forms are *driver directives*, not ordinary comments — they
|
|
carry module-bundle structure and lex to dedicated tokens:
|
|
|
|
- `//ww:module <dotted.path>` — set the mangling module path for the
|
|
decls that follow (token `MODPATH`).
|
|
- `//ww:module-reset` — reset the current module to the empty string
|
|
before a package-less bundled file (token `MODRESET`).
|
|
|
|
These exist for the build driver's source bundling and are not written
|
|
by hand in ordinary source.
|
|
|
|
### 2.3 Tokens
|
|
|
|
Tokens are identifiers, keywords, operators/punctuation, and literals.
|
|
Whitespace (space, tab, CR, LF) separates tokens and is otherwise
|
|
ignored. A semicolon `;` terminates declarations and statements and is
|
|
written explicitly (no automatic insertion).
|
|
|
|
### 2.4 Keywords
|
|
|
|
The following 30 identifiers are reserved and may not be used as ordinary
|
|
identifiers:
|
|
|
|
```
|
|
as break case chan const continue
|
|
def defer else enum export false
|
|
fn for if is import let
|
|
match nil package proc return static
|
|
struct switch true type void yield
|
|
```
|
|
|
|
`chan` and `proc` are reserved for the concurrency surface (§11,
|
|
reserved). `package` is the package-clause keyword; `import` introduces
|
|
an import.
|
|
|
|
### 2.5 Operators and punctuation
|
|
|
|
```
|
|
Assignment: = += -= *= /= %= &= |= ^= <<= >>=
|
|
Arithmetic: + - * / %
|
|
Bitwise: & | ^ ~ << >>
|
|
Comparison: == != < <= > >=
|
|
Logical: && || !
|
|
Grouping: ( ) { } [ ]
|
|
Punctuation: , ; : . .. ... @ ?
|
|
Arrows: <- -> =>
|
|
```
|
|
|
|
`<-` (channel send/receive) and `->` are tokenised but reserved (§11).
|
|
`=>` separates a `match` arm pattern from its body. `?` and `!` are the
|
|
error operators (§9). `:` introduces a type ascription / cast (§8) and a
|
|
field/binding type. `..` is the range operator (`for` range form, §7);
|
|
`...` is the variadic / spread marker (§6.3). `@` introduces an
|
|
attribute (§5.7).
|
|
|
|
### 2.6 Identifiers
|
|
|
|
```
|
|
ident = letter { letter | digit } .
|
|
letter = "A"…"Z" | "a"…"z" | "_" .
|
|
digit = "0"…"9" .
|
|
```
|
|
|
|
A lone `_` is the *discard* identifier: legal as a binding name to mean
|
|
"ignore this", never as a reference.
|
|
|
|
### 2.7 Literals
|
|
|
|
```
|
|
int_lit = decimal | hex | octal | binary [ suffix ] .
|
|
float_lit = digits "." digits [ exponent ] [ suffix ] .
|
|
rune_lit = "'" ( byte | escape ) "'" .
|
|
str_lit = '"' { byte | escape } '"' .
|
|
bool_lit = "true" | "false" .
|
|
nil_lit = "nil" .
|
|
```
|
|
|
|
- `int_lit` is held as a `u64`. `nil` is the null pointer value (§3.6).
|
|
- A numeric literal may carry a *type suffix* naming its type directly:
|
|
`42u8`, `0u64`, `1i64`, `3.0f32`. Suffixes are the fixed-width type
|
|
names (`i8 i16 i32 i64 u8 u16 u32 u64 f32 f64`). Without a suffix a
|
|
literal is an *untyped constant* (§3.5) whose type is inferred from
|
|
context.
|
|
- Escapes in rune and string literals: `\\ \" \' \n \t \r \0` and
|
|
`\xNN` (two hex digits). Rune literals additionally accept the
|
|
Unicode escapes `\uHHHH` and `\UHHHHHHHH`, yielding a codepoint.
|
|
|
|
---
|
|
|
|
## 3. Types
|
|
|
|
A type describes the layout and operations of a value. Every type has a
|
|
`size` and an alignment, both queryable with `size(T)` (§8). No size is
|
|
ever written as a bare literal in size-computing code — it is routed
|
|
through the type table (CLAUDE.md rule 13).
|
|
|
|
### 3.1 Boolean and void
|
|
|
|
`bool` (1 byte; `true`/`false`). `void` (size 0) is the empty type, used
|
|
as a fn result and as the second arm of a nullable pointer (§3.6).
|
|
|
|
### 3.2 Numeric types
|
|
|
|
Fixed-width integers: `i8 i16 i32 i64` (signed), `u8 u16 u32 u64`
|
|
(unsigned). Floats: `f32 f64`.
|
|
|
|
Machine-word types (8 bytes on amd64): `int`, `uint`, `uintptr`, `size`.
|
|
`int` and `uint` are a full machine word — Go-style, **not** Hare's 32-bit
|
|
`int`. The limit constants (`INT_MIN`, `INT_MAX`, `UINT_MAX`) are
|
|
**derived** from `size(int)`, never hardcoded to a width (§12).
|
|
|
|
`rune` is a 32-bit Unicode codepoint (4 bytes).
|
|
|
|
### 3.3 String and slice
|
|
|
|
`str` and `[]T` (slice of `T`) are both **24-byte three-word headers**:
|
|
`{ ptr, len, cap }`. Their `.ptr`, `.len`, and `.cap` pseudo-fields are
|
|
readable (§8.4). A `str` is UTF-8 bytes; reinterpreting `[]u8` as `str`
|
|
is `strings.frombytes` (a pure cast — there is no validation; §12).
|
|
|
|
Array: `[N]T` is `N` elements inline (size `N * size(T)`). `[_]T`
|
|
infers `N` from the initialiser's element count.
|
|
|
|
### 3.4 Aggregate and named types
|
|
|
|
```
|
|
type S = struct { f0: T0, f1: T1, … }; // struct
|
|
type Name = T; // named type / alias
|
|
```
|
|
|
|
- `struct` — named fields laid out in declaration order with natural
|
|
alignment padding. Duplicate field names are rejected.
|
|
- `tuple` — `(T0, T1, …)`. Each element occupies an **8-byte slot**
|
|
(`size((u32, u32)) == 16`), diverging from Hare's packed tuples (§12).
|
|
- `enum` — `enum i32 { A = 0, B, … }` (a named integer type with named
|
|
constants) or bare `enum { … }`.
|
|
- A named type / alias introduces a new spelling for an underlying type.
|
|
|
|
TODO(spec): structs carry an internal `packed` property (no padding);
|
|
surface its syntax (an `@packed` attribute? a `struct` modifier?) once
|
|
confirmed against `cmd/wcc`. Likewise pin the `static let` qualifier
|
|
spelling and whether `static` applies outside function bodies.
|
|
|
|
### 3.5 Untyped constants
|
|
|
|
A literal without a suffix has an *untyped* type (`untyped int`,
|
|
`untyped float`, `untyped str`, `untyped rune`, `untyped bool`,
|
|
`untyped nil`). An untyped constant takes a concrete type from its use
|
|
context (assignment target, operand of a typed operation, fn argument).
|
|
This is the inference that lets `let x: u8 = 5;` work without a suffix.
|
|
|
|
### 3.6 Pointers and nullability
|
|
|
|
`*T` is a pointer to `T`. **A plain `*T` is non-null**; dereferencing it
|
|
is unconditional and free. A *nullable* pointer is written as the tagged
|
|
union `(*T | void)`; `nil` inhabits it. This is a one-flag fold, zero
|
|
extra cost for the non-null case, and it is the only optionality
|
|
mechanism — there is no separate `nullable` qualifier keyword (§12).
|
|
ww has no runtime null-check net, so a non-null `*T` carries the
|
|
no-null obligation the way C does.
|
|
|
|
### 3.7 Tagged unions and the error tag
|
|
|
|
```
|
|
TaggedType = "(" Type { "|" Type } ")" .
|
|
```
|
|
|
|
A tagged union `(A | B | …)` holds one of its variants plus a runtime
|
|
tag. Variants are discriminated with `match` (§7) or tested with `is`
|
|
and narrowed with `as` (§8). A variant may carry the error mark `!T`,
|
|
which participates in the error model (§9): `(i64 | invalid | overflow)`
|
|
is a typical fallible result.
|
|
|
|
### 3.8 Function and channel types
|
|
|
|
`fn(P0, P1, …) R` is a function type. `chan T` is a channel of `T`
|
|
(**reserved**, §11). `never` is the bottom type of an expression that
|
|
does not return (e.g. a call to `abort`).
|
|
|
|
---
|
|
|
|
## 4. Packages and imports
|
|
|
|
```
|
|
SourceFile = PackageClause { ImportDecl } { TopDecl } .
|
|
PackageClause = "package" ident ";" .
|
|
ImportDecl = "import" ( ImportPath | ImportName ImportPath ) ";" .
|
|
ImportName = ident .
|
|
ImportPath = ident { "." ident } .
|
|
```
|
|
|
|
- Every source file begins with a package clause. Within one semantic source
|
|
action, eligible `.ww` files share one declared package name. Ordinary
|
|
production and same-package test sources use `p`; external test sources may
|
|
use the related `p_test`, and the actions remain separate even though one
|
|
canonical directory owns their test product. The declared name need not equal
|
|
the directory name or the final component of its canonical import identity.
|
|
- Each source file has one contiguous import section immediately after its
|
|
package clause. Once a non-import top-level declaration begins, a later
|
|
`import` is rejected as `imports must appear before other declarations`.
|
|
Parsing continues for recovery: consecutive imports in that late section
|
|
produce one ordering diagnostic, while another ordinary declaration followed
|
|
by another import starts a separately diagnosed late section. Existing
|
|
aggregate module/reset boundaries and constituent package clauses reset this
|
|
parser state per source; they do not relax the rule within a source or become
|
|
package/import identity.
|
|
A filename excluded by the target-selection rule below reaches no parser and
|
|
therefore cannot contribute an ordering diagnostic or import edge.
|
|
- Directory source eligibility uses Go 1.26.5 filename suffix semantics for
|
|
WW's fixed `linux/amd64` target. In the basename stem before the first dot, a
|
|
final `_test` token is ignored for platform matching. A final known OS or
|
|
architecture token must match `linux` or `amd64`; a final known OS followed
|
|
by a known architecture takes precedence and both must match. The known-name
|
|
sets are Go 1.26.5's `syslist.KnownOS` and `syslist.KnownArch`. Unknown or
|
|
misplaced suffixes are ordinary, and a platform word without a nonempty
|
|
underscore prefix is ordinary (`linux.ww` and `plan9_test.ww` are selected;
|
|
`x_windows.ww` and `x_plan9_test.ww` are not). Leading-dot/underscore entries
|
|
are ignored. Eligible names are byte-sorted before source validation.
|
|
Production excludes selected `*_test.ww`; test variants classify only those
|
|
selected test files. An excluded file contributes no declarations, imports,
|
|
filename collision, package edge, action, export, artifact, initialization,
|
|
test, diagnostic, or persistent invalidation. After eligibility, each
|
|
selected physical source is validated for malformed UTF-8 and raw NUL before
|
|
package-clause or import interpretation, in the existing byte-sorted file
|
|
order. After that source preflight, two distinct selected
|
|
basenames in one canonical directory that are equal under Go 1.26.5 Unicode
|
|
simple folding are rejected after the coordinator's required package-clause
|
|
classification and production `@test` validation parses, but before the
|
|
delegated graph-import scan or tools. An ordinary build compares production
|
|
names only; one test product compares its production, same-package test, and
|
|
external-test selections without merging their units.
|
|
- `import acme.codec;` loads the canonical package `acme.codec`. If that
|
|
package declares `package wire;`, the importing file sees its exported names
|
|
as `wire.Name`; `codec.Name` is not an additional binding. An explicit alias
|
|
replaces only that visible qualifier: `import stable acme.codec;` exposes
|
|
`stable.Name`, not `wire.Name` or `codec.Name`. Both kinds of binding are
|
|
scoped to that source file. A sibling file must declare its own import.
|
|
Neither form exposes an imported declaration as a bare `Name`; ordinary
|
|
unqualified lookup remains limited to lexical, builtin, and same-package
|
|
declarations.
|
|
- `import _ acme.codec;` is a blank side-effect import. The lone `_` creates no
|
|
qualifier, exposes no bare declaration, and is never diagnosed as unused.
|
|
It is nevertheless a real import occurrence: resolution and all missing,
|
|
self-import, cycle, `internal`, vendor, and imported-command checks use the
|
|
dotted path, and the dependency participates in executable and test package
|
|
initialization. Repeated blank occurrences and blank plus default/explicit
|
|
named occurrences of one path are valid; every named occurrence remains
|
|
independently subject to duplicate-binding and unused checks.
|
|
Each semantic action's package dependency graph is the sorted, deduplicated
|
|
union of real imports in that action's eligible source set. Occurrences retain
|
|
their owning file and position, but equal canonical targets create one graph
|
|
edge. Test-only occurrences never enter ordinary production. Self-import is
|
|
rejected, except that toolchain-owned external-test self wiring is rebound to
|
|
the effective augmented package action after ordinary per-site validation.
|
|
- Canonical import identity is exact and case-sensitive. After contextual local
|
|
or vendor expansion, two distinct effective identities that are equal under
|
|
Go 1.26.5 simple folding are a request-wide structural error. Folding is only
|
|
a temporary collision key: it never changes lookup, action identity, `.wwi`
|
|
ownership, symbols, artifacts, storage, or diagnostics. Repeated occurrences
|
|
of the same exact identity remain valid and deduplicate normally.
|
|
- An executable package is one declared `package main` and containing a
|
|
`fn main`; path and directory spelling do not classify commands. Within a
|
|
package declared `main`, a package-scope declaration named `main` must be a
|
|
function: `let`, `const`, `def`, and `type` forms reject as
|
|
`cannot declare main - must be func` and do not enter package scope. The
|
|
restriction depends only on the declared package name. A package with any
|
|
other declared name may use or export `main` regardless of its dotted import
|
|
path, path leaf, physical directory, or selection role. WW retains its
|
|
established program-entry ABI, so a function `main` may carry WW's supported
|
|
arguments and result; this rule does not adopt Go's source signature. An
|
|
ordinary import of a package declared `main` is rejected, except for the
|
|
toolchain's colocated external-test wiring.
|
|
- For `ww build`, the output-option name is exactly `o`. The accepted forms are
|
|
`-o VALUE`, `--o VALUE`, `-o=VALUE`, and `--o=VALUE`. An equals form splits
|
|
at its first `=` and preserves every later byte, including further `=`
|
|
characters; an empty value is valid. Repetition is last-value-wins, and a
|
|
final empty value means that there is no effective explicit output, so the
|
|
ordinary default-output or no-public-output rule applies. A concatenated
|
|
spelling such as `-oVALUE` or `--oVALUE` is an unknown flag, not an output
|
|
option. Build option parsing stops at the first package or source operand;
|
|
later flag-like arguments remain operands and are not reparsed as `-o`.
|
|
These spelling and placement rules select only caller-visible output
|
|
disposition and never supply package, import, graph, action, symbol,
|
|
artifact, `.wwi`, or persistence identity.
|
|
A nonempty effective `-o` names an output directory when ordinary
|
|
`stat` reports an existing directory (following symlinks) or its spelling
|
|
ends in `/`. This classification is independent of whether one or many
|
|
package roots were requested. Each selected command is published beneath
|
|
that directory using the final component of its requested contextual import
|
|
name, with the selected local directory leaf as the fallback when no such
|
|
identity exists. Independently selected non-main roots load but do not enter
|
|
the action list; non-main command dependencies retain ordinary actions, and
|
|
a selection containing no command rejects before tools or output creation.
|
|
A raw command-line `.ww` source uses its source basename without `.ww`, and
|
|
a raw non-main source is the same no-command rejection. Missing
|
|
trailing-slash hierarchies are created transactionally from `0777`, filtered
|
|
by the caller's umask.
|
|
Loading and package/import diagnostics precede no-main, derived-path,
|
|
duplicate-destination, implicit-default, and other output preflight; all of
|
|
those checks precede creation. Producer failure or interruption preserves
|
|
existing directory contents and removes only request-created prefixes and
|
|
stages. A non-directory output retains the single-product file/archive rule.
|
|
If a lone
|
|
command's synthesized default basename already names a directory, loading
|
|
and graph validation complete and the build rejects before tools without
|
|
changing that directory; a non-main package synthesizes no default public
|
|
output. With no path operand, the current directory is selected as if `.` had
|
|
been supplied. For a lone literal current-directory command and no explicit
|
|
`-o`, `.`, `./`, and equivalent single-dot-component spellings derive the
|
|
default executable name from the canonical selected directory's final
|
|
component; cold adjacent scratch uses that same leaf plus `.sepwork`. The dot
|
|
spelling itself is never an output name. A current-directory non-main package
|
|
uses the corrected scratch presentation but still links and publishes
|
|
nothing. This physical leaf is fallback presentation metadata only: it never
|
|
displaces a requested contextual dotted identity or becomes package, import,
|
|
graph, action, artifact, symbol, `.wwi`, or persistence identity.
|
|
Output paths and directory metadata never become package, import, graph, action,
|
|
symbol, artifact, `.wwi`, or persistence identity.
|
|
|
|
Every caller-visible build installation checks its destination after all
|
|
applicable compile, assemble, archive, and link producers finish. Ordinary
|
|
`stat` follows symlinks. An existing directory rejects as
|
|
`ww: build output "PATH" already exists and is a directory`; an existing
|
|
nonempty regular file rejects as `... is not an object file` unless its
|
|
leading bytes identify a Go 1.26.5 object/output form. The recognized table
|
|
is archive, ELF, Mach-O, PE, Plan 9, WASM, and XCOFF magic; WW additionally
|
|
recognizes its compiler-owned `//ww:module ` interface prefix. An absent
|
|
path, an empty regular reservation, or a non-directory non-regular path may
|
|
be replaced. A published non-main archive and its `.wwi` sidecar are checked
|
|
as one WW request transaction, so arbitrary caller text in either
|
|
destination preserves both old outputs and the committed persistent
|
|
generation. This safety check is output disposition only: it does not enter
|
|
package/import loading, graph or action identity, artifact bytes, or
|
|
invalidation.
|
|
Assembly-only `-S` retains the directory form's command-action selection and
|
|
no-main rejection, but it reaches no install action: destination length,
|
|
duplicate publication names, implicit destination collision, and output
|
|
parent creation are therefore inapplicable.
|
|
- A newly published `ww build` command is created with permission `0777`
|
|
filtered by the invoking process's umask. A newly published non-command
|
|
archive, and the adjacent WW interface required to consume it, use `0666`
|
|
filtered by that same umask. These inode permissions are output metadata:
|
|
they do not enter canonical package or action identity, artifact bytes,
|
|
import binding, symbols, `.wwi` contents, or persistent invalidation. A warm
|
|
build therefore reuses unchanged semantic actions while refreshing the
|
|
caller-visible output with the current invocation's mode. Assembly-only
|
|
builds publish no executable or archive.
|
|
- On the Unix target, exact `-o /dev/null` is a build-output discard request,
|
|
not an ordinary output filename. Package loading, import validation, graph
|
|
construction, compilation, assembly, archiving, command linking, failure,
|
|
and persistent invalidation proceed normally; only installation of a
|
|
caller-visible command, archive, interface, default basename, or adjacent
|
|
scratch tree is omitted. One such request may select any number of command
|
|
and non-command packages, and an empty recursive match is successful after
|
|
its normal warning. `-S` likewise needs no caller workdir merely to retain
|
|
discarded assembly. Raw-file and directory roots use the same rule. A
|
|
non-exact spelling is an ordinary output and keeps all established fan-out,
|
|
rejection, permission, scratch, and publication behavior. Output discard is
|
|
request metadata; it does not change package, import, graph, action,
|
|
artifact, symbol, `.wwi`, or persistence identity.
|
|
- Only names marked `export` (§5) are visible across module boundaries.
|
|
|
|
Import paths remain unquoted and dotted. Grouped imports, quoted import paths,
|
|
and dot imports are not implemented. `_` is reserved here for the blank form;
|
|
it is not an ordinary alias.
|
|
|
|
---
|
|
|
|
## 5. Declarations
|
|
|
|
```
|
|
TopDecl = [ "export" ] ( FnDecl | TypeDecl | LetDecl | ConstDecl
|
|
| DefDecl ) .
|
|
```
|
|
|
|
### 5.1 Functions
|
|
|
|
```
|
|
FnDecl = "fn" ident "(" [ Params ] ")" Result "=" Block .
|
|
| "fn" ident "(" [ Params ] ")" Result ";" . // bodiless: extern/FFI
|
|
Params = Param { "," Param } .
|
|
Param = ident ":" Type | ident ":" Type "..." | "..." .
|
|
Result = Type | "void" .
|
|
```
|
|
|
|
The `=` between signature and body is required. A bodiless fn (`;`
|
|
terminator) declares an external symbol (C FFI); the bare `...` C-style
|
|
variadic is legal only on a bodiless declaration (§6.3).
|
|
|
|
At package scope, exactly `fn init() void = Block;` declares a special package
|
|
initializer. It must have a body, no parameters, no result, no `export`, and no
|
|
attribute. Multiple init declarations are permitted and retain owner-file and
|
|
source order. `init` is not inserted into ordinary package scope: it cannot be
|
|
called as `init()`, selected as `pkg.init`, exported, or used by another kind
|
|
of declaration. Package-variable initialization completes before these
|
|
functions run.
|
|
|
|
In a package whose declared name is `main`, only a function declaration may
|
|
claim the package-scope name `main`. A rejected non-function declaration is not
|
|
installed and cannot satisfy the executable entry. This is independent of
|
|
canonical import identity and physical location. The accepted function shape
|
|
continues to use WW's entry ABI, including its supported argument and result
|
|
forms.
|
|
|
|
### 5.2 `let`
|
|
|
|
```
|
|
LetDecl = "let" ident ":" Type [ "=" Expr ] ";" .
|
|
```
|
|
|
|
A mutable binding. Without an initialiser, package-level storage is
|
|
zero-backed; an uninitialized function-local binding retains the existing
|
|
uninitialized-local rule.
|
|
A `static` qualifier (`static let …`) gives function-local storage
|
|
static lifetime.
|
|
|
|
For a package-level `let` with an initializer, values representable by the
|
|
static-data emitter are installed statically. Every other otherwise valid
|
|
initializer is evaluated exactly once at runtime. Checked references through
|
|
package functions contribute variable-dependency edges; dependencies precede
|
|
dependents, and source declaration order breaks ready ties. Initialization
|
|
cycles are errors. Runtime package lets execute after imported package tasks and
|
|
before the package's init functions. `const` and `def` are not broadened by
|
|
this runtime path.
|
|
|
|
### 5.3 `const`
|
|
|
|
`const` introduces an immutable storage binding of the same declaration shape
|
|
as `let`; assignment to it is rejected. At package scope its initializer stays
|
|
within the existing static-data forms. A `const` is not entered into the
|
|
runtime package-variable schedule described above; use mutable `let` when an
|
|
otherwise valid initializer requires runtime evaluation.
|
|
|
|
### 5.4 `def`
|
|
|
|
```
|
|
DefDecl = "def" ident ":" Type "=" ConstExpr ";" .
|
|
```
|
|
|
|
A compile-time constant definition, typically at module level
|
|
(`def AF_INET: i32 = 2;`). Hare's `def`.
|
|
|
|
### 5.5 `type`
|
|
|
|
```
|
|
TypeDecl = "type" ident "=" Type ";" .
|
|
```
|
|
|
|
### 5.6 Visibility
|
|
|
|
`export` on a top-level declaration makes the name visible to importing
|
|
modules. Unmarked names are module-private.
|
|
|
|
### 5.7 Attributes
|
|
|
|
`@name` prefixes a declaration with an attribute. The defined attribute
|
|
is `@test`: it marks a function as a test (§10).
|
|
|
|
---
|
|
|
|
## 6. Functions and calls
|
|
|
|
### 6.1 Blocks and results
|
|
|
|
A function body is a `Block` (§7). The body's value is its result; an
|
|
explicit `return Expr;` returns early.
|
|
|
|
### 6.2 Calls
|
|
|
|
```
|
|
Call = Expr "(" [ Args ] ")" .
|
|
Args = Arg { "," Arg } [ "..." ] .
|
|
```
|
|
|
|
Arguments are passed by value (a `str`/`[]T`/`struct` copy is a shallow
|
|
header/field copy). Aggregates larger than a register use the
|
|
aggregate/`sret` ABI.
|
|
|
|
### 6.3 Variadic
|
|
|
|
Two distinct variadic forms:
|
|
|
|
- **Hare-style** `args: T...` — the callee receives a `[]T`. A call
|
|
either gathers loose arguments (`fmt.println(1, "x")`) or forwards an
|
|
existing slice with the spread `xs...`.
|
|
- **C-style** bare `...` — only on a bodiless (extern) declaration, for
|
|
calling C variadic functions across the FFI. `f32` arguments in the
|
|
variadic tail are promoted to `f64` per the C ABI.
|
|
|
|
---
|
|
|
|
## 7. Statements
|
|
|
|
```
|
|
Stmt = Block | LetDecl | ConstDecl | Assign | If | For | Switch
|
|
| Match | Return | Break | Continue | Defer | Yield | ExprStmt .
|
|
Block = "{" { Stmt } "}" .
|
|
```
|
|
|
|
### 7.1 Assignment
|
|
|
|
```
|
|
Assign = Expr AssignOp Expr ";" .
|
|
AssignOp = "=" | "+=" | "-=" | "*=" | "/=" | "%="
|
|
| "&=" | "|=" | "^=" | "<<=" | ">>=" .
|
|
```
|
|
|
|
The compound forms apply to scalar, indexed, and pointer-field targets.
|
|
|
|
### 7.2 `if`
|
|
|
|
```
|
|
If = "if" "(" Expr ")" Block [ "else" ( If | Block ) ] .
|
|
```
|
|
|
|
### 7.3 `for`
|
|
|
|
```
|
|
For = "for" "(" Expr ")" Block // condition
|
|
| "for" "(" LetDecl Expr ";" Expr ")" Block // 3-clause
|
|
| "for" "(" "let" ident ".." Expr ")" Block . // range
|
|
```
|
|
|
|
The 3-clause form is `for (let i: size = 0; i < n; i += 1) { … }`. The
|
|
range form iterates the elements of a slice or array
|
|
(`for (let x .. xs) { … }`). `break` and `continue` apply to the nearest
|
|
enclosing loop; `continue` runs the post-clause / advances the range.
|
|
|
|
TODO(spec): does the range form bind the *element* or an index, and is
|
|
the bound name a copy or a view? Confirm against the checker's
|
|
`N_FORRANGE` lowering; the continue-post behaviour was a fixed miscompile
|
|
(project memory #138), so the desugaring is worth stating precisely.
|
|
|
|
### 7.4 `switch`
|
|
|
|
```
|
|
Switch = "switch" "(" Expr ")" "{" { SwCase } "}" .
|
|
SwCase = "case" ExprList ":" { Stmt } | "case" ":" { Stmt } .
|
|
```
|
|
|
|
Value-matching on a scalar. A `case` may list comma-separated values;
|
|
`case:` is the default arm. Arms do not fall through.
|
|
|
|
### 7.5 `match`
|
|
|
|
```
|
|
Match = "match" "(" Expr ")" "{" { MatchArm } "}" .
|
|
MatchArm = "case" [ "let" ident ":" ] Type "=>" ArmBody
|
|
| "case" "=>" ArmBody .
|
|
ArmBody = Stmt | Block .
|
|
```
|
|
|
|
Discriminates a tagged union (§3.7). `case let v: T =>` binds the
|
|
narrowed value to `v`; `case T =>` matches without binding; `case =>` is
|
|
the default. A `match` used as an expression produces a value via `yield`
|
|
(§7.7) from each arm.
|
|
|
|
### 7.6 `return`, `break`, `continue`, `defer`
|
|
|
|
`return [Expr];` `break;` `continue;`. `defer Stmt;` schedules a
|
|
statement to run when the enclosing block exits.
|
|
|
|
### 7.7 `yield`
|
|
|
|
`yield Expr;` produces the value of an enclosing block- or match-
|
|
expression.
|
|
|
|
---
|
|
|
|
## 8. Expressions
|
|
|
|
### 8.1 Operands
|
|
|
|
Identifiers, qualified names (`mod.name`), literals (§2.7), struct/array/
|
|
tuple literals (§8.5), and parenthesised expressions.
|
|
|
|
### 8.2 Operators and precedence
|
|
|
|
From loosest to tightest binding:
|
|
|
|
```
|
|
||
|
|
&&
|
|
== != < <= > >=
|
|
| ^
|
|
&
|
|
<< >>
|
|
+ -
|
|
* / %
|
|
unary: - ! ~ &(addr-of) *(deref)
|
|
postfix: call() index[] slice[:] field. ? !
|
|
```
|
|
|
|
`&e` takes the address of an addressable operand; `*p` dereferences.
|
|
|
|
TODO(spec): the precedence ladder is transcribed from the parser's
|
|
`bprec` (`parse.ww`); verify each tier against `cmd/wcc/parse.c` and
|
|
state associativity per tier. ww has no generics — no type parameters
|
|
anywhere in the grammar; call this out explicitly once confirmed.
|
|
|
|
### 8.3 Casts, `as`, `is`
|
|
|
|
```
|
|
Cast = Expr ":" Type . // type ascription / conversion
|
|
As = Expr "as" Type . // tagged-union narrowing
|
|
Is = Expr "is" Type . // tagged-union variant test (bool)
|
|
```
|
|
|
|
`expr: T` converts/ascribes (`len(xs): size`, `t.line: i64`). An integer
|
|
cast does not silently truncate beyond the target width. `e as T`
|
|
narrows a tagged union to variant `T`; `e is T` tests membership.
|
|
|
|
### 8.4 Index, slice, field, pseudo-fields
|
|
|
|
`a[i]` indexes an array/slice/str (index operand must be an integer).
|
|
`a[lo:hi]` produces a sub-slice. `e.f` selects a struct field or a
|
|
qualified name. `.ptr`, `.len`, `.cap` read the header words of a `str`
|
|
or slice.
|
|
|
|
### 8.5 Composite literals
|
|
|
|
```
|
|
StructLit = TypeName "{" [ FieldInit { "," FieldInit } ] "}" .
|
|
FieldInit = ident ":" Expr .
|
|
ArrayLit = "[" [ Expr { "," Expr } ] "]" .
|
|
TupleLit = "(" Expr "," Expr { "," Expr } ")" .
|
|
```
|
|
|
|
`S{}` is the zero value of `S`.
|
|
|
|
### 8.6 Builtins
|
|
|
|
`size(T)` (type size), `len(x)` (str/slice/array length), `alloc(v)!`
|
|
(heap-allocate a value, yield `*T`; §9), `append(s, x)` (grow a slice),
|
|
`delete(…)`, `abort(msg)` (terminate; type `never`).
|
|
|
|
`alloc` takes a *value*: `alloc(T{…})!`, `alloc(T{})!`, or
|
|
`alloc(expr)!`. Bare `alloc(T)!` is invalid (it parses `T` as a value
|
|
reference) (§12). There is no `free`: ww has no GC and no manual
|
|
reclamation in compiler-side code — allocation lives until process exit
|
|
(§12). `lib/*` modules that genuinely own a heap buffer free it
|
|
explicitly via `os.free`.
|
|
|
|
---
|
|
|
|
## 9. Errors
|
|
|
|
ww has no exceptions. A fallible operation returns a tagged union whose
|
|
error variants carry the `!T` mark (§3.7). Two postfix operators consume
|
|
them:
|
|
|
|
- `expr?` — if `expr` is an error variant, return it from the current
|
|
function (propagate); otherwise yield the success value.
|
|
- `expr!` — if `expr` is an error variant, abort; otherwise yield the
|
|
success value.
|
|
|
|
Error variants are ordinary sentinel types in the union
|
|
(`strconv.invalid`, `strconv.overflow`, …), discriminated with `match`.
|
|
This mirrors Hare's error idiom, spelled with ww's `!` tag.
|
|
|
|
---
|
|
|
|
## 10. Tests
|
|
|
|
A function marked `@test` is a test, run by the test harness, not part of
|
|
the program. Two placements, following the Go `foo` / `foo_test` model:
|
|
|
|
- **White-box**, in-package: a colocated file `package <mod>;` with
|
|
`@test` functions, for testing unexported internals.
|
|
- **Black-box**, external: `package <mod>_test;` with `import <mod>;`,
|
|
exercising only the public surface.
|
|
|
|
```ww
|
|
@test fn adds() void = {
|
|
if (1 + 1 != 2) { abort("math broke"); };
|
|
};
|
|
```
|
|
|
|
Production, production-plus-white-box-test, external black-box-test, test
|
|
support, recompiled-for-test dependencies, and generated test main are distinct
|
|
package actions. One canonical selected directory owns one test product and one
|
|
generated main, which imports every applicable same-package and external target
|
|
and produces one binary/result. The external action's import of the package
|
|
under test binds to the augmented white-box action when it exists; affected
|
|
transitive importers are copied and rewired so ordinary and augmented package
|
|
state do not coexist in the linked closure.
|
|
|
|
A non-function package-scope `main` in production or same-package test source
|
|
of a package declared `main` is a package-checker failure. Generated test-main
|
|
ownership does not hide or replace it: the product is not linked or executed,
|
|
no test accounting or package `ok` result is emitted, and ordinary build/test
|
|
failure presentation and rollback apply.
|
|
|
|
Those variants are action distinctions over exact package representatives, not
|
|
new ordinary package identities for case-fold comparison. Production,
|
|
same-package test, external test, and recompiled copies of one exact canonical
|
|
package therefore do not collide with each other. Their selected source units
|
|
remain separate, while the directory-owned filename preflight spans the
|
|
production/internal/external selections applicable to that test product.
|
|
|
|
Before a test function runs, the one generated product initializes its exact
|
|
effective graph dependency-first and once per canonical action. Imports,
|
|
runtime lets, and init declarations found only in `*_test.ww` never enter an
|
|
ordinary production build. Test-only same-package, external-package, and valid
|
|
mixed directories are accepted from their selected test files. A directory
|
|
with no selected test file validates ordinary production but creates no test
|
|
support, generated main, link, binary, captured runtime result, or process. The
|
|
coordinator reports that successful validation exactly as
|
|
`? <package> [no test files]\n`.
|
|
|
|
Every test-bearing directory product links one request-private runnable.
|
|
The test output-option name is exactly `o`, with the accepted forms `-o VALUE`,
|
|
`--o VALUE`, `-o=VALUE`, and `--o=VALUE`. Equals forms split only at their
|
|
first `=` and preserve an empty value or any additional `=` bytes. Repeated
|
|
occurrences are last-value-wins. A final empty value means no effective
|
|
explicit output and therefore requests no running-test retention; with `-c`,
|
|
the ordinary default retained name applies. A concatenated `-oVALUE` or
|
|
`--oVALUE` is unknown. Unlike build option parsing, known test options,
|
|
including these exact output forms, are recognized before or after package
|
|
operands. Invalid output-option names reject before package loading, product
|
|
construction, execution, or publication.
|
|
`-c` retains an executable copy and suppresses its execution; a nonempty
|
|
effective `-o` retains a copy at the named destination and still executes
|
|
unless `-c` is present. With no effective explicit output, `-c` writes
|
|
`<import-leaf>.test` in the invocation directory. An output ending in `/` or
|
|
naming an existing directory receives that basename and may have missing
|
|
parent directories created. One
|
|
non-directory output may name only one package. Multiple packages whose
|
|
visible import leaves would produce the same test-binary name reject before
|
|
tools or output creation; exact `/dev/null` is the discard exception. It keeps
|
|
the ordinary private link and, unless `-c` is present, the ordinary execution,
|
|
but installs no retained copy. The raw single-file compatibility route uses
|
|
the same private-output rule and never treats `/dev/null` as an explicit
|
|
artifact or adjacent-scratch stem. Declared
|
|
package names, test variants, source filenames, physical directories, output
|
|
paths, and retained binary names remain presentation or loader metadata and do
|
|
not become canonical package or action identity.
|
|
|
|
The retained file is an executable, byte-identical copy of the private
|
|
runnable. A compile-only retained binary joins package artifacts and statuses
|
|
in the request-wide atomic publication transaction. Build, link, stage, or
|
|
install failure preserves old destinations and removes temporary stages and
|
|
invocation-created output prefixes. For a running `-o` request, the private
|
|
binary executes first. Only a successful run enters the guarded install
|
|
action; a failed, signalled, timed-out, interrupted, or unstartable run
|
|
publishes no new copy and preserves any prior destination. The post-run guard
|
|
uses the same directory/nonempty-regular/object-magic rule as `ww build`.
|
|
Successful products in a multi-package running request install independently;
|
|
their visible result order remains package order. A no-test product publishes
|
|
no binary and does not create a directory solely for one.
|
|
`-w` may persist the unchanged semantic actions for either `-c` or running
|
|
retention without changing publication identity or introducing a test-result
|
|
cache.
|
|
|
|
When `ww test` executes a directory-owned product, the child process working
|
|
directory is that product's canonical absolute physical package source
|
|
directory. Its per-run environment has exactly one effective uppercase `PWD`,
|
|
with that same value. The physical source directory is execution context, not
|
|
canonical package identity: it does not enter dotted identity, import binding,
|
|
action identity, mangled symbols, `.wwi`, artifacts, or persistence keys.
|
|
Production, internal-test, external-test, recompiled-for-test, support, and
|
|
generated-main actions in the product share the one process context while
|
|
remaining separate actions. Reachable dependency initialization consequently
|
|
observes the tested product's directory; a dependency tested as its own product
|
|
observes its own directory.
|
|
|
|
Every test binary actually started by `ww test` receives one effective uppercase
|
|
`PATH` whose first element is the canonical absolute directory of the selected
|
|
WW driver. An absent or empty caller `PATH` yields only that directory; a
|
|
nonempty effective caller value follows it after `:`. Normal duplicate `PATH=`
|
|
entries collapse to that one value, using the caller's first effective value as
|
|
the suffix. This applies to directory products, their dependency initialization,
|
|
filters and list mode, running retained tests, and the raw single-file
|
|
compatibility route. It does not apply to build tools, `ww build`, `ww run`,
|
|
compile-only or assembly-only test requests, no-test products, or later direct
|
|
execution of a retained binary.
|
|
|
|
An executed directory product otherwise receives the caller's original Unix
|
|
environment, not the build plan's tool environment. The snapshot keeps the
|
|
first occurrence of each normal case-sensitive `key=value`, omits later normal
|
|
duplicates and raw empty entries, and retains every nonempty malformed entry in
|
|
order. It then applies only the effective `PATH` above and the package `PWD`.
|
|
Consequently caller `LC_ALL`, `TMPDIR`, empty-valued variables, case-distinct
|
|
keys, and arbitrary variables remain visible to production or test-only
|
|
initialization and test bodies. The compiler, assembler, archiver, linker, and
|
|
coordinator scratch continue to use their separate build-plan locale and
|
|
temporary directory.
|
|
|
|
The same coordinator-executed product reads standard input from the null
|
|
device. Its first read observes EOF regardless of the terminal, pipe, or file
|
|
connected to the invoking command. Each parallel product owns a separate null
|
|
descriptor, and production or test-only dependency initialization, filtering,
|
|
listing, no-match execution, failure, and timeout all retain that boundary.
|
|
Standard input is runtime process metadata and contributes no package, action,
|
|
artifact, or persistence identity.
|
|
|
|
The product's standard output and standard error refer to one product-local
|
|
capture. Bytes from either descriptor retain the order in which their writes
|
|
reach that shared open output, and the coordinator emits the completed capture
|
|
on its standard output. Immediately before an existing directory-product
|
|
`ok` or run-status `FAIL` trailer, a nonempty capture whose final byte is not
|
|
newline receives exactly one newline; an empty or already newline-terminated
|
|
capture receives none. The capture itself is unchanged, and raw single-file or
|
|
later direct retained-binary execution has no coordinator trailer and no such
|
|
separator. A runtime failure or child-setup failure appends the product status
|
|
diagnostic to standard output as well. Loader, source, compiler, assembler,
|
|
linker, and other build failures retain their diagnostic standard-error
|
|
channel; build-action stdout and stderr remain separate. Parallel products own
|
|
independent captures and are still emitted in canonical product order.
|
|
|
|
List mode starts the same directory-owned product and therefore performs its
|
|
ordinary package initialization, but it does not start any selected test child.
|
|
The harness emits exactly one newline-terminated qualified name for each test
|
|
selected by the command's filters, in descriptor order. If the selected set is
|
|
empty, the harness emits no list payload, warning, or accounting and returns
|
|
success; the coordinator still emits the ordinary successful package result
|
|
without a no-tests suffix. Ordinary non-list execution is distinct: a valid
|
|
filter selecting no tests writes exactly
|
|
`testing: warning: no tests to run\n` through the harness's standard error,
|
|
preserves the discovered/selected/started/completed accounting and successful
|
|
exit, and causes a successful directory package result to end in
|
|
` [no tests to run]`. The coordinator recognizes that exact warning only at
|
|
capture byte zero or after a newline. Directory execution combines the warning
|
|
and accounting in its product-local capture; a retained binary invoked directly
|
|
keeps the warning on standard error, its accounting on standard output, and has
|
|
no coordinator package result.
|
|
|
|
After those ordered results, an ordinary explicit `ww test` request that records
|
|
an attributable setup, build, or execution failure emits exactly one standalone
|
|
`FAIL\n` on standard output. The line is command-owned: it follows even a later
|
|
successful package result, and a single explicit directory, recursive or dotted
|
|
directory, or raw source file is sufficient. Runtime nonzero exit, signal,
|
|
timeout, or executable-start failure all set that status. Filters, list mode,
|
|
and the private execution of a retained test keep the same rule. Bare implicit
|
|
current-directory `ww test`, `-c`, `-S`, build requests, CLI usage/shape or
|
|
output preflight, publication-only failure, capture-only failure, cleanup-only
|
|
failure, and later direct execution of a retained binary do not emit it. The
|
|
marker changes no capture, diagnostic, executable, package/import/action
|
|
identity, publication decision, or persistent byte.
|
|
|
|
The coordinator does not change its own cwd or environment. Parallel products
|
|
receive independent child environments and each uses its own source directory.
|
|
Relative ordinary files, `testdata`, and writes resolve there for every
|
|
executing filter or list path. `ww build`, directory `ww test -c` (including
|
|
`-c -o`), and a no-selected-test directory execute no test child and receive no
|
|
execution-directory effect. A published test binary invoked directly, and the
|
|
raw single-file compatibility route, inherit the user's invocation cwd and
|
|
three standard descriptors; no package directory, input, or output policy is
|
|
embedded or forced by the binary. Directly invoked retained binaries inherit the
|
|
entire caller environment. Raw single-file tests preserve every caller
|
|
environment field except the test-only `PATH` transformation above.
|
|
|
|
---
|
|
|
|
## 11. Concurrency (reserved)
|
|
|
|
The headline `+CSP` surface — `chan T`, `proc`/`spawn`, channel send/
|
|
receive `<-`, and the `alt` select construct — is tokenised
|
|
(`chan`, `proc`, `<-`, `->`) but **not yet given semantics**. This
|
|
section is written when the feature lands; until then, use of these
|
|
tokens is rejected by the checker.
|
|
|
|
**Meta-ruling (normative).** CSP is *the* sanctioned departure from the
|
|
Hare-fidelity rules (CLAUDE.md rules 5 and 9) — Hare has no channels, and
|
|
the `+CSP` mandate is the project's reason to add them. The concurrency
|
|
surface therefore cites the Newsqueak → Limbo → Go lineage, **not** Hare,
|
|
and that citation is correct by construction, not a rule-9 violation. The
|
|
design of record is `.ai/csp-design-input.md` (library-first: `spawn`,
|
|
unbuffered bidirectional `chan T` via `mkchan(T)`, `<-` send/recv,
|
|
`recv → (T | closed)` error idiom, `alt` as v2). See §12 entry 9.
|
|
|
|
TODO(spec): fill in syntax + semantics (channel construction, send/recv
|
|
typing, `spawn` arg-copy ABI, `alt` arm grammar, the `closed` sentinel)
|
|
once the MVP lands and the USER meta-ruling is explicitly on record.
|
|
|
|
---
|
|
|
|
## 12. Divergences from Hare (normative)
|
|
|
|
ww follows Hare's semantics and `lib/` API shapes (CLAUDE.md rules 5, 9)
|
|
except for the deliberate, USER-blessed departures below. Each is
|
|
normative; the cited reference is the rationale of record.
|
|
|
|
1. **`int`/`uint` are a machine word (8 bytes), not Hare's 32-bit
|
|
`int`.** All limit constants derive from `size(int)`; `size`/`uintptr`
|
|
are 8 bytes. *Ref: project memory "int is a machine word"; USER
|
|
2026-05-26.*
|
|
|
|
2. **Tuples use 8-byte slots, not Hare's packed layout.**
|
|
`size((u32, u32)) == 16`. Routed through the type table only.
|
|
*Ref: USER 2026-06-04 tuple slot ruling; parity task #60.*
|
|
|
|
3. **Nullability is the union `(*T | void)`, not a `nullable`
|
|
qualifier.** Plain `*T` is non-null; the union fold is one flag,
|
|
zero-cost for the non-null path. *Ref: USER 2026-05-26 nullable-kept
|
|
ruling.*
|
|
|
|
4. **No `_unsafe` suffix convention.** ww is unmanaged (no GC, no safe
|
|
baseline), so Hare's `_unsafe` axis does not apply. `bytes`→`str` is
|
|
the pure reinterpret `strings.frombytes` (Hare's `fromutf8_unsafe`);
|
|
UTF-8 validation is opt-in at the IO source via `utf8.validate`, not
|
|
wrapped per construction. *Ref: CLAUDE.md rule 9; ref/hare/strings/
|
|
utf8.ha.*
|
|
|
|
5. **The compiler frontend is one package `lib/ww/syntax`** (tok + lex +
|
|
ast + sym + typ + parse), a Go-over-Hare consolidation of Hare's
|
|
`{ast, lex, parse}` split. Its single consumer is the `wcc` backend.
|
|
The internal data shapes still mirror `ref/hare/hare`. *Ref: CLAUDE.md
|
|
rule 6; USER #74.*
|
|
|
|
6. **Tests use the Go `foo` / `foo_test` model** (§10): external
|
|
black-box `package <mod>_test;` plus in-package white-box `@test`
|
|
files. ww hard-rejects self-import. *Ref: CLAUDE.md rule 9; task #16.*
|
|
|
|
7. **No arena / no `free` in compiler-side code.** Per-node
|
|
`alloc(value)!`; allocation is reclaimed at process exit. `alloc`
|
|
takes a *value* (`alloc(T{…})!`), never a bare type. *Ref: project
|
|
memory "drop amalloc", "Phase 0 closed", "alloc syntax form".*
|
|
|
|
8. **The checked AST carries a per-node `type_` stamp.** Hare's parse
|
|
AST is untyped and harec keeps a separate checked AST in C; ww stamps
|
|
`node.type_` in place as a checker invariant. *Ref: project memory
|
|
"Hare AST has no per-expr result".*
|
|
|
|
9. **CSP** (§11) is the sanctioned departure from rule-5/9 Hare fidelity
|
|
— Hare has no channels. The surface cites Newsqueak/Limbo/Go, not
|
|
Hare. *Ref: `.ai/csp-design-input.md`; pending explicit USER
|
|
meta-ruling.*
|
|
|
|
10. **The two compiler stages emit byte-identical asm** for the same
|
|
input (CLAUDE.md rule 10); when inference power differs, the richer
|
|
stage is aligned *down* to the leaner one. This is a project
|
|
invariant, listed here because it is the contract this spec's "the
|
|
text is the bug if stages disagree with it" rule rests on.
|
|
|
|
---
|
|
|
|
## Appendix A. Anti-rot
|
|
|
|
This spec is hand-maintained. To keep it from drifting against the
|
|
parser, every fenced ` ```ww ` example is intended to be extracted and
|
|
compiled through `w6c`, asserting it parses (or rejects, for
|
|
negative examples), wired as a `make spec-check` dependency of
|
|
`make test`. The EBNF
|
|
productions are not auto-verified; the compiling examples are the drift
|
|
alarm. *(Check not yet implemented; filed as a follow-up.)*
|