# qk Grammar (Current Parser Behavior)

This document describes the syntax accepted by the current `tokeniser` and `parser` packages. It is intentionally parser-accurate rather than aspirational.

## Notes

- Newlines and semicolons are statement separators in many positions.
- Module-qualified names use `module:name` (colon), not dot notation.
- Function definitions are expression-bodied or block-bodied.
- `if` can be a statement and an expression.
- `given { ... } -> expr` is an expression form.

## EBNF

```ebnf
(* ======================== *)
(* Lexical / separators     *)
(* ======================== *)

newline        = "\\n" ;
sep            = newline | ";" ;
opt_newlines   = { newline } ;

identifier     = ( "_" | letter ), { "_" | letter | digit } ;
number         = [ "-" ], digit, { digit } ;
string_lit     = '"', { string_char }, '"' ;
char_lit       = "'", char_char, "'" ;

(* float literals are parsed, not tokenised directly *)
float_lit      = number, ".", [ number ]
               | ".", number ;

bool_lit       = "true" | "false" ;
nil_lit        = "nil" ;

(* ======================== *)
(* Program / top level      *)
(* ======================== *)

program        = { sep }, [ top_item, { sep, top_item } ], { sep } ;

top_item       = module_decl
               | import_decl
               | let_top_item
               | extern_fn_def
               | pub_top_item ;

pub_top_item   = "pub", ( let_top_item | extern_fn_def ) ;

module_decl    = "module", identifier ;

import_decl    = "import", (
                   identifier
                 | "(", opt_newlines,
                     identifier,
                     { ( "," | newline ), opt_newlines, identifier },
                     [ "," | newline ], opt_newlines,
                   ")"
                 ) ;

let_top_item   = fn_def | declaration | type_alias ;

(* ======================== *)
(* Statements / blocks      *)
(* ======================== *)

block          = "{", opt_newlines,
                 [ statement, { sep, statement }, [ sep ] ],
                 "}" ;

statement      = declaration
               | fn_def
               | extern_fn_def
               | type_alias
               | assignment
               | index_assignment
               | pointer_assignment
               | call_stmt
               | module_access_stmt
               | if_stmt
               | for_stmt
               | control_stmt
               | block ;

if_stmt        = "if", opt_newlines, expression, opt_newlines, block,
                 { opt_newlines, "else", opt_newlines,
                   ( "if", opt_newlines, expression, opt_newlines, block
                   | block )
                 } ;

for_stmt       = "for", opt_newlines,
                 [ stmt_or_expr, { ";", opt_newlines, stmt_or_expr } ],
                 opt_newlines,
                 block ;

stmt_or_expr   = statement | expression ;

control_stmt   = "break"
               | "continue"
               | "return", [ expression ] ;

(* ======================== *)
(* Declarations / functions *)
(* ======================== *)

declaration    = "let", [ "mut" ], identifier,
                 [ ":", type_expr ],
                 "=", opt_newlines, expression ;

type_alias     = "let", identifier, "=", "type", type_expr ;

fn_def         = "let", identifier,
                 "(", [ fn_param_list | "..." ], ")",
                 [ ":", type_expr ],
                 "=", opt_newlines,
                 ( block | expression | extern_binding ) ;

extern_fn_def  = "extern", "let", identifier,
                 "(", [ fn_param_list | "..." ], ")",
                 [ ":", type_expr ],
                 "=", opt_newlines,
                 ( block | expression | extern_binding ) ;

extern_binding = "extern", "(", string_lit, ")" ;

fn_param_list  = fn_param, { ",", fn_param } ;
fn_param       = [ "mut" ], identifier, ":", type_expr ;

assignment        = identifier, "=", opt_newlines, expression ;
index_assignment  = identifier, "[", expression, "]", "=", opt_newlines, expression ;
pointer_assignment= "*", expression, "=", opt_newlines, expression ;

call_stmt       = module_access, "(", [ arg_list ], ")" ;
module_access_stmt = module_access ;

(* ======================== *)
(* Expressions              *)
(* ======================== *)

expression     = as_cast ;

as_cast        = logical_or, [ "as", opt_newlines, type_expr ] ;

logical_or     = logical_and,
                 { "or", opt_newlines, logical_and } ;

logical_and    = logical_not,
                 { "and", opt_newlines, logical_not } ;

logical_not    = ( "not", opt_newlines, logical_not )
               | comparison ;

comparison     = add_sub,
                 { ( "==" | "!=" | "<" | ">" | "<=" | ">=" ),
                   opt_newlines, add_sub } ;

add_sub        = mul_div,
                 { ( "+" | "-" ), opt_newlines, mul_div } ;

mul_div        = unary,
                 { ( "*" | "/" | "%" ), opt_newlines, unary } ;

unary          = ( "-" | "*" | "&" ), opt_newlines, unary
               | postfix ;

postfix        = term,
                 {
                   "[" , opt_newlines, expression, opt_newlines, "]"
                   | "(" [ arg_list ] ")"
                   | ".", opt_newlines, identifier
                 } ;

term           = "(", opt_newlines, expression, opt_newlines, ")"
               | if_expr
               | given_expr
               | sizeof_expr
               | struct_literal
               | slice_literal
               | module_access
               | identifier
               | bool_lit
               | nil_lit
               | number
               | float_lit
               | string_lit
               | char_lit ;

if_expr        = "if", opt_newlines, expression, opt_newlines, block_expr,
                 { opt_newlines, "else", opt_newlines,
                   ( "if", opt_newlines, expression, opt_newlines, block_expr
                   | block_expr )
                 } ;

given_expr     = "given", opt_newlines, block, opt_newlines,
                 "->", opt_newlines, expression ;

block_expr     = "{", opt_newlines, expression, opt_newlines, "}" ;

sizeof_expr    = "sizeof", opt_newlines, type_expr ;

arg_list       = expression, { ",", opt_newlines, expression } ;

module_access  = identifier, [ ":", opt_newlines, identifier ] ;

struct_literal = [ module_access ],
                 "{", opt_newlines,
                 [ field_init,
                   { ( "," | newline ), opt_newlines, field_init },
                   [ "," | newline ]
                 ],
                 "}" ;

field_init     = identifier, "=", opt_newlines, expression ;

slice_literal  = "[", expression,
                 { ( "," | newline ), opt_newlines, expression },
                 [ "," | newline ],
                 "]" ;

(* ======================== *)
(* Types                    *)
(* ======================== *)

type_expr      = pointer_type | slice_type | struct_type | named_type ;

pointer_type   = "*", type_expr ;

slice_type     = "[", opt_newlines, type_expr,
                 [ ",", opt_newlines, number ],
                 opt_newlines, "]" ;

struct_type    = "struct", "{", opt_newlines,
                 [ struct_field,
                   { ( "," | newline ), opt_newlines, struct_field },
                   [ "," | newline ]
                 ],
                 "}" ;

struct_field   = identifier, ":", type_expr ;

named_type     = identifier
               | identifier, ":", identifier ;
```

