2. Grammar
This section defines the notation used for grammar fragments throughout the specification, and then gives the complete grammar. Fragments quoted in other sections are taken from the complete grammar verbatim.
2.1 Notation
A variant of Extended Backus–Naur Form:
| Form | Meaning |
|---|---|
A = B ; | Rule: A is defined as B. |
"x" | A terminal: the literal source text x. |
UPPERCASE | A token produced by the lexer (§1), e.g. NAME, INT, NEWLINE. |
A , B | A followed by B. |
A | B | Either A or B. |
( … ) | Grouping. |
[ A ] | Zero or one A. |
{ A } | Zero or more A. |
2.2 What the grammar does and does not say
The grammar is context-free and written for readability. Two things are specified elsewhere:
- Operator precedence and associativity. The
expressionrule below is deliberately ambiguous (expression , binary_op , expression). The real precedence is the table in §4, implemented by a Pratt parser. Comparisons are non-associative —a < b < cis a syntax error. - Assignment targets. The grammar allows
assignable = NAME | attribute | index, but the parser actually parses a fullexpressionon the left of=and then checks that it is one of those three forms, so that a mistake likea + b = 1gets a precise "cannot assign to this expression" message (E0104) rather than a generic parse failure.
2.3 Layout tokens in the grammar
NEWLINE, INDENT, and DEDENT appear in the grammar as ordinary terminals.
They are produced by the lexer from indentation (§1.2, §1.4), so the grammar
itself never mentions whitespace. Every block is exactly:
block = ":" , NEWLINE , INDENT , statement , { statement } , DEDENT ;
2.4 The complete grammar
This is the whole of Korrin's Milestone 1 syntax. It is kept in step with
crates/korrin/src/parser/ and its tests.
Tokens
Produced by the lexer (§1):
NEWLINE end of a logical line
INDENT start of a more-indented block
DEDENT end of an indented block
NAME identifier that is not a keyword
INT integer literal
FLOAT float literal
STRING string literal with no interpolation holes
EOF end of input
An interpolated string (§1.7) is not one token — the lexer emits
STRSTART ( STRTEXT | STREXPRSTART expression STREXPREND )* STREND.
plus the 19 keywords and the operator / punctuation tokens listed in §1.8.
Modules and blocks
module = { NEWLINE } , { statement } , EOF ;
block = ":" , NEWLINE , INDENT , statement , { statement } , DEDENT ;
Statements
statement =
simple_statement
| if_statement
| while_statement
| for_statement
| function_definition
| class_definition ;
simple_statement =
( assignment | return_statement | "break" | "continue" | "pass" | expression ) ,
NEWLINE ;
assignment = assignable , "=" , expression ;
assignable = NAME | attribute | index ;
return_statement = "return" , [ expression ] ;
if_statement =
"if" , expression , block ,
{ "elif" , expression , block } ,
[ "else" , block ] ;
while_statement = "while" , expression , block ;
for_statement = "for" , NAME , "in" , expression , block ;
function_definition =
"fn" , NAME , "(" , [ parameter_list ] , ")" , block ;
parameter_list = NAME , { "," , NAME } , [ "," ] ;
class_definition =
"class" , NAME , [ "(" , NAME , ")" ] , ":" , NEWLINE ,
INDENT , class_member , { class_member } , DEDENT ;
class_member = function_definition | ( "pass" , NEWLINE ) ;
Expressions
Written ambiguously; see §4 for precedence.
expression =
literal
| interpolated_string
| NAME
| "super" , "." , NAME
| list_literal
| map_literal
| "(" , expression , ")"
| unary_op , expression
| expression , binary_op , expression
| call
| attribute
| index ;
call = expression , "(" , [ argument_list ] , ")" ;
argument_list = expression , { "," , expression } , [ "," ] ;
attribute = expression , "." , NAME ;
index = expression , "[" , expression , "]" ;
list_literal = "[" , [ expression , { "," , expression } , [ "," ] ] , "]" ;
map_literal = "{" , [ map_entry , { "," , map_entry } , [ "," ] ] , "}" ;
map_entry = expression , ":" , expression ;
interpolated_string =
STRSTART , { STRTEXT | ( STREXPRSTART , expression , STREXPREND ) } , STREND ;
unary_op = "-" | "not" ;
binary_op =
"+" | "-" | "*" | "/" | "%"
| "==" | "!=" | "<" | "<=" | ">" | ">="
| "and" | "or" ;
literal = INT | FLOAT | STRING | "true" | "false" | "nil" ;