Every block in Geno ends with the name of what it closes: end func, end if, end match. It looks old-fashioned, and that is a fair first reaction. Here is why we chose it anyway.
func count_vowels(text: String) -> Int
example "geno" -> 2
example "rhythm" -> 0
var total = 0
for ch: String in to_chars(text) do
if string_contains("aeiou", ch) then
total = total + 1
end if
end for
return total
end func
Where generated code loses its place
A model emits code one token at a time. With braces, the closing } of a loop and the closing } of a function are the same token, so a model that has lost count produces something that parses and means the wrong thing. With significant indentation, the structure lives in whitespace, which is the first thing to break when code passes through a chat interface, a diff or a copy-paste.
A named terminator fails loudly instead. end for where the parser expects end if is an error at that exact line, with the reason attached.
A stack you can ask about
Because every opener has a matching closer, Geno can tell you at any point in a partial program which blocks are still open. geno constrain exposes that directly:
$ printf 'func f(x: Int) -> Int\n example 1 -> 1\n if x > 0 then\n' \
| geno constrain
AllowedNext(keywords=['and', 'assert', ...], IDENT, INT, FLOAT, STRING, BOOL,
punct=['!=', '%', '(', ...], need_to_close=['if', 'func'])
The lists are shortened here; need_to_close is printed in full, innermost block first.
That is the foundation for constrained decoding: a generator can be steered so that it never stops while a block is still open and never closes the wrong one. Block-structure validation works today; full token-level grammar constraints are not implemented. The module is marked Research in the maturity matrix because we have not yet benchmarked it against real models.
Labels for people, too
The terminator can carry a label, which helps long functions and loops read well in review:
func fib_iterative(n: Int) -> Int
...
while i <= n do
let next = prev + curr
prev = curr
curr = next
i = i + 1
end while "count up to n"
return curr
end func fib_iterative
What it costs
More characters per block, and some keywords you cannot use as names (end, in, with, type). Editors absorb most of the typing: the VS Code extension's snippets write each block with its terminator already in place, and its indentation rules dedent as soon as you type end.