Renderingen-US

The Faber language

Everything you need to read Faber is on this page. The pages beneath it go deeper on each part, but nothing here is a placeholder — if you read this far and stop, you can read a Faber program.

A whole program#

fn saturate(int x)  int {
    if x ≺ 0 then return 0
    if x ≻ 255 then return 255
    return x
}

main {
    const int v  saturate(300)
    print v
}

That is a complete, compilable package entry point. Reading it left to right:

PieceWhat it is
fndeclares a function
numerus xthe type comes before the name — always, everywhere
→ numerusthe return type
si … ergoa compact single-branch conditional
returnreturn
mainthe program entry point, like main
constan immutable binding (var is the mutable one)
bind this value to that name
printprint

Type before name, always#

This is the single rule that makes the rest of the grammar small:

main {
    # a declaration
    const int count  0
    # another
    const string name  "Marcus"
    # a generic
    const list<int> scores  [1, 2, 3]
    # a nullable
    const int  null maybe  null
    print count, name, scores, maybe
}

There is no let, no :, and no type inference syntax to learn. A declaration is a type followed by a name, whether it is a parameter, a local, or a field. See Types and values.

Six glyphs, and what they mean#

Faber uses a small fixed set of symbols for structure. They never localize — they are the same in every reader locale, which is what keeps a program recognizable across languages.

GlyphMeaning
bind a value to a name
function return type
union type, most often T ∪ nihil for nullable
alternate exit — the error channel
closure joint, connecting a signature to its body
equality

Two more appear when you convert between types: for a compile-time ascription and for a runtime conversion that can fail. Full table in Glyphs and Latin.

The words are Latin, the structure is not#

fn, return, if, const are Latin verbs and adjectives chosen so the keyword carries the behaviour. But the vocabulary is a rendering, not the language. The same program in the model-facing pack:

fn saturate(int x) → int {
    if x < 0 then return 0
    if x > 255 then return 255
    return x
}

Identical program, identical semantics, different spelling. The glyphs and the type-first shape survive untouched. That is the point of reader locales — and it is why identifiers like saturate are never translated.

Where the rest lives#

PageWhat it covers
Types and valuesprimitives, collections, strings, nullability, conversion, bindings
Functions and control flowparameters, returns, branching, loops, generics
Errors and testingthe error channel, and inline probandum/proba/adfirma suites
Glyphs and Latinthe full glyph table, vocabulary rationale, canonical vs sugar
Reader localeshow one program renders in eight surfaces
Capabilities and framesad dispatch and the host I/O boundary