Constraints extension
Work in progress
argtype is early-stage and the extension mechanism is still being designed; this extension's surface may change. constraints in particular is a draft: the method set below is not finalized.
Extension name: constraints
Declares inter-argument constraints: relationships between named nodes that aren't expressible by the structure alone, like "if --cert is given, --key is required" or "--quiet and --verbose are mutually exclusive."
This is an extension, not part of the core, and that's on purpose: the core stays within regular languages so that parsing, autocomplete, wrapper generation, and well-formedness validation are tractable for every consumer. Inter-argument constraints can be modeled in the core by enumerating valid combinations with alt, and when the combination space is small that's the better choice; it keeps the dependency structure visible. The constraints extension exists for the cases where enumeration would explode the grammar, and for consumers (validators, form generators) that want to check constraints without the core paying that cost.
What consumers do with it
- Validators check the constraints and reject invocations that violate them.
- GUI / form generators may disable or require fields reactively based on them.
- Parsers, autocomplete, wrapper generators ignore the
constraintsextension entirely. They treat a grammar with constraints exactly as they would without one: the constraints never narrow the set of argv shapes the grammar describes, they only mark some of those shapes as semantically invalid.
In other words: constraints is a separate, smaller validation layer applied on top of the regular language, never folded into it.
Methods
Constraint methods attach to any named node (or a node with a named ancestor) and reference other nodes by name.
| Method | Meaning |
|---|---|
expr.requires("a") | If expr is present, the node named a must be present. |
expr.conflicts("a") | If expr is present, the node named a must be absent. |
expr.requiresAny("a", "b", …) | If expr is present, at least one of the named nodes must be present. |
---
exe: "server"
---
server: set(
/// TLS certificate
cert: opt("--cert", path).requires("key"),
/// TLS private key
key: opt("--key", path).requires("cert"),
verbose: opt("--verbose").conflicts("quiet"),
quiet: opt("--quiet"),
)Open questions: whether conflicts/requires should be symmetric (declare once vs. on both nodes), whether to add implies(condition) for value-dependent constraints, and how (or whether) to express "exactly one of" beyond what alt already gives.
Chaining API
// On any Node (provided by the constraints extension):
interface Node {
requires(name: string): Node
conflicts(name: string): Node
requiresAny(...names: string[]): Node
}