Add bilingual manual and localized docs routes
This commit is contained in:
@@ -14,7 +14,8 @@ Development notes live here rather than in the public manual. The manual describ
|
||||
- `packages/decodal-codemirror`: CodeMirror language support and formatter WebAssembly.
|
||||
- `editors/lezer-decodal` and `editors/tree-sitter-decodal`: downstream editor grammars.
|
||||
- `site/decodal-site`: documentation site and playground.
|
||||
- `doc/manual/souce`: public manual consumed by the site.
|
||||
- `doc/manual/source`: English public manual consumed by the site.
|
||||
- `doc/manual/source/jp`: Japanese translation of the public manual.
|
||||
|
||||
## Runtime architecture
|
||||
|
||||
@@ -43,7 +44,7 @@ Use `npm --prefix site/decodal-site run build:wasm` when Rust bindings or format
|
||||
|
||||
When syntax changes:
|
||||
|
||||
1. Update the public EBNF in `doc/manual/souce/language/grammar.md` and the relevant language page.
|
||||
1. Update the public EBNF in `doc/manual/source/language/grammar.md` and the relevant language page.
|
||||
2. Update the Rust lexer/parser and tests.
|
||||
3. Update the Lezer and Tree-sitter grammars and regenerate their committed outputs.
|
||||
4. Update formatter, playground, and language-service tests as applicable.
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
# Packages and Integrations
|
||||
|
||||
Decodal provides separate packages for execution, host embedding, language services, and editor integration.
|
||||
|
||||
## Rust
|
||||
|
||||
### `decodal`
|
||||
|
||||
The runtime and embedding API for Rust applications.
|
||||
It handles parsing, evaluation, and materialization of source; host globals; and imports containing either source or structured values.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
decodal = "0.4"
|
||||
```
|
||||
|
||||
Enable the `derive` feature to generate a Decodal schema and decoder from a Rust struct.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
decodal = { version = "0.4", features = ["derive"] }
|
||||
```
|
||||
|
||||
Enable the `regex` feature to validate regular-expression constraints.
|
||||
This feature requires `std`.
|
||||
|
||||
### `decodal-language-service`
|
||||
|
||||
Provides transport-independent semantic evaluation and completion.
|
||||
It accepts an application's `HostEnvironment` directly, allowing production and editor evaluation to share global bindings and import rules.
|
||||
|
||||
### `decodal-lsp`
|
||||
|
||||
Connects the language service to the Language Server Protocol.
|
||||
In addition to a stdio server, it exposes a library API that constructs an application-specific environment from the client's `InitializeParams`.
|
||||
|
||||
### `decodal-language-tools`
|
||||
|
||||
Provides shared tools for source text, including the formatter.
|
||||
The Rust, LSP, and WebAssembly integrations all use the same formatter implementation.
|
||||
|
||||
## JavaScript and WebAssembly
|
||||
|
||||
### `decodal-wasm`
|
||||
|
||||
Provides the evaluator and language service for browsers and other WebAssembly runtimes.
|
||||
|
||||
```sh
|
||||
npm install decodal-wasm
|
||||
```
|
||||
|
||||
It is also published on JSR as `@hare/decodal-wasm`.
|
||||
Pass `globals`, `loadImport`, and `completeImport` to `DecodalLanguageService` so JavaScript-owned environments are shared by evaluation and completion.
|
||||
|
||||
### `decodal-codemirror`
|
||||
|
||||
Language support for CodeMirror 6.
|
||||
It provides syntax highlighting, folding, indentation, and integration with the Decodal formatter.
|
||||
|
||||
```sh
|
||||
npm install decodal-codemirror
|
||||
```
|
||||
|
||||
It is also published on JSR as `@hare/decodal-codemirror`.
|
||||
Combine it with the `decodal-wasm` language service when semantic evaluation and completion are required.
|
||||
|
||||
## Editor syntax
|
||||
|
||||
The Tree-sitter grammar is an integration for parsing and highlighting in editors that use Tree-sitter.
|
||||
Tree-sitter is not required by the Decodal runtime, formatter, or LSP.
|
||||
|
||||
Choose packages according to the integration you need:
|
||||
|
||||
- Execution and embedding in Rust: `decodal`
|
||||
- Execution and semantic tooling in a browser: `decodal-wasm`
|
||||
- CodeMirror 6: `decodal-codemirror`
|
||||
- Transport-independent Rust language service: `decodal-language-service`
|
||||
- General editor clients: `decodal-lsp`
|
||||
- Syntax grammar for editors that use Tree-sitter: the Tree-sitter integration
|
||||
|
||||
See [Embedding](./embedding.md) for concrete environment setup.
|
||||
@@ -0,0 +1,193 @@
|
||||
# Embedding
|
||||
|
||||
The Decodal runtime does not read filesystems, networks, or environment variables directly.
|
||||
The host injects global bindings and import resolution, then receives the evaluation result as `Data` or an application type.
|
||||
|
||||
## Rust runtime
|
||||
|
||||
`Engine` owns an `ImportLoader` and global bindings.
|
||||
Use `EmptyLoader` for source that does not require external resources.
|
||||
|
||||
```rust
|
||||
use decodal::{EmptyLoader, Engine, Value};
|
||||
|
||||
let mut engine = Engine::new(EmptyLoader);
|
||||
engine.bind_global(
|
||||
"Service",
|
||||
Value::object([
|
||||
("name", Value::string_type()),
|
||||
("port", Value::int_type().gt(443).default_int(8443)?),
|
||||
("enabled", Value::bool_type().default_bool(true)?),
|
||||
]),
|
||||
)?;
|
||||
|
||||
let module = engine.add_root_source(
|
||||
"service.dcdl",
|
||||
"service.dcdl",
|
||||
r#"{ name = "api"; port = 9443; } as Service"#,
|
||||
)?;
|
||||
let value = engine.eval_module(module)?;
|
||||
let data = engine.materialize(&value)?;
|
||||
```
|
||||
|
||||
`Value` is the public type for both concrete values and host-defined ranges.
|
||||
Its main constructors are:
|
||||
|
||||
- `Value::string`, `int`, `float`, `bool`, `array`, and `object`: concrete values.
|
||||
- `Value::string_type`, `int_type`, `float_type`, and `bool_type`: primitive ranges.
|
||||
- `Value::unknown`: the top range, `Unknown`.
|
||||
- `Value::array_of`: an array range that applies one range to every element.
|
||||
- `Value::map_of`: an associative-array range that applies one range to every field value.
|
||||
- `Value::object_with_rest`: an object with named fields and a range for remaining fields.
|
||||
|
||||
## Typed Rust integration
|
||||
|
||||
The `Decodal` derive from the `derive` feature generates `DecodalSchema` and `DecodalDecode` implementations from a Rust struct.
|
||||
|
||||
```rust
|
||||
use decodal::{Decodal, DecodalDecode, DecodalSchema};
|
||||
|
||||
#[derive(Decodal)]
|
||||
struct Service {
|
||||
name: String,
|
||||
|
||||
#[decodal(gt = 443, default = 8443)]
|
||||
port: i64,
|
||||
|
||||
#[decodal(rename = "feature.enable", default = true)]
|
||||
feature_enabled: bool,
|
||||
}
|
||||
```
|
||||
|
||||
`DecodalSchema::decodal_schema()` returns a `Value` suitable for `Engine::bind_global`.
|
||||
Materialized `Data` can be converted to a Rust type with `DecodalDecode::decodal_decode`.
|
||||
|
||||
For a struct that accepts additional fields, mark a map field with `#[decodal(rest)]`.
|
||||
|
||||
```rust
|
||||
use std::collections::BTreeMap;
|
||||
use decodal::{Data, Decodal};
|
||||
|
||||
#[derive(Decodal)]
|
||||
struct OpenConfig {
|
||||
enabled: bool,
|
||||
|
||||
#[decodal(rest)]
|
||||
extra: BTreeMap<String, Data>,
|
||||
}
|
||||
```
|
||||
|
||||
`BTreeMap<String, Data>` corresponds to `...Unknown`, while `BTreeMap<String, String>` corresponds to `...String`.
|
||||
Named fields are excluded from the rest map.
|
||||
Only one `#[decodal(rest)]` field is allowed per struct, and it cannot be combined with `rename`, `default`, or field constraints.
|
||||
|
||||
## Imports
|
||||
|
||||
`ImportLoader::load` returns either `LoadedImport::Source` or `LoadedImport::Value`.
|
||||
The host controls specifier resolution, reading from the filesystem or other storage, and content-type detection.
|
||||
|
||||
`LoadedImport::Source` supplies DCDL source.
|
||||
`LoadedImport::Value` is intended for structured host-defined representations of formats such as Markdown, JSON, and TOML.
|
||||
|
||||
```text
|
||||
import "./post.md"
|
||||
-> host reads and parses Markdown
|
||||
-> LoadedImport::Value {
|
||||
key: "content/post.md",
|
||||
value: { frontmatter: {...}, body: "..." }
|
||||
}
|
||||
-> normal Decodal validation and materialization
|
||||
```
|
||||
|
||||
A stable `key` identifies an import and supplies diagnostic provenance.
|
||||
If a structured value violates a Decodal constraint, the diagnostic identifies the import key, logical value path, and source span of the violated DCDL constraint.
|
||||
The loader reports parse errors and positions in the external format itself.
|
||||
|
||||
## Shared host environment
|
||||
|
||||
`HostEnvironment` combines loader construction and global-binding setup.
|
||||
Passing the same environment to the runtime, language service, and LSP keeps runtime and editor evaluation rules aligned.
|
||||
|
||||
```rust
|
||||
use decodal::{Engine, HostEnvironment};
|
||||
|
||||
struct AppEnvironment;
|
||||
|
||||
impl HostEnvironment for AppEnvironment {
|
||||
type Loader = ContentLoader;
|
||||
|
||||
fn create_loader(&self) -> Self::Loader {
|
||||
ContentLoader::new()
|
||||
}
|
||||
|
||||
fn configure_engine(
|
||||
&self,
|
||||
engine: &mut Engine<Self::Loader>,
|
||||
) -> decodal::Result<()> {
|
||||
engine.bind_global("Site", site_schema())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Construct transport-independent tooling with `decodal_language_service::LanguageService::new(&environment)`.
|
||||
For LSP integration, implement `decodal_lsp::LspEnvironment` and start the server from an environment factory.
|
||||
|
||||
```rust
|
||||
use decodal_lsp::{LspEnvironment, run_stdio};
|
||||
|
||||
impl LspEnvironment for AppEnvironment {}
|
||||
|
||||
run_stdio(|initialize| {
|
||||
let _ = initialize;
|
||||
Ok(AppEnvironment)
|
||||
})?;
|
||||
```
|
||||
|
||||
The factory receives the client's `InitializeParams` once, before the server responds with its capabilities.
|
||||
Use the `LspEnvironment` document lifecycle hooks to reflect unsaved DCDL, Markdown, and other resources in a host-owned overlay.
|
||||
|
||||
## JavaScript and browser environments
|
||||
|
||||
The `DecodalLanguageService` in `decodal-wasm` does not assume a filesystem or virtual project.
|
||||
JavaScript supplies `globals`, `loadImport`, and `completeImport`.
|
||||
|
||||
```js
|
||||
import init, { DecodalLanguageService } from 'decodal-wasm';
|
||||
|
||||
await init();
|
||||
|
||||
const files = {
|
||||
'schema.dcdl': 'Server = { port = Int; };',
|
||||
};
|
||||
|
||||
const service = new DecodalLanguageService({
|
||||
globals: {
|
||||
App: {
|
||||
enabled: { $decodal: 'Bool', default: true },
|
||||
},
|
||||
},
|
||||
loadImport(_currentKey, specifier) {
|
||||
const key = specifier.replace(/^\.\//, '');
|
||||
return { kind: 'source', key, name: key, source: files[key] };
|
||||
},
|
||||
completeImport() {
|
||||
return ['./schema.dcdl'];
|
||||
},
|
||||
});
|
||||
|
||||
const result = service.evaluate('main.dcdl', 'main.dcdl', 'App');
|
||||
```
|
||||
|
||||
JavaScript primitives, arrays, and plain objects become concrete values.
|
||||
Use `$decodal` descriptors for ranges:
|
||||
|
||||
- `{ $decodal: 'String' | 'Int' | 'Float' | 'Bool' }`
|
||||
- `{ $decodal: 'Unknown' }`
|
||||
- `{ $decodal: 'Array', item }`
|
||||
- `{ $decodal: 'Map', value }`
|
||||
- `{ $decodal: 'Object', fields, rest }`
|
||||
- `{ $decodal: 'Range', constraints, default }`
|
||||
|
||||
Import callbacks are synchronous.
|
||||
Preload or cache asynchronous inputs such as network resources before evaluation.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Decodal Manual
|
||||
|
||||
This manual describes the Decodal language and the public APIs for embedding it in applications.
|
||||
Decodal is the project name for the Deferred Constraint Data Language, abbreviated DCDL.
|
||||
|
||||
## Contents
|
||||
|
||||
1. [Introduction](./introduction.md)
|
||||
2. [Language Specification](./language/index.md)
|
||||
1. [Lexical Structure and Syntax](./language/syntax.md)
|
||||
2. [Grammar](./language/grammar.md)
|
||||
3. [Value](./language/value/index.md)
|
||||
1. [String](./language/value/string.md)
|
||||
2. [Int](./language/value/int.md)
|
||||
3. [Float](./language/value/float.md)
|
||||
4. [Bool](./language/value/bool.md)
|
||||
4. [Expression](./language/expression/index.md)
|
||||
1. [Literal](./language/expression/literal.md)
|
||||
2. [Identifier](./language/expression/identifier.md)
|
||||
3. [Path Reference](./language/expression/path-reference.md)
|
||||
4. [Object](./language/expression/object.md)
|
||||
5. [Array](./language/expression/array.md)
|
||||
6. [Function](./language/expression/function.md)
|
||||
7. [Function Call](./language/expression/function-call.md)
|
||||
8. [Let](./language/expression/let.md)
|
||||
9. [Match](./language/expression/match.md)
|
||||
10. [Import](./language/expression/import.md)
|
||||
11. [Composition](./language/expression/composition.md)
|
||||
12. [Range Refinement](./language/expression/ascription.md)
|
||||
13. [Default](./language/expression/default.md)
|
||||
14. [Arithmetic](./language/expression/arithmetic.md)
|
||||
15. [Logical and Comparison](./language/expression/logical-and-comparison.md)
|
||||
5. [Constraints and Defaults](./language/constraints-and-defaults.md)
|
||||
6. [Operators](./language/operators.md)
|
||||
7. [Functions](./language/functions.md)
|
||||
8. [Modules and Imports](./language/modules-and-imports.md)
|
||||
9. [Evaluation Semantics](./language/evaluation.md)
|
||||
10. [Materialization and Errors](./language/materialization-and-errors.md)
|
||||
11. [Naming](./language/naming.md)
|
||||
12. [Examples](./language/examples.md)
|
||||
3. [Embedding](./embedding.md)
|
||||
4. [Packages and Integrations](./components.md)
|
||||
@@ -0,0 +1,54 @@
|
||||
# Introduction
|
||||
|
||||
Decodal is the **Deferred Constraint Data Language**, abbreviated **DCDL**.
|
||||
Its source files use the `.dcdl` extension.
|
||||
|
||||
Decodal is a language for describing configuration values, schemas, constraints, and derived configuration in one expression system, producing validated structured data.
|
||||
|
||||
## Purpose
|
||||
|
||||
In conventional configuration systems, values, schemas, defaults, derived settings, and validation are often handled by separate mechanisms.
|
||||
Decodal treats them as composable expressions.
|
||||
|
||||
```dcdl
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
|
||||
Service = {
|
||||
host = String;
|
||||
port = Port default 8080;
|
||||
};
|
||||
|
||||
Config = {
|
||||
host = "127.0.0.1";
|
||||
port = 8000;
|
||||
} as Service;
|
||||
```
|
||||
|
||||
`Service` describes a range of accepted values.
|
||||
Each value in `Config` is validated against `Service`, while ranges that exist only in `Service` remain abstract in the result.
|
||||
During materialization, explicit values take precedence and `default` supplies values for ranges that remain unspecified.
|
||||
|
||||
## Core concepts
|
||||
|
||||
- Values and constraints are expressions that can be referenced and composed in the same way.
|
||||
- `&` performs symmetric composition and preserves constraints from both sides.
|
||||
- `as` verifies that the left side is more specific and narrower than the right side, then combines them.
|
||||
- `//` performs a right-biased structural patch, including overrides.
|
||||
- `default` is not a constraint. It is a fallback selected only during materialization.
|
||||
- Object fields, function arguments, and imports are evaluated when they are needed.
|
||||
- `Unknown` is the top range that accepts any concrete value, but it cannot be materialized while it remains abstract.
|
||||
|
||||
## Scope
|
||||
|
||||
Decodal is specialized for describing configuration, schemas, constraints, and derived data.
|
||||
General-purpose state mutation, time, randomness, network access, and `try / catch` for treating errors as values are not language features.
|
||||
The host provides filesystem and external-format access. Given the same source, import results, and global bindings, Decodal evaluation is deterministic.
|
||||
|
||||
Regular-expression constraints depend on the runtime's `regex` feature.
|
||||
Decodal does not provide advanced type inference, exhaustiveness checking for `match`, or static detection of unreachable branches.
|
||||
|
||||
## Continue reading
|
||||
|
||||
- [Language Specification](./language/index.md): syntax, values, expressions, constraints, evaluation, and materialization.
|
||||
- [Embedding](./embedding.md): Rust and JavaScript execution, host environments, structured imports, and language services.
|
||||
- [Packages and Integrations](./components.md): crates and JavaScript packages for each use case.
|
||||
@@ -0,0 +1,43 @@
|
||||
# 算術式
|
||||
|
||||
Decodal は具体的な数値に対する算術演算をサポートする。
|
||||
|
||||
```dcdl
|
||||
{
|
||||
workers = 2 + 2;
|
||||
timeout = 30.0 / 2;
|
||||
port = 8000 + 80;
|
||||
negative = -1;
|
||||
}
|
||||
```
|
||||
|
||||
## 演算子
|
||||
|
||||
- `+`: 加算
|
||||
- `-`: 減算
|
||||
- `*`: 乗算
|
||||
- `/`: 除算
|
||||
- 単項 `-`: 符号反転
|
||||
|
||||
`*` と `/` は `+` と `-` より高い優先順位を持つ。
|
||||
括弧を使ってグループ化を明示できる。
|
||||
|
||||
```dcdl
|
||||
2 + 3 * 4 # 14
|
||||
(2 + 3) * 4 # 20
|
||||
```
|
||||
|
||||
## 数値の扱い
|
||||
|
||||
算術演算には具体的な `Int` または `Float` の operand が必要である。
|
||||
`Int + Int`、`Int - Int`、`Int * Int` は、overflow が発生しない場合に `Int` を返す。
|
||||
`Int` と `Float` が混在する演算は `Float` を返す。
|
||||
除算は常に `Float` を返す。
|
||||
|
||||
ゼロ除算と整数 overflow は評価エラーになる。
|
||||
|
||||
算術式は、default や数値制約を含め、具体的な数値式が必要な任意の場所で使える。
|
||||
|
||||
```dcdl
|
||||
port = Int & > 4000 + 42 default 8080;
|
||||
```
|
||||
@@ -0,0 +1,41 @@
|
||||
# 論理式と比較式
|
||||
|
||||
Decodal は具体的な `Bool` に対する論理演算と、具体的な scalar value に対する比較をサポートする。
|
||||
|
||||
```dcdl
|
||||
{
|
||||
is_prod = env == "prod";
|
||||
high_port = port > 9000;
|
||||
enabled = is_prod && high_port;
|
||||
disabled = !enabled;
|
||||
}
|
||||
```
|
||||
|
||||
## 論理演算子
|
||||
|
||||
- `!expr` は具体的な `Bool` を反転する。
|
||||
- `lhs && rhs` は論理積を返す。
|
||||
- `lhs || rhs` は論理和を返す。
|
||||
|
||||
`&&` と `||` は短絡評価され、右辺は必要な場合にだけ評価される。
|
||||
論理演算の operand は具体的な `Bool` へ評価される必要がある。
|
||||
|
||||
## 比較演算子
|
||||
|
||||
- `==`
|
||||
- `!=`
|
||||
- `<`
|
||||
- `<=`
|
||||
- `>`
|
||||
- `>=`
|
||||
|
||||
`==` と `!=` は、`String`、`Bool`、`Int`、`Float` の具体的な scalar value を比較する。
|
||||
`Int` と `Float` は数値として相互に比較できる。
|
||||
|
||||
順序比較演算子 `<`、`<=`、`>`、`>=` は、具体的な数値だけを比較する。
|
||||
これらは `> 443` のような prefix comparison constraint とは別のものである。
|
||||
|
||||
```dcdl
|
||||
port = Int & > 443 default 9443;
|
||||
is_high = port > 9000;
|
||||
```
|
||||
@@ -0,0 +1,113 @@
|
||||
# 文法
|
||||
|
||||
このページでは、Decodal ソーステキストの文法を定義する。
|
||||
|
||||
## 字句文法
|
||||
|
||||
```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 ? ;
|
||||
```
|
||||
|
||||
空白とコメントは token を区切り、それ以外では parser に無視される。
|
||||
|
||||
## 構文文法
|
||||
|
||||
```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 } , [ "," ] ;
|
||||
```
|
||||
|
||||
## 優先順位
|
||||
|
||||
優先順位は高い順に以下の通りである。
|
||||
|
||||
1. 関数呼び出しとフィールドのパス参照
|
||||
2. 単項 `!` と `-`
|
||||
3. `*` と `/`
|
||||
4. `+` と `-`
|
||||
5. `++`
|
||||
6. `==`、`!=`、`<`、`<=`、`>`、`>=`
|
||||
7. `&&`
|
||||
8. `||`
|
||||
9. `&`
|
||||
10. `//`
|
||||
11. `default`
|
||||
12. `as`
|
||||
|
||||
二項演算子は左結合だが、`default` だけは右結合である。
|
||||
@@ -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,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,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,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