Add bilingual manual and localized docs routes
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
# Constraints and Defaults
|
||||
|
||||
A constraint describes a range that a value must satisfy.
|
||||
|
||||
```dcdl
|
||||
Int
|
||||
String
|
||||
>= 1
|
||||
<= 65535
|
||||
/Hello! .*/
|
||||
```
|
||||
|
||||
Constraints can be composed with `&`.
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
NarrowedPort = Port & > 443;
|
||||
```
|
||||
|
||||
The result is the range that satisfies both sides.
|
||||
Incompatible constraints produce a conflict.
|
||||
|
||||
```dcdl
|
||||
Int & String # conflict
|
||||
> 10 & < 5 # conflict
|
||||
Int & > 10 & < 11 # conflict: integer candidate does not exist
|
||||
```
|
||||
|
||||
## Primitive and comparison constraints
|
||||
|
||||
The built-in primitive ranges are:
|
||||
|
||||
```dcdl
|
||||
String
|
||||
Int
|
||||
Float
|
||||
Bool
|
||||
```
|
||||
|
||||
Composing different primitive types with `&` produces a conflict.
|
||||
Numeric comparison constraints combine as bounds and conflict if they form an empty range.
|
||||
|
||||
```dcdl
|
||||
Int & >= 1 & <= 65535 & > 443
|
||||
```
|
||||
|
||||
Comparison constraints for `Int` use integer literals.
|
||||
Comparison constraints for `Float` may use either integer or float literals.
|
||||
|
||||
## Unknown
|
||||
|
||||
`Unknown` is the top range containing every Decodal value.
|
||||
It is not an `Any` that disables checking and erases concrete information; it means that the value's range has not yet been narrowed.
|
||||
|
||||
```dcdl
|
||||
Int & Unknown # Int
|
||||
42 as Unknown # 42
|
||||
Unknown as Int # conflict
|
||||
```
|
||||
|
||||
`Unknown` has no concrete value of its own and therefore cannot be materialized.
|
||||
It must be narrowed by a concrete value or given a `default`.
|
||||
|
||||
```dcdl
|
||||
Unknown default {}
|
||||
```
|
||||
|
||||
## Regex constraints
|
||||
|
||||
A regex literal is a string constraint.
|
||||
|
||||
```dcdl
|
||||
Host = /^api-[0-9]+$/;
|
||||
```
|
||||
|
||||
When several regex constraints are composed, a concrete string must match all of them.
|
||||
|
||||
```dcdl
|
||||
String & /^a/ & /z$/
|
||||
```
|
||||
|
||||
The intersection of regex constraints is not determined during composition.
|
||||
The following range therefore does not conflict when composed, but fails when a concrete value is validated.
|
||||
|
||||
```dcdl
|
||||
String & /^a$/ & /^b$/
|
||||
```
|
||||
|
||||
In the Rust runtime, enable the regex engine with the `regex` feature.
|
||||
Without that feature, validating a regex constraint against a concrete value produces an unsupported-feature diagnostic.
|
||||
|
||||
## Array constraints
|
||||
|
||||
Write an array constraint as `[...T]`; it applies `T` to every element.
|
||||
The element constraint is required.
|
||||
|
||||
```dcdl
|
||||
Names = [...String];
|
||||
PositiveInts = [...(Int & > 0)];
|
||||
```
|
||||
|
||||
When composed with a concrete array, each element is refined against `T` using the same rules as `as`.
|
||||
An empty array satisfies every array constraint.
|
||||
|
||||
If the element constraint is an object range, fields present only on the right remain abstract in every element.
|
||||
A field present only on the left is outside the right-hand field domain and produces a conflict.
|
||||
|
||||
## Associative-array and object rest constraints
|
||||
|
||||
Write an associative-array constraint as `{...T}`; it applies `T` to every field value in an object.
|
||||
|
||||
```dcdl
|
||||
Services = {...{
|
||||
port = Int;
|
||||
enabled = Bool default true;
|
||||
}};
|
||||
```
|
||||
|
||||
Keys are not enumerated, and an empty object is allowed.
|
||||
Writing `...T` at the end of an object with named fields applies `T` only to fields that were not named explicitly.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
enabled = Bool default true;
|
||||
...Unknown
|
||||
}
|
||||
```
|
||||
|
||||
A rest constraint does not generate fields.
|
||||
It only validates additional fields that actually exist.
|
||||
|
||||
## default
|
||||
|
||||
`default` is not a constraint.
|
||||
It is a fallback used only when no concrete value exists during materialization.
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
|
||||
Service = {
|
||||
port = Port default 8080;
|
||||
};
|
||||
```
|
||||
|
||||
When an explicit value has been composed into the range, that value is used and `default` is not evaluated.
|
||||
|
||||
```dcdl
|
||||
Config = {
|
||||
port = 9000;
|
||||
} as Service;
|
||||
```
|
||||
|
||||
Without an explicit value, materialization selects `8080` and verifies that it satisfies `Port`.
|
||||
|
||||
## Default composition
|
||||
|
||||
- If only one side of `&` has a `default`, that `default` is preserved.
|
||||
- If both sides of `&` have different defaults, they conflict.
|
||||
- If `&` between a constraint and a concrete value succeeds, the result is concrete and does not retain the `default`.
|
||||
- Because `//` is right-biased, the right-hand value or `default` replaces the left-hand one for the same field.
|
||||
- `as` does not inject a right-hand `default` into the left side. A field that exists only on the right, however, remains abstract in the result together with its `default`.
|
||||
|
||||
A `default` expression is evaluated when materialization requires it and must satisfy the constraints of the same range.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Lazy Evaluation
|
||||
|
||||
Decodal evaluates values when they are needed.
|
||||
|
||||
## Lazily evaluated values
|
||||
|
||||
The following values are not evaluated until they are referenced or materialized:
|
||||
|
||||
- Module roots and top-level fields
|
||||
- Object fields
|
||||
- `let` bindings
|
||||
- Function arguments
|
||||
- `default` expressions
|
||||
- Imported values
|
||||
|
||||
When the same binding is referenced more than once, its evaluation result is reused.
|
||||
Consequently, a failure in an unreferenced field or function argument does not occur until that value is needed.
|
||||
|
||||
```dcdl
|
||||
let
|
||||
safe = 42;
|
||||
unused = missing_name;
|
||||
in
|
||||
safe
|
||||
```
|
||||
|
||||
This expression evaluates to `42` because it never references `unused`.
|
||||
|
||||
## Cycle detection
|
||||
|
||||
A cycle diagnostic is produced when a value being evaluated depends on itself again.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
a = b + 1;
|
||||
b = a + 1;
|
||||
}
|
||||
```
|
||||
|
||||
Modules and imports may refer to one another cyclically as long as the dependency graph of the fields actually evaluated is not cyclic.
|
||||
|
||||
## Evaluation and materialization
|
||||
|
||||
A normal evaluation result may retain abstract values such as constraints, `Unknown`, `default`, and functions.
|
||||
Materialization converts that result to concrete data for use outside Decodal.
|
||||
|
||||
This separation allows schemas to be composed as values, only required fields to be evaluated, and selection of defaults to be deferred until output.
|
||||
See [Materialization and Errors](./materialization-and-errors.md) for details.
|
||||
@@ -0,0 +1,166 @@
|
||||
# Examples
|
||||
|
||||
This chapter combines Decodal's main constructs in complete examples.
|
||||
|
||||
## Basic configuration schema
|
||||
|
||||
```dcdl
|
||||
Host = String;
|
||||
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
NarrowedPort = Port & > 443;
|
||||
|
||||
MyConfig = {
|
||||
host = Host;
|
||||
port = NarrowedPort default 8080;
|
||||
feature_hoge = {
|
||||
enable = Bool default true;
|
||||
fuga = Int default 10;
|
||||
};
|
||||
};
|
||||
|
||||
NewConfig = MyConfig & {
|
||||
host = "127.0.0.1";
|
||||
port = 8000;
|
||||
};
|
||||
|
||||
disabled_config = NewConfig & {
|
||||
feature_hoge.enable = false;
|
||||
};
|
||||
|
||||
enabled_config = NewConfig;
|
||||
```
|
||||
|
||||
## Array schema
|
||||
|
||||
```dcdl
|
||||
Services = [...{
|
||||
name = String;
|
||||
port = Int default 8080;
|
||||
}];
|
||||
|
||||
[
|
||||
{ name = "api"; },
|
||||
{ name = "worker"; port = 9000; },
|
||||
] as Services
|
||||
```
|
||||
|
||||
An abstract array requires an element constraint.
|
||||
In this example, `port` in the first element materializes to `8080`.
|
||||
|
||||
## Associative arrays and range refinement
|
||||
|
||||
```dcdl
|
||||
Service = {
|
||||
port = Int;
|
||||
enabled = Bool default true;
|
||||
};
|
||||
|
||||
services = {
|
||||
api = { port = 8080; };
|
||||
worker = { port = 8081; enabled = false; };
|
||||
} as {...Service};
|
||||
```
|
||||
|
||||
`api` and `worker` are arbitrary keys, and each value is refined to a range narrower than `Service`.
|
||||
In the result of `as`, `api.enabled` remains abstract. Its default of `true` is selected only when the complete result is materialized.
|
||||
|
||||
## Functions and constraints
|
||||
|
||||
```dcdl
|
||||
let
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
add_offset = (base: Port, offset: Int) => base + offset;
|
||||
in
|
||||
add_offset(8000, 80)
|
||||
```
|
||||
|
||||
Evaluation result:
|
||||
|
||||
```text
|
||||
8080
|
||||
```
|
||||
|
||||
## match
|
||||
|
||||
```dcdl
|
||||
(
|
||||
input_a: {
|
||||
hoge = Int & >= 0;
|
||||
},
|
||||
input_b: {
|
||||
fuga = 20;
|
||||
}
|
||||
) =>
|
||||
let
|
||||
inputs = {
|
||||
a = input_a;
|
||||
b = input_b;
|
||||
};
|
||||
in
|
||||
{
|
||||
foo = match inputs.a.hoge {
|
||||
>= 20: {
|
||||
value = 200;
|
||||
};
|
||||
>= 10: {
|
||||
value = 100;
|
||||
};
|
||||
_: {
|
||||
value = 300;
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
`match` evaluates arms in order, so place narrower conditions before broader ones.
|
||||
|
||||
## Deep patch
|
||||
|
||||
```dcdl
|
||||
Base = {
|
||||
feature_hoge = {
|
||||
enable = Bool default true;
|
||||
fuga = Int default 10;
|
||||
};
|
||||
};
|
||||
|
||||
Patched = Base // {
|
||||
feature_hoge.enable = false;
|
||||
};
|
||||
```
|
||||
|
||||
`Patched` is equivalent to:
|
||||
|
||||
```dcdl
|
||||
{
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
fuga = Int default 10;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Cyclic imports
|
||||
|
||||
```dcdl
|
||||
# main.dcdl
|
||||
{
|
||||
schema = {
|
||||
hoge = String;
|
||||
};
|
||||
|
||||
result = (import "./func.dcdl")(schema);
|
||||
}
|
||||
```
|
||||
|
||||
```dcdl
|
||||
# func.dcdl
|
||||
(input: (import "./main.dcdl").schema) =>
|
||||
{
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
`func.dcdl` imports `main.dcdl`, but it references only `main.schema`.
|
||||
The cyclic import is valid as long as `main.schema` does not depend on `main.result`.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Arithmetic Expression
|
||||
|
||||
Decodal supports arithmetic over concrete numeric values.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
workers = 2 + 2;
|
||||
timeout = 30.0 / 2;
|
||||
port = 8000 + 80;
|
||||
negative = -1;
|
||||
}
|
||||
```
|
||||
|
||||
## Operators
|
||||
|
||||
- `+` addition
|
||||
- `-` subtraction
|
||||
- `*` multiplication
|
||||
- `/` division
|
||||
- unary `-` negation
|
||||
|
||||
`*` and `/` bind tighter than `+` and `-`.
|
||||
Parentheses can be used to make grouping explicit.
|
||||
|
||||
```dcdl
|
||||
2 + 3 * 4 # 14
|
||||
(2 + 3) * 4 # 20
|
||||
```
|
||||
|
||||
## Numeric behavior
|
||||
|
||||
Arithmetic requires concrete `Int` or `Float` operands.
|
||||
`Int + Int`, `Int - Int`, and `Int * Int` produce `Int` when no overflow occurs.
|
||||
Mixed `Int` / `Float` arithmetic produces `Float`.
|
||||
Division always produces `Float`.
|
||||
|
||||
Division by zero and integer overflow are evaluation errors.
|
||||
|
||||
Arithmetic expressions can be used anywhere a concrete numeric expression is expected, including defaults and numeric constraints.
|
||||
|
||||
```dcdl
|
||||
port = Int & > 4000 + 42 default 8080;
|
||||
```
|
||||
@@ -0,0 +1,71 @@
|
||||
# Array Expression
|
||||
|
||||
An array expression represents an ordered sequence of values.
|
||||
|
||||
```dcdl
|
||||
[1, 2, 3]
|
||||
["a", "b", "c"]
|
||||
```
|
||||
|
||||
An array literal is a concrete value and does not require all elements to have the same type.
|
||||
|
||||
```dcdl
|
||||
["api", 8080, true]
|
||||
```
|
||||
|
||||
## Array constraint
|
||||
|
||||
Write an array constraint by placing an ellipsis immediately after `[`, followed by an element constraint.
|
||||
|
||||
```dcdl
|
||||
Names = [...String];
|
||||
Ports = [...(Int & >= 1 & <= 65535)];
|
||||
```
|
||||
|
||||
`[...T]` represents an array of zero or more elements, all of which satisfy `T`.
|
||||
An empty array satisfies every array constraint.
|
||||
|
||||
```dcdl
|
||||
[...String] & []
|
||||
[...String] & ["api", "worker"]
|
||||
```
|
||||
|
||||
The element constraint can be an object range.
|
||||
A field with a default that exists only on the right remains abstract in every element when the array constraint is composed.
|
||||
Its default is selected only if the array is later materialized.
|
||||
|
||||
```dcdl
|
||||
Services = [...{
|
||||
name = String;
|
||||
enabled = Bool default true;
|
||||
}];
|
||||
|
||||
Services & [{ name = "api"; }]
|
||||
```
|
||||
|
||||
Applying an array constraint to a concrete array refines each element against the element range using the same rules as `as`.
|
||||
For an object element range, a field present only on the left produces an error, while a field present only on the right remains abstract.
|
||||
|
||||
Decodal does not provide an abstract array type without an element constraint.
|
||||
The former `Array` primitive type is unavailable, and `T` is required in `[...T]`.
|
||||
`[String]` is not an array constraint; it is a concrete one-element array containing the unresolved `String` constraint.
|
||||
An array constraint describes only the element range. It has no length, positional tuple, or uniqueness constraints.
|
||||
|
||||
## Array concatenation
|
||||
|
||||
`++` concatenates two concrete arrays.
|
||||
|
||||
```dcdl
|
||||
base = ["read", "write"];
|
||||
extra = ["admin"];
|
||||
roles = base ++ extra;
|
||||
```
|
||||
|
||||
`roles` has the same value as:
|
||||
|
||||
```dcdl
|
||||
["read", "write", "admin"]
|
||||
```
|
||||
|
||||
`++` does not transform array elements.
|
||||
Elements from the right side follow those from the left.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Range Refinement
|
||||
|
||||
`narrower as wider` verifies that the left side is more concrete and narrower than the right side, then composes them asymmetrically.
|
||||
|
||||
```dcdl
|
||||
Int & > 10 as Int & > 0
|
||||
```
|
||||
|
||||
This succeeds and returns the narrower left-hand range, `Int & > 10`.
|
||||
The reverse, `Int as Int & > 0`, fails because the left side is not guaranteed to satisfy the right side.
|
||||
|
||||
## Object
|
||||
|
||||
For objects, the right side defines the field domain used to check the left:
|
||||
|
||||
- A field found only on the left lies outside the right-hand domain and produces an error.
|
||||
- A field found on both sides is checked recursively, and the narrower left-hand result is used.
|
||||
- A field found only on the right remains unevaluated in the result, whether or not it has a default.
|
||||
- Field order in the result follows the right-hand side.
|
||||
|
||||
```dcdl
|
||||
partial = {
|
||||
port = 8080;
|
||||
} as {
|
||||
host = String;
|
||||
port = Int;
|
||||
enabled = Bool default true;
|
||||
};
|
||||
```
|
||||
|
||||
`partial.port` becomes the concrete value `8080`.
|
||||
`partial.host` and `partial.enabled` remain abstract fields from the right; `as` neither selects nor forces the default for `enabled`.
|
||||
|
||||
If `partial` is later materialized, the normal materialization rules apply.
|
||||
In this example, unresolved `host` produces an error. If `host` is also made concrete, the default for `enabled` is selected during materialization.
|
||||
|
||||
## Default
|
||||
|
||||
A default is not evidence of range containment.
|
||||
When the same field exists on both sides, the result uses the left side and does not inject the right-hand default.
|
||||
|
||||
```dcdl
|
||||
Int & > 0 as (Int default 1)
|
||||
```
|
||||
|
||||
The result is `Int & > 0` without a default.
|
||||
A right-only object field, however, is retained as a whole and therefore retains any default its abstract value already had.
|
||||
|
||||
## Map and array ranges
|
||||
|
||||
`{...T}` allows arbitrary object keys and verifies that each value is narrower than `T`.
|
||||
|
||||
```dcdl
|
||||
ports = {
|
||||
http = 80;
|
||||
https = 443;
|
||||
} as {...(Int & >= 1 & <= 65535)};
|
||||
```
|
||||
|
||||
When `[...T]` is used on the right, every array element is refined against `T` in the same way.
|
||||
|
||||
## Concrete right-hand ranges
|
||||
|
||||
Primitive and composed constraints validate values in the usual way.
|
||||
If the right side is a concrete scalar or array literal, the left must have the same value or the same length and element structure.
|
||||
A function cannot be used as the right-hand range.
|
||||
|
||||
The left side does not have to be concrete.
|
||||
Abstract values can be used when their containment relation can be determined.
|
||||
Primitive types, numeric bounds, identical regexes or predicates, and element ranges of arrays and maps support containment checks.
|
||||
|
||||
A function parameter written as `name: range` uses the same refinement rules as `as` when its argument is forced.
|
||||
|
||||
## Precedence
|
||||
|
||||
`as` has the lowest precedence, below `default`, and is left-associative.
|
||||
|
||||
```dcdl
|
||||
narrow & overrides as Wider
|
||||
```
|
||||
|
||||
This is interpreted as `(narrow & overrides) as Wider`.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Composition Expression
|
||||
|
||||
A composition expression combines multiple values, constraints, or structures.
|
||||
|
||||
## `&`
|
||||
|
||||
`&` performs constraint-preserving composition.
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
Config = MyConfig & { port = 8000; };
|
||||
```
|
||||
|
||||
Because object fields present on only one side are retained, `&` has no direction.
|
||||
Use [`as`](./ascription.md) to verify that the left side is narrower than the right and treat the right side as the field domain.
|
||||
|
||||
## `//`
|
||||
|
||||
`//` performs a right-biased structural patch.
|
||||
|
||||
```dcdl
|
||||
Patched = Base // {
|
||||
feature_hoge.enable = false;
|
||||
};
|
||||
```
|
||||
|
||||
See [Operators](../operators.md) for details.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Default Expression
|
||||
|
||||
A `default` expression specifies a fallback selected during materialization when no explicit value exists.
|
||||
|
||||
```dcdl
|
||||
port = Int default 8080;
|
||||
```
|
||||
|
||||
`default` is not a constraint.
|
||||
See [Constraints and Defaults](../constraints-and-defaults.md) for details.
|
||||
|
||||
## Evaluation
|
||||
|
||||
The fallback expression is evaluated only if materialization requires it.
|
||||
When an explicit value exists, the `default` is neither evaluated nor selected.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Function Call Expression
|
||||
|
||||
A function call expression applies a function value to arguments.
|
||||
|
||||
```dcdl
|
||||
increment(41)
|
||||
```
|
||||
|
||||
## Evaluation
|
||||
|
||||
An argument is evaluated when the function body references it.
|
||||
If the argument is referenced several times, its evaluation result is reused.
|
||||
When a parameter has a range, the argument is refined using the same rules as `narrower as wider`.
|
||||
Fields present only in the parameter range remain abstract, and defaults are not selected at this point.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Function Expression
|
||||
|
||||
A function expression is a value that accepts arguments and returns an expression.
|
||||
|
||||
```dcdl
|
||||
(value: Int) => value + 1
|
||||
```
|
||||
|
||||
See [Functions](../functions.md) for the complete function specification.
|
||||
|
||||
## Evaluation
|
||||
|
||||
A function can reference bindings from the lexical scope where it was defined.
|
||||
Its body is evaluated when the function is called, not when the function value is created.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Identifier Expression
|
||||
|
||||
An identifier expression references a name bound in the lexical scope.
|
||||
|
||||
```dcdl
|
||||
Port
|
||||
MyConfig
|
||||
mkConfig
|
||||
```
|
||||
|
||||
## Evaluation
|
||||
|
||||
An identifier evaluates the corresponding binding when its value is needed.
|
||||
If no such binding exists, evaluation produces an unresolved-identifier error.
|
||||
@@ -0,0 +1,14 @@
|
||||
# Import Expression
|
||||
|
||||
An import expression returns a DCDL module or structured value resolved by the host.
|
||||
|
||||
```dcdl
|
||||
import "./config.dcdl"
|
||||
```
|
||||
|
||||
The specifier is a string literal. The host defines its interpretation as a path, resource name, or another identifier.
|
||||
See [Modules and Imports](../modules-and-imports.md) for details.
|
||||
|
||||
## Evaluation
|
||||
|
||||
Imported values are evaluated lazily, and unreferenced fields are not evaluated.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Expression
|
||||
|
||||
This chapter classifies the expressions evaluated by the language.
|
||||
|
||||
An expression is the basic unit for representing a value, constraint, structure, or computation.
|
||||
|
||||
```text
|
||||
Expr
|
||||
├─ literal
|
||||
├─ identifier
|
||||
├─ path reference
|
||||
├─ object
|
||||
├─ map constraint
|
||||
├─ array
|
||||
├─ array constraint
|
||||
├─ function
|
||||
├─ function call
|
||||
├─ let
|
||||
├─ match
|
||||
├─ import
|
||||
├─ composition
|
||||
├─ range refinement (`as`)
|
||||
└─ default
|
||||
```
|
||||
|
||||
Links to each expression specification are collected in the [manual contents](../../index.md).
|
||||
@@ -0,0 +1,16 @@
|
||||
# Let Expression
|
||||
|
||||
A `let` expression creates local bindings.
|
||||
|
||||
```dcdl
|
||||
let
|
||||
base = 8000;
|
||||
offset = 80;
|
||||
in
|
||||
base + offset
|
||||
```
|
||||
|
||||
## Evaluation
|
||||
|
||||
A binding is evaluated when it is referenced.
|
||||
An unreferenced binding is not evaluated, and the result is reused if the same binding is referenced several times.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Literal Expression
|
||||
|
||||
A literal expression is a concrete value written directly in source.
|
||||
|
||||
## Kinds
|
||||
|
||||
```dcdl
|
||||
"hello"
|
||||
123
|
||||
3.14
|
||||
true
|
||||
false
|
||||
```
|
||||
|
||||
## Corresponding primitive types
|
||||
|
||||
- A string literal satisfies `String`.
|
||||
- An integer literal satisfies `Int`.
|
||||
- A floating-point literal satisfies `Float`.
|
||||
- `true` and `false` satisfy `Bool`.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Logical and Comparison Expressions
|
||||
|
||||
Decodal supports boolean logic over concrete `Bool` values and comparison over concrete scalar values.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
is_prod = env == "prod";
|
||||
high_port = port > 9000;
|
||||
enabled = is_prod && high_port;
|
||||
disabled = !enabled;
|
||||
}
|
||||
```
|
||||
|
||||
## Logical operators
|
||||
|
||||
- `!expr` negates a concrete `Bool`.
|
||||
- `lhs && rhs` returns boolean AND.
|
||||
- `lhs || rhs` returns boolean OR.
|
||||
|
||||
`&&` and `||` short-circuit: the right-hand side is evaluated only when needed.
|
||||
Logical operands must evaluate to concrete `Bool` values.
|
||||
|
||||
## Comparison operators
|
||||
|
||||
- `==`
|
||||
- `!=`
|
||||
- `<`
|
||||
- `<=`
|
||||
- `>`
|
||||
- `>=`
|
||||
|
||||
`==` and `!=` compare concrete scalar values: `String`, `Bool`, `Int`, and `Float`.
|
||||
`Int` and `Float` can be compared to each other numerically.
|
||||
|
||||
Ordering operators `<`, `<=`, `>`, and `>=` compare concrete numeric values only.
|
||||
They are separate from prefix comparison constraints such as `> 443`.
|
||||
|
||||
```dcdl
|
||||
port = Int & > 443 default 9443;
|
||||
is_high = port > 9000;
|
||||
```
|
||||
@@ -0,0 +1,24 @@
|
||||
# Match Expression
|
||||
|
||||
A `match` expression compares a target value against patterns from top to bottom and selects the first matching arm.
|
||||
|
||||
```dcdl
|
||||
foo = match inputs.a.hoge {
|
||||
>= 20: {
|
||||
value = 200;
|
||||
};
|
||||
>= 10: {
|
||||
value = 100;
|
||||
};
|
||||
_: {
|
||||
value = 300;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
`_` is the fallback pattern.
|
||||
|
||||
## Ordering
|
||||
|
||||
Arms are ordered; Decodal does not automatically select the most specific pattern.
|
||||
If a broad condition appears first, narrower conditions after it are unreachable.
|
||||
@@ -0,0 +1,90 @@
|
||||
# Object Expression
|
||||
|
||||
An object expression is a collection of named fields.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
host = "127.0.0.1";
|
||||
port = 8000;
|
||||
}
|
||||
```
|
||||
|
||||
Objects can represent both configuration values and schemas.
|
||||
|
||||
```dcdl
|
||||
MyConfig = {
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
};
|
||||
```
|
||||
|
||||
## Dot-path fields
|
||||
|
||||
A nested field can be defined with a dot path.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
feature_hoge.enable = false;
|
||||
}
|
||||
```
|
||||
|
||||
This represents the same structure as:
|
||||
|
||||
```dcdl
|
||||
{
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Map constraint
|
||||
|
||||
An associative-array constraint applies the same schema to every field value in an object.
|
||||
|
||||
```dcdl
|
||||
Services = {...{
|
||||
port = Int;
|
||||
enabled = Bool default true;
|
||||
}};
|
||||
```
|
||||
|
||||
`{...T}` represents an object whose keys are not fixed and whose values all satisfy `T`.
|
||||
An empty object is allowed. After materialization, it becomes the same object data as an object with named fields.
|
||||
|
||||
```dcdl
|
||||
services = {
|
||||
api = { port = 8080; };
|
||||
worker = { port = 8081; enabled = false; };
|
||||
} as Services;
|
||||
```
|
||||
|
||||
Each entry is refined by `as` into the right-hand field domain, so a field other than `port` or `enabled` on the left produces an error.
|
||||
A field found only on the right remains abstract whether or not it has a default.
|
||||
An object supplied by the host can preserve string keys that are not valid identifiers, although field names written in DCDL source are limited to ordinary identifiers.
|
||||
|
||||
## Object rest constraint
|
||||
|
||||
Fixed fields and a value range for arbitrary keys can coexist in one object.
|
||||
|
||||
```dcdl
|
||||
OpenConfig = {
|
||||
enabled = Bool default true;
|
||||
...Unknown
|
||||
};
|
||||
```
|
||||
|
||||
`...T` applies only to remaining fields that were not named explicitly.
|
||||
Each named field uses its own range; the rest range is not additionally applied to it.
|
||||
A rest constraint does not generate fields. Materialization outputs only additional fields that actually exist.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
enabled = false;
|
||||
plugin = { name = "cache"; };
|
||||
} as OpenConfig
|
||||
```
|
||||
|
||||
An object can contain one `...T`, at its end.
|
||||
An object without it is closed, and an undeclared left-hand field under `as` produces an error.
|
||||
`{...T}` with no named fields is an abstract map constraint; materializing it by itself requires a default or a concrete value.
|
||||
@@ -0,0 +1,16 @@
|
||||
# Path Reference Expression
|
||||
|
||||
A path reference expression accesses a field of an object.
|
||||
|
||||
```dcdl
|
||||
config.host
|
||||
config.feature_hoge.enable
|
||||
```
|
||||
|
||||
## Evaluation
|
||||
|
||||
The expression on the left is evaluated as an object, then the named field is referenced.
|
||||
The referenced field is not evaluated until needed.
|
||||
|
||||
Referencing a field that does not exist produces a diagnostic.
|
||||
An explicit `Unknown` range does not represent a missing field, so it does not make field presence ambiguous.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Functions
|
||||
|
||||
A function is a pure expression that accepts values and returns a value.
|
||||
|
||||
## Syntax
|
||||
|
||||
```dcdl
|
||||
(value: Int) => value + 1
|
||||
```
|
||||
|
||||
Use ordinary call syntax to invoke a function.
|
||||
|
||||
```dcdl
|
||||
let
|
||||
increment = (value: Int) => value + 1;
|
||||
in
|
||||
increment(41)
|
||||
```
|
||||
|
||||
A function can have multiple parameters.
|
||||
|
||||
```dcdl
|
||||
(input_a: { hoge = Int & >= 0; }, input_b: { fuga = Int; }) =>
|
||||
{
|
||||
hoge = input_a.hoge;
|
||||
fuga = input_b.fuga;
|
||||
}
|
||||
```
|
||||
|
||||
A parameter range is optional.
|
||||
When present, the argument is validated and refined using the same rules as `as` when the argument is referenced.
|
||||
|
||||
## Scope and evaluation
|
||||
|
||||
Functions have lexical scope and can reference bindings from where they were defined.
|
||||
Arguments are evaluated lazily, so an argument not referenced by the function body is not evaluated.
|
||||
Recursive field or argument dependencies produce a cycle diagnostic.
|
||||
|
||||
Functions can be referenced and called as intermediate values, but cannot be materialized as data.
|
||||
An unapplied function remaining in a materialization target produces a diagnostic.
|
||||
Function equality is not defined, and composing two function values with `&` produces a conflict.
|
||||
@@ -0,0 +1,113 @@
|
||||
# Grammar
|
||||
|
||||
This page defines the grammar of Decodal source text.
|
||||
|
||||
## Lexical grammar
|
||||
|
||||
```ebnf
|
||||
source_character = ? any Unicode scalar value ? ;
|
||||
newline = "\n" | "\r\n" | "\r" ;
|
||||
space = " " | "\t" | newline ;
|
||||
comment = "#" , { ? any character except newline ? } ;
|
||||
|
||||
digit = "0" … "9" ;
|
||||
letter = "A" … "Z" | "a" … "z" ;
|
||||
identifier = letter , { letter | digit | "_" } ;
|
||||
|
||||
integer = digit , { digit } ;
|
||||
float = digit , { digit } , "." , digit , { digit } ;
|
||||
|
||||
string = '"' , { string_character | escape } , '"' ;
|
||||
string_character = ? any character except '"', "\\", or newline ? ;
|
||||
escape = "\\" , source_character ;
|
||||
|
||||
regex = "/" , regex_character , { regex_character } , "/" ;
|
||||
regex_character = escape | ? any character except "/", "\\", or newline ? ;
|
||||
```
|
||||
|
||||
Whitespace and comments separate tokens and are otherwise ignored by the parser.
|
||||
|
||||
## Syntactic grammar
|
||||
|
||||
```ebnf
|
||||
module = { statement } ;
|
||||
statement = field_definition , [ ";" ]
|
||||
| expression , [ ";" ] ;
|
||||
|
||||
expression = as_expression ;
|
||||
|
||||
as_expression = default_expression , { "as" , default_expression } ;
|
||||
default_expression = patch_expression , [ "default" , default_expression ] ;
|
||||
patch_expression = compose_expression , { "//" , compose_expression } ;
|
||||
compose_expression = logical_or_expression , { "&" , logical_or_expression } ;
|
||||
|
||||
logical_or_expression = logical_and_expression , { "||" , logical_and_expression } ;
|
||||
logical_and_expression = comparison_expression , { "&&" , comparison_expression } ;
|
||||
comparison_expression = concat_expression , [ comparison_operator , concat_expression ] ;
|
||||
concat_expression = additive_expression , { "++" , additive_expression } ;
|
||||
additive_expression = multiplicative_expression , { ( "+" | "-" ) , multiplicative_expression } ;
|
||||
multiplicative_expression = unary_expression , { ( "*" | "/" ) , unary_expression } ;
|
||||
|
||||
unary_expression = [ "!" | "-" ] , postfix_expression ;
|
||||
postfix_expression = primary_expression , { call_suffix | path_suffix } ;
|
||||
call_suffix = "(" , [ argument_list ] , ")" ;
|
||||
path_suffix = "." , identifier ;
|
||||
|
||||
primary_expression = literal
|
||||
| identifier
|
||||
| comparison_constraint
|
||||
| map_constraint
|
||||
| object
|
||||
| array_constraint
|
||||
| array
|
||||
| let_expression
|
||||
| function_expression
|
||||
| match_expression
|
||||
| import_expression
|
||||
| "(" , expression , ")" ;
|
||||
|
||||
literal = string | integer | float | "true" | "false" | regex ;
|
||||
comparison_operator = "==" | "!=" | "<" | "<=" | ">" | ">=" ;
|
||||
comparison_constraint = ( "<" | "<=" | ">" | ">=" ) , expression ;
|
||||
|
||||
object = "{" , [ field_definition , { ";" , field_definition }
|
||||
, [ ";" , object_rest ] , [ ";" ] ] , "}" ;
|
||||
object_rest = "..." , expression ;
|
||||
map_constraint = "{" , "..." , expression , "}" ;
|
||||
field_definition = field_path , "=" , expression ;
|
||||
field_path = identifier , { "." , identifier } ;
|
||||
|
||||
array = "[" , [ expression , { "," , expression } , [ "," ] ] , "]" ;
|
||||
array_constraint = "[" , "..." , expression , [ "," ] , "]" ;
|
||||
|
||||
let_expression = "let" , { field_definition , ";" } , "in" , expression ;
|
||||
function_expression = "(" , [ parameter_list ] , ")" , "=>" , expression ;
|
||||
parameter_list = parameter , { "," , parameter } , [ "," ] ;
|
||||
parameter = identifier , [ ":" , expression ] ;
|
||||
|
||||
match_expression = "match" , expression , "{" , [ match_arm , { ";" , match_arm } , [ ";" ] ] , "}" ;
|
||||
match_arm = pattern , ":" , expression ;
|
||||
pattern = "_" | expression ;
|
||||
|
||||
import_expression = "import" , string ;
|
||||
argument_list = expression , { "," , expression } , [ "," ] ;
|
||||
```
|
||||
|
||||
## Precedence
|
||||
|
||||
Precedence is highest first.
|
||||
|
||||
1. function call and field path reference
|
||||
2. unary `!` and `-`
|
||||
3. `*` and `/`
|
||||
4. `+` and `-`
|
||||
5. `++`
|
||||
6. `==`, `!=`, `<`, `<=`, `>`, `>=`
|
||||
7. `&&`
|
||||
8. `||`
|
||||
9. `&`
|
||||
10. `//`
|
||||
11. `default`
|
||||
12. `as`
|
||||
|
||||
Binary operators are left-associative except `default`, which is right-associative.
|
||||
@@ -0,0 +1,12 @@
|
||||
# Language Specification
|
||||
|
||||
This chapter defines the syntax of Decodal source and the rules that determine evaluation results.
|
||||
|
||||
- Syntax and grammar
|
||||
- Primitives, objects, arrays, and functions
|
||||
- Constraints, `Unknown`, and `default`
|
||||
- `&`, `//`, `as`, and other operators
|
||||
- Modules, imports, and lazy evaluation
|
||||
- Materialization and diagnostics
|
||||
|
||||
See the [manual contents](../index.md) for the complete chapter list.
|
||||
@@ -0,0 +1,73 @@
|
||||
# Materialization and Errors
|
||||
|
||||
A normal evaluation result may retain constraints, `default` values, functions, and other abstract values.
|
||||
Materialization converts an evaluation result into concrete data that can be passed outside Decodal.
|
||||
|
||||
## Materialization rules
|
||||
|
||||
Materialization performs the following work:
|
||||
|
||||
- Evaluate required fields.
|
||||
- Apply `default` to abstract ranges that have no explicit value.
|
||||
- Verify that concrete values and selected defaults satisfy their constraints.
|
||||
- Reject `Unknown` and other unresolved ranges without a `default`.
|
||||
- Reject unapplied functions and other values that cannot be represented as data.
|
||||
|
||||
```dcdl
|
||||
Service = {
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
};
|
||||
```
|
||||
|
||||
Materializing `Service` directly fails because `host` has neither a concrete value nor a `default`.
|
||||
|
||||
```dcdl
|
||||
Config = {
|
||||
host = "localhost";
|
||||
} as Service;
|
||||
```
|
||||
|
||||
Materializing `Config` produces the following data:
|
||||
|
||||
```dcdl
|
||||
{
|
||||
host = "localhost";
|
||||
port = 8080;
|
||||
}
|
||||
```
|
||||
|
||||
A `default` is not selected for a field that has an explicit value.
|
||||
|
||||
## Diagnostics
|
||||
|
||||
Errors are returned as diagnostics, not ordinary values.
|
||||
Expressions cannot branch on a diagnostic's kind or contents.
|
||||
|
||||
Representative diagnostics include:
|
||||
|
||||
- Syntax errors
|
||||
- Unresolved identifiers or fields
|
||||
- Type mismatches and constraint violations
|
||||
- Conflicts in `&` or `default`
|
||||
- Dependency cycles
|
||||
- Import failures
|
||||
- Match failures
|
||||
- Materialization failures
|
||||
|
||||
A diagnostic identifies the relevant DCDL source span.
|
||||
When several expressions conflict, it also points to the related fields, constraints, values, and defaults.
|
||||
If a structured import value has no source span, the diagnostic identifies the stable key supplied by the host and the logical value path.
|
||||
|
||||
## Fallback
|
||||
|
||||
A `match` with no matching arm and no fallback arm produces a diagnostic.
|
||||
|
||||
```dcdl
|
||||
match value {
|
||||
>= 10: "large";
|
||||
}
|
||||
```
|
||||
|
||||
Decodal does not provide general-purpose `try / catch`, optional imports, or optional field access for catching diagnostics.
|
||||
Use `default` for a missing-value fallback and `match` for branching over a finite set of values.
|
||||
@@ -0,0 +1,88 @@
|
||||
# Modules and Imports
|
||||
|
||||
`import` returns a DCDL module or structured value resolved by the host.
|
||||
|
||||
## Syntax
|
||||
|
||||
```dcdl
|
||||
import "./config.dcdl"
|
||||
```
|
||||
|
||||
An import specifier is a string literal.
|
||||
The host decides whether a specifier represents a path, URL, resource name, or another form of identifier.
|
||||
|
||||
## Modules
|
||||
|
||||
A sequence of field definitions at the top level creates a recursive module scope.
|
||||
A top-level field can refer to another top-level field in the same module by identifier.
|
||||
|
||||
```dcdl
|
||||
schema = {
|
||||
name = String;
|
||||
};
|
||||
|
||||
result = schema;
|
||||
```
|
||||
|
||||
Fields in an ordinary object literal do not implicitly refer to sibling fields by identifier.
|
||||
To reference a value from inside an object, use a binding in an outer scope or an explicit path reference.
|
||||
|
||||
Modules and their fields are evaluated lazily.
|
||||
An unreferenced field of an imported module is not evaluated.
|
||||
|
||||
## Host-defined resolution
|
||||
|
||||
Decodal does not define filesystem or network rules for import specifiers.
|
||||
The host receives the importing module and specifier, then returns one of:
|
||||
|
||||
- DCDL source
|
||||
- A structured value constructed by the host
|
||||
|
||||
The latter allows a host to parse Markdown, JSON, TOML, and other formats according to its own rules and expose the result as an ordinary Decodal value.
|
||||
|
||||
```dcdl
|
||||
Post = {
|
||||
frontmatter = {
|
||||
title = String;
|
||||
draft = Bool default false;
|
||||
};
|
||||
body = String;
|
||||
};
|
||||
|
||||
post = (import "./hello.md") as Post;
|
||||
```
|
||||
|
||||
Structured values follow the same rules as source-derived values for path references, composition, constraint validation, and materialization.
|
||||
See [Embedding](../embedding.md#imports) for the loader API and diagnostic provenance.
|
||||
|
||||
## Cyclic imports
|
||||
|
||||
Modules may import one another cyclically as long as the dependency graph of the fields actually evaluated is not cyclic.
|
||||
|
||||
```dcdl
|
||||
# main.dcdl
|
||||
schema = {
|
||||
name = String;
|
||||
};
|
||||
|
||||
result = (import "./func.dcdl")(schema);
|
||||
```
|
||||
|
||||
```dcdl
|
||||
# func.dcdl
|
||||
(input: (import "./main.dcdl").schema) => input
|
||||
```
|
||||
|
||||
Here, `func.dcdl` imports `main.dcdl`, but the referenced `schema` does not depend on `result`, so evaluation succeeds.
|
||||
Reaching the same field again while it is being evaluated produces a cycle-dependency diagnostic.
|
||||
|
||||
## Import failures
|
||||
|
||||
The following conditions produce an import failure:
|
||||
|
||||
- The host cannot resolve the specifier.
|
||||
- The resource cannot be read.
|
||||
- Parsing DCDL source fails.
|
||||
- A required value in the imported module cannot be evaluated.
|
||||
- Reading or converting a structured value fails.
|
||||
- The evaluated dependency graph is cyclic.
|
||||
@@ -0,0 +1,17 @@
|
||||
# Naming Conventions
|
||||
|
||||
Identifier capitalization has no language-defined meaning.
|
||||
Values, constraints, schemas, and derived configurations are all expressions of the same kind.
|
||||
|
||||
For readability, the following conventions are recommended:
|
||||
|
||||
- Object values: `lower_snake`
|
||||
- Functions: `lowerCamel`
|
||||
- Primitives, schemas, and abstract constraints: `UpperCamel`
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1;
|
||||
Service = { port = Port; };
|
||||
service = { port = 8080; } as Service;
|
||||
mkService = (port: Port) => { port = port; };
|
||||
```
|
||||
@@ -0,0 +1,250 @@
|
||||
# Operators
|
||||
|
||||
This chapter defines the meaning of Decodal's operators.
|
||||
|
||||
## Operator reference
|
||||
|
||||
| Operator | Form | Kind | Operands | Result / meaning |
|
||||
|---|---|---|---|---|
|
||||
| `.` | `object.field` | field reference | object / abstract object | field value |
|
||||
| call | `fn(arg)` | function call | function | function result |
|
||||
| `!` | `!expr` | unary logical | concrete `Bool` | concrete `Bool` |
|
||||
| `-` | `-expr` | unary arithmetic | concrete `Int` / `Float` | negated number |
|
||||
| `*` | `lhs * rhs` | arithmetic | concrete `Int` / `Float` | numeric product |
|
||||
| `/` | `lhs / rhs` | arithmetic | concrete `Int` / `Float` | `Float` quotient |
|
||||
| `+` | `lhs + rhs` | arithmetic | concrete `Int` / `Float` | numeric sum |
|
||||
| `-` | `lhs - rhs` | arithmetic | concrete `Int` / `Float` | numeric difference |
|
||||
| `++` | `lhs ++ rhs` | array concat | concrete arrays | concatenated array |
|
||||
| `==` | `lhs == rhs` | equality | concrete scalar | concrete `Bool` |
|
||||
| `!=` | `lhs != rhs` | equality | concrete scalar | concrete `Bool` |
|
||||
| `<` | `lhs < rhs` | ordering | concrete `Int` / `Float` | concrete `Bool` |
|
||||
| `<=` | `lhs <= rhs` | ordering | concrete `Int` / `Float` | concrete `Bool` |
|
||||
| `>` | `lhs > rhs` | ordering | concrete `Int` / `Float` | concrete `Bool` |
|
||||
| `>=` | `lhs >= rhs` | ordering | concrete `Int` / `Float` | concrete `Bool` |
|
||||
| `>` | `> value` | comparison constraint | numeric constraint value | abstract constraint |
|
||||
| `>=` | `>= value` | comparison constraint | numeric constraint value | abstract constraint |
|
||||
| `<` | `< value` | comparison constraint | numeric constraint value | abstract constraint |
|
||||
| `<=` | `<= value` | comparison constraint | numeric constraint value | abstract constraint |
|
||||
| `&&` | `lhs && rhs` | logical | concrete `Bool` | short-circuit AND |
|
||||
| `||` | `lhs || rhs` | logical | concrete `Bool` | short-circuit OR |
|
||||
| `&` | `lhs & rhs` | composition | value / constraint / object | constraint-preserving composition |
|
||||
| `//` | `lhs // rhs` | patch | object / value | right-biased structural patch |
|
||||
| `default` | `base default fallback` | default | abstract value | materialization fallback |
|
||||
| `as` | `narrower as wider` | range refinement | value / constraint / structure | narrower result plus untouched right-only ranges |
|
||||
|
||||
A concrete scalar is a `String`, `Bool`, `Int`, or `Float` value.
|
||||
|
||||
## Precedence
|
||||
|
||||
Precedence is highest first:
|
||||
|
||||
1. Function calls and field references
|
||||
2. Unary `!` and `-`
|
||||
3. `*` and `/`
|
||||
4. `+` and `-`
|
||||
5. `++`
|
||||
6. `==`, `!=`, `<`, `<=`, `>`, and `>=`
|
||||
7. `&&`
|
||||
8. `||`
|
||||
9. `&`
|
||||
10. `//`
|
||||
11. `default`
|
||||
12. `as`
|
||||
|
||||
Binary operators at the same precedence are left-associative.
|
||||
`default` is right-associative.
|
||||
|
||||
## Arithmetic operators
|
||||
|
||||
`+`, `-`, `*`, and `/` perform arithmetic on concrete `Int` and `Float` values.
|
||||
See [Arithmetic Expression](./expression/arithmetic.md) for details.
|
||||
|
||||
## Array concatenation operator
|
||||
|
||||
`++` concatenates two concrete arrays.
|
||||
It does not transform the elements; elements from the right side follow elements from the left.
|
||||
|
||||
```dcdl
|
||||
["read", "write"] ++ ["admin"]
|
||||
```
|
||||
|
||||
## Logical and comparison operators
|
||||
|
||||
`!`, `&&`, and `||` operate on concrete `Bool` values.
|
||||
`&&` and `||` short-circuit.
|
||||
|
||||
`==` and `!=` compare concrete scalar values.
|
||||
`<`, `<=`, `>`, and `>=` compare concrete numeric values.
|
||||
See [Logical and Comparison Expressions](./expression/logical-and-comparison.md) for details.
|
||||
|
||||
## `&`: constraint-preserving composition
|
||||
|
||||
`&` composes values, constraints, and structures.
|
||||
|
||||
```dcdl
|
||||
A & B
|
||||
```
|
||||
|
||||
The basic rules are:
|
||||
|
||||
- Two constraints become a constraint that satisfies both.
|
||||
- A constraint and a concrete value require the concrete value to satisfy the constraint.
|
||||
- Two identical concrete values produce that value.
|
||||
- Two different concrete values conflict.
|
||||
- Two objects are composed field by field.
|
||||
- A field present on both sides is composed with `&`.
|
||||
- A field present on only one side is retained unchanged.
|
||||
- Any contradiction produces an error.
|
||||
|
||||
For example:
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
NarrowedPort = Port & > 443;
|
||||
|
||||
MyConfig = {
|
||||
port = NarrowedPort default 8080;
|
||||
};
|
||||
|
||||
Config = MyConfig & {
|
||||
port = 8000;
|
||||
};
|
||||
```
|
||||
|
||||
Conceptually, `Config.port` becomes:
|
||||
|
||||
```text
|
||||
NarrowedPort & 8000
|
||||
```
|
||||
|
||||
This succeeds because `8000` satisfies `NarrowedPort`.
|
||||
The following fails:
|
||||
|
||||
```dcdl
|
||||
BadConfig = MyConfig & {
|
||||
port = 80;
|
||||
};
|
||||
```
|
||||
|
||||
`80` does not satisfy `> 443`.
|
||||
|
||||
## `//`: patch composition
|
||||
|
||||
`//` is a right-biased structural patch operator.
|
||||
Where `&` preserves constraints, `//` changes or overrides configuration and schemas.
|
||||
|
||||
```dcdl
|
||||
A // B
|
||||
```
|
||||
|
||||
The basic rules are:
|
||||
|
||||
- Two objects are patched recursively field by field.
|
||||
- The same field containing object/object is patched recursively.
|
||||
- The same field containing anything other than object/object is replaced by the right side.
|
||||
- Fields present only on the left are retained.
|
||||
- Fields present only on the right are added.
|
||||
- Arrays are replaced by the right side by default.
|
||||
- Function values are replaced by the right side by default.
|
||||
|
||||
Thus, `//` is a deep patch rather than a shallow merge.
|
||||
|
||||
```dcdl
|
||||
Base = {
|
||||
feature_hoge = {
|
||||
enable = Bool default true;
|
||||
fuga = Int default 10;
|
||||
};
|
||||
};
|
||||
|
||||
Patched = Base // {
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
`Patched` is equivalent to:
|
||||
|
||||
```dcdl
|
||||
{
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
fuga = Int default 10;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
The same patch can be written with a dot path:
|
||||
|
||||
```dcdl
|
||||
Patched = Base // {
|
||||
feature_hoge.enable = false;
|
||||
};
|
||||
```
|
||||
|
||||
## Replacing an entire object
|
||||
|
||||
Under `//`, object/object is always patched deeply.
|
||||
The core language does not include a special `replace(...)` syntax or built-in function for replacing an entire object field.
|
||||
|
||||
To use an entirely different object structure, construct the value outside the object being patched.
|
||||
|
||||
## `as`: range refinement
|
||||
|
||||
`as` is an asymmetric refinement operator that verifies the left side is more concrete and narrower than the right side.
|
||||
|
||||
```dcdl
|
||||
Refined = {
|
||||
port = 8000;
|
||||
} as {
|
||||
port = Int & >= 1 & <= 65535;
|
||||
host = String;
|
||||
enabled = Bool default true;
|
||||
};
|
||||
```
|
||||
|
||||
`port` becomes the concrete value `8000`.
|
||||
`host` and `enabled`, which exist only on the right, remain abstract in the result. Whether a field has a default does not affect preservation of a right-only field.
|
||||
`as` does not select defaults; normal default rules apply only if the result is later materialized.
|
||||
|
||||
A field present only in the left object lies outside the right-hand field domain and produces an error.
|
||||
Nested objects, array constraints, and map constraints present on both sides are refined recursively using the same rules.
|
||||
|
||||
Containment can also be checked between abstract ranges.
|
||||
|
||||
```dcdl
|
||||
Int & > 10 as Int & > 0 # succeeds and returns Int & > 10
|
||||
Int as Int & > 0 # fails
|
||||
```
|
||||
|
||||
`&` is symmetric and retains object fields found on only one side.
|
||||
Do not use it in place of directional refinement.
|
||||
See [Range Refinement](./expression/ascription.md) for details.
|
||||
|
||||
## Choosing between `&`, `//`, and `as`
|
||||
|
||||
Use `&` for symmetric composition that preserves constraints and partial structure.
|
||||
|
||||
```dcdl
|
||||
Combined = MyConfig & {
|
||||
port = 8000;
|
||||
};
|
||||
```
|
||||
|
||||
Use `//` to override or reshape an existing structure.
|
||||
|
||||
```dcdl
|
||||
ModifiedSchema = MyConfig // {
|
||||
port = Int default 9000;
|
||||
};
|
||||
```
|
||||
|
||||
Because `//` is a right-biased patch, it does not always preserve constraints from the left.
|
||||
Use `&` when those constraints must remain.
|
||||
|
||||
Use `as` to compose while verifying that the left side is narrower than the right.
|
||||
|
||||
```dcdl
|
||||
RefinedConfig = NarrowConfig as WideConfig;
|
||||
```
|
||||
@@ -0,0 +1,129 @@
|
||||
# Lexical Structure and Syntax
|
||||
|
||||
This chapter summarizes the surface syntax.
|
||||
See the EBNF in [Grammar](./grammar.md) for the precise grammar.
|
||||
|
||||
## Language name and extension
|
||||
|
||||
The project is named **Decodal**.
|
||||
Its full descriptive name is **Deferred Constraint Data Language**, abbreviated **DCDL**.
|
||||
Source files use the `.dcdl` extension.
|
||||
|
||||
```text
|
||||
config.dcdl
|
||||
schema.dcdl
|
||||
service.dcdl
|
||||
```
|
||||
|
||||
## Module source
|
||||
|
||||
An entire file can contain a single expression.
|
||||
A sequence of field definitions at the top level is treated as an implicit object.
|
||||
|
||||
```dcdl
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
```
|
||||
|
||||
The source above is equivalent to:
|
||||
|
||||
```dcdl
|
||||
{
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
}
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
A comment begins with `#` and continues to the end of the line.
|
||||
|
||||
```dcdl
|
||||
# comment
|
||||
host = "127.0.0.1"; # trailing comment
|
||||
```
|
||||
|
||||
## Semicolons
|
||||
|
||||
Semicolons separate object fields, `let` bindings, and `match` arms.
|
||||
A trailing semicolon is allowed.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
host = "127.0.0.1";
|
||||
port = 8000;
|
||||
}
|
||||
```
|
||||
|
||||
## Identifiers
|
||||
|
||||
An identifier begins with an ASCII letter and may continue with ASCII letters, digits, or `_`.
|
||||
Naming conventions can use `lower_snake`, `lowerCamel`, or `UpperCamel`.
|
||||
|
||||
```dcdl
|
||||
my_config
|
||||
mkConfig
|
||||
IPv4Address
|
||||
```
|
||||
|
||||
## Path references
|
||||
|
||||
Use a dot to reference a field.
|
||||
|
||||
```dcdl
|
||||
config.host
|
||||
config.feature_hoge.enable
|
||||
```
|
||||
|
||||
Fields inside an object can also be defined with a dot path.
|
||||
|
||||
```dcdl
|
||||
{
|
||||
feature_hoge.enable = false;
|
||||
}
|
||||
```
|
||||
|
||||
This represents the same structure as:
|
||||
|
||||
```dcdl
|
||||
{
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Reserved words
|
||||
|
||||
The following words are reserved:
|
||||
|
||||
```text
|
||||
let
|
||||
in
|
||||
match
|
||||
import
|
||||
default
|
||||
as
|
||||
true
|
||||
false
|
||||
```
|
||||
|
||||
## Operators
|
||||
|
||||
The primary operators are:
|
||||
|
||||
```text
|
||||
+ - * / arithmetic
|
||||
++ array concatenation
|
||||
! && || logical operations
|
||||
== != < <= > >= comparisons
|
||||
& constraint-preserving composition
|
||||
// patch composition
|
||||
default fallback specification
|
||||
as range containment check and refinement from left to right
|
||||
=> function
|
||||
. field reference / dot-path definition
|
||||
... value constraint for an array or associative array
|
||||
```
|
||||
|
||||
Operator precedence is defined in [Operators](./operators.md).
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bool
|
||||
|
||||
`Bool` is the primitive type constraint for Boolean values.
|
||||
|
||||
## Examples
|
||||
|
||||
```dcdl
|
||||
enable = Bool default true;
|
||||
disable = Bool default false;
|
||||
```
|
||||
|
||||
## Literals
|
||||
|
||||
`Bool` accepts the following literals:
|
||||
|
||||
```dcdl
|
||||
true
|
||||
false
|
||||
```
|
||||
@@ -0,0 +1,18 @@
|
||||
# Float
|
||||
|
||||
`Float` is the primitive type constraint for floating-point values.
|
||||
|
||||
## Examples
|
||||
|
||||
```dcdl
|
||||
ratio = Float;
|
||||
threshold = Float default 0.5;
|
||||
```
|
||||
|
||||
## Relationship to Int
|
||||
|
||||
`Int` and `Float` are distinct primitive type constraints.
|
||||
A `Float` constraint requires a concrete `Float`, and an `Int` constraint requires a concrete `Int`.
|
||||
|
||||
Arithmetic and comparison expressions can treat `Int` and `Float` as numeric values.
|
||||
Mixed arithmetic produces a `Float` result.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Value
|
||||
|
||||
This chapter defines the kinds of values handled by the language.
|
||||
|
||||
Primitive types are built-in constraints that values must satisfy, rather than ordinary data values.
|
||||
|
||||
```dcdl
|
||||
name = String;
|
||||
retry = Int default 3;
|
||||
ratio = Float;
|
||||
enable = Bool default true;
|
||||
tags = [...String];
|
||||
```
|
||||
|
||||
The primitive types are `String`, `Int`, `Float`, and `Bool`.
|
||||
`Unknown` is not a primitive type; it is the top abstract range containing every Decodal value. An `Unknown` without a concrete value or default cannot be materialized.
|
||||
An array is not a primitive type and is described as `[...T]` with a required element constraint.
|
||||
Links to each type specification are collected in the [manual contents](../../index.md).
|
||||
|
||||
See [Constraints and Defaults](../constraints-and-defaults.md) for details about primitive types and constraint composition.
|
||||
@@ -0,0 +1,18 @@
|
||||
# Int
|
||||
|
||||
`Int` is the primitive type constraint for integer values.
|
||||
|
||||
## Examples
|
||||
|
||||
```dcdl
|
||||
retry = Int default 3;
|
||||
port = Int & >= 1 & <= 65535;
|
||||
```
|
||||
|
||||
## Constraint composition
|
||||
|
||||
`Int` can be composed with numeric comparison constraints.
|
||||
|
||||
```dcdl
|
||||
NarrowedPort = Int & >= 1 & <= 65535 & > 443;
|
||||
```
|
||||
@@ -0,0 +1,30 @@
|
||||
# String
|
||||
|
||||
`String` is the primitive type constraint for string values.
|
||||
|
||||
A string literal is enclosed in `"`.
|
||||
|
||||
```dcdl
|
||||
"hello"
|
||||
"line 1\nline 2"
|
||||
```
|
||||
|
||||
`$`, `{`, and `}` have no special meaning inside a string; Decodal does not perform string interpolation.
|
||||
|
||||
## Examples
|
||||
|
||||
```dcdl
|
||||
name = String;
|
||||
greeting = String default "hello";
|
||||
```
|
||||
|
||||
## Constraint composition
|
||||
|
||||
`String` can be composed with string constraints.
|
||||
|
||||
```dcdl
|
||||
message = String & /Hello! .*/;
|
||||
```
|
||||
|
||||
Enable regex-constraint validation with the Rust crate's `regex` feature.
|
||||
Without that feature, a regex constraint cannot be validated against a concrete string and produces a diagnostic.
|
||||
Reference in New Issue
Block a user