Module dsl
Expand description
The string DSL: a compact text notation for a Distribution.
This is the zero-code path (two ways to write a space, and a third for
configuration files): any
TOML-configured or CLI-driven program becomes tunable by writing a
distribution as text, without a Rust type. The same notation is shared by
the CLI (atune run --param lr=loguniform(1e-5,1e-2)) and the TOML overlay
(the oniro path), so it is total — every byte sequence maps to either a
Distribution or a descriptive Error, and nothing here can panic.
Everything lowers to the existing Distribution constructors; the DSL
invents no parallel types and duplicates none of their validation — it
surfaces the constructors’ own errors.
§Grammar
Whitespace (ASCII or Unicode) is insignificant everywhere. Function names
and the keywords bool, log, step, open, limit, times, plus,
up and down are lower-case and case-sensitive.
| Form | Lowers to | Notes |
|---|---|---|
uniform(low, high) | Distribution::float | continuous linear float |
uniform(low, high, step) | Distribution::float_step | third arg is the step; step=<v> is also accepted |
loguniform(low, high) | Distribution::float_log | a log range cannot be stepped |
int(low, high) | Distribution::int | unit-step integer |
int(low, high, log) | Distribution::int_log | the bare keyword log |
int(low, high, step=<n>) | Distribution::int_step | the step keyword is required for ints |
choice(a, b, c) | Distribution::cat_labels | labels in order, stable indices |
bool | Distribution::boolean | also written bool() |
<number> | a fixed value (see below) | e.g. 0.001, 42, 1e-5 |
§Numeric literals
A literal is read as an integer when it is written with no decimal point
and no exponent (42, -7), and as a float otherwise (0.1, 1e-5,
-2.5E3). This is exactly the “integer vs float inferred from the literal”
rule: it decides the type of a bare fixed value and of an int bound, and
it is why int(2.5, 3) is rejected (an int bound must be an integer
literal) while uniform(0, 1) is fine (an integer literal is accepted
wherever a float is wanted, and widened). Negative numbers and scientific
notation are accepted; NaN and inf literals are rejected (they can
only ever name an invalid distribution).
§The step syntax
The step is written differently for the two numeric kinds, on purpose:
uniformtakes the step as its third positional argument (uniform(0, 1, 0.25)); the explicituniform(0, 1, step=0.25)is accepted too and means the same thing.intrequires thestep=<n>keyword (int(2, 512, step=4)), because anint’s third slot is shared with the barelogkeyword and a bare number there would read ambiguously.
§Fixed values
A bare number is a parameter that is present but not tuned — it always takes that one value. It lowers to the degenerate range of the matching numeric kind:
- an integer literal
n→Distribution::int(n, n), which is single-valued (the study loop assigns it without sampling); - a float literal
x→Distribution::float(x, x), a degenerate continuous range. This is notis_single_valued(a continuous float reports no cardinality), but its transform pins every unit coordinate tox, so the independent-sampling path returns the constant just the same. A single-valued float would require a fabricated step, which would then have to be invented on every round trip; the degenerate continuous range avoids that and still yields the constant.
§Growing declarations
parse_growing, parse_schema_growing and parse_schema_lines_growing
are a parallel set of entry points – parse and its own three callers
are untouched – that accept everything above plus zero or more trailing
keyword arguments declaring an Open growth policy. render_growing is
the counterpart that renders a distribution back through them, since Open
is deliberately not part of Distribution and Display cannot carry it.
| Form | Adds | Notes |
|---|---|---|
<form>, open | an Open with Sides::Both | any form above, plus a bare open |
<form>, open=up | an Open with Sides::Up | only the upper bound may grow |
<form>, open=down | an Open with Sides::Down | only the lower bound may grow |
<form>, limit=..=<n> | a high Open::limit | inclusive; ..<n> (exclusive) is rejected |
<form>, limit=<n>.. | a low Open::limit | inclusive; the two limit= forms combine |
around(c, times=<n>) | Distribution::float_log seed [c/n, c*n], plus an Open | multiplicative “no idea” declaration |
around(c, plus=<n>) | Distribution::float seed [c-n, c+n], plus an Open | additive “no idea” declaration |
open, open=up, open=down and limit=<range> may follow any call
form above (uniform, loguniform, int) or an around(...), in any
combination and any order, after that form’s own arguments – which is why
loguniform(1e-5, 1e-2, open=up, limit=..=1.0) has four arguments where
parse accepts exactly two. around has no closed-form meaning (there is
no plain Distribution for “no idea yet”), so it is recognized only by
parse_growing and always produces a policy, with or without additional
open/limit keywords. limit is independently optional per side, exactly
as Open::limit is; setting the same side twice keeps the later value.
around ... times lowers to a logarithmic seed, which cannot be stepped
(compare Distribution::float_log), so a step= keyword after around
is rejected outright rather than silently accepted.
What reaches text is only the seed, the sides and the limit – factor
and max_expansions keep their defaults in text and are reachable only from
the Open builder in Rust.
§Round trip
Distribution implements Display, and
parse(&d.to_string()) reconstructs an equal distribution for every
distribution parse can produce (a property test in this module asserts it
across every kind). The canonical text a valid distribution renders to is
one of the grammar forms above; floats are rendered in the shortest form
that round-trips and always carry float syntax where a bare integer would
otherwise be re-read as an int. Display of a hand-built invalid
distribution is best-effort and is not promised to re-parse.
render_growing extends the same guarantee to a growth policy:
parse_growing(&render_growing(&d, open.as_ref())) reconstructs an equal
(Distribution, Option<Open>) pair for every pair parse_growing can
produce. render_growing(&d, None) renders identically to d.to_string(),
since Open – not Distribution – is the only thing it adds.
§Whole spaces
parse_schema turns an ordered sequence of (name, spec) pairs (a TOML
table, a map, CLI --param pairs) into a SpaceSchema, preserving
declaration order; parse_named parses a single name = spec line; and
parse_schema_lines parses a block of such lines (blank and #-comment
lines are skipped).
parse_schema_growing and parse_schema_lines_growing are the growing
counterparts: each returns a SpacePolicy alongside the SpaceSchema,
holding one Open per parameter whose spec carried a growth keyword. A
spec with none contributes nothing to the policy, so the returned
SpacePolicy only ever names parameters that actually declared themselves
open.
Functions§
- parse
- Parses a DSL spec into a
Distribution. - parse_
growing - Parses a growing DSL spec into a distribution and its optional growth
policy – the parallel entry point that adds
open,open=up,open=down,limit=<range>andaround(...)on top of everythingparsealready accepts – see the module documentation for the full grammar.parseitself is untouched: a spec with no growth keyword parses to the identicalDistribution, withNonein the second slot. - parse_
named - Parses a single
name = specline into a name and itsDistribution. - parse_
schema - Parses an ordered sequence of
(name, spec)pairs into aSpaceSchema. - parse_
schema_ growing - Parses an ordered sequence of
(name, spec)pairs into aSpaceSchemaand theSpacePolicyits growth keywords declare – the growing counterpart ofparse_schema. A parameter whose spec carries no growth keyword contributes nothing to the returned policy. - parse_
schema_ lines - Parses a block of
name = speclines into aSpaceSchema. - parse_
schema_ lines_ growing - Parses a block of
name = speclines into aSpaceSchemaand theSpacePolicyits growth keywords declare – the growing counterpart ofparse_schema_lines. Blank lines and#-comments are skipped, exactly asparse_schema_linesskips them. - render_
growing - Renders a distribution and its optional growth policy in the string DSL –
the counterpart of
parse_growing, and what makes its round trip expressible at all:Openis deliberately not part ofDistribution, soDisplayalone cannot carry a policy.