Status: Normative pre-1.0 language contract, synced with Geno 0.5.0. Phases 1-4 of the roadmap are complete (modules, JSON/HTTP, dev tools, defaults, comprehensions, throw, async/await, CSV/TOML, process exec, package manager, LSP). Integer division and modulo use truncation-toward-zero semantics across all backends.
This document defines the accepted Geno 0.5 source-language contract. Where a backend or execution surface has additional constraints, those constraints are called out here or in the linked target/runtime references. Changes to this contract follow Geno's compatibility policy.
There are four contract changes from 0.4. Section 4.1.1 gives main() -> Int
executable exit semantics, from accepted proposal
0001. Section 4.2
requires a type or type alias name to be defined once per module, forbids
reusing a built-in type name, and requires the constructors of one type to have
distinct names. Section 4.6 requires the methods of one impl block to have
distinct names. Section 7.15 makes an Async[T] value run its computation at
most once, so awaiting it again produces that run's result. Everything else in
this document is unchanged from v0.4, which remains the normative
contract for the 0.4 series.
1. Overview
Geno is a programming language designed for LLM code generation. Key principles:
- Explicit block delimiters (
end func,end if) instead of braces - Explicit function signatures with local type inference
- Specification-first design with
example,requires,ensures - Immutability by default (
letvsvar) - Named parameters required for 3+ argument calls
2. Lexical Structure
2.1 Whitespace and Comments
- Whitespace (spaces, tabs, newlines) separates tokens
- Single-line comments:
// comment to end of line - Block comments:
/* multi-line comment */
2.2 Identifiers
- Variables/functions:
[a-z_][a-zA-Z0-9_]*(snake_case) - Types/constructors:
[A-Z][a-zA-Z0-9]*(PascalCase)
2.3 Literals
- Integers:
-?[0-9]+(e.g.,42,-17) - Floats:
-?[0-9]+\.[0-9]+(e.g.,3.14,-0.5) - Strings:
"..."with escapes\n,\t,\\,\" - Multi-line strings:
"""...""" - F-strings:
f"text {expr} more text"with interpolated expressions - Booleans:
true,false
2.4 Keywords
and assert async await break catch continue do else end ensures example export
false fn for func if impl import in let match not or ref requires return test
then throw trait true try type var where while with
Note:
refis a reserved keyword but is not currently implemented.breakandcontinueare supported insidewhileandforloops.
2.5 Operators
+ - * / % // Arithmetic
** // Exponentiation (right-associative)
& ^ << >> ~ // Bitwise (AND, XOR, left shift, right shift, NOT)
== != < > <= >= // Comparison
and or not // Logical
= // Assignment
-> // Arrow (return type, lambda)
|> // Pipeline
| // Pattern separator
? // Propagation (Option/Result)
Note: Bitwise OR uses the
bit_or(a, b)builtin instead of|to avoid ambiguity with the pattern separator in match expressions. Unary-binds more tightly than exponentiation in Geno, so-2 ** 2parses as(-2) ** 2.
2.6 Delimiters
( ) [ ] { } , : ; . _ ?
3. Types
3.1 Primitive Types
Int // Integer
Float // Floating point
String // Text
Bool // Boolean (true/false)
Unit // Empty tuple ()
For code that targets JavaScript, portable Int values range from
-(2^53 - 1) through 2^53 - 1. JavaScript target checks and compilation
reject integer literals and integer pattern literals outside that range;
runtime operations reject out-of-range results rather than rounding them.
Interpreter and compiled Python execution instead use the configurable
max_integer_bits limit (33,219 bits by default).
3.2 Compound Types
List[T] // Homogeneous immutable list
Map[K, V] // Immutable key-value map
Option[T] // Some(value) | None
Result[T, E] // Ok(value) | Err(error)
Async[T] // Result of an async function (resolved with await)
(T1, T2, ...) // Tuple types
3.2.1 Mutable Collection Types
Array[T] // Fixed-size mutable array (reference type)
Vec[T] // Growable mutable vector (reference type)
Set[T] // Mutable hash set (reference type)
MutableMap[K, V] // Mutable hash map (reference type)
Mutable collections are reference types: assignments share the underlying data. Use them for performance-critical or interactive code (e.g., games).
User-defined constructor values have value semantics. Binding an existing
constructor value creates a snapshot, and a with expression returns an
independent constructor value. Subsequent field assignment through a var
binding does not mutate an earlier snapshot.
3.2.2 Built-in ADT Types
These types are pre-defined and available without explicit type declarations:
type JsonValue = JsonString(value: String)
| JsonInt(value: Int)
| JsonFloat(value: Float)
| JsonBool(value: Bool)
| JsonNull
| JsonArray(items: List[JsonValue])
| JsonObject(entries: List[(String, JsonValue)])
type HttpResponse = HttpResponse(status: Int, body: String, headers: List[(String, String)])
type ProcessResult = ProcessResult(exit_code: Int, stdout: String, stderr: String)
type FileKind = FileKindFile
| FileKindDirectory
| FileKindSymlink
| FileKindOther
type FileMetadata = FileMetadata(kind: FileKind, size: Int, modified_ms: Int)
3.3 Function Types
(T1, T2) -> R // Function taking T1, T2, returning R
(Int) -> Int // Single parameter
() -> Unit // No parameters, no return value
3.4 Generic Types
List[Int]
Option[String]
Result[Int, String]
Map[String, Int]
3.5 Function Effects
Function types carry a set of semantic effects. An effect annotation follows
the return type and uses with:
@untested("host filesystem example")
func read_config(path: String) -> String with fs
return fs_read_text(path)
end func
type Callback = (String) -> Unit with io
The valid effect names in Geno 0.5 are clock, env, fs, http, io,
mutation, process, random, regex, stdin, and throw.
- Calling an effectful function propagates its effects to the caller.
- Mutating a
var, mutable field,Array,Vec,Set, orMutableMapcontributesmutation. - Throwing contributes
throw; a matchingtry/catchhandles that effect for the protected body. - Capability-gated builtins contribute the corresponding semantic effect.
- When a function has no annotation, its effects are inferred.
- When an annotation is present, every inferred effect must be included in the declared set. A pure function can be used where a more-effectful callback is accepted, but not the reverse.
Effects are a static type-system property. Capabilities are runtime authority;
declaring with fs does not grant the fs capability.
4. Declarations
4.1 Function Definitions
func <name>(<params>) -> <return_type>
<specs>
<body>
end func [<name>]
Example:
func add(x: Int, y: Int) -> Int
requires x >= 0
requires y >= 0
ensures result >= x
example 2, 3 -> 5
example 0, 0 -> 0
return x + y
end func add
The closing end func may optionally include the function name for clarity.
4.1.1 Entrypoint Results and Imports
Entrypoint selection. Only a main declared in the selected entry program
is executable entrypoint state. A function named main in an imported module is
an ordinary function, and import discovery never changes entrypoint ownership.
An entrypoint's result is classified from its resolved static return annotation, including aliases imported into the entry program.
Execution form. An async main is awaited exactly once before its result is
classified. The language also permits await directly inside a synchronous
main; such an entrypoint is an asynchronous execution form even without the
async modifier, and hosts must lower and await it exactly once. A synchronous
main that merely returns an async value without awaiting it is not
implicitly awaited; it follows the ordinary rule for its declared return type.
Result normalization at an executable boundary. In the 0.5 language series:
main() -> Unitproduces status 0.main() -> Intproducesresult % 256, in the inclusive range 0 through 255, and produces no implicit stdout. Modulo is mathematical, so-1normalizes to 255 and258normalizes to 2. Normalization happens once, at the host boundary, after the language value has been produced.- Other accepted
mainreturn types keep their v0.4 surface behavior and status 0. The primary CLI and standalone artifacts keep displaying those results; the legacy self-hosted command adapter keeps omitting the inner result. - An entry program with no
mainand no other target-defined lifecycle entrypoint is a successful no-op: status 0 and no implicit output. App-mode programs (section 9.12) are excluded and keep their own behavior.
A type error or an uncaught runtime error is not a normal result; existing diagnostics and nonzero failure behavior apply.
Output and diagnostics. All output accepted before a normal main result is
delivered before the host terminates. A normal nonzero integer result is not
wrapped as a runtime exception and emits no traceback or runtime diagnostic.
Genuine uncaught errors still produce the target's Geno-facing diagnostic and a
nonzero process status.
Embedding and imports. Embedding APIs and generic sandbox result channels
return the raw Geno value without normalizing it and without terminating their
host: an embedded integer result of 258 is returned as 258, not 2. geno run --json is an executable CLI boundary rather than an embedding API: its envelope
carries the raw, unnormalized value while the command exits with the normalized
status. Importing generated Python or Node ESM does not invoke main or exit
the importing process. Browser-targeted ESM has no process boundary and remains
an auto-start artifact that displays an integer result.
This section is the entrypoint contract change from v0.4; sections 4.2, 4.6 and 7.15 carry the other three. See accepted proposal 0001 for this section's rationale, migration path and full compatibility analysis.
4.2 Type Definitions
type <Name>[<type_params>] = <variants>
Variants may be separated across lines, and an optional leading | may
precede the first variant:
type Tx =
| Deposit(amount: Int)
| Withdraw(amount: Int)
Examples:
// Simple enum
type Color = Red | Green | Blue
// Generic type with fields
type Maybe[T] = Just(value: T) | Nothing
// Recursive type
type Stack[T] = Push(top: T, rest: Stack[T]) | Empty
// Record-like type
type Point = Point(x: Int, y: Int)
A type or type alias name may be defined once per module and may not reuse a
built-in type name (Int, Float, Bool, String, Unit, List, Array,
Map, MutableMap, Vec, Set, Tuple, Async, Option, Result,
JsonValue, HttpRequest, HttpResponse, ProcessResult, FileKind,
FileMetadata). The constructors of one type must have distinct names.
Target-aware checks reject record field names that the selected backend cannot
represent safely. Python targets reject Python keywords and dunder fields.
JavaScript targets reject JavaScript keywords, _tag, _withGenoFormatter,
__proto__, constructor, and prototype. Without a declared target,
geno check, geno test, and editor diagnostics validate Python lowering when
the selected entrypoint module defines main, matching the default geno run.
Libraries whose entrypoint has no main remain permissive; a main in an
imported module does not change their role. Targetless embedding API
geno.check() remains an interpreter-oriented language check and makes no
backend-lowering guarantee unless a target is explicitly selected.
4.3 Parameters
Parameters require explicit type annotations:
func greet(name: String, times: Int) -> String
All parameters are passed by value. Geno emphasizes immutability, so there are no reference parameters.
4.3.1 Default Parameter Values
Parameters may have default values. Required parameters must precede optional ones:
func connect(host: String, port: Int = 8080, timeout: Int = 30) -> String
example "localhost" -> "localhost:8080"
return host + ":" + to_string(port)
end func
Callers may omit trailing optional arguments or override them positionally or by name:
connect("localhost") // port=8080, timeout=30
connect("localhost", 3000) // port=3000, timeout=30
connect(host: "localhost", timeout: 60) // port=8080, timeout=60
4.3.2 Async Functions
Prefixing a function with async makes it return Async[T]:
async func fetch(url: String) -> String
return "data"
end func
- The return type annotation is the inner type
T, notAsync[T] - The actual return type is
Async[T]; useawaitto resolve it - Async functions are exempt from
exampleclause requirements awaitis valid insideasync funcand insidemain()
4.4 Type Aliases
Type aliases create a new name for an existing type:
type Coordinate = (Int, Int)
type Predicate = (Int) -> Bool
type StringList = List[String]
Generic type aliases are supported:
type Pair[T] = (T, T)
type Mapping[K, V] = Map[K, V]
4.5 Trait Definitions
Traits define a set of method signatures that types can implement:
trait Describable
func describe(self: Self) -> String
end trait
The Self type in the first parameter refers to the implementing type.
Traits can declare multiple methods.
4.6 Impl Blocks
Impl blocks provide concrete method implementations for a trait on a specific type:
type Circle = Circle(radius: Float)
type Square = Square(side: Float)
impl Describable for Circle
func describe(self: Circle) -> String
example Circle(1.0) -> "Circle"
return "Circle"
end func
end impl
impl Describable for Square
func describe(self: Square) -> String
example Square(1.0) -> "Square"
return "Square"
end func
end impl
Trait methods are called like regular functions with the value as the first argument:
let c: Circle = Circle(3.14)
let desc: String = describe(c) // "Circle"
Dispatch works with multi-constructor ADTs:
type Shape = Circle(r: Float) | Square(s: Float)
impl Describable for Shape
func describe(self: Shape) -> String
example Circle(1.0) -> "circle"
match self with
| Circle(r) -> return "circle"
| Square(s) -> return "square"
end match
end func
end impl
Duplicate impl blocks for the same trait-type pair are rejected at type-check time. Within one impl block each method name may be defined once; a second definition of the same name is rejected at that definition rather than silently replacing the first.
4.7 Module Constants
A module-level let binds a compile-time constant that every definition in the
same module can read:
let usage: String = "usage: prog [options]"
let numerals = ["M", "D", "C"]
func help_text() -> String
example () -> "usage: prog [options]"
return usage
end func
Rules:
- The initializer must be a literal: an
Int,Float,String, orBoolliteral, a negated numeric literal, or a list or tuple whose elements are themselves literals. A call, an operator expression, a reference to another binding, or any other computed form is rejected at parse time. - The type annotation is optional when the literal determines the type. An
Intliteral bound to a declaredFloatis promoted, as in a locallet. - Constants are immutable;
varis not allowed at module level. - Names are
snake_case. A leading-uppercase name is a type identifier and is rejected. - Tuple destructuring is not available; bind each name with its own
let. - Constants are module-private. They cannot be exported and are not visible to importing modules, which must call an exported function instead.
- A constant name may not collide with a function, another constant, a trait method dispatcher, or a reserved runtime name in the same module.
- Backend validation also rejects distinct names that compile to the same
identifier as a constant. For example,
classandclass_kwboth becomeclass_kwin Python and JavaScript. This includes imported functions and local bindings; a local with the same source name may still shadow a constant.
5. Specifications
5.1 Requires Clauses
Preconditions that must be true when function is called:
requires x > 0
requires length(xs) > 0
requires a != b
5.2 Ensures Clauses
Postconditions that must be true when function returns:
ensures result >= 0
ensures result == x + y
ensures length(result) <= length(input)
The special identifier result refers to the return value.
5.3 Example Clauses
Concrete input-output pairs (required for non-main functions):
example 5 -> 120 // Single argument
example 2, 3 -> 5 // Multiple arguments
example [1, 2, 3] -> 6 // List argument
example "hello" -> 5 // String argument
example () -> "usage: prog" // Zero-argument function
example -> "usage: prog" // Zero-argument function (equivalent)
6. Statements
6.1 Let Binding (Immutable)
let x: Int = 5
let name: String = "Alice"
let items: List[Int] = [1, 2, 3]
6.1.1 Tuple Destructuring
Tuples can be destructured in let and var bindings:
let (x, y): (Int, Int) = get_position()
var (a, b): (String, Int) = ("hello", 42)
6.2 Var Binding (Mutable)
var count: Int = 0
var buffer: String = ""
count = count + 1 // Assignment allowed
6.3 Assignment
Variable reassignment (for var bindings):
x = x + 1
Index assignment on mutable collections (Array, Vec, MutableMap):
var arr: Array[Int] = array_new(3, 0)
arr[0] = 42
arr[1] = 99
var m: MutableMap[String, Int] = mutable_map_new()
m["key"] = 10
Field assignment on constructor values:
var p: Point = Point(x: 0, y: 0)
p.x = 10
p.y = 20
6.4 If Statement
if <condition> then
<then_body>
else
<else_body>
end if
The else clause is optional:
if x > 0 then
print("positive")
end if
Chained conditions with else if:
if x > 10 then
return "large"
else if x > 0 then
return "positive"
else
return "non-positive"
end if
6.5 While Loop
while <condition> do
<body>
end while
break exits the loop immediately. continue skips to the next iteration:
while x < 10 do
if x == 5 then
break
end if
x = x + 1
end while
6.6 For Loop
for <var>: <type> in <iterable> do
<body>
end for
break and continue are also supported in for loops:
for x: Int in [1, 2, 3] do
if x == 2 then
continue
end if
print(x)
end for
6.7 Match Statement
match <expr> with
| <pattern1> -> <body1>
| <pattern2> -> <body2>
| _ -> <default_body>
end match
Example:
match opt with
| Some(x) -> return x
| None -> return 0
end match
6.8 Return Statement
return <expr>
return () // Return unit
6.9 Try/Catch Statement
try
<body>
catch <var>: <Type>
<handler>
end try
The catch clause binds either a String (for runtime errors and thrown strings)
or a user-defined type (for structured errors):
// Catching runtime errors as strings
try
let result: Int = divide(10, 0)
catch e: String
print(f"Error: {e}")
end try
// Catching structured error types
type AppError = AppError(message: String)
try
throw AppError("bad input")
catch e: AppError
print(e.message)
end try
Type matching rules:
catch e: Stringcatches runtime errors andthrow "string"valuescatch e: MyTypecatches onlythrowvalues whose constructor belongs toMyType- Unmatched types propagate to the next enclosing
try/catch
Try/catch can be nested and used inside loops.
6.10 Import Statement
import ModuleName
Imports make definitions from another module available in the current scope. Module resolution is in-memory only — there is no filesystem lookup.
7. Expressions
7.1 Literals
42 // Int
3.14 // Float
"hello" // String
f"Hi {name}" // F-string (interpolated)
true // Bool
[1, 2, 3] // List
(1, "a", true) // Tuple
() // Unit
7.1.1 F-String Interpolation
F-strings allow embedding expressions inside string literals:
let name: String = "Alice"
let age: Int = 30
let greeting: String = f"Hello {name}, you are {age} years old"
Expressions inside {...} are evaluated and converted to strings. Escape
sequences (\n, \t, \\, \") are supported in the text portions. Nested
braces and string literals inside expressions are not allowed.
7.2 Arithmetic Operators
x + y // Addition
x - y // Subtraction
x * y // Multiplication
x / y // Division (truncation-toward-zero for Int, float for Float)
x % y // Modulo (truncation-toward-zero: sign follows dividend)
x ** y // Exponentiation (right-associative: 2 ** 3 ** 2 == 2 ** 9)
-x // Negation
The divide builtin follows the operand types: integer operands truncate
toward zero, while Float operands retain the fractional result.
7.2.1 Bitwise Operators
x & y // Bitwise AND (Int only)
x ^ y // Bitwise XOR (Int only)
x << y // Left shift (Int only)
x >> y // Right shift (Int only)
~x // Bitwise NOT (unary, Int only)
bit_or(x, y) // Bitwise OR (builtin function, Int only)
Shift operators bind tighter than &, and & binds tighter than ^. Bitwise
expressions bind tighter than comparisons, so a & b == 0 parses as
(a & b) == 0.
7.3 Comparison Operators
x == y // Equal
x != y // Not equal
x < y // Less than
x > y // Greater than
x <= y // Less or equal
x >= y // Greater or equal
Equality between an Int and a Float is numeric, so 2 == 2.0 is true.
Structural equality applies the same rule recursively within compound values.
Comparisons are non-associative: a < b < c is invalid. Write
a < b and b < c instead.
7.4 Logical Operators
a and b // Logical AND
a or b // Logical OR
not a // Logical NOT
7.5 Function Calls
// Positional arguments (up to 2 params)
add(1, 2)
length(items)
// Named arguments (required for 3+ params)
make_point(x: 10, y: 20, z: 30)
configure(host: "localhost", port: 8080, timeout: 30)
7.6 Lambda Expressions
Inline lambdas (single expression):
fn(x: Int) -> x * 2
fn(a: Int, b: Int) -> a + b
fn() -> 42
Block lambdas (multiple statements):
fn(x: Int) do
if x > 0 then
return x * 2
else
return 0
end if
end fn
Block lambdas support all statement forms (if, while, for, match, etc.)
and their return type is inferred from return statements.
7.7 Pipeline Expressions
Left-to-right data flow:
[1, 2, 3, 4, 5]
|> filter(_, fn(x: Int) -> x % 2 == 0)
|> map(_, fn(x: Int) -> x * x)
|> fold(_, 0, fn(acc: Int, x: Int) -> acc + x)
The _ placeholder indicates where the previous value is inserted.
7.8 Constructor Calls
Some(42)
None
Point(x: 10, y: 20)
Cons(1, Nil)
7.9 Index Access
items[0] // First element
items[-1] // Last element (negative indexing)
items[-2] // Second to last
matrix[i][j]
Negative indices count from the end: -1 is the last element, -2 is
second-to-last, etc. Out-of-bounds indices (including negative indices that
exceed the length) raise a runtime error.
Map indexing is also partial: m[key] returns the value or raises a Geno
runtime error when the key is absent. Use map_get(m, key) when absence is
expected and should be represented as Option[V].
7.10 Field Access
point.x
person.name
7.11 Match Expression
let result: Int = match opt with
| Some(x) -> x
| None -> 0
end match
Match arms support optional guard conditions with when:
let label: String = match value with
| x when x > 0 -> "positive"
| x when x < 0 -> "negative"
| _ -> "zero"
end match
Guards are evaluated after the pattern matches. If the guard is false, the next arm is tried.
7.12 With Expression
Functional record update — creates a modified copy of a value:
let moved: Player = player with (x: player.x + 1, y: player.y + 2)
The original value is unchanged. Only the specified fields are updated in the copy.
7.13 List Comprehensions
Concise syntax for transforming and filtering lists:
let doubled: List[Int] = [x * 2 for x: Int in numbers]
let evens: List[Int] = [x for x: Int in numbers if x % 2 == 0]
Syntax: [element_expr for var: Type in iterable] or
[element_expr for var: Type in iterable if condition].
The iterable must be a List[T]. The condition (if present) must be Bool.
The result type is List[U] where U is the type of element_expr.
7.14 Throw Expression
Throws a value as an error, transferring control to the nearest matching
catch clause:
throw "something went wrong" // throws a String
throw AppError("bad input") // throws a user-defined type
Only String and user-defined (ADT) types may be thrown. throw never returns
a value — its type is compatible with any context.
7.15 Await Expression
Resolves an Async[T] value, producing the inner T:
let data: String = await fetch("https://example.com")
await may only appear inside async func or main(). In the interpreter,
await executes the deferred computation synchronously. In compiled output,
it maps to the target language's native await.
An Async[T] value runs its computation at most once. Awaiting the same value
again, including while an earlier await of it is still in progress, produces
the result of that one run; if the run threw, every await of the value throws
the same error:
let a = load(1) // load is an async func
let x = await a // runs load once
let y = await a // reuses that result; load does not run again
7.16 Propagation Operator (?)
The ? operator unwraps Option or Result values, returning early from the
enclosing function on failure:
func get_name(data: Option[String]) -> Option[String]
let name: String = data? // returns None if data is None
return Some(name)
end func
For Option[T]:
- If the value is
Some(x), unwraps tox - If the value is
None, immediately returnsNonefrom the enclosing function
For Result[T, E]:
- If the value is
Ok(x), unwraps tox - If the value is
Err(e), immediately returnsErr(e)from the enclosing function
7.17 Typed Holes
Typed holes are placeholders for expressions that are not yet implemented. They let the type checker verify the rest of the program while marking gaps:
let x: Int = ?todo: Int
let y: String = ?name: String where length(name) > 0
Syntax: ?name: Type or ?name: Type where constraint.
At runtime, evaluating a typed hole raises an error. They are intended for incremental development and LLM-assisted code generation.
8. Patterns
8.1 Variable Pattern
Binds matched value to a name:
| x -> ...
| value -> ...
8.2 Wildcard Pattern
Matches anything, discards value:
| _ -> ...
8.3 Literal Pattern
Matches specific value:
| 0 -> "zero"
| 1 -> "one"
| "hello" -> "greeting"
8.4 Constructor Pattern
Matches and destructures:
| Some(x) -> x
| None -> default
| Cons(head, tail) -> head
| Point(x, y) -> x + y
8.5 List Pattern
| [] -> "empty"
| [x] -> "single"
| [x, y] -> "pair"
8.6 Tuple Pattern
Matches and destructures a tuple. The pattern's arity must equal the tuple type's arity, and tuple patterns need at least two elements:
| (a, b) -> a + b
| (x, _, z) -> x + z
| Some((key, value)) -> key // nested inside a constructor pattern
Because every value of a tuple type has that type's arity, a single tuple pattern whose elements are all variables or wildcards is exhaustive and needs no default arm.
8.7 Rest Pattern (Spread)
Matches the remaining elements of a list:
| [head, ...tail] -> head // tail binds to remaining elements
| [first, second, ...rest] -> first + second
| [x, ...] -> x // anonymous rest (discards remainder)
The rest pattern must appear at most once per list pattern. Elements after the rest pattern match from the end of the list.
9. Built-in Functions
9.1 List Operations
length(list) // -> Int
head(list) // -> T
tail(list) // -> List[T]
append(list, item) // -> List[T]
concat(list1, list2) // -> List[T]
reverse(list) // -> List[T]
map(list, fn) // -> List[U]
filter(list, predicate) // -> List[T]
fold(list: List[T], initial: U, reducer: (U, T) -> U) -> U
9.2 Option Operations
is_some(opt) // -> Bool
is_none(opt) // -> Bool
unwrap(opt) // -> T (panics if None)
unwrap_or(opt, default) // -> T
9.3 String Operations
substring(str, start, end) // -> String
split(str, delimiter) // -> List[String]
trim(str) // -> String
join(list, separator) // -> String
split_once(str, delimiter) // -> Option[(String, String)]
starts_with(str, prefix) // -> Bool
ends_with(str, suffix) // -> Bool
to_lower(str) // -> String
to_upper(str) // -> String
replace(str, old, new) // -> String (replaces all occurrences)
to_chars(str) // -> List[String] (list of single characters)
sort_strings(list) // -> List[String] (sorted alphabetically)
Note:
length()accepts lists, strings, and arrays.concat()operates on lists only. String concatenation uses the+operator.
9.4 I/O
Note:
print(value) writes the canonical Geno representation followed by a newline.
A top-level String is written without surrounding quotes; strings nested in
collections or constructors keep the quoted canonical representation used by
to_string.
9.5 Type Conversions
to_string(value) // -> String (converts any value)
parse_int(s) // -> Option[Int] (trims then parses -?[0-9]+)
parse_float(s) // -> Option[Float] (parses string to float)
float_to_int(f) // -> Int (truncates decimal)
int_to_float(n) // -> Float
char_code(s) // -> Int (Unicode code point of first character)
from_char_code(n) // -> String (string from Unicode code point)
9.6 Extended List Operations
take_while(list, predicate) // -> List[T] (takes elements while predicate is true)
all(list, predicate) // -> Bool (true if all elements satisfy predicate)
is_sorted(list) // -> Bool (true if list is sorted ascending)
is_permutation(list1, list2) // -> Bool (true if lists have same elements)
9.7 Extended String Operations
is_numeric_string(str) // -> Bool (true if trimmed string matches -?[0-9]+)
9.8 Math Helpers
add(a, b) // -> Int/Float (addition)
subtract(a, b) // -> Int/Float (subtraction)
multiply(a, b) // -> Int/Float (multiplication)
divide(a, b) // -> Int/Float (division, raises on div by zero)
sqrt(n) // -> Float (square root)
abs(n) // -> Int/Float (absolute value)
square(n) // -> Int/Float (n * n)
max(a, b) // -> Int/Float (maximum of two values)
floor(x) // -> Int (round down)
ceil(x) // -> Int (round up)
round(x) // -> Int (floor(x + 0.5); .5 rounds toward positive infinity)
is_positive(n) // -> Bool (n > 0)
bit_or(a, b) // -> Int (bitwise OR)
9.9 Map Operations
map_insert(map, key, value) // -> Map[K, V] (returns new map with key-value added)
map_get(map, key) // -> Option[V] (returns Some(value) or None)
Maps preserve insertion order. Updating an existing key changes its value
without moving that key; inserting a new key appends it. Direct map[key]
access is partial, while map_get is the total lookup operation.
9.9a Set Operations
Set[T] is a mutable reference type. set_add and set_remove mutate in
place and return Unit.
set_new() // -> Set[T] (create empty mutable set)
set_from_list(list) // -> Set[T] (create set from list, deduplicating)
set_add(set, item) // -> Unit (add item in place)
set_remove(set, item) // -> Unit (remove item in place)
set_contains(set, item) // -> Bool
set_size(set) // -> Int
set_to_list(set) // -> List[T] (sorted)
set_union(a, b) // -> Set[T] (returns new set)
set_intersection(a, b) // -> Set[T] (returns new set)
9.9b Range and Sort
range(start, end) // -> List[Int] (start inclusive, end exclusive, step 1)
range(start, end, step) // -> List[Int] (with custom step; step can be negative)
sort(list, comparator) // -> List[T] (sort with (T, T) -> Int comparator)
sort_by(list, key_fn) // -> List[T] (sort by key extraction function)
9.9.1 Array Operations (mutable, fixed-size)
array_new(size, default) // -> Array[T] (create array of given size)
array_from_list(list) // -> Array[T] (create array from list)
array_get(array, index) // -> T (read element, or use arr[i])
array_set(array, index, value) // -> Unit (mutate in place)
array_length(array) // -> Int
array_to_list(array) // -> List[T]
array_copy(array) // -> Array[T] (shallow copy)
array_fill(array, value) // -> Unit (fill all elements)
9.9.2 Vec Operations (mutable, growable)
vec_new() // -> Vec[T] (create empty vec)
vec_from_list(list) // -> Vec[T] (create vec from list)
vec_push(vec, item) // -> Unit (append in place)
vec_pop(vec) // -> Option[T] (remove and return last, or None if empty)
vec_get(vec, index) // -> T (read element)
vec_set(vec, index, value) // -> Unit (mutate in place)
vec_length(vec) // -> Int
vec_to_list(vec) // -> List[T]
9.9.3 MutableMap Operations
mutable_map_new() // -> MutableMap[K, V]
mutable_map_set(map, key, value) // -> Unit (insert or update)
mutable_map_get(map, key) // -> Option[V]
mutable_map_contains(map, key) // -> Bool
mutable_map_delete(map, key) // -> Unit
mutable_map_keys(map) // -> List[K]
mutable_map_size(map) // -> Int
9.10 CSV/TOML Parsing
Always-available builtins for parsing structured text formats.
csv_parse(text) // -> List[List[String]]
csv_parse_with_headers(text) // -> List[Map[String, String]]
toml_parse(text) // -> Result[JsonValue, String]
csv_parse splits text into rows and columns. csv_parse_with_headers uses
the first row as keys, returning a list of maps. Both handle quoted fields with
embedded commas and newlines.
toml_parse converts TOML text to the existing JsonValue ADT. Returns
Err(message) on invalid input or unsupported types (e.g., TOML datetimes).
In JS compiled output, toml_parse returns Err("TOML parsing not available in JS target").
9.11 Capability-Gated Builtins
The following builtins require explicit capability grants (via --cap on the
CLI or the embedding API). They are unavailable by default.
// Print (requires "print")
print(value) // -> Unit
// Clock (requires "clock")
clock_now() // -> Int (Unix timestamp in seconds)
clock_format(timestamp: Float, fmt: String) // -> String (strftime format)
clock_parse(text: String, fmt: String) // -> Option[Float] (parse to timestamp)
clock_elapsed(start: Float, end: Float) // -> Float (seconds between timestamps)
sleep_ms(ms) // -> Unit
// Random (requires "random")
random_int(lo, hi) // -> Int
random_float() // -> Float
// File system (requires "fs")
fs_read_text(path) // -> String
fs_write_text(path, content) // -> Unit
fs_list_dir(path) // -> Result[List[String], String]
fs_exists(path) // -> Bool
fs_metadata(path) // -> Result[FileMetadata, String] (follows final link)
fs_symlink_metadata(path) // -> Result[FileMetadata, String] (does not follow final link)
fs_canonicalize(path) // -> Result[String, String] (existing absolute real path)
// HTTP (requires "http")
http_fetch(url) // -> String
http_post(url, body) // -> String
http_request(method, url, headers, body) // -> Result[HttpResponse, String]
// Regex (requires "regex")
regex_match(pattern, text) // -> Option[String] (first match)
regex_find_all(pattern, text) // -> List[String] (all matches)
regex_replace(pattern, repl, text) // -> String
// Environment variables (requires "env")
env_get(name) // -> Option[String]
env_get_or(name, default) // -> String
// Process execution (requires "process")
exec(command) // -> Result[ProcessResult, String]
exec_with_input(command, stdin) // -> Result[ProcessResult, String]
spawn(program, args) // -> Result[ProcessResult, String]
spawn_with_input(program, args, stdin) // -> Result[ProcessResult, String]
// Standard input (requires "stdin")
stdin_read_all() // -> String
ProcessResult, HttpResponse, FileKind, and FileMetadata are built-in
types (see 3.2.2). Filesystem metadata reports the
host byte-size field and the modification time in whole Unix milliseconds,
rounded down. Canonical paths use / separators on every host. Ordinary OS and
configured-root policy failures return Err; capability and portable resource
violations remain runtime errors. The browser target does not provide these
filesystem operations.
Process commands are split via shlex.split (Python) and run with a 30-second
timeout. In JS compiled output, exec returns Err("process execution not available in browser context").
Across the Python interpreter, compiled Python, and compiled Node targets,
http_fetch, http_post, and http_request require only the http
capability. http_post sends its UTF-8 body as application/json.
http_request accepts and returns ordered List[(String, String)] header
pairs, including duplicate response headers.
Every received HTTP status is a response: http_fetch and http_post return
its body, while http_request returns Ok(HttpResponse). Scheme, transport,
redirect-policy, and network-policy failures throw from http_fetch and
http_post and return Err from http_request. Responses are decoded as
UTF-8 and streamed to the runtime collection limit.
Only http and https URLs are allowed. Resolved loopback, private,
link-local, multicast, reserved, and unspecified addresses are denied by
default; every redirect is revalidated, at most ten redirects are followed,
and all caller headers are removed on a cross-origin redirect. Trusted hosts
may opt into private targets with GENO_HTTP_ALLOW_PRIVATE=1. Compiled Node's
internal child bridge receives request data over stdin and does not grant the
Geno program the process capability. Empty bodies on GET and HEAD are
omitted.
Note: Regex patterns are limited to 1000 characters for ReDoS mitigation. Clock timestamps must be non-negative.
9.12 App Mode (Interactive/Game Programs)
Programs that define init, update, and render functions (and no main)
run in app mode -- a game-loop style execution model. In app mode, all
user-defined functions are exempt from the example clause requirement.
The lifecycle functions have these signatures:
func init() -> State // Initial state
func update(state: State) -> State // Called each frame
func render(state: State) -> Unit // Draw the frame
Where State is a user-defined type.
The following always-available builtins support graphics and input. In interpreter mode they are no-ops/stubs; in compiled JS output they drive a canvas:
// Graphics
clear_screen(color) // -> Unit
draw_rect(x, y, width, height, color) // -> Unit
draw_rect_outline(x, y, width, height, color) // -> Unit
draw_circle(x, y, radius, color) // -> Unit
draw_line(x1, y1, x2, y2, color) // -> Unit
draw_text(text, x, y, size, color) // -> Unit
screen_width() // -> Int (default 800)
screen_height() // -> Int (default 600)
// Keyboard input
is_key_down(key) // -> Bool
is_key_pressed(key) // -> Bool
// Mouse input
mouse_x() // -> Int
mouse_y() // -> Int
is_mouse_down() // -> Bool
is_mouse_clicked() // -> Bool
// Text input
get_text_input() // -> String
clear_text_input() // -> Unit
10. Type Checking
10.1 Type Inference
Types are inferred for:
letandvarinitializers when no explicit annotation is present- Lambda return types (parameter types required)
- Generic type parameters
Function parameters and function return values require explicit type
annotations. Local let and var annotations are optional when the initializer
has an unambiguous type; explicit annotations are still accepted and checked.
10.2 Exhaustiveness Checking
Match expressions must cover all cases:
// Error: non-exhaustive, missing None
match opt with
| Some(x) -> x
end match
// OK: wildcard covers remaining cases
match opt with
| Some(x) -> x
| _ -> 0
end match
10.3 Return Path Analysis
Functions must return on all paths:
// Error: else branch doesn't return
func bad(x: Int) -> Int
if x > 0 then
return 1
else
let y: Int = 0 // No return!
end if
end func
11. Named Arguments Rule
Named arguments are required when a function has 3 or more required parameters,
or when a call supplies 3 or more arguments. The three-argument
range(start, end, step) form is the positional exception:
// Definition
func make_rect(x: Int, y: Int, width: Int, height: Int) -> Rect
...
end func
// Call - MUST use named arguments
make_rect(x: 0, y: 0, width: 100, height: 50)
// Error: positional arguments not allowed for 3+ params
make_rect(0, 0, 100, 50)
This prevents argument ordering bugs common in LLM-generated code.
12. Example Program
type Tree[T] = Node(value: T, left: Tree[T], right: Tree[T]) | Leaf
func sum_tree(tree: Tree[Int]) -> Int
requires true
ensures result >= 0
example Leaf -> 0
example Node(5, Leaf, Leaf) -> 5
match tree with
| Leaf -> return 0
| Node(value, left, right) ->
let left_sum: Int = sum_tree(left)
let right_sum: Int = sum_tree(right)
return value + left_sum + right_sum
end match
end func sum_tree
func main() -> Unit
let tree: Tree[Int] = Node(
10,
Node(5, Leaf, Leaf),
Node(15, Leaf, Leaf)
)
let total: Int = sum_tree(tree)
print(total) // 30
end func main
13. Compatibility
Geno remains pre-1.0, but accepted programs are not treated as disposable.
- Patch releases do not intentionally break programs accepted by the current versioned conformance corpus.
- A pre-1.0 minor release may change the contract only with an accepted design proposal, a changelog migration note, and an explicit conformance-corpus update.
- Stable features receive at least one minor release of deprecation before removal when security or correctness does not require an immediate change.
- Published diagnostic codes are never repurposed. A phase fallback may become a more specific code in a later release.
- Every release runs the current and retained previous-minor conformance corpora.
The complete policy, including the status of tooling, standard-library, and embedding surfaces, is in Compatibility and Stability.
14. Appendix: Grammar Summary
program = (definition | import_stmt)* ;
definition = func_def | type_def | type_alias | trait_def | impl_def
| const_def ;
import_stmt = "import" TYPE_IDENT ;
func_def = "async"? "func" IDENT "(" params? ")" "->" type effects?
spec_clause*
statement*
"end" "func" IDENT? ;
type_def = "type" TYPE_IDENT type_params? "=" "|"? variants ;
type_alias = "type" TYPE_IDENT type_params? "=" type ;
trait_def = "trait" TYPE_IDENT
trait_method*
"end" "trait" ;
trait_method = "func" IDENT "(" params ")" "->" type effects? ;
impl_def = "impl" TYPE_IDENT "for" TYPE_IDENT
func_def*
"end" "impl" ;
const_def = "let" IDENT (":" type)? "=" const_expr ;
const_expr = INTEGER | FLOAT | STRING | "true" | "false"
| "-" (INTEGER | FLOAT)
| "[" (const_expr ("," const_expr)*)? "]"
| "(" const_expr ("," const_expr)+ ")" ;
params = param ("," param)* ;
param = IDENT ":" type ("=" expr)? ;
type = simple_type | func_type | tuple_type ;
simple_type = TYPE_IDENT type_args? ;
func_type = "(" types? ")" "->" type effects? ;
effects = "with" effect_name ("," effect_name)* ;
effect_name = IDENT | "throw" ;
tuple_type = "(" types ")" ;
type_args = "[" types "]" ;
types = type ("," type)* ;
spec_clause = requires | ensures | example ;
requires = "requires" expr ;
ensures = "ensures" expr ;
example = "example" exprs? "->" expr ;
statement = let_stmt | var_stmt | assign_stmt | index_assign | field_assign
| if_stmt | while_stmt | for_stmt
| match_stmt | return_stmt | try_stmt
| break_stmt | continue_stmt | expr_stmt ;
index_assign = expr "[" expr "]" "=" expr ;
field_assign = expr "." IDENT "=" expr ;
break_stmt = "break" ;
continue_stmt = "continue" ;
let_stmt = "let" (IDENT | tuple_destruct) ":" type "=" expr ;
var_stmt = "var" (IDENT | tuple_destruct) ":" type "=" expr ;
tuple_destruct = "(" IDENT ("," IDENT)+ ")" ;
assign_stmt = lvalue "=" expr ;
if_stmt = "if" expr "then" statement* ("else" statement*)? "end" "if" ;
while_stmt = "while" expr "do" statement* "end" "while" ;
for_stmt = "for" IDENT ":" type "in" expr "do" statement* "end" "for" ;
match_stmt = "match" expr "with" match_arm+ "end" "match" ;
return_stmt = "return" expr? ;
try_stmt = "try" statement* "catch" IDENT ":" type statement* "end" "try" ;
match_arm = "|" pattern ("when" expr)? "->" statement+ ;
pattern = wildcard | var_pat | ctor_pat | literal_pat | list_pat | tuple_pat ;
wildcard = "_" ;
var_pat = IDENT ;
ctor_pat = TYPE_IDENT ("(" patterns? ")")? ;
literal_pat = INTEGER | FLOAT | STRING | "true" | "false" ;
list_pat = "[" pattern_elems? "]" ;
tuple_pat = "(" pattern "," pattern ("," pattern)* ")" ;
pattern_elems = pattern_elem ("," pattern_elem)* ;
pattern_elem = "..." IDENT? | pattern ;
expr = or_expr ;
or_expr = and_expr ("or" and_expr)* ;
and_expr = not_expr ("and" not_expr)* ;
not_expr = "not" not_expr | cmp_expr ;
cmp_expr = bxor_expr (("==" | "!=" | "<" | ">" | "<=" | ">=") bxor_expr)? ; // non-associative
bxor_expr = band_expr ("^" band_expr)* ;
band_expr = shift_expr ("&" shift_expr)* ;
shift_expr = add_expr (("<<" | ">>") add_expr)* ;
add_expr = mul_expr (("+" | "-") mul_expr)* ;
mul_expr = exp_expr (("*" | "/" | "%") exp_expr)* ;
exp_expr = unary_expr ("**" exp_expr)? ; // right-associative
unary_expr = ("-" | "~") unary_expr | postfix_expr ; // binds before **
postfix_expr = primary_expr (call | index | field | pipeline | with_expr | "?")* ;
call = "(" args? ")" ;
index = "[" expr "]" ;
field = "." IDENT ;
pipeline = "|>" expr ;
with_expr = "with" "(" named_fields ")" ;
named_fields = IDENT ":" expr ("," IDENT ":" expr)* ;
primary_expr = INTEGER | FLOAT | STRING | fstring | "true" | "false"
| IDENT | TYPE_IDENT
| list_lit | list_comp | tuple_lit | lambda
| throw_expr | await_expr | typed_hole | "(" expr ")" ;
list_lit = "[" exprs? "]" ;
list_comp = "[" expr "for" IDENT ":" type "in" expr ("if" expr)? "]" ;
throw_expr = "throw" expr ;
await_expr = "await" expr ;
typed_hole = "?" IDENT ":" type ("where" expr)? ;
tuple_lit = "(" exprs ")" ;
lambda = "fn" "(" params? ")" "->" expr
| "fn" "(" params? ")" "do" statement* "end" "fn" ;
fstring = "f\"" (text | "{" expr "}")* "\"" ;
args = arg ("," arg)* ;
arg = (IDENT ":")? expr ;
Note: Statement labels on block closers (e.g.,
end func name) are optional and omitted from the grammar for brevity.
Rendered from docs/spec/v0.5.md in the Geno repository.