GENO

Blog/Design

No. 004

Why Geno writes end func

David IachOctober 14, 20262 min read

Abstract

Braces and indentation work well for people with editors. Generated code needs boundaries that survive truncation, reformatting and streaming, so every Geno block closes by name.

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.