Ridgun
The language itself: a compiler that compiles itself, the stable project tool, the specification and the book
| 1 | --- |
| 2 | id: FR-COMP-003 |
| 3 | title: Unified `=` Block Grammar (`(none)`, `|`, `||` without `where`) and Explicit `mut` Variable Bindings |
| 4 | status: implemented |
| 5 | priority: must |
| 6 | created: 2026-10-20 |
| 7 | updated: 2026-10-20 |
| 8 | depends_on: |
| 9 | - FR-COMP-002 |
| 10 | verified_by: [] |
| 11 | --- |
| 12 | # FR-COMP-003 — Unified `=` Block Grammar (`(none)`, `|`, `||` without `where`) and Explicit `mut` Variable Bindings |
| 13 | |
| 14 | ## Statement |
| 15 | The Ridgun language and `ridgunc` compiler SHALL unify block, conditional, parallel, and variable-binding syntax under four rules: |
| 16 | 1. **Universal `=` Right-Hand Side (`(none)` / `|` / `||`)**: Every function clause (`fn ::`), operator clause (`op ::`), local variable binding (`x =` / `mut x =`), and guard branch (`| cond =` / `| otherwise =`) SHALL require `=` and SHALL accept any of the following right-hand-side forms: |
| 17 | - Inline expression (optionally followed on subsequent lines by `|| expr -> var` parallel bindings), |
| 18 | - Sequential block (indented lines with no prefix), |
| 19 | - Conditional guard block (`| cond = ...` and `| otherwise = ...`), |
| 20 | - Parallel DAG block (`|| expr -> var` or `|| expr`). |
| 21 | 2. **Guard Blocks (`|`) Behind `=` & Nested Block Closers (`|.` / `||.`)**: |
| 22 | - Guarded function and operator clauses SHALL include `=` after their pattern list (`name pats =` followed by `| cond = ...`). |
| 23 | - Local variable bindings SHALL support conditional guard blocks (`x =` followed by `| cond = ...`). |
| 24 | - Guard branches (`| cond =` and `| otherwise =`) SHALL support nested `|` guard blocks to form arbitrary decision trees (3–4+ levels deep). |
| 25 | - When a nested `|` or `||` block finishes and execution continues at an outer/parent level below it, the nested block MAY be explicitly closed with **`|.`** (for `|`) or **`||.`** (for `||`), either **inline at the end of the last line** (`| otherwise = expr |.`, the canonical compact form) or on the following line (`|.`). At the end of a function or outer block where no ambiguity exists, `|.` and `||.` are omitted. |
| 26 | 3. **Elimination of `where` in Favor of `||`**: |
| 27 | - The `where` keyword SHALL be removed from the language grammar. |
| 28 | - Named parallel sub-computations feeding an expression SHALL be written directly as `|| expr -> var` (or `|| expr -> target[i, j]`) beneath the expression. |
| 29 | 4. **Immutable-by-Default (`x = ...`), Explicit Mutable Variables (`mut x = ...`), and Lowercase Logical Operators (`and`, `or`, `xor`, `not`)**: |
| 30 | - A binding `x = expr` without `mut` SHALL declare an immutable local variable; reassigning an immutable variable in the same scope SHALL be rejected at compile time, unless `x` was previously declared with `mut x = expr` in that scope. |
| 31 | - Reassigning a `mut` variable (`x = new_expr`) SHALL integrate with `RMM2` (`FR-COMP-002`) by incrementing (`+1`) the new structure before decrementing (`-1`, and destroying on `0` with child cascade) the previous structure held by `x`. |
| 32 | - Word-based logical operators SHALL be lowercase (`and`, `or`, `xor`, `not`), while also remaining usable as contextual words inside natural-language operator declarations (`op ::`). |
| 33 | |
| 34 | ## Rationale |
| 35 | - Placing `|` behind `=` eliminates the only syntactic exception where function clauses omitted `=`, aligns `=` vertically under `=>` across 100% of declarations, enables inline conditional variable assignments (`x =` followed by `|`), and allows recursive decision trees (`| otherwise =` followed by nested `|`). |
| 36 | - Replacing `where` with `|| expr -> var` removes a redundant keyword and unifies Ridgun's block prefixes into a clean triad: **nothing** (sequential), **`|`** (conditional OR choice), and **`||`** (parallel AND execution). |
| 37 | - Immutable-by-default (`x = ...`) keeps mathematical expressions clean while `mut x = ...` (at declaration, followed by `x = ...` on update) makes mutability explicit to both human readers and the `RMM2` memory manager. |
| 38 | |
| 39 | ## Functional Specification |
| 40 | |
| 41 | ### 1. Case 1 — Guarded Function & Operator Clauses (`=` Before `|`) |
| 42 | ```ridgun |
| 43 | op :: clamp Int between Int and Int => Int |
| 44 | clamp x between lo and hi = |
| 45 | | x < lo = lo |
| 46 | | x > hi = hi |
| 47 | | otherwise = x |
| 48 | ``` |
| 49 | |
| 50 | ### 2. Case 2 — Conditional Local Variable Assignment (`x =` Followed by `|`) |
| 51 | ```ridgun |
| 52 | fn :: compute_invoice Int Bool => Int |
| 53 | compute_invoice base is_vip = |
| 54 | discount = |
| 55 | | is_vip = 20 |
| 56 | | base > 50 = 10 |
| 57 | | otherwise = 0 |
| 58 | base - discount |
| 59 | ``` |
| 60 | |
| 61 | ### 3. Case 3 — Deeply Nested Decision Trees (`|` Inside `| cond =` / `| otherwise =`) & Compact `|.` / `||.` Closers |
| 62 | When a nested `|` or `||` sub-tree finishes and the outer level continues below it, **`|.`** (or **`||.`**) closes the inner block and returns to the parent level. Both inline placement at the end of the last branch (`| otherwise = val |.`, canonical compact style) and next-line placement (`|.`) are valid; at the final branch of a function, no closer is needed: |
| 63 | |
| 64 | ```ridgun |
| 65 | fn :: evaluate_payment Account Customer Payment => String |
| 66 | evaluate_payment account customer payment = |
| 67 | | is_locked account = "REJECTED_ACCOUNT_LOCKED" |
| 68 | | amount payment <= 0 = "ERROR_INVALID_AMOUNT" |
| 69 | | otherwise = |
| 70 | | tier customer == "ENTERPRISE" = |
| 71 | | amount payment > 50000 = |
| 72 | | has_dual_signature payment = "APPROVED_ENTERPRISE_DUAL_SIG" |
| 73 | | tenure customer > 10 = "RISK_COMMITTEE_REVIEW" |
| 74 | | otherwise = "REJECTED_ENTERPRISE_LIMIT" |. |
| 75 | | country payment != "US" = |
| 76 | | is_whitelisted (country payment) = "APPROVED_ENTERPRISE_INTL" |
| 77 | | otherwise = "HELD_COMPLIANCE_INTL" |. |
| 78 | | otherwise = "APPROVED_ENTERPRISE_STANDARD" |. |
| 79 | | tier customer == "VIP" = |
| 80 | | amount payment > 10000 = |
| 81 | | has_2fa payment = "APPROVED_VIP_2FA" |
| 82 | | otherwise = "REQUEST_VIP_SMS" |. |
| 83 | | otherwise = "APPROVED_VIP_DIRECT" |. |
| 84 | | otherwise = |
| 85 | | amount payment > 1000 = |
| 86 | | has_2fa payment and risk payment < 30 = "APPROVED_RETAIL_2FA" |
| 87 | | otherwise = "REJECTED_RETAIL_RISK" |. |
| 88 | | otherwise = "APPROVED_RETAIL_LOW_AMOUNT" |
| 89 | ``` |
| 90 | |
| 91 | Similarly, when a parallel `||` block or a conditional `|` assignment sits in the middle of a sequential block and execution continues with subsequent statements, `||.` / `|.` marks the return to the sequential level: |
| 92 | |
| 93 | ```ridgun |
| 94 | fn :: charge_order Payment => Int |
| 95 | charge_order payment = |
| 96 | || fetch_base_fee payment -> base_fee |
| 97 | || compute_risk_score payment -> risk_score ||. |
| 98 | surcharge = |
| 99 | | risk_score > 80 = 50 |
| 100 | | otherwise = 0 |. |
| 101 | base_fee + surcharge |
| 102 | ``` |
| 103 | |
| 104 | ### 4. Case 4 — Parallel `||` DAG Blocks (Replacing `where`) |
| 105 | - **Named parallel DAG feeding a header expression (`|| expr -> var`)**: |
| 106 | ```ridgun |
| 107 | fn :: parallel_dag_calc => Int |
| 108 | parallel_dag_calc = a + b |
| 109 | || fib 10 -> a |
| 110 | || fact 6 -> b |
| 111 | ``` |
| 112 | - **Anonymous parallel tuple branches (`|| expr`)**: |
| 113 | ```ridgun |
| 114 | fn :: both_metrics Int => (Int, Int) |
| 115 | both_metrics x = |
| 116 | || fib x |
| 117 | || fact x |
| 118 | ``` |
| 119 | - **Indexed parallel slot assignment (`|| expr -> target[i, j]`)**: |
| 120 | ```ridgun |
| 121 | fn :: fill_row => Tensor |
| 122 | fill_row = m |
| 123 | || 10.0 -> m[0, 0] |
| 124 | || 20.0 -> m[0, 1] |
| 125 | ``` |
| 126 | |
| 127 | ### 5. Case 5 — Immutable (`x = ...`) vs Mutable (`mut x = ...`) Variables & `RMM2` Lifecycle |
| 128 | - **Immutable binding (`x = expr`)**: |
| 129 | - `x` is bound once (`+1` on structure assignment). Any subsequent `x = ...` in the same scope without a prior `mut x = ...` raises a compile-time error (`ErrImmutableReassign`). |
| 130 | - **Mutable declaration & reassignment (`mut x = expr`, then `x = new_expr`)**: |
| 131 | - First declaration `mut x = e1`: marks `x` as mutable in the current scope and assigns `e1` (`+1`). |
| 132 | - Subsequent update `x = e2`: |
| 133 | 1. Evaluates `e2` and increments its reference count (`+1`). |
| 134 | 2. Decrements (`-1`) the old value stored in `x` (destroying it and cascading `-1` to its children if its count reaches `0`). |
| 135 | 3. Stores `e2` into `x`. |
| 136 | *(Incrementing `e2` before decrementing the old `x` guarantees safety on self-referential updates such as `x = choose_list cond x other` or `x = x[0]`).* |
| 137 | |
| 138 | ## Acceptance Criteria |
| 139 | - AC1: `=` precedes `|` in function/operator clauses, local variable bindings, and nested `|` decision trees. |
| 140 | - AC2: `|| expr -> var` replaces `where` for parallel DAG bindings, and `where` is no longer a reserved keyword. |
| 141 | - AC3: `mut x = ...` enables subsequent `x = ...` reassignments in the same scope under `RMM2`, while reassigning a non-`mut` variable fails typechecking. |