A configuration language
Stav is a statically typed, eager and total configuration language with block syntax. It checks the whole program before it runs any of it, merges what every file contributes by priority, and renders the result to JSON, TOML or YAML.
cargo install stav-cli
type Level = debug | info | warn | error
struct Upstream {
url: Str where matches(r"https?://.*")
weight: Int where 1..=100 = 10
}
struct Root {
listen: Str = "127.0.0.1:8080"
log_level: Level = info
upstreams: Map<Str, Upstream> = {} block upstream
}
log_level = warn
upstream primary { url = "https://a.example.org", weight = 90 }
upstream backup { url = "https://b.example.org" }listen = "127.0.0.1:8080"
log_level = "warn"
[upstreams.backup]
url = "https://b.example.org"
weight = 10
[upstreams.primary]
url = "https://a.example.org"
weight = 90These are not conventions to follow. The checker and the evaluator enforce them for every program.
The whole program is type checked before any of it runs. Unknown names, broken imports and wrong types are reported up front, with the closest valid name as a hint.
weight: Int where 1..=100Evaluation always ends. Functions cannot call themselves, types cannot be recursive, loops run over finite collections, and fuel limits stop runaway growth.
fold(range(1, 6), 1, f)Statements run in order, once. The only deferred value is a late-bound self, read after merging along a graph that is checked to be acyclic.
url = "#{self.version}"Evaluation reads nothing but the source files it is given. No environment, no clock, no network. The same inputs give the same value, byte for byte.
stav eval --canonicalEvery file contributes to one value. Lists concatenate, maps merge per key, and single values settle by priority, from declared default up to force.
port = force(443)Typed fragments of a body, written as functions or files. Pass parameters in, then include them wherever their struct fits.
include web("api")Every file is parsed and the whole program is type checked against its root struct.
Statements run eagerly, in order, and each one contributes values at a path and a priority.
Contributions meet by path. The highest priority wins, collections combine, conflicts are reported.
The final value is checked once more and printed as JSON, TOML or YAML, or read into Rust types.
Declare the shape of your configuration once, then let every file, part and overlay fill it in. Stav keeps the pieces square.