# 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, ";", opt_newlines, [ expression ], opt_newlines, ";", 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 is split into optional init / condition / post slots separated by semicolons 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.