Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

6. The keyword set

  • Status: Accepted — amended for Milestone 2 (see below)
  • Date: 2026-09-02

Amendment (2026-09-03, Milestone 2): six keywords were added, taking the set from 19 to 25.

  • Exceptions (ADR 0017): try, except, finally, raise. Three distinct block roles that cannot share a word, plus raise, which is a statement (it unwinds — no return value, cannot be passed around). raise / except were already listed as "deferred to a later milestone" below.
  • Modules (ADR 0018): import, as. import is a statement (it runs a file and binds a name — not an expression); as is the rename clause, shared with no other construct. import was likewise already listed as deferred.

None of the six has an alias. The set is larger than the "small, deliberate" goal implied at 19, but each word buys a whole capability (structured error handling; multi-file programs) that the language could not express at all before, and the total is still well below Python's ~35.

Context

"A small, deliberate set of keywords — nothing bloated" is a core design goal. The keyword set needs to be fixed early because it affects the lexer, the grammar, and what identifiers user code may use.

Decision

Milestone 1 defines exactly these 19 reserved words:

GroupKeywords
Functionsfn return
Conditionalsif elif else
Loopswhile for in break continue
Boolean logicand or not
Literalstrue false nil
Classesclass super
Empty blockpass

Rationale for the debatable members:

  • fn over def/func/function — shortest unambiguous choice.
  • elif over else if — one token, no nesting ambiguity, matches the single-keyword-per-concept style.
  • and/or/not as words, not &&/||/! — they read as English, they cannot be confused with future bitwise operators, and Korrin has no C heritage to honour.
  • self is not a keyword. It is a conventional identifier — the name of a method's first parameter (0005). Using it outside a method is an ordinary "cannot find self" error, which is clear enough; making it a keyword would buy a marginally better message at the cost of one more reserved word and a special case in the resolver.
  • super is a keyword, because super.method is special syntax: there is no value to bind a plain identifier to.
  • pass exists because indentation-defined blocks cannot be empty (0001).

Deliberately not keywords in M1: self, import, try, raise, catch, let, var, const, match, lambda, global, nonlocal, async, await, yield, with, del. Each is either deferred to a later milestone with its own ADR or ruled out by another decision (let/var by 0011; global/nonlocal likewise).

Consequences

  • The lexer's keyword table is a fixed 19-entry match.
  • import/try/raise land later and will each get an ADR when their design is settled; user code today may use those words as identifiers, and reserving them later is a documented breaking change. Accepted for a pre-1.0 language.
  • Booleans-as-words means not participates in operator precedence as a unary operator, handled in the Pratt parser.

Alternatives considered

  • Symbol operators for logic (&&, ||, !). No benefit here; costs readability and a lexer that has to distinguish &/&&.
  • Reserving the post-M1 words now. Would break fewer future programs, but reserves words for features whose design (and therefore whose exact keywords) is not yet decided. Prefer to reserve a word when its ADR lands.
  • def for functions. Fine, but fn is shorter and equally clear.