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
+81
View File
@@ -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.
+193
View File
@@ -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.
+42
View File
@@ -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)
+54
View File
@@ -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.
+81
View File
@@ -0,0 +1,81 @@
# Packages and Integrations
Decodal は実行、ホスト組み込み、言語サービス、エディタ統合を用途別の package として提供する。
## Rust
### `decodal`
Rust アプリケーション向けの runtime と embedding API である。
source の parse・evaluate・materialize、host global、source または structured value の import を扱う。
```toml
[dependencies]
decodal = "0.4"
```
Rust struct から Decodal schema と decoder を生成する場合は `derive` feature を有効にする。
```toml
[dependencies]
decodal = { version = "0.4", features = ["derive"] }
```
正規表現制約を検証する場合は `regex` feature を有効にする。
この feature は `std` を必要とする。
### `decodal-language-service`
transport に依存しない semantic evaluation と completion を提供する。
アプリケーションの `HostEnvironment` をそのまま受け取るため、本番評価と編集時評価で global bindings と import 規則を共有できる。
### `decodal-lsp`
language service を Language Server Protocol に接続する。
stdio server のほか、client の `InitializeParams` からアプリケーション固有の environment を構築する library API を提供する。
### `decodal-language-tools`
source formatter など、source text を扱う共通ツールを提供する。
formatter は Rust、LSP、WebAssembly の各統合から同じ実装を利用できる。
## JavaScript and WebAssembly
### `decodal-wasm`
browser と WebAssembly 対応 runtime 向けの evaluator と language service である。
```sh
npm install decodal-wasm
```
JSR では `@hare/decodal-wasm` として提供される。
`DecodalLanguageService``globals``loadImport``completeImport` を渡すことで、JavaScript が所有する environment を評価と補完で共有できる。
### `decodal-codemirror`
CodeMirror 6 向けの language support である。
syntax highlighting、folding、indentation、Decodal formatter との統合を提供する。
```sh
npm install decodal-codemirror
```
JSR では `@hare/decodal-codemirror` として提供される。
semantic evaluation と completion が必要な場合は `decodal-wasm` の language service と組み合わせる。
## Editor syntax
Tree-sitter grammar は、Tree-sitter を採用するエディタで構文解析と highlighting を行うための統合である。
Decodal runtime、formatter、LSP の利用には Tree-sitter は必要ない。
用途ごとの選択は次の通りである。
- Rust での実行と組み込み: `decodal`
- browser での実行と semantic tooling: `decodal-wasm`
- CodeMirror 6: `decodal-codemirror`
- transport 非依存の Rust language service: `decodal-language-service`
- 一般的な editor client: `decodal-lsp`
- Tree-sitter 採用 editor の syntax grammar: Tree-sitter integration
具体的な environment の構築方法は [Embedding](./embedding.md) を参照する。
+193
View File
@@ -0,0 +1,193 @@
# Embedding
Decodal runtime は filesystem、network、environment variable を直接読み込まない。
ホストは global bindings と import の解決方法を注入し、評価結果を `Data` または application type として受け取る。
## Rust runtime
`Engine``ImportLoader` と global bindings を持つ。
外部 resource を必要としない source には `EmptyLoader` を使える。
```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` は concrete value と host-defined range の両方を表す公開型である。
主な constructor は次の通りである。
- `Value::string``int``float``bool``array``object`: concrete value。
- `Value::string_type``int_type``float_type``bool_type`: primitive range。
- `Value::unknown`: 最上位 range `Unknown`
- `Value::array_of`: 全要素へ同じ range を適用する array range。
- `Value::map_of`: 全 field value へ同じ range を適用する associative-array range。
- `Value::object_with_rest`: named fields と残りの field range を持つ object。
## Typed Rust integration
`derive` feature の `Decodal` derive は、Rust struct から `DecodalSchema``DecodalDecode` を生成する。
```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()``Engine::bind_global` に渡せる `Value` を返す。
materialize 済みの `Data``DecodalDecode::decodal_decode` で Rust type に変換できる。
追加 field を受け取る struct では、map field に `#[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>``...Unknown``BTreeMap<String, String>``...String` に対応する。
named fields は rest map から除外される。
`#[decodal(rest)]` は struct ごとに1つだけ指定でき、`rename``default`、field constraints とは併用できない。
## Imports
`ImportLoader::load``LoadedImport::Source` または `LoadedImport::Value` を返す。
ホストは specifier の解決、filesystem や他の storage からの読み込み、content type の判定をすべて管理する。
`LoadedImport::Source` は DCDL source を返す。
`LoadedImport::Value` は Markdown、JSON、TOML などをホスト独自の規則で構造化して返す用途に使える。
```text
import "./post.md"
-> host reads and parses Markdown
-> LoadedImport::Value {
key: "content/post.md",
value: { frontmatter: {...}, body: "..." }
}
-> normal Decodal validation and materialization
```
stable `key` は import の同一性と diagnostic の provenance に使われる。
構造化した値が Decodal constraint に違反した場合、diagnostic は import key と logical value path、および違反した DCDL constraint の source span を示す。
外部形式そのものの parse error と位置情報は loader が報告する。
## Shared host environment
`HostEnvironment` は loader の作成と global binding の設定を1つにまとめる。
同じ environment を runtime、language service、LSP へ渡すことで、実行時と編集時の評価規則を一致させられる。
```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(())
}
}
```
transport 非依存の tooling は `decodal_language_service::LanguageService::new(&environment)` で構築する。
LSP integration では `decodal_lsp::LspEnvironment` を実装し、environment factory から server を起動する。
```rust
use decodal_lsp::{LspEnvironment, run_stdio};
impl LspEnvironment for AppEnvironment {}
run_stdio(|initialize| {
let _ = initialize;
Ok(AppEnvironment)
})?;
```
factory は server capability の応答前に client の `InitializeParams` を一度受け取る。
`LspEnvironment` の document lifecycle hooks を使うと、未保存の DCDL、Markdown、その他の resource を host-owned overlay に反映できる。
## JavaScript and browser environments
`decodal-wasm``DecodalLanguageService` は filesystem や仮想 project を仮定しない。
JavaScript 側が `globals``loadImport``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 の primitive、array、plain object は concrete value になる。
range は `$decodal` descriptor で指定する。
- `{ $decodal: 'String' | 'Int' | 'Float' | 'Bool' }`
- `{ $decodal: 'Unknown' }`
- `{ $decodal: 'Array', item }`
- `{ $decodal: 'Map', value }`
- `{ $decodal: 'Object', fields, rest }`
- `{ $decodal: 'Range', constraints, default }`
import callbacks は同期 API である。
network resource などの非同期入力は、評価の前に preload または cache しておく。
+42
View File
@@ -0,0 +1,42 @@
# Decodal Manual
このマニュアルでは、Decodal の言語仕様と、アプリケーションへ組み込むための公開 API を説明する。
Decodal は Deferred Constraint Data Language、略称 DCDL のプロジェクト名である。
## 目次
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)
+54
View File
@@ -0,0 +1,54 @@
# Introduction
Decodal は **Deferred Constraint Data Language**、略称 **DCDL** である。
ファイル拡張子には `.dcdl` を使う。
Decodal は、設定値・スキーマ・制約・派生設定を同じ式体系で記述し、検証済みの構造化データを得るための言語である。
## 目的
一般的な設定システムでは、値、スキーマ、デフォルト値、派生設定、バリデーションが別々の仕組みになりやすい。
Decodal では、それらを合成可能な式として扱う。
```dcdl
Port = Int & >= 1 & <= 65535;
Service = {
host = String;
port = Port default 8080;
};
Config = {
host = "127.0.0.1";
port = 8000;
} as Service;
```
`Service` は許容する値の範囲を表す。
`Config` の各値は `Service` によって検証され、`Service` にだけ存在する範囲は抽象的なまま結果へ残る。
materialize 時には明示値が優先され、値がない範囲には `default` が使われる。
## 中心概念
- 値と制約は同じ式として参照・合成できる。
- `&` は両辺の制約を保つ対称な合成を行う。
- `as` は左辺が右辺より具体的で狭いことを検証しながら合成する。
- `//` は右辺優先の構造的な patch を行う。
- `default` は制約ではなく、materialize 時にだけ選ばれる fallback である。
- object field、関数引数、import は必要になった時点で評価される。
- `Unknown` は任意の具体値を受け入れる最上位 range だが、抽象的なまま materialize はできない。
## 適用範囲
Decodal は、設定、スキーマ、制約、派生データの記述に特化している。
汎用的な状態変更、時刻・乱数・ネットワークアクセス、例外を値として捕捉する `try / catch` は言語機能に含まれない。
filesystem や外部形式の読み込みはホストが提供し、Decodal の評価は同じ source・import 結果・global bindings に対して決定的に動作する。
正規表現制約は利用する runtime の `regex` feature に依存する。
高度な型推論、match の網羅性検査、到達不能分岐の静的検査は提供しない。
## 次に読む章
- [Language Specification](./language/index.md): 構文、値、式、制約、評価、materialization。
- [Embedding](./embedding.md): Rust・JavaScript からの実行、host environment、structured import、language service。
- [Packages and Integrations](./components.md): 用途ごとに選ぶ crate と JavaScript package。
@@ -0,0 +1,163 @@
# 制約と default
constraint は、値が満たすべき範囲を表す。
```dcdl
Int
String
>= 1
<= 65535
/Hello! .*/
```
constraint は `&` で合成できる。
```dcdl
Port = Int & >= 1 & <= 65535;
NarrowedPort = Port & > 443;
```
合成結果は両辺を同時に満たす範囲になる。
両立しない constraint は conflict になる。
```dcdl
Int & String # conflict
> 10 & < 5 # conflict
Int & > 10 & < 11 # conflict: integer candidate does not exist
```
## Primitive and comparison constraints
組み込みの primitive range は次の通りである。
```dcdl
String
Int
Float
Bool
```
異なる primitive type を `&` で合成すると conflict になる。
数値比較 constraint は上下限として合成され、空の範囲になる場合は conflict になる。
```dcdl
Int & >= 1 & <= 65535 & > 443
```
`Int` の比較 constraint は integer literal を使う。
`Float` の比較 constraint は integer または float literal を使える。
## Unknown
`Unknown` はすべての Decodal value を含む最上位 range である。
検査を無効化して具体値の情報を消す `Any` ではなく、値の範囲がまだ絞られていないことを表す。
```dcdl
Int & Unknown # Int
42 as Unknown # 42
Unknown as Int # conflict
```
`Unknown` 自体は concrete value を持たないため materialize できない。
concrete value で絞り込むか `default` を指定する必要がある。
```dcdl
Unknown default {}
```
## Regex constraints
regex literal は string constraint である。
```dcdl
Host = /^api-[0-9]+$/;
```
複数の regex constraint を合成した場合、concrete string はすべてに一致する必要がある。
```dcdl
String & /^a/ & /z$/
```
regex constraint 同士の交差は合成時には判定されない。
そのため、次の範囲は合成時には conflict にならず、concrete value の検証時に失敗する。
```dcdl
String & /^a$/ & /^b$/
```
Rust runtime では regex engine を `regex` feature で有効にする。
feature が無効な場合、regex constraint の concrete value 検証は unsupported feature diagnostic になる。
## Array constraints
array constraint は `[...T]` と書き、すべての要素へ `T` を適用する。
要素 constraint は必須である。
```dcdl
Names = [...String];
PositiveInts = [...(Int & > 0)];
```
concrete array と合成した場合、各要素は `T` に対して `as` と同じ規則で絞り込まれる。
空 array は任意の array constraint を満たす。
要素 constraint が object range の場合、右辺にだけある field は abstract のまま各要素へ残る。
左辺にしかない field は右辺の field domain 外なので conflict になる。
## Associative-array and object rest constraints
associative-array constraint は `{...T}` と書き、object の任意の field value へ `T` を適用する。
```dcdl
Services = {...{
port = Int;
enabled = Bool default true;
}};
```
key は列挙されず、空 object も許容される。
named field を持つ object の末尾へ `...T` を書くと、列挙されていない field だけに `T` を適用できる。
```dcdl
{
enabled = Bool default true;
...Unknown
}
```
rest constraint は field を生成しない。
実在する追加 field の検証にだけ使われる。
## default
`default` は constraint ではない。
materialize 時に concrete value がない場合だけ使われる fallback である。
```dcdl
Port = Int & >= 1 & <= 65535;
Service = {
port = Port default 8080;
};
```
明示値が合成された場合は明示値が使われ、`default` は評価されない。
```dcdl
Config = {
port = 9000;
} as Service;
```
明示値がない場合、materialize 時に `8080` が採用され、`Port` を満たすか検証される。
## default composition
- `&` で片方だけが `default` を持つ場合、その `default` は保持される。
- `&` で両辺が異なる `default` を持つ場合は conflict になる。
- constraint と concrete value の `&` が成功した場合、結果は concrete value になり `default` は残らない。
- `//` は右辺優先なので、同じ field では右辺の値または `default` が左辺を置き換える。
- `as` は右辺の `default` を左辺へ注入しない。ただし右辺にだけ存在する field は、その field が持つ `default` とともに abstract なまま結果へ残る。
`default` expression は materialize 時に必要になった時点で評価され、同じ range の constraint を満たす必要がある。
@@ -0,0 +1,48 @@
# 遅延評価
Decodal は値を必要になった時点で評価する。
## 遅延評価される値
次の値は参照または materialize されるまで評価されない。
- module の root と top-level field
- object field
- `let` binding
- function argument
- `default` expression
- import 先の値
同じ binding を複数回参照した場合、その評価結果は再利用される。
このため、未参照の field や function argument にある失敗は、値が必要になるまで発生しない。
```dcdl
let
safe = 42;
unused = missing_name;
in
safe
```
この式は `unused` を参照しないため `42` になる。
## 循環検出
評価中の値が自身へ再び依存した場合は cycle diagnostic になる。
```dcdl
{
a = b + 1;
b = a + 1;
}
```
module や import の参照関係自体が循環していても、実際に評価される field の依存関係が循環していなければ評価できる。
## 評価と materialize
通常の評価結果には、constraint、`Unknown``default`、function などの abstract value が残り得る。
外部へ concrete data として取り出すときに materialize を行う。
この分離により、schema を値として合成し、必要な field だけを評価し、`default` の選択を出力時まで遅らせられる。
詳細は [materialize とエラー](./materialization-and-errors.md) を参照する。
+166
View File
@@ -0,0 +1,166 @@
# 例
この章では、Decodal の主要な記法を組み合わせた例を示す。
## 基本的な設定スキーマ
```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;
```
## 配列スキーマ
```dcdl
Services = [...{
name = String;
port = Int default 8080;
}];
[
{ name = "api"; },
{ name = "worker"; port = 9000; },
] as Services
```
抽象配列には要素制約が必須である。
この例では、1 番目の要素の `port``8080` に materialize される。
## 連想配列と範囲絞り込み
```dcdl
Service = {
port = Int;
enabled = Bool default true;
};
services = {
api = { port = 8080; };
worker = { port = 8081; enabled = false; };
} as {...Service};
```
`api``worker` は任意の key であり、それぞれの value は `Service` より狭い範囲へ絞り込まれる。
`as` の結果では `api.enabled` は abstract のまま残り、最終的に結果全体を materialize した時点で default の `true` が使われる。
## 関数と制約
```dcdl
let
Port = Int & >= 1 & <= 65535;
add_offset = (base: Port, offset: Int) => base + offset;
in
add_offset(8000, 80)
```
評価結果:
```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` は上から順に評価されるため、広い条件より狭い条件を先に書く。
## deep patch
```dcdl
Base = {
feature_hoge = {
enable = Bool default true;
fuga = Int default 10;
};
};
Patched = Base // {
feature_hoge.enable = false;
};
```
`Patched` は以下に相当する。
```dcdl
{
feature_hoge = {
enable = false;
fuga = Int default 10;
};
}
```
## 循環 import
```dcdl
# main.dcdl
{
schema = {
hoge = String;
};
result = (import "./func.dcdl")(schema);
}
```
```dcdl
# func.dcdl
(input: (import "./main.dcdl").schema) =>
{
# ...
}
```
`func.dcdl``main.dcdl` を import しているが、参照しているのは `main.schema` である。
`main.schema``main.result` に依存していなければ、この循環 import は成立する。
@@ -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,71 @@
# Array Expression
array expression は、順序付きの値の列を表す。
```dcdl
[1, 2, 3]
["a", "b", "c"]
```
配列リテラルは concrete value であり、要素型を揃える必要はない。
```dcdl
["api", 8080, true]
```
## Array constraint
配列制約は `[` の直後に ellipsis を置き、その後へ要素制約を記述する。
```dcdl
Names = [...String];
Ports = [...(Int & >= 1 & <= 65535)];
```
`[...T]` は、すべての要素が `T` を満たす長さ 0 以上の配列を表す。
空配列は任意の配列制約を満たす。
```dcdl
[...String] & []
[...String] & ["api", "worker"]
```
要素制約は object range にもできる。
右辺だけにある default 付き field は、配列へ制約を合成した時点では abstract のまま各要素に残る。
その配列を後から materialize した場合にだけ default が使われる。
```dcdl
Services = [...{
name = String;
enabled = Bool default true;
}];
Services & [{ name = "api"; }]
```
配列制約を concrete array に適用すると、各要素は要素 range に対して `as` と同じ規則で絞り込まれる。
object の要素 range では左辺にしかない field がエラーになり、右辺にしかない field は abstract のまま残る。
要素制約のない抽象配列型は提供しない。
旧来の `Array` primitive type は使用できず、`[...T]``T` は必須である。
`[String]` は配列制約ではなく、未解決の `String` 制約を 1 要素に持つ concrete array になる。
配列制約は要素範囲だけを表す。長さ制約、位置別の tuple 制約、unique 制約は持たない。
## Array concat
`++` は concrete array 同士を連結する。
```dcdl
base = ["read", "write"];
extra = ["admin"];
roles = base ++ extra;
```
`roles` は以下と同じ値になる。
```dcdl
["read", "write", "admin"]
```
`++` は配列要素を変換しない。
左辺の要素の後に右辺の要素が並ぶ。
@@ -0,0 +1,75 @@
# Range Refinement
`narrower as wider` は、左辺が右辺より具体的で狭い範囲であることを確認し、両者を非対称に合成する演算である。
```dcdl
Int & > 10 as Int & > 0
```
この例は成功し、結果は左辺の狭い範囲 `Int & > 10` のままになる。逆向きの `Int as Int & > 0` は、左辺が右辺を満たすとは限らないため失敗する。
## Object
object では、右辺を field domain として左辺の field を確認する。
- 左辺にしかない field は、右辺の domain 外なのでエラーになる。
- 両側にある field は、左辺が右辺より狭いかを再帰的に確認し、左辺の狭い結果を使う。
- 右辺にしかない field は、default の有無に関係なく未評価のまま結果へ残す。
- 結果の field order は右辺の順序に従う。
```dcdl
partial = {
port = 8080;
} as {
host = String;
port = Int;
enabled = Bool default true;
};
```
`partial.port``8080` に具体化される。`partial.host``partial.enabled` は右辺由来の abstract field のままであり、`as``enabled` の default を選択も force もしない。
その後 `partial` 全体を materialize すれば、通常の materialize 規則が適用される。この例では unresolved な `host` がエラーになり、`host` も具体化されていれば `enabled` の default がその時点で利用される。
## Default
default は範囲包含の根拠ではない。両側に同じ field がある場合、結果には左辺を使うため、右辺の default を左辺へ注入しない。
```dcdl
Int & > 0 as (Int default 1)
```
結果は default のない `Int & > 0` である。一方、右辺だけに残る object field は field 全体を保持するため、その abstract value が元から持つ default も保持される。
## Map and array ranges
`{...T}` は任意の object key を許可し、各 value が `T` より狭いことを確認する。
```dcdl
ports = {
http = 80;
https = 443;
} as {...(Int & >= 1 & <= 65535)};
```
`[...T]` を右辺に使う場合も、すべての array element を `T` に対して絞り込む。
## Concrete right-hand ranges
primitive constraint や合成 constraint は通常どおり値を検証する。
右辺が concrete scalar または array literal の場合は、左辺も同じ値または同じ長さ・要素構造である必要がある。
function は右辺の範囲として使用できない。
左辺は concrete value に限らない。包含関係を判定できる constraint 同士であれば abstract value も使用できる。primitive type、numeric bound、同一 regex / predicate、array/map の要素範囲は包含確認の対象になる。
関数 parameter の `name: range` も、引数が force された時点で `as` と同じ絞り込み規則を使う。
## Precedence
`as``default` より低い、最も低い優先順位を持ち、左結合である。
```dcdl
narrow & overrides as Wider
```
これは `(narrow & overrides) as Wider` と解釈される。
@@ -0,0 +1,27 @@
# Composition Expression
composition expression は、複数の値・制約・構造を合成する式である。
## `&`
`&` は制約を保った合成を行う。
```dcdl
Port = Int & >= 1 & <= 65535;
Config = MyConfig & { port = 8000; };
```
object 同士では片側だけにある field も保持するため、`&` は方向を持たない。
左辺が右辺より狭いことを確認し、右辺を field domain として合成したい場合は [`as`](./ascription.md) を使う。
## `//`
`//` は右辺優先の構造的 patch を行う。
```dcdl
Patched = Base // {
feature_hoge.enable = false;
};
```
詳細は [合成演算子](../operators.md) を参照する。
@@ -0,0 +1,15 @@
# Default Expression
`default` expression は、明示値が存在しない場合に materialize 時に採用される fallback を指定する。
```dcdl
port = Int default 8080;
```
`default` は制約ではない。
詳細は [制約と default](../constraints-and-defaults.md) を参照する。
## 評価
fallback expression は materialize 時に必要になった場合だけ評価される。
明示値がある場合、`default` は評価も採用もされない。
@@ -0,0 +1,14 @@
# Function Call Expression
function call expression は、関数値を引数に適用する式である。
```dcdl
increment(41)
```
## 評価
引数は関数本体から参照された時点で評価される。
同じ引数を複数回参照した場合は評価結果が再利用される。
parameter に range が指定されている場合、引数は `narrower as wider` と同じ規則で絞り込まれる。
parameter 側だけにある field は abstract のまま残り、default はこの時点では選択されない。
@@ -0,0 +1,14 @@
# Function Expression
function expression は、引数を受け取り式を返す値である。
```dcdl
(value: Int) => value + 1
```
関数仕様の詳細は [関数](../functions.md) を参照する。
## 評価
関数は定義された lexical scope の bindings を参照する。
関数本体は、関数値の生成時ではなく呼び出し時に評価される。
@@ -0,0 +1,14 @@
# Identifier Expression
identifier expression は、lexical scope に束縛された名前を参照する式である。
```dcdl
Port
MyConfig
mkConfig
```
## 評価
識別子は対応する binding の値を必要になった時点で評価する。
束縛が存在しない場合は未定義識別子エラーになる。
@@ -0,0 +1,14 @@
# Import Expression
import expression は、ホストが解決した DCDL module または structured value を返す。
```dcdl
import "./config.dcdl"
```
specifier は string literal であり、path や resource name としての解釈はホストが定義する。
詳しくは [モジュールと import](../modules-and-imports.md) を参照する。
## 評価
import 先は遅延評価され、参照されない field は評価されない。
@@ -0,0 +1,26 @@
# Expression
この章では、言語が評価対象として持つ式の分類を定義する。
式は、値・制約・構造・計算を表す基本単位である。
```text
Expr
├─ literal
├─ identifier
├─ path reference
├─ object
├─ map constraint
├─ array
├─ array constraint
├─ function
├─ function call
├─ let
├─ match
├─ import
├─ composition
├─ range refinement (`as`)
└─ default
```
各式の個別仕様へのリンクは [Manual Index](../../index.md) に集約する。
@@ -0,0 +1,16 @@
# Let Expression
let expression は、ローカル束縛を作る。
```dcdl
let
base = 8000;
offset = 80;
in
base + offset
```
## 評価
binding は参照された時点で評価される。
参照されない binding は評価されず、同じ binding を複数回参照した場合は評価結果が再利用される。
@@ -0,0 +1,20 @@
# Literal Expression
literal expression は、ソース上に直接書かれる具体値である。
## 種類
```dcdl
"hello"
123
3.14
true
false
```
## 対応する primitive type
- 文字列リテラルは `String` を満たす。
- 整数リテラルは `Int` を満たす。
- 浮動小数リテラルは `Float` を満たす。
- `true` / `false``Bool` を満たす。
@@ -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,24 @@
# Match Expression
match expression は、対象値を上から順に pattern と照合し、最初に一致した分岐を採用する。
```dcdl
foo = match inputs.a.hoge {
>= 20: {
value = 200;
};
>= 10: {
value = 100;
};
_: {
value = 300;
};
};
```
`_` は fallback pattern である。
## 順序
分岐は順序付きであり、「最も具体的な pattern」を自動選択しない。
広い条件を先に書くと、後続の狭い条件には到達しない。
@@ -0,0 +1,88 @@
# Object Expression
object expression は、名前付き field の集合を表す。
```dcdl
{
host = "127.0.0.1";
port = 8000;
}
```
object は設定値にもスキーマにも使う。
```dcdl
MyConfig = {
host = String;
port = Int default 8080;
};
```
## Dot-path Field
ネストした field はドットパスでも定義できる。
```dcdl
{
feature_hoge.enable = false;
}
```
これは以下と同じ構造を表す。
```dcdl
{
feature_hoge = {
enable = false;
};
}
```
## Map constraint
連想配列の制約は、object の任意の field value に同じ schema を適用する。
```dcdl
Services = {...{
port = Int;
enabled = Bool default true;
}};
```
`{...T}` は key の集合を固定せず、すべての value が `T` を満たす object を表す。
空 object も許容される。materialize 後は named fields を持つ object と同じ object data になる。
```dcdl
services = {
api = { port = 8080; };
worker = { port = 8081; enabled = false; };
} as Services;
```
各 entry は `as` によって右辺の field domain 内へ絞り込まれるため、左辺に `port``enabled` 以外の field があればエラーになる。
右辺にしかない field は default の有無にかかわらず abstract のまま残る。
host から渡した object は識別子構文に収まらない文字列 key も保持できるが、DCDL source の object field name は通常の識別子に限られる。
## Object rest constraint
固定 field と任意 key の value range は、一つの object に混在できる。
```dcdl
OpenConfig = {
enabled = Bool default true;
...Unknown
};
```
`...T` は明示されていない残余 field にだけ適用する。明示 field にはそれぞれの field range を適用し、rest range を重ねて適用しない。
rest constraint は field を生成せず、materialize 時には実際に存在する追加 field だけを出力する。
```dcdl
{
enabled = false;
plugin = { name = "cache"; };
} as OpenConfig
```
`...T` は object の末尾に一つだけ書ける。省略した object は閉じており、`as` の左辺に未宣言 field があればエラーになる。
named field を持たない `{...T}` は abstract な map constraint であり、単独で materialize するには `default` または concrete value が必要になる。
@@ -0,0 +1,16 @@
# Path Reference Expression
path reference expression は、object のフィールドを参照する式である。
```dcdl
config.host
config.feature_hoge.enable
```
## 評価
左側の式を object として評価し、指定された field を参照する。
参照先 field は必要になるまで評価されない。
存在しない field への参照は diagnostic になる。
明示的な `Unknown` range は存在しないfieldを表さないため、fieldの有無を曖昧にはしない。
@@ -0,0 +1,41 @@
# 関数
関数は値を受け取り、値を返す純粋な式である。
## 構文
```dcdl
(value: Int) => value + 1
```
関数呼び出しは通常の呼び出し構文で行う。
```dcdl
let
increment = (value: Int) => value + 1;
in
increment(41)
```
複数の parameter を指定できる。
```dcdl
(input_a: { hoge = Int & >= 0; }, input_b: { fuga = Int; }) =>
{
hoge = input_a.hoge;
fuga = input_b.fuga;
}
```
parameter range は省略できる。
range がある場合、引数は参照された時点で `as` と同じ規則によって検証・絞り込みされる。
## Scope and evaluation
関数は lexical scope を持ち、定義された場所の bindings を参照する。
引数は遅延評価され、関数本体から参照されない引数は評価されない。
再帰的な field または argument の依存は cycle diagnostic になる。
関数は中間値として参照・呼び出しできるが、data として materialize できない。
未適用の関数が materialize 対象に残っている場合は diagnostic になる。
関数値同士の等価性は定義されず、`&` で関数値同士を合成すると conflict になる。
+113
View File
@@ -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` だけは右結合である。
+12
View File
@@ -0,0 +1,12 @@
# 言語仕様
この章では、Decodal source の構文と評価結果を決める規則を説明する。
- syntax と grammar
- primitive、object、array、function
- constraint、`Unknown``default`
- `&``//``as` とその他の operators
- module、import、lazy evaluation
- materialization と diagnostics
章の一覧は [Manual Index](../index.md) を参照する。
@@ -0,0 +1,73 @@
# materialize とエラー
通常の評価結果には constraint、`default`、function などが残り得る。
materialize は評価結果を外部へ渡せる concrete data に変換する。
## materialize の規則
materialize は次の処理を行う。
- 必要な field を評価する。
- 明示値のない abstract range に `default` を適用する。
- concrete value と採用した `default` が constraint を満たすか検証する。
- `default` のない `Unknown` や他の未解決 range を拒否する。
- 未適用の function など、data に変換できない値を拒否する。
```dcdl
Service = {
host = String;
port = Int default 8080;
};
```
`Service` をそのまま materialize すると、`host` に concrete value も `default` もないため失敗する。
```dcdl
Config = {
host = "localhost";
} as Service;
```
`Config` を materialize すると次の data になる。
```dcdl
{
host = "localhost";
port = 8080;
}
```
明示値がある field では `default` は採用されない。
## Diagnostics
エラーは通常の値ではなく diagnostic として返される。
式は diagnostic の種類や内容に基づいて分岐できない。
代表的な diagnostic は次の通りである。
- syntax error
- unresolved identifier または field
- type mismatch と constraint violation
- `&` または `default` の conflict
- cycle dependency
- import failure
- match failure
- materialization failure
diagnostic は問題のある DCDL source span を示す。
複数の式が conflict した場合は、関係する field、constraint、value、`default` の位置も示される。
structured import の値に source span がない場合は、host が返した stable key と logical value path が示される。
## Fallback
`match` に fallback arm がなく、どの arm にも一致しない場合は diagnostic になる。
```dcdl
match value {
>= 10: "large";
}
```
Decodal は diagnostic を捕捉する汎用 `try / catch`、optional import、optional field access を提供しない。
値がない場合の fallback は `default`、有限の値分岐は `match` で表現する。
@@ -0,0 +1,88 @@
# モジュールと import
`import` はホストが解決した DCDL module または structured value を返す。
## 構文
```dcdl
import "./config.dcdl"
```
import specifier は string literal である。
specifier が path、URL、resource name のどれを表すかはホストが決める。
## モジュール
top-level に field 定義列を書いた module は recursive module scope を作る。
top-level field は同じ module の他の top-level field から identifier として参照できる。
```dcdl
schema = {
name = String;
};
result = schema;
```
通常の object literal の field は sibling field を identifier として暗黙参照しない。
object 内の値を参照する場合は、外側で束縛された値または明示的な path reference を使う。
module とその field は遅延評価される。
import した module の未参照 field は評価されない。
## Host-defined resolution
Decodal は import specifier に対する filesystem や network の規則を定義しない。
ホストが import 元の module と specifier を受け取り、次のどちらかを返す。
- DCDL source
- ホストが構築した structured value
後者を使うと、Markdown、JSON、TOML などをホスト独自の規則で構造化し、通常の Decodal value として扱える。
```dcdl
Post = {
frontmatter = {
title = String;
draft = Bool default false;
};
body = String;
};
post = (import "./hello.md") as Post;
```
structured value も path reference、composition、constraint validation、materialization では DCDL source 由来の値と同じ規則に従う。
loader API と diagnostic provenance は [Embedding](../embedding.md#imports) を参照する。
## 循環 import
module 間に循環参照があっても、実際に評価される field の依存関係が循環していなければ成功する。
```dcdl
# main.dcdl
schema = {
name = String;
};
result = (import "./func.dcdl")(schema);
```
```dcdl
# func.dcdl
(input: (import "./main.dcdl").schema) => input
```
この例で `func.dcdl``main.dcdl` を import するが、参照する `schema``result` に依存しないため評価できる。
評価中の同じ field へ再び到達した場合は循環依存の diagnostic になる。
## import の失敗
次の状態は import failure になる。
- ホストが specifier を解決できない。
- resource を読み込めない。
- DCDL source の構文解析に失敗する。
- 必要な import 先の値を評価できない。
- structured value の読み込みまたは変換に失敗する。
- 評価対象の依存関係が循環する。
+17
View File
@@ -0,0 +1,17 @@
# 命名規約
identifier の大文字・小文字に言語上の意味はない。
値、constraint、schema、派生設定はいずれも同じ式として扱われる。
読みやすさのため、次の命名を推奨する。
- object value: `lower_snake`
- function: `lowerCamel`
- primitive、schema、抽象的な constraint: `UpperCamel`
```dcdl
Port = Int & >= 1;
Service = { port = Port; };
service = { port = 8080; } as Service;
mkService = (port: Port) => { port = port; };
```
+251
View File
@@ -0,0 +1,251 @@
# 演算子
この章では、Decodal の演算子の意味を定義する。
## 演算子一覧
| 演算子 | 形 | 種類 | 対象 | 結果 / 意味 |
|---|---|---|---|---|
| `.` | `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 |
`concrete scalar``String``Bool``Int``Float` を指す。
## 優先順位
優先順位は高い順に以下である。
1. 関数呼び出しとフィールド参照
2. unary `!` `-`
3. `*` `/`
4. `+` `-`
5. `++`
6. `==` `!=` `<` `<=` `>` `>=`
7. `&&`
8. `||`
9. `&`
10. `//`
11. `default`
12. `as`
同じ優先順位の二項演算子は左結合である。
`default` は右結合である。
## Arithmetic operators
`+` `-` `*` `/` は具体的な `Int` / `Float` に対する四則演算である。
詳しくは [Arithmetic Expression](./expression/arithmetic.md) を参照する。
## Array concat operator
`++` は concrete array 同士を連結する演算子である。
要素は変換されず、左辺の要素の後に右辺の要素が並ぶ。
```dcdl
["read", "write"] ++ ["admin"]
```
## Logical and comparison operators
`!` `&&` `||` は concrete `Bool` に対する論理演算である。
`&&``||` は短絡評価される。
`==` `!=` は concrete scalar value を比較する。
`<` `<=` `>` `>=` は concrete numeric value を比較する。
詳しくは [Logical and Comparison Expressions](./expression/logical-and-comparison.md) を参照する。
## `&`: 制約合成
`&` は値・制約・構造を合成する演算子である。
```dcdl
A & B
```
基本規則:
- 両方が制約なら、両方を満たす制約になる。
- 制約と具体値なら、具体値が制約を満たす必要がある。
- 両方が同じ具体値なら、その値になる。
- 両方が異なる具体値なら conflict になる。
- 両方が object なら、フィールドごとに合成する。
- 同じフィールドが両方にある場合、そのフィールド値を `&` で合成する。
- 片方にしかないフィールドは、そのまま結果に保持する。
- 矛盾が発生した場合はエラーになる。
例:
```dcdl
Port = Int & >= 1 & <= 65535;
NarrowedPort = Port & > 443;
MyConfig = {
port = NarrowedPort default 8080;
};
Config = MyConfig & {
port = 8000;
};
```
`Config.port` は概念的には以下になる。
```text
NarrowedPort & 8000
```
`8000``NarrowedPort` を満たすため成功する。
一方、以下は失敗する。
```dcdl
BadConfig = MyConfig & {
port = 80;
};
```
`80``> 443` を満たさないためである。
## `//`: patch 合成
`//` は右辺優先の構造的 patch 演算子である。
`&` が制約を保った合成であるのに対し、`//` は設定やスキーマを上書き・変更するために使う。
```dcdl
A // B
```
基本規則:
- 両方が object なら、フィールドごとに再帰的に patch する。
- 同じフィールドが object/object なら、さらに再帰的に patch する。
- 同じフィールドが object/object 以外なら、右辺で置き換える。
- 左辺にしかないフィールドは保持する。
- 右辺にしかないフィールドは追加する。
- 配列はデフォルトでは右辺で置き換える。
- 関数値はデフォルトでは右辺で置き換える。
つまり `//` は shallow merge ではなく deep patch とする。
```dcdl
Base = {
feature_hoge = {
enable = Bool default true;
fuga = Int default 10;
};
};
Patched = Base // {
feature_hoge = {
enable = false;
};
};
```
`Patched` は以下に相当する。
```dcdl
{
feature_hoge = {
enable = false;
fuga = Int default 10;
};
}
```
ドットパスを使うと以下のようにも書ける。
```dcdl
Patched = Base // {
feature_hoge.enable = false;
};
```
## object 全体の置換
`//` では object/object は常に deep patch される。
object field 全体を特別に置き換えるための `replace(...)` 構文や組み込み関数は core には含めない。
object 全体を別構造にしたい場合は、patch 対象より外側で値を作り直す。
## `as`: range refinement
`as` は左辺が右辺より具体的で狭い範囲であることを確認する、非対称な絞り込み演算子である。
```dcdl
Refined = {
port = 8000;
} as {
port = Int & >= 1 & <= 65535;
host = String;
enabled = Bool default true;
};
```
`port` は左辺の `8000` に具体化される。
右辺にしかない `host``enabled` は abstract のまま結果へ残る。default の有無は、右辺だけの field を保持するかどうかに影響しない。
`as` 自体は default を選択せず、後から結果を materialize した場合だけ通常の default 規則が働く。
左辺にしかない object field は右辺の field domain 外なのでエラーになる。
両側にある nested object、array constraint、map constraint は同じ規則で再帰的に絞り込む。
abstract range 同士も包含を確認できる。
```dcdl
Int & > 10 as Int & > 0 # 成功し、Int & > 10 を返す
Int as Int & > 0 # 失敗
```
`&` は対称な合成であり、片方だけにある object field を保持する。
したがって、左右に方向を持つ絞り込みを `&` で代用しない。
詳細は [Range Refinement](./expression/ascription.md) を参照する。
## `&``//``as` の使い分け
`&` は制約や部分構造を失わず、対称に合成する。
```dcdl
Combined = MyConfig & {
port = 8000;
};
```
`//` は既存構造の上書きや変形に使う。
```dcdl
ModifiedSchema = MyConfig // {
port = Int default 9000;
};
```
`//` は右辺優先の patch であり、左辺の制約を常に保持するとは限らない。
制約を保持したい場合は `&` を使う。
左辺が右辺より狭いことを検証しながら合成する場合は `as` を使う。
```dcdl
RefinedConfig = NarrowConfig as WideConfig;
```
+129
View File
@@ -0,0 +1,129 @@
# 構文と字句
この章では、表層構文の方針をまとめる。
厳密な構文は [Grammar](./grammar.md) の EBNF を参照する。
## 言語名と拡張子
プロジェクト名は **Decodal** とする。
正式な説明名は **Deferred Constraint Data Language**、略称は **DCDL** とする。
ファイル拡張子は `.dcdl` とする。
```text
config.dcdl
schema.dcdl
service.dcdl
```
## Module source
ファイル全体は単一の式として書ける。
また、top-level に field 定義列を書いた場合は、暗黙の object として扱う。
```dcdl
host = String;
port = Int default 8080;
```
上の source は以下と同じ意味である。
```dcdl
{
host = String;
port = Int default 8080;
}
```
## コメント
コメントは `#` から行末までとする。
```dcdl
# comment
host = "127.0.0.1"; # trailing comment
```
## セミコロン
オブジェクトフィールド、let 束縛、match 分岐はセミコロンで区切る。
末尾セミコロンは許可する。
```dcdl
{
host = "127.0.0.1";
port = 8000;
}
```
## 識別子
識別子は ASCII 英字で始まり、ASCII 英数字または `_` を続けられる。
命名規則には `lower_snake``lowerCamel``UpperCamel` を使用できる。
```dcdl
my_config
mkConfig
IPv4Address
```
## パス参照
ドットによるフィールド参照を許可する。
```dcdl
config.host
config.feature_hoge.enable
```
オブジェクト内では、ドットパスによるフィールド定義も許可する。
```dcdl
{
feature_hoge.enable = false;
}
```
これは以下と同じ構造を表す。
```dcdl
{
feature_hoge = {
enable = false;
};
}
```
## 予約語
以下は予約語として扱う。
```text
let
in
match
import
default
as
true
false
```
## 演算子
主要な演算子は以下である。
```text
+ - * / 四則演算
++ 配列結合
! && || 論理演算
== != < <= > >= 比較式
& 制約合成
// patch 合成
default fallback 指定
as 左辺から右辺への範囲包含確認と絞り込み
=> 関数
. フィールド参照 / ドットパス定義
... 配列または連想配列の値制約
```
演算子の優先順位は [合成演算子](./operators.md) で定義する。
@@ -0,0 +1,19 @@
# Bool
`Bool` は真偽値を表す primitive type constraint である。
## 例
```dcdl
enable = Bool default true;
disable = Bool default false;
```
## リテラル
`Bool` が受け入れるリテラルは以下である。
```dcdl
true
false
```
@@ -0,0 +1,18 @@
# Float
`Float` は浮動小数値を表す primitive type constraint である。
## 例
```dcdl
ratio = Float;
threshold = Float default 0.5;
```
## Int との関係
`Int``Float` は primitive type constraint としては別の型である。
`Float` constraint は concrete `Float` を要求し、`Int` constraint は concrete `Int` を要求する。
数値演算や比較式では `Int``Float` を同じ numeric value として扱える。
混在した四則演算の結果は `Float` になる。
@@ -0,0 +1,20 @@
# Value
この章では、言語が扱う値の分類を定義する。
primitive type は、通常のデータ値ではなく、値が満たすべき組み込み制約として扱う。
```dcdl
name = String;
retry = Int default 3;
ratio = Float;
enable = Bool default true;
tags = [...String];
```
primitive type は `String``Int``Float``Bool` である。
`Unknown` はprimitive typeではなく、すべてのDecodal値を含む最上位の抽象rangeである。具体値またはdefaultがない `Unknown` はmaterializeできない。
配列は primitive type ではなく、必須の要素制約を持つ `[...T]` で表現する。
各型の個別仕様へのリンクは [Manual Index](../../index.md) に集約する。
primitive type と制約合成の詳細は [制約と default](../constraints-and-defaults.md) も参照する。
@@ -0,0 +1,18 @@
# Int
`Int` は整数値を表す primitive type constraint である。
## 例
```dcdl
retry = Int default 3;
port = Int & >= 1 & <= 65535;
```
## 制約合成
数値比較制約と合成できる。
```dcdl
NarrowedPort = Int & >= 1 & <= 65535 & > 443;
```
@@ -0,0 +1,30 @@
# String
`String` は文字列値を表す primitive type constraint である。
文字列リテラルは `"` で囲む。
```dcdl
"hello"
"line 1\nline 2"
```
文字列内の `$``{}` に特別な意味はなく、文字列補間は行わない。
## 例
```dcdl
name = String;
greeting = String default "hello";
```
## 制約合成
`String` は文字列制約と合成できる。
```dcdl
message = String & /Hello! .*/;
```
正規表現制約の検証は Rust crate の `regex` feature で有効化される。
feature が無効な場合、正規表現制約は具体 string に対して検証できず diagnostic になる。
@@ -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.