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 は実行、ホスト組み込み、言語サービス、エディタ統合を用途別の 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 になる。