## Markdown Description

### 1. Tokenisation model

- Identifiers start with a letter or underscore and then continue with letters, digits, or underscore.
- Keywords include: `let`, `mut`, `extern`, `struct`, `type`, `if`, `else`, `given`, `for`, `break`, `continue`, `return`, `import`, `module`, `pub`, `and`, `or`, `not`, `true`, `false`, `nil`, `as`, `sizeof`.
- Integer tokens are decimal with optional leading minus.
- Floating-point literals are assembled by the parser from integer tokens separated by a dot (for example `12.34`, `12.`, `.34`).
- Strings and chars support escape sequences: `\\`, `\"`, `\n`, `\r`, `\t`, `\b`, `\f`, `\v`, `\a`, `\0`.
- `//` and `/* ... */` comments are ignored by the tokeniser.
- A backslash followed by newline is treated as line continuation and skipped.

### 2. Program structure

- A file is a sequence of top-level items separated by newline or semicolon.
- Accepted top-level forms are: `module`, `import`, `let ...` definitions/declarations/type aliases, and `extern let ...` function definitions.
- `pub` is only accepted before `let ...` or `extern let ...` forms that result in function, declaration, or type-alias nodes.

### 3. Functions

- Function syntax is based on `let`:
  - `let name(args) = expr`
  - `let name(args): Ret = { ... }`
- Parameters are `name: Type` with optional `mut`.
- Variadic marker `...` is accepted in parameter lists.
- External symbol binding is expressed in function body position:
  - `let puts(...): i32 = extern("puts")`
- Current parser behavior rejects variadics unless using the `extern("...")` binding form.

### 4. Statements and separators

- Inside blocks, statements are generally separated by newline or semicolon.
- `if` and `for` manage their own internal block boundaries and do not require an additional separator immediately after their parse in some contexts.
- `for` header accepts a semicolon-separated list of statements or expressions before the loop body block.

### 5. Expressions and precedence

From lowest to highest precedence:

1. `as` cast
2. `or`
3. `and`
4. `not`
5. comparisons: `== != < > <= >=`
6. addition/subtraction: `+ -`
7. multiplication/division/modulo: `* / %`
8. unary: `- * &`
9. postfix indexing and slice-length postfix `[]`
10. terms (literals, names, calls, struct/slice literals, parenthesized expressions, `if` expression, `given` expression, `sizeof`)

### 6. Types

- Named type: `T` or module-qualified `mod:T`.
- Pointer type: `*T`.
- Slice/array-like type form: `[T]` or `[T, N]` where `N` is a decimal integer.
- Struct type literal: `struct { field: Type, ... }`.

### 7. Parser-specific caveats

- Compound assignment tokens (`+=`, `-=`, `*=`, `/=`, `%=`) are tokenised but are not currently parsed as assignment statements.
- Slice literal parsing currently requires at least one element in practice.
- A module-qualified access uses `:` consistently for both value and type positions.