# Gonidium Grammar

This document describes the surface syntax of Gonidium.
Expression precedence/associativity is defined by an explicit operator table (Pratt-style).

## Top-level

```ebnf
program       = func_def ;
func_def      = '|' param_list '|' body ;
param_list    = (param (',' param)*)? ;
param         = ident ':' dtype ;
body          = stmt* expr ;
stmt          = ident '=' expr newline ;
```

## Expressions

```ebnf
expr          = if_expr | pratt_expr ;
if_expr       = 'if' expr 'then' expr 'else' expr ;

(* pratt_expr uses the operator table below. *)
pratt_expr    = prefix_op* atom (infix_op prefix_op* atom)* ;

prefix_op     = '-' | '!' ;
infix_op      = '**'
              | '*' | '/' | '//' | '%'
              | '+' | '-'
              | '==' | '!=' | '<' | '<=' | '>' | '>='
              | '&&'
              | '||' ;

atom          = (cast | call | primary) ('@' dtype)* ;
cast          = 'cast' '<' dtype '>' '(' expr ')' ;
call          = ident '(' arg_list ')' | primary ;

primary       = '(' expr ')'
              | '(' expr ',' expr (',' expr)* ')'    (* tuple, >= 2 items *)
              | literal
              | const_sym
              | ident ;

arg_list      = (expr (',' expr)*)? ;
```

### Operator precedence (high → low)

| Prec. | Operators                   | Kind   | Assoc.                  |
| ----: | --------------------------- | ------ | ----------------------- |
|     8 | `**`                        | infix  | right                   |
|     7 | `-` `!`                     | prefix | right                   |
|     6 | `*` `/` `//` `%`            | infix  | left                    |
|     5 | `+` `-`                     | infix  | left                    |
|     4 | `==` `!=` `<` `<=` `>` `>=` | infix  | non-assoc (no chaining) |
|     3 | `&&`                        | infix  | left                    |
|     2 | `\|\|`                      | infix  | left                    |

Notes:

- `if-then-else` has the lowest precedence (below all operators).
- Parentheses, `cast<dtype>(...)`, and function calls bind tighter than any operator.
- `-` binds weaker than `**`: `-8**2` parses as `-(8**2)`.
- Comparisons do not chain: `a < b < c` is rejected.

## Literals

```ebnf

literal       = literal '@' dtype
              | int_lit | float_lit | complex_lit
              | bool_lit ;

bool_lit      = 'true' | 'false' ;
int_lit       = digits ;
float_lit     = digits '.' digits exponent?
              | digits exponent ;
exponent      = ('e' | 'E') ('+' | '-')? digits ;
complex_lit   = float_lit 'j' ;

const_sym     = 'e' | 'pi' ;
```

## Comments

```ebnf
comment       = '#' { any-char-except-newline } newline ;
```

- A `#` starts a line comment, similar to Python.
- Everything from `#` to the end of the line is ignored by the parser.

- Default literal types (without `@dtype`):
  - int → smallest signed integer dtype that fits the value
  - float → smallest IEEE float that represents the value exactly (`f16` → `f32` → `f64`)
  - complex → smallest IEEE complex that represents both parts exactly (`c64` → `c128`)
- `2j` is invalid; write `2.0j` or `2e0j`.
- `@dtype` can be applied to literals or arbitrary expressions as postfix cast sugar:
  - `expr @ dtype` is equivalent to `cast<dtype>(expr)`.
  - For literals, `8@i8` keeps its specialised literal handling (range checks, default dtype rules) while still fitting this mental model.

## Types

```ebnf
dtype         = 'bool'
              | 'u8'  | 'u16' | 'u32' | 'u64'
              | 'i8'  | 'i16' | 'i32' | 'i64'
              | 'f16' | 'bf16'| 'f32' | 'f64'
              | 'c64' | 'c128' ;
```

## Examples

```gonidium
|a: f32, b: f64|
q1 = a + 1
q2 = b + 1
q1 * q2
```

```gonidium
|x: f32|
if x > 0.0 then x else 0.0
```

```gonidium
|x: f32, w: f32, b: f32|
sigmoid(x * w + b)
```

```gonidium
|z: c128|
z + 2.0j
```

```gonidium
|x: f32|
2.0 ** 3.0 ** 2.0
```

```gonidium
|x: f32|
-8@i8 + x
```
