Refocus manual on public language usage

This commit is contained in:
2026-08-14 08:17:25 +09:00
parent e0a9a8efb7
commit e0970acdcc
41 changed files with 611 additions and 1982 deletions
@@ -1,10 +1,6 @@
# 制約と default
この章では、制約と `default` の意味を定義する
## 制約
制約は、値が満たすべき条件を表す。
constraint は、値が満たすべき範囲を表す
```dcdl
Int
@@ -14,148 +10,104 @@ String
/Hello! .*/
```
制約`&` により合成できる。
constraint `&` 合成できる。
```dcdl
Port = Int & >= 1 & <= 65535;
NarrowedPort = Port & > 443;
```
制約合成の意味は、すべての制約を同時に満たすことである。
```text
A & B = A と B の両方を満たす値または制約
```
矛盾する制約はエラーになる。
```dcdl
Int & String # エラー
> 10 & < 5 # エラー
Int & > 10 & < 11 # エラー。整数値の候補が存在しない
```
## 制約の正規化
`&` によって abstract value 同士を合成した場合、処理系は軽量に判定できる制約を正規化する。
正規化対象:
- primitive type 制約。
- 数値比較制約。
primitive type 制約は、異なる型が同時に要求された場合 conflict になる。
```dcdl
Int & Float
Int & String
```
数値比較制約は上下限として正規化される。
```dcdl
Int & >= 1 & <= 65535 & > 443
```
これは概念的に以下へ正規化される。
```text
Type(Int)
> 443
<= 65535
```
上下限の交差が空であれば conflict になる。
`Int` 制約がある場合は、整数候補が存在するかも判定する。
合成結果は両辺を同時に満たす範囲になる。
両立しない constraint は conflict になる。
```dcdl
Int & String # conflict
> 10 & < 5 # conflict
Int & > 10 & < 11 # conflict
Int & >= 10 & <= 10 # OK
Int & > 10 & < 11 # conflict: integer candidate does not exist
```
`Int` の比較制約は整数リテラルを使う。
`Float` の比較制約は整数リテラルまたは浮動小数リテラルを使える。
## Primitive and comparison constraints
## 組み込み制約
最小の組み込み制約は以下である。
組み込みの primitive range は次の通りである。
```dcdl
Unknown
String
Int
Float
Bool
```
`Unknown` はすべてのDecodal値を含む最上位rangeである。検査を無効化する `Any` ではなく、具体的な値またはより狭いrangeがまだ決まっていないことを表す
異なる 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 # エラー
Unknown as Int # conflict
```
`Unknown` 自体は具体値を持たないためmaterializeできない。defaultを与えるか、具体値で絞り込む必要がある。
`Unknown` 自体は concrete value を持たないため materialize できない。
concrete value で絞り込むか `default` を指定する必要がある。
```dcdl
Unknown default {} # {}
Unknown default {}
```
追加の述語制約はライブラリまたは組み込みとして提供できる。
## Regex constraints
regex literal は string constraint である。
```dcdl
IPv4Address
Host = /^api-[0-9]+$/;
```
## 正規表現制約
正規表現リテラルは文字列制約として使える。
```dcdl
Host = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
```
正規表現制約は積み重ね可能である。
複数の正規表現制約が同じ abstract value に付与された場合、具体文字列はすべての正規表現制約に一致しなければならない。
複数の regex constraint を合成した場合、concrete string はすべてに一致する必要がある。
```dcdl
String & /^a/ & /z$/
```
処理系は、正規表現制約同士の交差が空であるかを合成時に判定する必要はない。
つまり、以下は合成時には conflict にならず、具体値検証時に失敗する。
regex constraint 同士の交差は合成時に判定されない。
そのため、次の範囲は合成時には conflict にならず、concrete value の検証時に失敗する。
```dcdl
String & /^a$/ & /^b$/
```
正規表現エンジンは optional feature にできる。
正規表現 feature が無効な処理系では、正規表現制約の検証は unsupported feature diagnostic になる。
軽量実装では代表的な制約を組み込み述語として提供してもよい。
Rust runtime では regex engine を `regex` feature で有効にする。
feature が無効な場合、regex constraint の concrete value 検証は unsupported feature diagnostic になる。
```dcdl
Host = IPv4Address;
```
## Array constraints
## 配列制約
配列制約には要素制約が必須であり、`[...T]` と書く。
array constraint は `[...T]` と書き、すべての要素へ `T` を適用する。
要素 constraint は必須である。
```dcdl
Names = [...String];
PositiveInts = [...(Int & > 0)];
```
複数の配列制約を `&` で合成した場合、各 concrete 要素をすべての要素 range で絞り込
要素が object range の場合、右辺にしかない field は default の有無にかかわらず abstract のまま各要素へ残る
左辺にしかない field は右辺の field domain 外なのでエラーになる。
要素制約のない `Array` primitive type は存在しない。
concrete array と合成した場合、各要素は `T` に対して `as` と同じ規則で絞り込まれる
空 array は任意の array constraint を満たす
## 連想配列制約
要素 constraint が object range の場合、右辺にだけある field は abstract のまま各要素へ残る。
左辺にしかない field は右辺の field domain 外なので conflict になる。
連想配列制約は `{...T}` と書き、object の任意の field value を `T` に対して絞り込む。
## Associative-array and object rest constraints
associative-array constraint は `{...T}` と書き、object の任意の field value へ `T` を適用する。
```dcdl
Services = {...{
@@ -164,8 +116,8 @@ Services = {...{
}};
```
key は schema で列挙せず、空 object も許容る。
固定 object field と任意 key の value constraint は、末尾 `...T` で混在できる。
key は列挙されず、空 object も許容される。
named field を持つ object の末尾 `...T` を書くと、列挙されていない field だけに `T` を適用できる。
```dcdl
{
@@ -174,78 +126,38 @@ key は schema で列挙せず、空 object も許容する。
}
```
このrest constraintは明示されていないfieldだけに適用され、field自体は生成しない。
rest constraintfield生成しない。
実在する追加 field の検証にだけ使われる。
## default
`default`制約ではない。
`default` は、最終評価時に明示値が存在しない場合だけ使われる fallback である。
`default` constraint ではない。
materialize 時に concrete value がない場合だけ使われる fallback である。
```dcdl
port = NarrowedPort default 8080;
```
Port = Int & >= 1 & <= 65535;
これは概念的には以下を表す。
```text
Abstract {
constraints: [NarrowedPort]
default: 8080
}
```
明示値が合成された場合、`default` は採用されない。
```dcdl
MyConfig = {
port = NarrowedPort default 8080;
};
Config = MyConfig & {
port = 8000;
Service = {
port = Port default 8080;
};
```
この場合、最終値は `8000` である
`8080` は評価されない、または評価されても採用されない。
明示値が合成された場合は明示値が使われ、`default` は評価されない
明示値がない場合、最終 materialize 時に `default` が採用される。
採用された default 値は、同じフィールドに定義された制約を満たす必要がある。
```text
Abstract {
constraints: [NarrowedPort]
default: 8080
}
finalize => 8080 が NarrowedPort を満たせば成功
```dcdl
Config = {
port = 9000;
} as Service;
```
## default の内部表現
明示値がない場合、materialize 時に `8080` が採用され、`Port` を満たすか検証される。
`default` は abstract value に付随する fallback thunk として保持できる。
これにより、default 値自体も必要になるまで評価しない。
## default composition
```text
RuntimeValue =
Concrete(ConcreteValue)
Abstract {
constraints: Vec<Constraint>
default: Option<Thunk>
}
```
- `&` で片方だけが `default` を持つ場合、その `default` は保持される。
- `&` で両辺が異なる `default` を持つ場合は conflict になる。
- constraint と concrete value の `&` が成功した場合、結果は concrete value になり `default` は残らない。
- `//` は右辺優先なので、同じ field では右辺の値または `default` が左辺を置き換える。
- `as` は右辺の `default` を左辺へ注入しない。ただし右辺にだけ存在する field は、その field が持つ `default` とともに abstract なまま結果へ残る。
明示値は `Concrete` として表現し、`default` を保持しない
`Abstract & Concrete` が成功した場合、制約検証後に `Concrete` になり、default は消える。
## default の合成
`default` の合成規則は以下である。
- `&` で片方だけが default を持つ場合、その default を保持する。
- `&` で両方が異なる default を持つ場合、conflict になる。
- `Abstract & Concrete` が成功した場合、結果は concrete value になり default は消える。
- `//` では右辺が左辺を置き換える。object/object の場合は field ごとに再帰 patch されるため、右辺 field の default が左辺 field の default を置き換える。
`default` thunk は materialize 時に必要になった時点で評価する。
評価された default value は、同じ abstract value に残っている制約を満たす必要がある。
`default` expression は materialize 時に必要になった時点で評価され、同じ range の constraint を満たす必要がある
+28 -58
View File
@@ -1,51 +1,35 @@
# 遅延評価
この言語はフィールド単位で遅延評価する。
Decodal は値を必要になった時点で評価する。
## 基本方針
## 遅延評価される値
次の値は参照または materialize されるまで評価されない。
- module の root と top-level field
- object field
- `let` binding
- function argument
- `default` expression
- import 先の値
同じ binding を複数回参照した場合、その評価結果は再利用される。
このため、未参照の field や function argument にある失敗は、値が必要になるまで発生しない。
```dcdl
{
schema = {
hoge = String;
};
result = expensive(schema);
}
let
safe = 42;
unused = missing_name;
in
safe
```
`schema` のみが必要な場合、`result` は評価されない
## thunk
各フィールドや let 束縛は thunk として保持できる。
```text
Thunk {
expr: ExprId
env: EnvRef
state: Unevaluated | Evaluating | Evaluated(Value) | Error
}
```
評価済み thunk は memoize する。
同じフィールドを複数回参照しても、評価は一度だけでよい。
## 評価状態
thunk は以下の状態を持つ。
```text
Unevaluated 未評価
Evaluating 評価中
Evaluated 評価済み
Error 評価失敗
```
`Evaluating` の thunk を再度評価しようとした場合、循環依存として扱う。
この式は `unused` を参照しないため `42` になる
## 循環検出
評価中の値が自身へ再び依存した場合は cycle diagnostic になる。
```dcdl
{
a = b + 1;
@@ -53,26 +37,12 @@ Error 評価失敗
}
```
この場合、`a` または `b` を評価すると循環エラーになる。
module や import の参照関係自体が循環していても、実際に評価される field の依存関係が循環していなければ評価できる。
一方、同じモジュール内または import 間に循環があっても、評価対象のフィールドが循環していなければ成功する。
## 評価と materialize
## 評価と materialize の分離
通常の評価結果には、constraint、`Unknown``default`、function などの abstract value が残り得る。
外部へ concrete data として取り出すときに materialize を行う。
通常の評価では、制約や default を含む中間値が残ることがある。
外部へデータとして出力する段階で materialize を行う
この分離により、以下が可能になる。
- スキーマを値として扱う。
- default を必要になるまで評価しない。
- import されたモジュールの未使用フィールドを評価しない。
- 制約だけのフィールドを中間状態として保持する。
## 関数呼び出しとの関係
関数引数は thunk として渡せる。
関数本体内で引数が参照されたときに評価する。
関数呼び出し結果そのものはグローバルには memoize しない。
ただし、フィールドに束縛された呼び出し結果は、そのフィールド thunk の評価結果として memoize される。
この分離により、schema を値として合成し、必要な field だけを評価し、`default` の選択を出力時まで遅らせられる。
詳細は [materialize とエラー](./materialization-and-errors.md) を参照する
+7 -7
View File
@@ -1,11 +1,11 @@
# 例
この章は、仕様を説明するための例を置く
この章は、Decodal の主要な記法を組み合わせた例を示す
## 基本的な設定スキーマ
```dcdl
Host = IPv4Address;
Host = String;
Port = Int & >= 1 & <= 65535;
NarrowedPort = Port & > 443;
@@ -144,23 +144,23 @@ Patched = Base // {
## 循環 import
```dcdl
# main.n
# main.dcdl
{
schema = {
hoge = String;
};
result = (import ./func.n)(schema);
result = (import "./func.dcdl")(schema);
}
```
```dcdl
# func.n
(input: (import ./main.n).schema) =>
# func.dcdl
(input: (import "./main.dcdl").schema) =>
{
# ...
}
```
`func.n``main.n` を import しているが、参照しているのは `main.schema` である。
`func.dcdl``main.dcdl` を import しているが、参照しているのは `main.schema` である。
`main.schema``main.result` に依存していなければ、この循環 import は成立する。
@@ -49,7 +49,7 @@ object の要素 range では左辺にしかない field がエラーになり
要素制約のない抽象配列型は提供しない。
旧来の `Array` primitive type は使用できず、`[...T]``T` は必須である。
`[String]` は配列制約ではなく、未解決の `String` 制約を 1 要素に持つ concrete array になる。
長さ制約、位置別の tuple 制約、unique 制約は現在サポートしない。
配列制約は要素範囲だけを表す。長さ制約、位置別の tuple 制約、unique 制約は持たない。
## Array concat
@@ -60,7 +60,7 @@ primitive constraint や合成 constraint は通常どおり値を検証する
右辺が concrete scalar または array literal の場合は、左辺も同じ値または同じ長さ・要素構造である必要がある。
function は右辺の範囲として使用できない。
左辺は concrete value に限らない。処理系が包含を確認できる constraint 同士であれば abstract value も使用できる。primitive type、numeric bound、同一 regex / predicate、array/map の要素範囲は包含確認の対象になる。
左辺は concrete value に限らない。包含関係を判定できる constraint 同士であれば abstract value も使用できる。primitive type、numeric bound、同一 regex / predicate、array/map の要素範囲は包含確認の対象になる。
関数 parameter の `name: range` も、引数が force された時点で `as` と同じ絞り込み規則を使う。
@@ -24,4 +24,4 @@ Patched = Base // {
};
```
詳細は [合成演算子](../operators.md) に置く
詳細は [合成演算子](../operators.md) を参照する
@@ -7,9 +7,9 @@ port = Int default 8080;
```
`default` は制約ではない。
詳細は [制約と default](../constraints-and-defaults.md) に置く
詳細は [制約と default](../constraints-and-defaults.md) を参照する
## 評価
fallback 値は thunk として保持できる。
明示値がある場合、default採用されない。
fallback expression は materialize 時に必要になった場合だけ評価される。
明示値がある場合、`default` は評価も採用されない。
@@ -8,10 +8,7 @@ increment(41)
## 評価
引数は thunk として渡せる。
関数本体内で引数が参照されたときに評価する。
引数は関数本体から参照された時点で評価される。
同じ引数を複数回参照した場合は評価結果が再利用される。
parameter に range が指定されている場合、引数は `narrower as wider` と同じ規則で絞り込まれる。
parameter 側だけにある field は abstract のまま残り、default はこの時点では選択されない。
関数呼び出し結果そのものはグローバルには memoize しない。
フィールドに束縛された呼び出し結果は、そのフィールド thunk の評価結果として memoize される。
@@ -6,9 +6,9 @@ function expression は、引数を受け取り式を返す値である。
(value: Int) => value + 1
```
関数仕様の詳細は [関数](../functions.md) に置く
関数仕様の詳細は [関数](../functions.md) を参照する
## 評価
関数は定義時の環境を参照として保持する。
関数は定義された lexical scope の bindings を参照する。
関数本体は、関数値の生成時ではなく呼び出し時に評価される。
@@ -1,6 +1,6 @@
# Identifier Expression
identifier expression は、現在の環境に束縛された名前を参照する式である。
identifier expression は、lexical scope に束縛された名前を参照する式である。
```dcdl
Port
@@ -10,5 +10,5 @@ mkConfig
## 評価
識別子は対応する束縛の thunk を参照する。
識別子は対応する binding の値を必要になった時点で評価する。
束縛が存在しない場合は未定義識別子エラーになる。
@@ -1,15 +1,14 @@
# Import Expression
import expression は、外部ファイルを読み込み、そのファイルの評価結果を返す。
import expression は、ホストが解決した DCDL module または structured value を返す。
```dcdl
import ./config.n
import "./config.n"
import "./config.dcdl"
```
import 仕様の詳細は [モジュールと import](../modules-and-imports.md) に置く
specifier は string literal であり、path や resource name としての解釈はホストが定義する
詳しくは [モジュールと import](../modules-and-imports.md) を参照する。
## 評価
import 先はモジュール単位で読み込まれる
ただし、各 field は thunk として保持され、必要になるまで評価されない。
import 先は遅延評価され、参照されない field は評価されない
@@ -20,8 +20,7 @@ Expr
├─ import
├─ composition
├─ range refinement (`as`)
─ default
└─ string interpolation
─ default
```
各式の個別仕様へのリンクは [Manual Index](../../index.md) に集約する。
+2 -2
View File
@@ -12,5 +12,5 @@ in
## 評価
let 束縛は thunk として保持される。
参照されない束縛は評価されない
binding は参照された時点で評価される。
参照されない binding は評価されず、同じ binding を複数回参照した場合は評価結果が再利用される
@@ -50,7 +50,7 @@ Services = {...{
```
`{...T}` は key の集合を固定せず、すべての value が `T` を満たす object を表す。
空 object も許容される。runtime と materialize 後の表現は通常の object と共通であり、別の map value variant は持たない
空 object も許容される。materialize 後は named fields を持つ object と同じ object data になる
```dcdl
services = {
@@ -85,4 +85,4 @@ rest constraint は field を生成せず、materialize 時には実際に存在
```
`...T` は object の末尾に一つだけ書ける。省略した object は閉じており、`as` の左辺に未宣言 field があればエラーになる。
名前付き field を持たない `{...T}`従来どおり抽象的な map constraint であり、単独でmaterializeするにはdefaultまたは具体値が必要になる。
named field を持たない `{...T}` abstract な map constraint であり、単独で materialize するには `default` または concrete value が必要になる。
@@ -1,8 +0,0 @@
# String Interpolation
文字列補間は初期実装には含めない。
Decodal の string literal は、現時点では literal text として扱う。
式を埋め込む構文は定義しない。
必要になった場合は、文字列連結や明示的な formatting function として別途設計する。
+11 -29
View File
@@ -1,6 +1,6 @@
# 関数
関数は構造を受け取り、構造を返す式として扱う
関数はを受け取り、を返す純粋な式である
## 構文
@@ -17,7 +17,7 @@ in
increment(41)
```
複数引数も指定できる。
複数の parameter を指定できる。
```dcdl
(input_a: { hoge = Int & >= 0; }, input_b: { fuga = Int; }) =>
@@ -27,33 +27,15 @@ in
}
```
## 関数の意味
parameter range は省略できる。
range がある場合、引数は参照された時点で `as` と同じ規則によって検証・絞り込みされる。
関数は runtime value として扱えるが、最終データとして materialize することはできない。
未適用の関数値が materialize 対象に残っている場合は diagnostic になる。
## Scope and evaluation
関数opaque であり、関数値同士の等価性は提供しない
`&` で関数値同士を合成すると conflict になる
関数は lexical scope を持ち、定義された場所の bindings を参照する
引数は遅延評価され、関数本体から参照されない引数は評価されない
再帰的な field または argument の依存は cycle diagnostic になる。
## 評価方針
関数は以下の方針で評価する。
- 関数は純粋である。
- 関数はレキシカルスコープを持つ。
- 関数は定義時の環境を参照として保持する。
- 引数は thunk として渡され、必要になるまで評価されない。
- parameter range がある引数は、force 時に `as` と同じ範囲包含・絞り込み規則で合成される。
- 関数呼び出し結果そのものはグローバルには memoize しない。
- フィールドに束縛された関数呼び出し結果は、そのフィールド thunk の評価結果として memoize される。
- 再帰的な依存は thunk cycle として diagnostic になる。
関数値の内部モデル例:
```text
Function {
params: Vec<Param>
body: ExprId
env: EnvRef
}
```
関数は中間値として参照・呼び出しできるが、data として materialize できない。
未適用の関数が materialize 対象に残っている場合は diagnostic になる。
関数値同士の等価性は定義されず、`&` で関数値同士を合成すると conflict になる。
+1 -7
View File
@@ -1,7 +1,6 @@
# Grammar
This page is the canonical grammar reference for Decodal syntax.
Parser implementations such as the Rust parser, Tree-sitter grammar, and Lezer grammar should follow this grammar and may add implementation-specific precedence annotations where needed.
This page defines the grammar of Decodal source text.
## Lexical grammar
@@ -112,8 +111,3 @@ Precedence is highest first.
12. `as`
Binary operators are left-associative except `default`, which is right-associative.
## Tooling mapping
Syntax tooling should derive token categories from this grammar rather than making a tool-specific grammar canonical.
Tree-sitter and Lezer grammars are implementation artifacts that follow this page.
+8 -8
View File
@@ -1,12 +1,12 @@
# 言語仕様
このディレクトリは、Decodal / DCDL の仕様本文を章ごとに分割して管理する。
この章では、Decodal source の構文と評価結果を決める規則を説明する。
目次はマニュアル直下の [Manual Index](../index.md) に集約する。
このファイルは `Language Specification` 章の入口としてだけ使う。
- syntax と grammar
- primitive、object、array、function
- constraint、`Unknown``default`
- `&``//``as` とその他の operators
- module、import、lazy evaluation
- materialization と diagnostics
## Language
言語仕様は、構文、値、式、制約、合成演算子、関数、モジュール、評価意味論、materialize、エラーを定義する。
詳細な章構成と各ファイルへのリンクは [Manual Index](../index.md) を参照する。
章の一覧は [Manual Index](../index.md) を参照する。
@@ -1,37 +1,34 @@
# materialize とエラー
通常の評価では、制約や default を含む中間値が残ることがある。
外部へデータとして出力する段階では、materialize を行う
通常の評価結果には constraint、`default`、function などが残り得る。
materialize は評価結果を外部へ渡せる concrete data に変換する
## materialize の責務
## materialize の規則
materialize は以下を行う。
materialize は次の処理を行う。
- 必要なフィールドを評価する。
- 明示値ない abstract value に default を適用する。
- 採用された値が制約を満たすか検証する。
- default を持たない未解決の abstract value をエラーにする。
- 未適用の関数など、データとして出力できない値をエラーにする。
## 例
- 必要な field を評価する。
- 明示値ない abstract range に `default` を適用する。
- concrete value と採用した `default` が constraint を満たすか検証する。
- `default` のない `Unknown` や他の未解決 range を拒否する。
- 未適用の function など、data に変換できない値を拒否する。
```dcdl
MyConfig = {
Service = {
host = String;
port = Int default 8080;
};
```
`MyConfig` materialize すると、`host` は具体値も default もないためエラーになる。
`port``8080` が採用される。
`Service` をそのまま materialize すると、`host` に concrete value も `default` もないため失敗する。
```dcdl
Config = MyConfig & {
Config = {
host = "localhost";
};
} as Service;
```
`Config` を materialize すると以下になる。
`Config` を materialize すると次の data になる。
```dcdl
{
@@ -40,46 +37,31 @@ Config = MyConfig & {
}
```
## default の適用
明示値がある field では `default` は採用されない。
`default` は materialize 時にのみ fallback として採用される。
## Diagnostics
```text
Abstract {
constraints: [Int]
default: 8080
}
```
エラーは通常の値ではなく diagnostic として返される。
式は diagnostic の種類や内容に基づいて分岐できない。
この abstract value を materialize すると、`8080` が採用され、`Int` を満たすか検証される。
代表的な diagnostic は次の通りである。
明示値がある場合、default は採用しない。
明示値は concrete value として表現され、default を保持しない。
- syntax error
- unresolved identifier または field
- type mismatch と constraint violation
- `&` または `default` の conflict
- cycle dependency
- import failure
- match failure
- materialization failure
```text
Concrete(Int(9000))
```
diagnostic は問題のある DCDL source span を示す。
複数の式が conflict した場合は、関係する field、constraint、value、`default` の位置も示される。
structured import の値に source span がない場合は、host が返した stable key と logical value path が示される。
この値の最終値は `9000` である。
## Fallback
## エラー分類
代表的なエラー:
- 構文エラー
- 未定義識別子
- 型不一致
- 制約違反
- `&` の conflict
- `default` の conflict
- 循環依存
- import 失敗
- match の非網羅による失敗
- materialize 不能な値の出力
## match の失敗
`match` に fallback 分岐がなく、どの分岐にも一致しなかった場合はエラーになる。
`match` に fallback arm がなく、どの arm にも一致しない場合は diagnostic になる。
```dcdl
match value {
@@ -87,13 +69,5 @@ match value {
}
```
`value``10` 未満であれば失敗する
## エラーは値ではない
エラーは runtime value ではなく diagnostic として扱う。
通常の式はエラー内容に基づいて分岐できない。
汎用 `try / catch` は core には含めない。
fallback は `default``match` で表現する。
optional import や optional field access は core には含めない。
Decodal は diagnostic を捕捉する汎用 `try / catch`、optional import、optional field access を提供しない
値がない場合の fallback は `default`、有限の値分岐は `match` で表現する。
+35 -106
View File
@@ -1,6 +1,6 @@
# モジュールと import
`import`外部ファイルを読み込み、そのファイルの評価結果を返す。
`import`ホストが解決した DCDL module または structured value を返す。
## 構文
@@ -8,79 +8,37 @@
import "./config.dcdl"
```
import specifier は文字列リテラルとする。
パスリテラル構文は採用しない
import specifier は string literal である。
specifier が path、URL、resource name のどれを表すかはホストが決める
## モジュール
import 先はモジュール単位で読み込まれる。
ただし、モジュール全体を即時評価する必要はない
各フィールドは thunk として保持され、必要になったときだけ評価される。
top-level に field 定義列を書いた module は、recursive module scope を作る。
つまり、top-level field は同じ module の他の top-level field から識別子として参照できる。
top-level に field 定義列を書いた module は recursive module scope を作る。
top-level field は同じ module の他の top-level field から identifier として参照できる
```dcdl
schema = {
hoge = String;
name = String;
};
result = schema;
```
この場合、`result` の右辺の `schema` は同じ module の top-level field `schema` を参照する
通常の object literal 内の field は、その object 内の sibling field を暗黙には識別子として参照できない
object 内の値を参照する場合は、外側で束縛された値や明示的な path reference を使う。
通常の object literal の field は sibling field を identifier として暗黙参照しない
object 内の値を参照する場合は、外側で束縛された値または明示的な path reference を使う
## ImportLoader
module とその field は遅延評価される。
import した module の未参照 field は評価されない。
`import` specifier の解決は処理系 core ではなく host 側の `ImportLoader` が行う。
CLI では、specifier を現在の module path からの相対 path として解決する。
組み込み利用では、resource table や static source map など、filesystem 以外の loader を使える。
## Host-defined resolution
module cache の key は loader が返す安定 key を使う
CLI では canonical path を key とする
Decodal は import specifier に対する filesystem や network の規則を定義しない
ホストが import 元の module と specifier を受け取り、次のどちらかを返す
### 構造化 import
- DCDL source
- ホストが構築した structured value
`ImportLoader::load` は DCDL source または host が構築した `Value` を import 結果として返す
Markdown、JSON、TOML などの解釈規則は core に固定せず、loader がファイル種別を判定して構造化する。
```rust
use decodal::{Value, ImportLoader, LoadedImport};
struct ContentLoader;
impl ImportLoader for ContentLoader {
fn load(
&mut self,
current_key: Option<&str>,
specifier: &str,
) -> decodal::Result<LoadedImport> {
if specifier.ends_with(".md") {
let markdown = read_content(current_key, specifier)?;
let parsed = parse_frontmatter(&markdown)?;
return Ok(LoadedImport::value(
parsed.key,
Value::object([
("frontmatter", parsed.frontmatter),
("body", Value::string(parsed.body)),
]),
));
}
let source = read_dcdl(current_key, specifier)?;
Ok(LoadedImport::source(
source.key,
specifier,
source.text,
))
}
}
```
`read_content``parse_frontmatter` は host 独自の処理であり、Decodal core は Markdown や YAML parser に依存しない。
上の loader を使うと、DCDL 側から次のように扱える。
後者を使うと、Markdown、JSON、TOML などをホスト独自の規則で構造化し、通常の Decodal value として扱える
```dcdl
Post = {
@@ -91,69 +49,40 @@ Post = {
body = String;
};
post = Post & import "./hello.md";
title = post.frontmatter.title;
body = post.body;
post = (import "./hello.md") as Post;
```
`LoadedImport::Value` は通常の concrete runtime value に internalize される
そのため、path reference、object composition、constraint validation、materialize は source 由来の値と同じ規則を使う
安定した `key` が同じ構造化 import は、engine 内で同じ値としてキャッシュされる。
`load` が唯一の import hook である。
loader は拡張子、media type、または host 独自の規則で振り分け、対応する `LoadedImport` variant を直接返す。
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 = {
hoge = String;
};
schema = {
name = String;
};
result = (import "./func.dcdl")(schema);
}
result = (import "./func.dcdl")(schema);
```
```dcdl
# func.dcdl
(input: (import "./main.dcdl").schema) =>
{
# ...
}
(input: (import "./main.dcdl").schema) => input
```
`func.dcdl``main.dcdl` を import しているが、参照しているのは `main.schema` である。
`main.schema``main.result` に依存していなければ、この循環 import は成立する。
この例で `func.dcdl``main.dcdl` を import るが、参照する `schema``result` に依存しないため評価できる。
評価中の同じ field へ再び到達した場合は循環依存の diagnostic になる。
## import の評価単位
## import の失敗
実装上は、以下の単位で管理するのが自然である。
次の状態は import failure になる。
```text
Module main
schema -> thunk
result -> thunk
Module func
root -> thunk
```
各 thunk は一度だけ評価して memoize する。
評価中に同じ thunk へ戻った場合は循環依存としてエラーにする。
## import 失敗
以下は import 失敗として扱う。
- ファイルが存在しない。
- ファイルが読めない。
- import 先の構文解析に失敗する。
- import 先の評価で必要な値がエラーになる。
- host による非 DCDL content の読み込みまたは構造化に失敗する。
- 実装が禁止する import 循環に該当する。
- ホストが specifier を解決できない。
- resource を読み込めない。
- DCDL source の構文解析に失敗する。
- 必要な import 先の値を評価できない。
- structured value の読み込みまたは変換に失敗する。
- 評価対象の依存関係が循環する。
+10 -20
View File
@@ -1,27 +1,17 @@
# 命名規約
具体値と抽象値がグラデーションになるため、大文字・小文字による厳密な意味分けは設けない。
identifier の大文字・小文字に言語上の意味はない。
値、constraint、schema、派生設定はいずれも同じ式として扱われる。
ただし、読みやすさのために慣習を定める。
読みやすさのため、次の命名を推奨する。
## 推奨規約
- object 値: `lower_snake`
- 関数: `lowerCamel`
- 組み込み型・抽象的な制約名: `UpperCamel`
例:
- object value: `lower_snake`
- function: `lowerCamel`
- primitive、schema、抽象的な constraint: `UpperCamel`
```dcdl
IPv4Address
MyConfig
new_config
mkConfig
Port = Int & >= 1;
Service = { port = Port; };
service = { port = 8080; } as Service;
mkService = (port: Port) => { port = port; };
```
## 厳密な規則にしない理由
この言語では、値・制約・スキーマ・派生設定が同じ式体系に乗る。
そのため、ある名前が「具体値」か「抽象的な制約」かは文脈によってグラデーションになる。
大文字なら型、小文字なら値、のような厳密な規則を設けると、実際の利用に対して過剰に硬くなる可能性がある。
+1 -1
View File
@@ -58,7 +58,7 @@ host = "127.0.0.1"; # trailing comment
## 識別子
識別子は ASCII 英字で始まり、ASCII 英数字または `_` を続けられる。
慣習として`lower_snake``lowerCamel``UpperCamel` を使える想定とする。
命名規則に`lower_snake``lowerCamel``UpperCamel` を使用できる。
```dcdl
my_config
+1 -1
View File
@@ -12,7 +12,7 @@ enable = Bool default true;
tags = [...String];
```
現在の primitive type は `String``Int``Float``Bool` である。
primitive type は `String``Int``Float``Bool` である。
`Unknown` はprimitive typeではなく、すべてのDecodal値を含む最上位の抽象rangeである。具体値またはdefaultがない `Unknown` はmaterializeできない。
配列は primitive type ではなく、必須の要素制約を持つ `[...T]` で表現する。
各型の個別仕様へのリンクは [Manual Index](../../index.md) に集約する。
@@ -2,6 +2,15 @@
`String` は文字列値を表す primitive type constraint である。
文字列リテラルは `"` で囲む。
```dcdl
"hello"
"line 1\nline 2"
```
文字列内の `$``{}` に特別な意味はなく、文字列補間は行わない。
## 例
```dcdl