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 declarationDocumentation 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.
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:
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.