Scope and ownership
LinkLexical structure identifies source spellings and statement boundaries. Literal values, escapes and template forms belong to Literals and templates. Name lookup and binding restrictions belong to Bindings and scope. Operator grouping belongs to Expressions and operators. Contextual forms retain the owners listed below.
Edition: Topaz 5.21 language mode.
Rules cite owner decisions and reviewed SELF source. Each executable example keeps its independent expectation and verification result. The line-break decision was applied against 570530e69141ba3a2014870c84c6759916a10833; the documented implementation limitations identify the remaining scanner differences.
Identifiers, underscore and labels
LinkIdentifierSpelling = IdentifierStart IdentifierContinue* ;IdentifierStart = ( "_" | UnicodeLetter | NonAsciiEmoji ) - DefaultIgnorable ;IdentifierContinue = ( IdentifierStart | UnicodeNumber | UnicodeMark ) - DefaultIgnorable ;Identifier = IdentifierSpelling - ReservedWord - "_" ;Underscore = "_" ;Label = "'" IdentifierSpelling ;
In this EBNF excerpt, * denotes repetition and - excludes a scalar or spelling. Unicode comprises Mn, Mc, and Me. Default is Unicode 16.0.0 Default_Ignorable_Code_Point. The complete Identifier must already be NFC. The NFC condition applies to the whole sequence, not to each scalar separately. Reserved is defined in the next section.
Source evidence
- Identifier scalar predicates and scanning at
af88afbc1d99· 164-241 - Unicode categories and non-ASCII Emoji property at
af88afbc1d99· 7-65 - keywordKind and identifierHeadKind at
af88afbc1d99· 68-109 - scanLabel at
af88afbc1d99· 519-544 - LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Unicode 16 General_Category Mn, Mc and Me at
Unicode-16.0 - Unicode 16 Default_Ignorable_Code_Point at
Unicode-16.0· Default_Ignorable_Code_Point - Canonical normalization forms at
Unicode-16.0· 3.11.7
Identifier characters
An identifier starts with _, a Unicode letter, or a non-ASCII scalar with the Unicode Emoji property. Later scalars may also be Unicode numbers and combining marks.
The character classes use Unicode 16.0.0. Letters use Lu, Ll, Lt, Lm, Lo, numbers use Nd, Nl, No, and combining marks use Mn, Mc, Me. A combining mark cannot start a name.
Exclude every scalar with Default_ from both positions, even when it is a letter or combining mark. This includes variation selectors, join controls, combining grapheme joiner, Hangul fillers, and emoji tag characters. No script or emoji sequence receives an exception. Each scalar in a multi-scalar emoji name must qualify independently. String data follows the string rules.
Why
Programs can name values in the languages of the documents they process. Combining marks continue a name so those spellings do not depend on precomposed letters. A scalar that is normally invisible must not distinguish two names. Excluding Default_Ignorable_Code_Point from both positions gives names one rule without script, font, or emoji-presentation exceptions.
Source evidence
- Identifier scalar predicates and scanning at
af88afbc1d99· 164-241 - Unicode categories and non-ASCII Emoji property at
af88afbc1d99· 7-65 - LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Current identifier character predicates at
72a59a31125f· 164-172 - Unicode 16 General_Category Mn, Mc and Me at
Unicode-16.0 - Unicode 16 Default_Ignorable_Code_Point at
Unicode-16.0· Default_Ignorable_Code_Point - issues/31 at
a545e8811f8f
Complete identifier spellings
When no string delimiter immediately follows, the lexer classifies the complete identifier spelling. A spelling in Reserved becomes a keyword token. The complete spelling _ becomes an underscore token. Other spellings become identifier tokens.
Why
Classifying a complete word prevents a keyword prefix from splitting a longer name. A distinct wildcard token keeps discarded values separate from named bindings.
Source evidence
- Identifier scalar predicates and scanning at
af88afbc1d99· 164-241 - keywordKind and identifierHeadKind at
af88afbc1d99· 68-109
Label spellings
' followed by Identifier forms a label token. Its body is not reclassified as a keyword.
Why
The label prefix distinguishes a control target from a value name. Its separate token class makes the target visible before name lookup.
Source evidence
- scanLabel at
af88afbc1d99· 519-544
See Pattern matching and Expressions and operators for uses of _. Blocks and control flow describes label binding and jump targets.
Canonical source spelling
The complete name must already be in Unicode 16.0.0 NFC form in the source. This applies to declarations, references, members, fields, label bodies, and template tag names. Check the whole sequence before keyword or tag classification.
Bindings and lookup compare the admitted scalar sequence exactly. They do not normalize a spelling silently or create a second alias. NFC uses canonical equivalence. It does not apply compatibility normalization or case folding.
A non-NFC name produces TPZ0007 during lex. Mark the complete name span, excluding a label's apostrophe and a tag's string delimiter, and suggest its NFC spelling. Review the replacement for collisions before changing the source.
Why
Canonically equivalent name spellings denote the same written name to a reader. Requiring one NFC source spelling prevents hidden aliases during binding and lookup. An explicit rejection and suggested replacement let the programmer review any resulting collision before changing the source. String contents remain data because normalizing them would change bytes the program may need to preserve.
Source evidence
- LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Canonical normalization forms at
Unicode-16.0· 3.11.7 - Current label and identifier scanning without an NFC check at
72a59a31125f· 517-569 - Existing lexical identities TPZ0001 through TPZ0006; TPZ0007 is newly specified by LL-3 at
72a59a31125f - issues/31 at
a545e8811f8f
Reserved words
LinkReservedWord ="let" | "mut" | "const" | "type" | "function" | "return" | "if"| "else" | "match" | "case" | "for" | "in" | "by" | "while"| "break" | "continue" | "defer" | "concurrent" | "true" | "false" | "null" ;
The literal values of true, false and null are specified in Literals and templates.
Source evidence
- keywordKind and identifierHeadKind at
af88afbc1d99· 68-109
Identifier name positions
A name position that requires an identifier rejects keyword tokens.
Why
Keeping keywords out of binding positions prevents a declaration from replacing the meaning of a language form.
Source evidence
- rejectReservedBinding, identifier and fieldName at
af88afbc1d99· 176-235 - parseFunction parameter recognition at
af88afbc1d99· 140-161
Field name positions
A field-name position accepts an identifier token or a keyword token.
Why
A field position already identifies a data member. Accepting keyword spellings there preserves names from structured data without an escape syntax.
Source evidence
- rejectReservedBinding, identifier and fieldName at
af88afbc1d99· 176-235
Bindings and scope owns binding restrictions, including the treatment of None. Types and Functions and generics own compiler-defined type names and type-parameter restrictions.
Contextual spellings and their owners
LinkContextual identifier spellings
For identifier tokens with the spellings below, the parser assigns a contextual role when the surrounding form matches.
| Spelling | Role | Canonical owner |
|---|---|---|
enum, record, newtype | Nominal declaration heads | Records, enums and newtypes |
derives | Derivation clause after a nominal declaration head | Records, enums and newtypes; Protocols and implementations |
protocol, impl, using | Protocol, implementation and using forms | Protocols and implementations |
import, export | Program-level module forms | Modules and visibility |
as | Import alias clause | Modules and visibility |
use | Recognized and rejected module-item spelling | Modules and visibility |
set, map | Collection forms before a brace | Collections and comprehensions |
loop | Loop head, optionally followed by a label | Blocks and control flow |
self | First-parameter recognition | Functions and generics; receiver contexts in Protocols and implementations |
timeout | Named timeout option in a concurrent form | Concurrency |
Why
Recognizing these words at their construct heads lets them remain ordinary names elsewhere. The surrounding grammar supplies the role.
Source evidence
- keywordKind and identifierHeadKind at
af88afbc1d99· 68-109 - parseStatement at
af88afbc1d99· 901-948 - parseDerives and contextual declaration lookahead at
af88afbc1d99· 231-294 - exportDeclarationHead and parseProgramItem at
af88afbc1d99· 1240-1346 - parseImportSpec at
af88afbc1d99· 1040-1077 - parseIdentifierAtom at
af88afbc1d99· 1004-1034 - parseFunction parameter recognition at
af88afbc1d99· 140-161 - parseConcurrentTimeout at
af88afbc1d99· 721-751
The named chapters own these forms' grammar and meaning. Literals and templates owns tags and interpolation.
Punctuation tokens
LinkPunctuation ="..." | "..<" | "??=" | "**" | ".." | "?." | "??" | "<="| ">=" | ">>" | "==" | "!=" | "+=" | "-=" | "*=" | "/="| "%=" | "&&" | "||" | "=>" | "->" | "(" | ")"| "[" | "]" | "{" | "}" | "+" | "-" | "*" | "/"| "%" | "." | "?" | "<" | ">" | "=" | "!" | "~"| "|" | "," | ":" | ";" ;
See Expressions and operators, Types, and Pattern matching for the syntactic roles of these tokens. The adjacent >> token supplies two type-closing delimiters and has no expression-operator meaning. The retired |> spelling is not a punctuation token or an expression form.
Source evidence
- punctuation at
af88afbc1d99· 111-161
Punctuation token boundaries
The punctuation scanner chooses the longest matching punctuation spelling. Comments, string delimiters, and label prefixes have separate lexical forms. The adjacent >> token supplies two type-closing delimiters. It has no expression-operator meaning.
Why
Longest matching keeps a compound operator together. Its meaning does not depend on trying shorter operator spellings first.
Source evidence
- punctuation at
af88afbc1d99· 111-161
Whitespace and comments
LinkHorizontalSpace = " " | HT ;Newline = LF | CR LF | CR ;LineComment = "//" NonNewlineScalar* ;
Non denotes a source scalar other than LF or CR. A comment starts with / and ends before LF, CRLF, CR, or the end of the source. Each physical break remains available to the normal layout rules.
Source evidence
- lineCommentEnd and blockCommentEnd at
af88afbc1d99· 41-63 - scanCode whitespace and newline branches at
af88afbc1d99· 568-633 - issues/31 at
570530e69141
Horizontal space and newlines
Space (U+0020) and horizontal tab (U+0009) separate tokens. LF (U+000A), CRLF, and bare CR (U+000D) each supply one line break to layout processing. Each spelling preserves the same statement boundary. Indentation does not delimit blocks. Braces and the surrounding syntax determine block and list boundaries.
Why
Equivalent line endings preserve a program when a file moves between editors or operating systems. Braces keep scope independent of indentation.
Source evidence
- scanCode whitespace and newline branches at
af88afbc1d99· 568-633 - observeTokenStructure and normalize at
af88afbc1d99· 443-510 - issues/31 at
570530e69141 - scanCode line-break dispatch at
570530e69141· 611-634 - observeNewline and flushPendingSeparator at
570530e69141· 245-318
Line comments
A line comment begins with / and ends before the next LF, CRLF, or bare CR, or at the end of the source. Its terminating line break remains available to layout processing.
Why
One comment opener covers both explanatory lines and trailing comments. Preserving its terminating line break keeps comments from joining surrounding statements.
Source evidence
- lineCommentEnd and blockCommentEnd at
af88afbc1d99· 41-63 - scanCode whitespace and newline branches at
af88afbc1d99· 568-633 - issues/31 at
570530e69141 - lineCommentEnd at
570530e69141· 46-51 - scanCode line-break dispatch at
570530e69141· 611-634 - observeNewline and flushPendingSeparator at
570530e69141· 245-318
Block comment spellings are rejected
Write every comment line with /. The / spelling is rejected with TPZ2001 during parsing. Its opener is tokenized as ordinary / and * punctuation. Comment markers inside a string remain text.
Why
Line comments give ignored source text one spelling. Prefixing each line handles a paragraph of commentary without a closing delimiter or a nesting exception. Line comments also preserve the physical break that separates surrounding statements. A block comment and successive line comments express the same intent to set explanatory text aside. A short mid-line comment can move to a preceding line without changing that intent.
Source evidence
- lineCommentEnd and blockCommentEnd at
af88afbc1d99· 41-63 - scanComment at
af88afbc1d99· 446-465 - scanComment at
570530e69141· 446-465 - observeNewline and flushPendingSeparator at
570530e69141· 245-318 - parseAtom at
271329b482bc· 1291-1299 - issues/31 at
23582e1d56db
Line breaks and statement boundaries
LinkStatementSeparator = StatementNewline ;
StatementNewline denotes a significant LF, CRLF, or CR line ending. A semicolon is rejected. Blocks and control flow defines final values and explicit discard.
Source evidence
- observeNewline, consumeSemicolon and flushPendingSeparator at
af88afbc1d99· 245-327
Statement boundaries
LF, CRLF, and bare CR separate completed items at program level and in statement blocks when no continuation condition applies, including the break that ends a line comment. A semicolon is rejected and never discards a value. Blank lines do not introduce an extra item. No implicit separator is inserted immediately before a closing delimiter or the end of the source. The final expression remains the block value across trailing line breaks and comments. To discard that value explicitly, write let _.
Why
A semicolon separator and a line boundary both separate completed statements. Newlines express that separation. Discarding a result is a different intent and is written explicitly with let _. Trailing whitespace and comments therefore leave the final expression value unchanged.
Source evidence
- observeNewline, consumeSemicolon and flushPendingSeparator at
af88afbc1d99· 245-327 - observeTokenStructure and normalize at
af88afbc1d99· 443-510 - issues/31 at
570530e69141 - scanCode line-break dispatch at
570530e69141· 611-634 - observeNewline and flushPendingSeparator at
570530e69141· 245-318 - scanComment at
570530e69141· 446-465 - lineCommentEnd at
570530e69141· 46-51 - itemBoundary at
570530e69141· 443-456 - issues/31 at
23582e1d56db
Line breaks inside delimiter forms
A line break continues the surrounding form while its active context is parentheses, square brackets, interpolation, record fields, or import selections. An explicit ; in those contexts produces TPZ1001. Use , between list elements.
Why
The enclosing delimiter identifies an unfinished expression or list. Commas keep element boundaries explicit when a long form spans several lines.
Source evidence
- classifyBrace at
af88afbc1d99· 182-201 - observeNewline, consumeSemicolon and flushPendingSeparator at
af88afbc1d99· 245-327 - observeTokenStructure and normalize at
af88afbc1d99· 443-510
Continuation around tokens
A newline continues the form at these token positions.
| Position of the line break | Token spellings |
|---|---|
| Before | >, >>, else |
| After | +, -, !, ~, ,, =>, (, [ |
| Either side | **, *, /, %, ., ., <, <=, >=, ==, !=, in, &&, \|\|, ??, =, +=, -, *=, /, %=, ??=, ., ?., by |
Why
Continuation keeps an unfinished operator expression together. The lexer records line breaks and the layout pass uses adjacent tokens to decide its boundaries.
Source evidence
- lineContinuation at
af88afbc1d99· 31-99 - observeNewline, consumeSemicolon and flushPendingSeparator at
af88afbc1d99· 245-327
Continuation around pattern alternatives
A line break on either side of | continues a binding, case, or for pattern while that pattern is being read.
Why
The alternatives belong to one pattern. Keeping them together lets the same binding requirements apply across a multiline alternative list.
Source evidence
- observeNewline, consumeSemicolon and flushPendingSeparator at
af88afbc1d99· 245-327 - observeTokenStructure and normalize at
af88afbc1d99· 443-510
Collections and comprehensions, Modules and visibility, and Functions and generics own list grammars. Expressions and operators owns precedence. Pattern matching owns pattern grammar.
Explicit call type arguments have a recorded implementation limitation involving the gap between > and (. Its intended contract and workaround remain in Functions and generics. That spacing-sensitive behavior does not add a lexical prohibition.
Source locations
LinkUnknown source characters
An unrecognized source character produces TPZ0001 at that character's location.
Why
Rejecting an unknown character at its location prevents silent removal from changing a program.
Source evidence
- scanUnknown at
af88afbc1d99· 559-577
Diagnostic source positions
CLI diagnostic labels use 1-based line, col, end, and end. Columns count Unicode scalar values from the start of the line. The lo and hi fields use 0-based UTF-8 byte offsets. Both end positions are exclusive.
Why
Scalar columns locate characters without depending on UTF-8 byte width. Byte offsets address exact source slices, and exclusive ends compose adjacent spans.
Source evidence
- LineCol scalar-column convention at
af88afbc1d99· 8-15 - SourceFile.line_col at
af88afbc1d99· 116-128 - render_json and push_label_json at
af88afbc1d99· 50-110
Examples and non-examples
LinkIdentifier spellings and contextual names
Linklet answer_2: int = 42let 값: int = answer_2let 🟦: int = 값let ifx: int = 🟦let record: int = ifxlet loop: int = record
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Observed assertions · passed · 5.21.3
Exit code 0.
No stdout.
No stderr.
- Passed · expected 0; observed 0
- Passed · expected 0; observed 0
- Passed · expected 0; observed 0
- Passed · expected 0; observed 0
Source evidence
- Identifier scalar predicates and scanning at
af88afbc1d99· 164-241 - Unicode categories and non-ASCII Emoji property at
af88afbc1d99· 7-65 - keywordKind and identifierHeadKind at
af88afbc1d99· 68-109 - parseStatement at
af88afbc1d99· 901-948 - parseIdentifierAtom at
af88afbc1d99· 1004-1034
Comments and continued expressions
Link// A comment occupies its prefixed line.let first: int = 1 // A line comment.let second: int =first +2let third: int = (second* 2)
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- lineCommentEnd and blockCommentEnd at
af88afbc1d99· 41-63 - scanCode whitespace and newline branches at
af88afbc1d99· 568-633 - lineContinuation at
af88afbc1d99· 31-99 - observeNewline, consumeSemicolon and flushPendingSeparator at
af88afbc1d99· 245-327
The retired block opener is rejected without a closer
Linklet value: int = 1/* unfinished
Expected result
Source evidence
- scanComment at
af88afbc1d99· 446-465 - parseAtom at
271329b482bc· 1291-1299
A line comment needs no closing delimiter
Linklet value: int = 1// A line comment also ends at the end of the source.
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- scanComment at
af88afbc1d99· 446-465
A semicolon in an array list
Linklet values: Array<int> = [1; 2]
No stdout.
error[TPZ1001]: `;` does not separate items inside a delimiter list; use `,` --> example.tpz:1:28 | 1 | let values: Array<int> = [1; 2] | ^ error[TPZ2001]: expected `]` --> example.tpz:1:30 | 1 | let values: Array<int> = [1; 2] | ^ error[TPZ2001]: expected an expression --> example.tpz:1:31 | 1 | let values: Array<int> = [1; 2] | ^ error[TPZ2001]: expected a statement --> example.tpz:1:31 | 1 | let values: Array<int> = [1; 2] | ^ example.tpz: 4 diagnostics
Expected result
Expected result
Rejection during layout. Nonzero exit.
TPZ1001semicolon-in-delimiter-list atexample.tpz:1:28
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ1001]: `;` does not separate items inside a delimiter list; use `,`--> example.tpz:1:28|1 | let values: Array<int> = [1; 2]| ^error[TPZ2001]: expected `]`--> example.tpz:1:30|1 | let values: Array<int> = [1; 2]| ^error[TPZ2001]: expected an expression--> example.tpz:1:31|1 | let values: Array<int> = [1; 2]| ^error[TPZ2001]: expected a statement--> example.tpz:1:31|1 | let values: Array<int> = [1; 2]| ^example.tpz: 4 diagnostics
- Passed · checking TPZ1001; cause semicolon-in-delimiter-list; primary location asserted; additional diagnostics allow
- Passed · expected nonzero; observed 1
Source evidence
- observeNewline, consumeSemicolon and flushPendingSeparator at
af88afbc1d99· 245-327
Separate array elements with a comma
Linklet values: Array<int> = [1, 2]
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Observed assertions · passed · 5.21.3
Exit code 0.
No stdout.
No stderr.
- Passed · expected 0; observed 0
Source evidence
- observeNewline, consumeSemicolon and flushPendingSeparator at
af88afbc1d99· 245-327
A keyword used as a function name
Linkfunction if() { }
No stdout.
error[TPZ2001]: expected a function name
--> example.tpz:1:10
|
1 | function if() { }
| ^^
example.tpz: 1 diagnostic
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001expected-function-name atexample.tpz:1:10
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ2001]: expected a function name--> example.tpz:1:10|1 | function if() { }| ^^example.tpz: 1 diagnostic
- Passed · checking TPZ2001; cause expected-function-name; primary location asserted; additional diagnostics allow
- Passed · checking TPZ2001; cause expected-function-name; primary location asserted; additional diagnostics allow
- Passed · expected nonzero; observed 1
Source evidence
- keywordKind and identifierHeadKind at
af88afbc1d99· 68-109 - rejectReservedBinding, identifier and fieldName at
af88afbc1d99· 176-235
Give the function an identifier
Linkfunction choose() { }
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Observed assertions · passed · 5.21.3
Exit code 0.
No stdout.
No stderr.
- Passed · expected 0; observed 0
- Passed · expected 0; observed 0
Source evidence
- keywordKind and identifierHeadKind at
af88afbc1d99· 68-109 - rejectReservedBinding, identifier and fieldName at
af88afbc1d99· 176-235
An unknown character after a Unicode name
Linklet 이름 = @
No stdout.
error[TPZ0001]: unknown character `@` --> example.tpz:1:10 | 1 | let 이름 = @ | ^ error[TPZ2001]: expected an expression --> example.tpz:2:1 | 2 | | ^ example.tpz: 2 diagnostics
Expected result
Expected result
Rejection during lex. Nonzero exit.
TPZ0001unknown-source-character atexample.tpz:1:10
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ0001]: unknown character `@`--> example.tpz:1:10|1 | let 이름 = @| ^error[TPZ2001]: expected an expression--> example.tpz:2:1|2 || ^example.tpz: 2 diagnostics
- Passed · checking TPZ0001; cause unknown-source-character; primary location asserted; additional diagnostics allow
- Passed · checking TPZ0001; cause unknown-source-character; primary location asserted; additional diagnostics allow
- Passed · expected nonzero; observed 1
This example exercises the primary line and scalar column. Byte offsets and end positions remain pending product verification.
Source evidence
- scanUnknown at
af88afbc1d99· 559-577 - LineCol scalar-column convention at
af88afbc1d99· 8-15 - SourceFile.line_col at
af88afbc1d99· 116-128 - render_json and push_label_json at
af88afbc1d99· 50-110
LF separates completed statements
Linkimport std.iolet value = 1-2io.writeLine("{value}")
1
Expected result
Expected result
Execution completes. Exit code 0.
1
Source evidence
- issues/31 at
570530e69141 - scanCode line-break dispatch at
570530e69141· 611-634 - observeNewline and flushPendingSeparator at
570530e69141· 245-318
A block comment with LF is rejected
Linkimport std.iolet value = 1 /* beforeafter */ +2io.writeLine("{value}")
Expected result
Source evidence
- issues/31 at
570530e69141 - scanComment at
570530e69141· 446-465 - observeNewline and flushPendingSeparator at
570530e69141· 245-318 - parseAtom at
271329b482bc· 1291-1299
LF ends a line comment
Linkimport std.io// commentio.writeLine("after")io.writeLine("tail")
after tail
Expected result
Expected result
Execution completes. Exit code 0.
aftertail
Source evidence
- issues/31 at
570530e69141 - lineCommentEnd at
570530e69141· 46-51 - scanCode line-break dispatch at
570530e69141· 611-634 - observeNewline and flushPendingSeparator at
570530e69141· 245-318
CRLF separates completed statements
Linkimport std.iolet value = 1-2io.writeLine("{value}")
1
Expected result
Expected result
Execution completes. Exit code 0.
1
Source evidence
- issues/31 at
570530e69141 - scanCode line-break dispatch at
570530e69141· 611-634 - observeNewline and flushPendingSeparator at
570530e69141· 245-318
A block comment with CRLF is rejected
Linkimport std.iolet value = 1 /* beforeafter */ +2io.writeLine("{value}")
Expected result
Source evidence
- issues/31 at
570530e69141 - scanComment at
570530e69141· 446-465 - observeNewline and flushPendingSeparator at
570530e69141· 245-318 - parseAtom at
271329b482bc· 1291-1299
CRLF ends a line comment
Linkimport std.io// commentio.writeLine("after")io.writeLine("tail")
after tail
Expected result
Expected result
Execution completes. Exit code 0.
aftertail
Source evidence
- issues/31 at
570530e69141 - lineCommentEnd at
570530e69141· 46-51 - scanCode line-break dispatch at
570530e69141· 611-634 - observeNewline and flushPendingSeparator at
570530e69141· 245-318
Bare CR separates completed statements
Linkimport std.iolet value = 1 -2io.writeLine("{value}")
1
Expected result
Expected result
Execution completes. Exit code 0.
1
Source evidence
- issues/31 at
570530e69141 - scanCode line-break dispatch at
570530e69141· 611-634 - observeNewline and flushPendingSeparator at
570530e69141· 245-318
A block comment with CR is rejected
Linkimport std.iolet value = 1 /* before after */ +2io.writeLine("{value}")
Expected result
Source evidence
- issues/31 at
570530e69141 - scanComment at
570530e69141· 446-465 - observeNewline and flushPendingSeparator at
570530e69141· 245-318 - parseAtom at
271329b482bc· 1291-1299
Bare CR ends a line comment
Linkimport std.io// comment io.writeLine("after")io.writeLine("tail")
after tail
Expected result
Expected result
Execution completes. Exit code 0.
aftertail
Source evidence
- issues/31 at
570530e69141 - lineCommentEnd at
570530e69141· 46-51 - scanCode line-break dispatch at
570530e69141· 611-634 - observeNewline and flushPendingSeparator at
570530e69141· 245-318
A block comment on one line is rejected
Linkimport std.iolet value = 1 /* no line break */ +2io.writeLine("{value}")
Expected result
Source evidence
- issues/31 at
570530e69141 - scanComment at
570530e69141· 446-465 - observeNewline and flushPendingSeparator at
570530e69141· 245-318 - parseAtom at
271329b482bc· 1291-1299
Parentheses do not admit block comments
Linkimport std.iolet value = (1 /* before after */ +2)io.writeLine("{value}")
Expected result
Source evidence
- issues/31 at
570530e69141 - scanComment at
570530e69141· 446-465 - observeNewline and flushPendingSeparator at
570530e69141· 245-318 - parseAtom at
271329b482bc· 1291-1299
Adjacent declarations need a statement boundary
Linklet first = 1 let second = 2
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001expected-statement-separator atexample.tpz:1:15
Source evidence
- issues/31 at
570530e69141 - scanComment at
570530e69141· 446-465 - itemBoundary at
570530e69141· 443-456
A newline separates commented items
Linklet first = 1let second = 2 // The newline separates the items.
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- issues/31 at
570530e69141 - scanComment at
570530e69141· 446-465 - itemBoundary at
570530e69141· 443-456
A semicolon cannot separate program items
Linklet first = 1; let second = 2
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001expected-statement-separator atexample.tpz:1:14
A semicolon cannot discard a block tail
Linkfunction value() { 7; }
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001expected-statement-separator atexample.tpz:1:21
Combining marks continue NFC names
Linklet q́: int = 1let का: int = q́let x⃝: int = काloop 'q́ { break 'q́ }
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
q + U+0301 is Mn, U+093E in का is Mc, and U+20DD in x⃝ is Me. Each complete name is already NFC.
Source evidence
- LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Current identifier character predicates at
72a59a31125f· 164-172 - Current label and identifier scanning without an NFC check at
72a59a31125f· 517-569 - Unicode 16 General_Category Mn, Mc and Me at
Unicode-16.0 - Unicode 16 Default_Ignorable_Code_Point at
Unicode-16.0· Default_Ignorable_Code_Point - Canonical normalization forms at
Unicode-16.0· 3.11.7
Use the NFC spelling throughout the source
Linklet café: int = 1let 가: int = 2let K: int = 3let value: int = café + 가 + K
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Current identifier character predicates at
72a59a31125f· 164-172 - Current label and identifier scanning without an NFC check at
72a59a31125f· 517-569 - Unicode 16 General_Category Mn, Mc and Me at
Unicode-16.0 - Unicode 16 Default_Ignorable_Code_Point at
Unicode-16.0· Default_Ignorable_Code_Point - Canonical normalization forms at
Unicode-16.0· 3.11.7
A combining mark cannot start a name
Linklet ́name = 1
Expected result
Expected result
Rejection during lex. Nonzero exit.
TPZ0001unknown-source-character atexample.tpz:1:5
The leading scalar is U+0301 COMBINING ACUTE ACCENT.
Source evidence
- LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Current identifier character predicates at
72a59a31125f· 164-172 - Current label and identifier scanning without an NFC check at
72a59a31125f· 517-569 - Unicode 16 General_Category Mn, Mc and Me at
Unicode-16.0 - Unicode 16 Default_Ignorable_Code_Point at
Unicode-16.0· Default_Ignorable_Code_Point - Canonical normalization forms at
Unicode-16.0· 3.11.7
Reject an invisible Hangul filler at the start
Linklet ᅟname = 1
Expected result
Expected result
Rejection during lex. Nonzero exit.
TPZ0001unknown-source-character atexample.tpz:1:5
U+115F HANGUL CHOSEONG FILLER is a Letter and Default_Ignorable_Code_Point.
Source evidence
- LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Current identifier character predicates at
72a59a31125f· 164-172 - Current label and identifier scanning without an NFC check at
72a59a31125f· 517-569 - Unicode 16 General_Category Mn, Mc and Me at
Unicode-16.0 - Unicode 16 Default_Ignorable_Code_Point at
Unicode-16.0· Default_Ignorable_Code_Point - Canonical normalization forms at
Unicode-16.0· 3.11.7
Reject an invisible Hangul filler in a name
Linklet aㅤ = 1
Expected result
Expected result
Rejection during lex. Nonzero exit.
TPZ0001unknown-source-character atexample.tpz:1:6
U+3164 HANGUL FILLER is excluded even though its General_Category is Lo.
Source evidence
- LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Current identifier character predicates at
72a59a31125f· 164-172 - Current label and identifier scanning without an NFC check at
72a59a31125f· 517-569 - Unicode 16 General_Category Mn, Mc and Me at
Unicode-16.0 - Unicode 16 Default_Ignorable_Code_Point at
Unicode-16.0· Default_Ignorable_Code_Point - Canonical normalization forms at
Unicode-16.0· 3.11.7
Reject a variation selector in a name
Linklet ✈️ = 1
Expected result
Expected result
Rejection during lex. Nonzero exit.
TPZ0001unknown-source-character atexample.tpz:1:6
The rejected scalar is U+FE0F VARIATION SELECTOR-16. Use the admitted name ✈ without the selector.
Source evidence
- LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Current identifier character predicates at
72a59a31125f· 164-172 - Current label and identifier scanning without an NFC check at
72a59a31125f· 517-569 - Unicode 16 General_Category Mn, Mc and Me at
Unicode-16.0 - Unicode 16 Default_Ignorable_Code_Point at
Unicode-16.0· Default_Ignorable_Code_Point - Canonical normalization forms at
Unicode-16.0· 3.11.7
Reject a joiner in an emoji name
Linklet 👩💻 = 1
Expected result
Expected result
Rejection during lex. Nonzero exit.
TPZ0001unknown-source-character atexample.tpz:1:6
The rejected scalar is U+200D ZERO WIDTH JOINER. A displayed emoji cluster does not override scalar admission.
Source evidence
- LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Current identifier character predicates at
72a59a31125f· 164-172 - Current label and identifier scanning without an NFC check at
72a59a31125f· 517-569 - Unicode 16 General_Category Mn, Mc and Me at
Unicode-16.0 - Unicode 16 Default_Ignorable_Code_Point at
Unicode-16.0· Default_Ignorable_Code_Point - Canonical normalization forms at
Unicode-16.0· 3.11.7
Reject a combining grapheme joiner
Linklet a͏ = 1
Expected result
Expected result
Rejection during lex. Nonzero exit.
TPZ0001unknown-source-character atexample.tpz:1:6
U+034F COMBINING GRAPHEME JOINER is Mn and Default_Ignorable_Code_Point. The exclusion takes precedence over combining-mark continuation.
Source evidence
- LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Current identifier character predicates at
72a59a31125f· 164-172 - Current label and identifier scanning without an NFC check at
72a59a31125f· 517-569 - Unicode 16 General_Category Mn, Mc and Me at
Unicode-16.0 - Unicode 16 Default_Ignorable_Code_Point at
Unicode-16.0· Default_Ignorable_Code_Point - Canonical normalization forms at
Unicode-16.0· 3.11.7
Reject decomposed source names
Linklet café = 1
Expected result
NFC spells this complete name café.
Source evidence
- LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Current label and identifier scanning without an NFC check at
72a59a31125f· 517-569 - Existing lexical identities TPZ0001 through TPZ0006; TPZ0007 is newly specified by LL-3 at
72a59a31125f - Canonical normalization forms at
Unicode-16.0· 3.11.7
Reject composable Hangul jamo in a name
Linklet 가 = 1
Expected result
The name contains U+1100 and U+1161. NFC replaces the sequence with U+AC00 가.
Source evidence
- LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Current label and identifier scanning without an NFC check at
72a59a31125f· 517-569 - Existing lexical identities TPZ0001 through TPZ0006; TPZ0007 is newly specified by LL-3 at
72a59a31125f - Canonical normalization forms at
Unicode-16.0· 3.11.7
Reject a scalar with a different NFC spelling
Linklet K = 1
Expected result
U+212A KELVIN SIGN has the canonical NFC spelling U+004B K. This is a canonical normalization rule, not NFKC or case folding.
Source evidence
- LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Current label and identifier scanning without an NFC check at
72a59a31125f· 517-569 - Existing lexical identities TPZ0001 through TPZ0006; TPZ0007 is newly specified by LL-3 at
72a59a31125f - Canonical normalization forms at
Unicode-16.0· 3.11.7
Apply NFC to label bodies
Linkloop '가 { break }
Expected result
The primary name span excludes the opening apostrophe. Write the label body 가.
Source evidence
- LL-3 recommendation: NFC source names, combining continuations, and preserved string data at
72a59a31125f - Current label and identifier scanning without an NFC check at
72a59a31125f· 517-569 - Existing lexical identities TPZ0001 through TPZ0006; TPZ0007 is newly specified by LL-3 at
72a59a31125f - Canonical normalization forms at
Unicode-16.0· 3.11.7