Add bilingual manual and localized docs routes

This commit is contained in:
2026-08-14 11:16:37 +09:00
parent 3645a2cc2d
commit cda32cc260
85 changed files with 2316 additions and 71 deletions
@@ -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.
+48
View File
@@ -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.
+166
View File
@@ -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.
+41
View File
@@ -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.
+113
View File
@@ -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.
+12
View File
@@ -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.
+17
View File
@@ -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; };
```
+250
View File
@@ -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;
```
+129
View File
@@ -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).
+19
View File
@@ -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
```
+18
View File
@@ -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.
+20
View File
@@ -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.
+18
View File
@@ -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.