# §2 · Lexical structure

> **Status: DRAFT.** The keyword set (§2.3) is probed. Literals and operators are **UNVERIFIED** and marked so.

## 2.1 Source text

An Osy# source file is Unicode text with the extension `.osy`.

⚑ **A generated migration is NOT Osy# and does not use this extension.** Migrations are `<name>.migration`, in
their own grammar, and are specified in §13. The extension is deliberate: a migration carried the `.osy` extension
until 2026-08-26 and was swept up by every `*.osy` glob — including globs outside this project, which is why the
fix is an extension and not a filter.

## 2.2 Comments

```
// line comment, to end of line
/* block comment */
/// documentation comment, attached to the following declaration
```

Documentation comments are **not** decoration: they are carried into the model and served by `osy docs` and the
language server. **UNVERIFIED**: whether a `///` in a position with no following declaration is an error.

## 2.3 Keywords

⭐ **Osy# has a SMALL reserved set and a LARGE contextual vocabulary, and this is the first thing that surprises a
reader.** Exactly **22** words are reserved. Every other word that looks like a keyword — `entity`, `component`,
`render`, `workflow`, `state`, `event`, `subscribe`, `security`, `allow`, `deny`, `policy`, `terminal`, `live` —
is **contextual**: recognised by the grammar at the position where it means something, and an ordinary identifier
everywhere else.

### 2.3.1 Reserved words

These MUST NOT be used as identifiers:

```
async     await     break     catch     continue  else      false     finally
foreach   if        in        is        new       not       null      return
throw     true      try       var       void      while
```

⚑ **Probe (established, and RUN BY CI).** A reserved word used as an identifier is refused. Source of the set:
the lexer's `Keywords` table, `OsySharpLexer.cs:94`.

```osy probe=refuses PARSE_ERROR
int Probe() { var new = 1; return new; }
```

### 2.3.2 Contextual words

A word that introduces a declaration is not reserved, and MAY be used as an identifier.

⚑ **Probe (established, and RUN BY CI).** Every one of these is a declaration keyword somewhere in the
grammar, and an ordinary identifier here:

```osy probe=accepts
int Probe() {
  var entity    = 1;
  var component = 2;
  var render    = 3;
  var state     = 4;
  return entity + component + render + state;
}
```

*Informative.* This follows C#, which reserves a fixed set and makes later additions contextual so that adding a
word to the language cannot break existing programs. Osy# takes the same position, and takes it further: the words
that introduce its most distinctive constructs are all contextual, so a program that used `workflow` as a variable
name before workflows existed still compiles.

⚠ **Consequence for tooling, and for readers:** you cannot decide what a word means by looking it up in a list.
`state` is a declaration keyword inside a `workflow` body and a variable name in a function body. Syntax
highlighting that colours it unconditionally is wrong, and so is a mental model that treats these as reserved.

### 2.3.3 Words that are NOT in the language

**UNVERIFIED as a complete list**, but recorded because each has been mistakenly assumed to exist:

| assumed | actual |
|---|---|
| `out`, `ref` | **refused** — a parameter passes a value in, and only the return comes back out. A call can suspend and resume elsewhere, so no caller frame is guaranteed to still be waiting for a write-back. See §11. |
| `[Display]` | replaced by `[Label]` |
| `Now` | removed entirely; use `DateTime.UtcNow`, which the compiler lowers to the ambient clock |

## 2.4 Identifiers

**UNVERIFIED.** Expected to follow C# — a letter or `_` followed by letters, digits or `_`, compared ordinally.
The probe that would establish this (a Unicode identifier, a leading digit, `@`-escaping) has not been written.

Members are `PascalCase` and parameters `camelCase` by convention; this is style, not grammar, and the linter —
not the compiler — enforces it.

## 2.5 Literals

**UNVERIFIED.** The forms below are observed in real source but their exact grammar has not been probed:

| kind | observed |
|---|---|
| integer | `1`, `0` |
| decimal | `2m` — a suffixed decimal literal |
| string | `"…"` and interpolated `$"…{expr}…"` |
| boolean | `true`, `false` |
| null | `null` |

## 2.6 Operators and punctuation

**UNVERIFIED as a complete table.** Two facts are established elsewhere and belong here when this section is
written properly:

- ⚑ `??` over numeric operands **WIDENS** to the wider type, rather than dropping to the narrower one. The
  previous rule produced wrong numbers, not merely a dropped cast (fixed 2026-08-26, `f15275418`).
- ⚑ `xs[i]` on a collection is **not** a nullable read — the resolver briefly said it was, and a MUST-tier lint
  believed it (fixed 2026-08-26, `8ed45bfbd`).

---

**Next:** §3 Types — the type system, nullability, and the required-by-default rule (R12). Not started.
