init
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
# 制約と default
|
||||
|
||||
この章では、制約と `default` の意味を定義する。
|
||||
|
||||
## 制約
|
||||
|
||||
制約は、値が満たすべき条件を表す。
|
||||
|
||||
```n
|
||||
Int
|
||||
String
|
||||
>= 1
|
||||
<= 65535
|
||||
/Hello! .*/
|
||||
```
|
||||
|
||||
制約は `&` により合成できる。
|
||||
|
||||
```n
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
NarrowedPort = Port & > 443;
|
||||
```
|
||||
|
||||
制約合成の意味は、すべての制約を同時に満たすことである。
|
||||
|
||||
```text
|
||||
A & B = A と B の両方を満たす値または制約
|
||||
```
|
||||
|
||||
矛盾する制約はエラーになる。
|
||||
|
||||
```n
|
||||
Int & String # エラー
|
||||
>= 10 & <= 5 # エラーになりうる
|
||||
```
|
||||
|
||||
## 組み込み制約
|
||||
|
||||
最小の組み込み制約は以下である。
|
||||
|
||||
```n
|
||||
String
|
||||
Int
|
||||
Float
|
||||
Bool
|
||||
```
|
||||
|
||||
追加の述語制約はライブラリまたは組み込みとして提供できる。
|
||||
|
||||
```n
|
||||
IPv4Address
|
||||
```
|
||||
|
||||
## 正規表現制約
|
||||
|
||||
正規表現リテラルは文字列制約として使える候補である。
|
||||
|
||||
```n
|
||||
Host = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
|
||||
```
|
||||
|
||||
ただし、組み込み向け実装では正規表現エンジンを optional feature にできる。
|
||||
軽量実装では代表的な制約を組み込み述語として提供してもよい。
|
||||
|
||||
```n
|
||||
Host = IPv4Address;
|
||||
```
|
||||
|
||||
## default
|
||||
|
||||
`default` は制約ではない。
|
||||
`default` は、最終評価時に明示値が存在しない場合だけ使われる fallback 値である。
|
||||
|
||||
```n
|
||||
port = NarrowedPort default 8080;
|
||||
```
|
||||
|
||||
これは概念的には以下を表す。
|
||||
|
||||
```text
|
||||
constraint = NarrowedPort
|
||||
value = none
|
||||
default = 8080
|
||||
```
|
||||
|
||||
明示値が合成された場合、`default` は採用されない。
|
||||
|
||||
```n
|
||||
MyConfig = {
|
||||
port = NarrowedPort default 8080;
|
||||
};
|
||||
|
||||
Config = MyConfig & {
|
||||
port = 8000;
|
||||
};
|
||||
```
|
||||
|
||||
この場合、最終値は `8000` である。
|
||||
`8080` は評価されない、または評価されても採用されない。
|
||||
|
||||
明示値がない場合、最終 materialize 時に `default` が採用される。
|
||||
採用された default 値は、同じフィールドに定義された制約を満たす必要がある。
|
||||
|
||||
```text
|
||||
constraint = NarrowedPort
|
||||
value = none
|
||||
default = 8080
|
||||
|
||||
finalize => 8080 が NarrowedPort を満たせば成功
|
||||
```
|
||||
|
||||
## default の内部表現
|
||||
|
||||
`default` は fallback thunk として保持できる。
|
||||
これにより、default 値自体も必要になるまで評価しない。
|
||||
|
||||
```text
|
||||
Cell {
|
||||
constraint: ConstraintSet
|
||||
value: Option<Value>
|
||||
default: Option<Thunk>
|
||||
}
|
||||
```
|
||||
|
||||
## default の合成
|
||||
|
||||
同じフィールドに複数の `default` が合成された場合の詳細規則は未確定である。
|
||||
現時点の単純な方針は以下である。
|
||||
|
||||
- `&` による default 同士の衝突はエラーにする。
|
||||
- 同一 default 値は許可してよい。
|
||||
- `//` による patch では右辺 default が左辺 default を置き換える。
|
||||
|
||||
この方針により、`&` は制約を保った合成、`//` は上書き操作として説明できる。
|
||||
@@ -0,0 +1,78 @@
|
||||
# 遅延評価
|
||||
|
||||
この言語はフィールド単位で遅延評価する。
|
||||
|
||||
## 基本方針
|
||||
|
||||
```n
|
||||
{
|
||||
schema = {
|
||||
hoge = String;
|
||||
};
|
||||
|
||||
result = expensive(schema);
|
||||
}
|
||||
```
|
||||
|
||||
`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 を再度評価しようとした場合、循環依存として扱う。
|
||||
|
||||
## 循環検出
|
||||
|
||||
```n
|
||||
{
|
||||
a = b + 1;
|
||||
b = a + 1;
|
||||
}
|
||||
```
|
||||
|
||||
この場合、`a` または `b` を評価すると循環エラーになる。
|
||||
|
||||
一方、同じモジュール内または import 間に循環があっても、評価対象のフィールドが循環していなければ成功する。
|
||||
|
||||
## 評価と materialize の分離
|
||||
|
||||
通常の評価では、制約や default を含む中間値が残ることがある。
|
||||
外部へデータとして出力する段階で materialize を行う。
|
||||
|
||||
この分離により、以下が可能になる。
|
||||
|
||||
- スキーマを値として扱う。
|
||||
- default を必要になるまで評価しない。
|
||||
- import されたモジュールの未使用フィールドを評価しない。
|
||||
- 制約だけのフィールドを中間状態として保持する。
|
||||
|
||||
## 関数呼び出しとの関係
|
||||
|
||||
関数引数は thunk として渡せる。
|
||||
関数本体内で引数が参照されたときに評価する。
|
||||
|
||||
任意の関数呼び出し結果をグローバルに memoize することは必須ではない。
|
||||
ただし、フィールドに束縛された呼び出し結果は、そのフィールド thunk の評価結果として memoize される。
|
||||
@@ -0,0 +1,142 @@
|
||||
# 例
|
||||
|
||||
この章には、仕様を説明するための例を置く。
|
||||
|
||||
## 基本的な設定スキーマ
|
||||
|
||||
```n
|
||||
rec {
|
||||
Host = IPv4Address;
|
||||
|
||||
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;
|
||||
```
|
||||
|
||||
## 関数と文字列生成
|
||||
|
||||
```n
|
||||
let
|
||||
maybe_hw = String & /Hello! .*/;
|
||||
part = {
|
||||
greet = String;
|
||||
target = String;
|
||||
};
|
||||
mk_hw = (part: part) =>
|
||||
maybe_hw & "${part.greet}! ${part.target}";
|
||||
in
|
||||
mk_hw({
|
||||
greet = "Hello";
|
||||
target = "World";
|
||||
})
|
||||
```
|
||||
|
||||
評価結果:
|
||||
|
||||
```text
|
||||
"Hello! World"
|
||||
```
|
||||
|
||||
## match
|
||||
|
||||
```n
|
||||
(
|
||||
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
|
||||
|
||||
```n
|
||||
Base = {
|
||||
feature_hoge = {
|
||||
enable = Bool default true;
|
||||
fuga = Int default 10;
|
||||
};
|
||||
};
|
||||
|
||||
Patched = Base // {
|
||||
feature_hoge.enable = false;
|
||||
};
|
||||
```
|
||||
|
||||
`Patched` は以下に相当する。
|
||||
|
||||
```n
|
||||
{
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
fuga = Int default 10;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 循環 import
|
||||
|
||||
```n
|
||||
# main.n
|
||||
{
|
||||
schema = {
|
||||
hoge = String;
|
||||
};
|
||||
|
||||
result = (import ./func.n)(schema);
|
||||
}
|
||||
```
|
||||
|
||||
```n
|
||||
# func.n
|
||||
(input: (import ./main.n).schema) =>
|
||||
{
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
`func.n` は `main.n` を import しているが、参照しているのは `main.schema` である。
|
||||
`main.schema` が `main.result` に依存していなければ、この循環 import は成立する。
|
||||
@@ -0,0 +1,15 @@
|
||||
# Array Expression
|
||||
|
||||
array expression は、順序付きの値の列を表す。
|
||||
|
||||
```n
|
||||
[1, 2, 3]
|
||||
["a", "b", "c"]
|
||||
```
|
||||
|
||||
## 未確定事項
|
||||
|
||||
- 配列要素の制約表現。
|
||||
- 異種配列を許可するか。
|
||||
- `//` による patch を右辺置換だけにするか。
|
||||
- append / prepend / remove などの操作を提供するか。
|
||||
@@ -0,0 +1,24 @@
|
||||
# Composition Expression
|
||||
|
||||
composition expression は、複数の値・制約・構造を合成する式である。
|
||||
|
||||
## `&`
|
||||
|
||||
`&` は制約を保った合成を行う。
|
||||
|
||||
```n
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
Config = MyConfig & { port = 8000; };
|
||||
```
|
||||
|
||||
## `//`
|
||||
|
||||
`//` は右辺優先の構造的 patch を行う。
|
||||
|
||||
```n
|
||||
Patched = Base // {
|
||||
feature_hoge.enable = false;
|
||||
};
|
||||
```
|
||||
|
||||
詳細は [合成演算子](../operators.md) に置く。
|
||||
@@ -0,0 +1,15 @@
|
||||
# Default Expression
|
||||
|
||||
`default` expression は、明示値が存在しない場合に materialize 時に採用される fallback を指定する。
|
||||
|
||||
```n
|
||||
port = Int default 8080;
|
||||
```
|
||||
|
||||
`default` は制約ではない。
|
||||
詳細は [制約と default](../constraints-and-defaults.md) に置く。
|
||||
|
||||
## 評価
|
||||
|
||||
fallback 値は thunk として保持できる。
|
||||
明示値がある場合、default は採用されない。
|
||||
@@ -0,0 +1,17 @@
|
||||
# Function Call Expression
|
||||
|
||||
function call expression は、関数値を引数に適用する式である。
|
||||
|
||||
```n
|
||||
mk_hw({
|
||||
greet = "Hello";
|
||||
target = "World";
|
||||
})
|
||||
```
|
||||
|
||||
## 評価
|
||||
|
||||
引数は thunk として渡せる。
|
||||
関数本体内で引数が参照されたときに評価する。
|
||||
|
||||
任意の関数呼び出し結果をグローバルに memoize することは必須ではない。
|
||||
@@ -0,0 +1,18 @@
|
||||
# Function Expression
|
||||
|
||||
function expression は、引数を受け取り式を返す値である。
|
||||
|
||||
```n
|
||||
(part: {
|
||||
greet = String;
|
||||
target = String;
|
||||
}) =>
|
||||
"${part.greet}! ${part.target}"
|
||||
```
|
||||
|
||||
関数仕様の詳細は [関数](../functions.md) に置く。
|
||||
|
||||
## 評価
|
||||
|
||||
関数は定義時の環境を参照として保持する。
|
||||
関数本体は、関数値の生成時ではなく呼び出し時に評価される。
|
||||
@@ -0,0 +1,14 @@
|
||||
# Identifier Expression
|
||||
|
||||
identifier expression は、現在の環境に束縛された名前を参照する式である。
|
||||
|
||||
```n
|
||||
Port
|
||||
MyConfig
|
||||
mkConfig
|
||||
```
|
||||
|
||||
## 評価
|
||||
|
||||
識別子は、対応する束縛の thunk を参照する。
|
||||
束縛が存在しない場合は未定義識別子エラーになる。
|
||||
@@ -0,0 +1,15 @@
|
||||
# Import Expression
|
||||
|
||||
import expression は、外部ファイルを読み込み、そのファイルの評価結果を返す。
|
||||
|
||||
```n
|
||||
import ./config.n
|
||||
import "./config.n"
|
||||
```
|
||||
|
||||
import 仕様の詳細は [モジュールと import](../modules-and-imports.md) に置く。
|
||||
|
||||
## 評価
|
||||
|
||||
import 先はモジュール単位で読み込まれる。
|
||||
ただし、各 field は thunk として保持され、必要になるまで評価されない。
|
||||
@@ -0,0 +1,24 @@
|
||||
# Expression
|
||||
|
||||
この章では、言語が評価対象として持つ式の分類を定義する。
|
||||
|
||||
式は、値・制約・構造・計算を表す基本単位である。
|
||||
|
||||
```text
|
||||
Expr
|
||||
├─ literal
|
||||
├─ identifier
|
||||
├─ path reference
|
||||
├─ object
|
||||
├─ array
|
||||
├─ function
|
||||
├─ function call
|
||||
├─ let
|
||||
├─ match
|
||||
├─ import
|
||||
├─ composition
|
||||
├─ default
|
||||
└─ string interpolation
|
||||
```
|
||||
|
||||
各式の個別仕様へのリンクは [Manual Index](../../index.md) に集約する。
|
||||
@@ -0,0 +1,18 @@
|
||||
# Let Expression
|
||||
|
||||
let expression は、ローカル束縛を作る。
|
||||
|
||||
```n
|
||||
let
|
||||
part = {
|
||||
greet = "Hello";
|
||||
target = "World";
|
||||
};
|
||||
in
|
||||
"${part.greet}! ${part.target}"
|
||||
```
|
||||
|
||||
## 評価
|
||||
|
||||
let 束縛は thunk として保持される。
|
||||
参照されない束縛は評価されない。
|
||||
@@ -0,0 +1,20 @@
|
||||
# Literal Expression
|
||||
|
||||
literal expression は、ソース上に直接書かれる具体値である。
|
||||
|
||||
## 種類
|
||||
|
||||
```n
|
||||
"hello"
|
||||
123
|
||||
3.14
|
||||
true
|
||||
false
|
||||
```
|
||||
|
||||
## 対応する primitive type
|
||||
|
||||
- 文字列リテラルは `String` を満たす。
|
||||
- 整数リテラルは `Int` を満たす。
|
||||
- 浮動小数リテラルは `Float` を満たす。
|
||||
- `true` / `false` は `Bool` を満たす。
|
||||
@@ -0,0 +1,24 @@
|
||||
# Match Expression
|
||||
|
||||
match expression は、対象値を上から順に pattern と照合し、最初に一致した分岐を採用する。
|
||||
|
||||
```n
|
||||
foo = match inputs.a.hoge {
|
||||
>= 20: {
|
||||
value = 200;
|
||||
};
|
||||
>= 10: {
|
||||
value = 100;
|
||||
};
|
||||
_: {
|
||||
value = 300;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
`_` は fallback pattern である。
|
||||
|
||||
## 順序
|
||||
|
||||
分岐は順序付きであり、「最も具体的な pattern」を自動選択しない。
|
||||
広い条件を先に書くと、後続の狭い条件には到達しない。
|
||||
@@ -0,0 +1,39 @@
|
||||
# Object Expression
|
||||
|
||||
object expression は、名前付き field の集合を表す。
|
||||
|
||||
```n
|
||||
{
|
||||
host = "127.0.0.1";
|
||||
port = 8000;
|
||||
}
|
||||
```
|
||||
|
||||
object は設定値にもスキーマにも使う。
|
||||
|
||||
```n
|
||||
MyConfig = {
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
};
|
||||
```
|
||||
|
||||
## Dot-path Field
|
||||
|
||||
ネストした field はドットパスでも定義できる。
|
||||
|
||||
```n
|
||||
{
|
||||
feature_hoge.enable = false;
|
||||
}
|
||||
```
|
||||
|
||||
これは以下と同じ構造を表す。
|
||||
|
||||
```n
|
||||
{
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
};
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
# Path Reference Expression
|
||||
|
||||
path reference expression は、object のフィールドを参照する式である。
|
||||
|
||||
```n
|
||||
config.host
|
||||
config.feature_hoge.enable
|
||||
```
|
||||
|
||||
## 評価
|
||||
|
||||
左側の式を object として評価し、指定された field を参照する。
|
||||
参照先 field は必要になるまで評価されない。
|
||||
|
||||
存在しない field への参照をエラーにするか、open schema として扱うかは未確定である。
|
||||
@@ -0,0 +1,12 @@
|
||||
# String Interpolation Expression
|
||||
|
||||
string interpolation は、文字列内に式を埋め込む候補機能である。
|
||||
|
||||
```n
|
||||
"${part.greet}! ${part.target}"
|
||||
```
|
||||
|
||||
## 評価
|
||||
|
||||
補間式の評価タイミングは通常の遅延評価に従う。
|
||||
文字列補間を初期実装に含めるかは未確定である。
|
||||
@@ -0,0 +1,91 @@
|
||||
# 関数
|
||||
|
||||
関数は構造を受け取り、構造を返す式として扱う。
|
||||
|
||||
## 構文
|
||||
|
||||
```n
|
||||
(part: {
|
||||
greet = String;
|
||||
target = String;
|
||||
}) =>
|
||||
"${part.greet}! ${part.target}"
|
||||
```
|
||||
|
||||
関数呼び出しは通常の呼び出し構文で行う。
|
||||
|
||||
```n
|
||||
mk_hw({
|
||||
greet = "Hello";
|
||||
target = "World";
|
||||
})
|
||||
```
|
||||
|
||||
複数引数の構文は候補として以下を想定する。
|
||||
|
||||
```n
|
||||
(
|
||||
input_a: {
|
||||
hoge = Int & >= 0;
|
||||
},
|
||||
input_b: {
|
||||
fuga = 20;
|
||||
}
|
||||
) =>
|
||||
{
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
## 関数の意味
|
||||
|
||||
関数は値として扱える。
|
||||
ただし、最終データとして関数値を出力できるかどうかは別途定める。
|
||||
設定ファイルを materialize する段階では、未適用の関数値は出力不能な値として扱うのが自然である。
|
||||
|
||||
## 評価方針
|
||||
|
||||
関数は以下の方針を基本とする。
|
||||
|
||||
- 関数は純粋である。
|
||||
- 関数はレキシカルスコープを持つ。
|
||||
- 関数は定義時の環境を参照として保持する。
|
||||
- 引数は必要になるまで評価しない。
|
||||
- フィールドに束縛された関数呼び出し結果は、そのフィールド評価結果として memoize される。
|
||||
- 任意の関数呼び出しそのものをグローバルに memoize することは必須ではない。
|
||||
|
||||
関数値の内部モデル例:
|
||||
|
||||
```text
|
||||
Function {
|
||||
params: Vec<Param>
|
||||
body: ExprId
|
||||
env: EnvRef
|
||||
}
|
||||
```
|
||||
|
||||
## 関数と重さ
|
||||
|
||||
関数の文法・パーサー自体は大きくない。
|
||||
実装上の重さは、主に評価モデルと意味論から発生する。
|
||||
|
||||
注意点:
|
||||
|
||||
- クロージャが環境を掴む。
|
||||
- 引数を lazy にするか strict にするかを決める必要がある。
|
||||
- 関数呼び出し結果をどこまで memoize するかを決める必要がある。
|
||||
- 再帰関数を許可するかを決める必要がある。
|
||||
- 関数値同士の `&` をどう扱うかを決める必要がある。
|
||||
- 関数値を object に入れたとき、materialize 可能かを決める必要がある。
|
||||
- import 循環と関数適用が絡んだときの cycle detection が必要になる。
|
||||
|
||||
軽量に保つため、初期仕様では以下の制限を検討できる。
|
||||
|
||||
- 関数は pure。
|
||||
- lexical closure は許可。
|
||||
- 引数は thunk として渡す。
|
||||
- 関数本体は必要になるまで評価しない。
|
||||
- 関数値は opaque。
|
||||
- 関数同士の `&` は、同一関数参照以外は conflict。
|
||||
- 関数値は最終データとして出力できない。
|
||||
- 再帰は cycle error として扱う、または v1 では禁止する。
|
||||
@@ -0,0 +1,12 @@
|
||||
# 言語仕様
|
||||
|
||||
このディレクトリは、遅延評価データ記述言語の仕様本文を章ごとに分割して管理する。
|
||||
|
||||
目次はマニュアル直下の [Manual Index](../index.md) に集約する。
|
||||
このファイルは `Language Specification` 章の入口としてだけ使う。
|
||||
|
||||
## Language
|
||||
|
||||
言語仕様は、構文、値、式、制約、合成演算子、関数、モジュール、評価意味論、materialize、エラーを定義する。
|
||||
|
||||
詳細な章構成と各ファイルへのリンクは [Manual Index](../index.md) を参照する。
|
||||
@@ -0,0 +1,102 @@
|
||||
# materialize とエラー
|
||||
|
||||
通常の評価では、制約や default を含む中間値が残ることがある。
|
||||
外部へデータとして出力する段階では、materialize を行う。
|
||||
|
||||
## materialize の責務
|
||||
|
||||
materialize は以下を行う。
|
||||
|
||||
- 必要なフィールドを評価する。
|
||||
- 明示値がないフィールドに default を適用する。
|
||||
- 採用された値が制約を満たすか検証する。
|
||||
- 未解決の制約だけが残っているフィールドをエラーにする。
|
||||
- 未適用の関数など、データとして出力できない値をエラーにする。
|
||||
|
||||
## 例
|
||||
|
||||
```n
|
||||
MyConfig = {
|
||||
host = String;
|
||||
port = Int default 8080;
|
||||
};
|
||||
```
|
||||
|
||||
`MyConfig` を materialize すると、`host` は具体値も default もないためエラーになる。
|
||||
`port` は `8080` が採用される。
|
||||
|
||||
```n
|
||||
Config = MyConfig & {
|
||||
host = "localhost";
|
||||
};
|
||||
```
|
||||
|
||||
`Config` を materialize すると以下になる。
|
||||
|
||||
```n
|
||||
{
|
||||
host = "localhost";
|
||||
port = 8080;
|
||||
}
|
||||
```
|
||||
|
||||
## default の適用
|
||||
|
||||
`default` は materialize 時にのみ fallback として採用される。
|
||||
|
||||
```text
|
||||
constraint = Int
|
||||
value = none
|
||||
default = 8080
|
||||
```
|
||||
|
||||
この cell を materialize すると、`8080` が採用され、`Int` を満たすか検証される。
|
||||
|
||||
明示値がある場合、default は採用しない。
|
||||
|
||||
```text
|
||||
constraint = Int
|
||||
value = 9000
|
||||
default = 8080
|
||||
```
|
||||
|
||||
この cell の最終値は `9000` である。
|
||||
|
||||
## エラー分類
|
||||
|
||||
代表的なエラー:
|
||||
|
||||
- 構文エラー
|
||||
- 未定義識別子
|
||||
- 型不一致
|
||||
- 制約違反
|
||||
- `&` の conflict
|
||||
- `default` の conflict
|
||||
- 循環依存
|
||||
- import 失敗
|
||||
- match の非網羅による失敗
|
||||
- materialize 不能な値の出力
|
||||
|
||||
## match の失敗
|
||||
|
||||
`match` に fallback 分岐がなく、どの分岐にも一致しなかった場合はエラーになる。
|
||||
|
||||
```n
|
||||
match value {
|
||||
>= 10: "large";
|
||||
}
|
||||
```
|
||||
|
||||
`value` が `10` 未満であれば失敗する。
|
||||
|
||||
## try / catch
|
||||
|
||||
`try / catch` は将来的な候補である。
|
||||
組み込み向けの小さな核では必須ではない。
|
||||
|
||||
失敗は主に以下から発生する。
|
||||
|
||||
- assert 相当の制約検証。
|
||||
- `&` による合成。
|
||||
- import。
|
||||
- materialize。
|
||||
@@ -0,0 +1,73 @@
|
||||
# モジュールと import
|
||||
|
||||
`import` は外部ファイルを読み込み、そのファイルの評価結果を返す。
|
||||
|
||||
## 構文
|
||||
|
||||
```n
|
||||
import ./config.n
|
||||
import "./config.n"
|
||||
```
|
||||
|
||||
パス表記の詳細は未確定である。
|
||||
パスリテラルと文字列リテラルの両方を許可するか、どちらかに統一するかは今後決める。
|
||||
|
||||
## モジュール
|
||||
|
||||
import 先はモジュール単位で読み込まれる。
|
||||
ただし、モジュール全体を即時評価する必要はない。
|
||||
各フィールドは thunk として保持され、必要になったときだけ評価される。
|
||||
|
||||
## 循環 import
|
||||
|
||||
モジュール間に循環参照があっても、必要なフィールドの依存関係が循環していなければ評価できる。
|
||||
|
||||
例:
|
||||
|
||||
```n
|
||||
# main.n
|
||||
{
|
||||
schema = {
|
||||
hoge = String;
|
||||
};
|
||||
|
||||
result = (import ./func.n)(schema);
|
||||
}
|
||||
```
|
||||
|
||||
```n
|
||||
# func.n
|
||||
(input: (import ./main.n).schema) =>
|
||||
{
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
`func.n` は `main.n` を import しているが、参照しているのは `main.schema` である。
|
||||
`main.schema` が `main.result` に依存していなければ、この循環 import は成立する。
|
||||
|
||||
## import の評価単位
|
||||
|
||||
実装上は、以下の単位で管理するのが自然である。
|
||||
|
||||
```text
|
||||
Module main
|
||||
schema -> thunk
|
||||
result -> thunk
|
||||
|
||||
Module func
|
||||
root -> thunk
|
||||
```
|
||||
|
||||
各 thunk は一度だけ評価して memoize する。
|
||||
評価中に同じ thunk へ戻った場合は循環依存としてエラーにする。
|
||||
|
||||
## import 失敗
|
||||
|
||||
以下は import 失敗として扱う。
|
||||
|
||||
- ファイルが存在しない。
|
||||
- ファイルが読めない。
|
||||
- import 先の構文解析に失敗する。
|
||||
- import 先の評価で必要な値がエラーになる。
|
||||
- 実装が禁止する import 循環に該当する。
|
||||
@@ -0,0 +1,27 @@
|
||||
# 命名規約
|
||||
|
||||
具体値と抽象値がグラデーションになるため、大文字・小文字による厳密な意味分けは設けない。
|
||||
|
||||
ただし、読みやすさのために慣習を定める。
|
||||
|
||||
## 推奨規約
|
||||
|
||||
- object 値: `lower_snake`
|
||||
- 関数: `lowerCamel`
|
||||
- 組み込み型・抽象的な制約名: `UpperCamel`
|
||||
|
||||
例:
|
||||
|
||||
```n
|
||||
IPv4Address
|
||||
MyConfig
|
||||
new_config
|
||||
mkConfig
|
||||
```
|
||||
|
||||
## 厳密な規則にしない理由
|
||||
|
||||
この言語では、値・制約・スキーマ・派生設定が同じ式体系に乗る。
|
||||
そのため、ある名前が「具体値」か「抽象的な制約」かは文脈によってグラデーションになる。
|
||||
|
||||
大文字なら型、小文字なら値、のような厳密な規則を設けると、実際の利用に対して過剰に硬くなる可能性がある。
|
||||
@@ -0,0 +1,146 @@
|
||||
# 合成演算子
|
||||
|
||||
この章では、`&` と `//` の意味を定義する。
|
||||
|
||||
## `&`: 制約合成
|
||||
|
||||
`&` は値・制約・構造を合成する演算子である。
|
||||
|
||||
```n
|
||||
A & B
|
||||
```
|
||||
|
||||
基本規則:
|
||||
|
||||
- 両方が制約なら、両方を満たす制約になる。
|
||||
- 制約と具体値なら、具体値が制約を満たす必要がある。
|
||||
- 両方が同じ具体値なら、その値になる。
|
||||
- 両方が異なる具体値なら conflict になる。
|
||||
- 両方が object なら、フィールドごとに合成する。
|
||||
- 同じフィールドが両方にある場合、そのフィールド値を `&` で合成する。
|
||||
- 矛盾が発生した場合はエラーになる。
|
||||
|
||||
例:
|
||||
|
||||
```n
|
||||
Port = Int & >= 1 & <= 65535;
|
||||
NarrowedPort = Port & > 443;
|
||||
|
||||
MyConfig = {
|
||||
port = NarrowedPort default 8080;
|
||||
};
|
||||
|
||||
Config = MyConfig & {
|
||||
port = 8000;
|
||||
};
|
||||
```
|
||||
|
||||
`Config.port` は概念的には以下になる。
|
||||
|
||||
```text
|
||||
NarrowedPort & 8000
|
||||
```
|
||||
|
||||
`8000` は `NarrowedPort` を満たすため成功する。
|
||||
|
||||
一方、以下は失敗する。
|
||||
|
||||
```n
|
||||
BadConfig = MyConfig & {
|
||||
port = 80;
|
||||
};
|
||||
```
|
||||
|
||||
`80` は `> 443` を満たさないためである。
|
||||
|
||||
## `//`: patch 合成
|
||||
|
||||
`//` は右辺優先の構造的 patch 演算子である。
|
||||
`&` が制約を保った合成であるのに対し、`//` は設定やスキーマを上書き・変更するために使う。
|
||||
|
||||
```n
|
||||
A // B
|
||||
```
|
||||
|
||||
基本規則:
|
||||
|
||||
- 両方が object なら、フィールドごとに再帰的に patch する。
|
||||
- 同じフィールドが object/object なら、さらに再帰的に patch する。
|
||||
- 同じフィールドが object/object 以外なら、右辺で置き換える。
|
||||
- 左辺にしかないフィールドは保持する。
|
||||
- 右辺にしかないフィールドは追加する。
|
||||
- 配列はデフォルトでは右辺で置き換える。
|
||||
- 関数値はデフォルトでは右辺で置き換える。
|
||||
|
||||
つまり `//` は shallow merge ではなく deep patch とする。
|
||||
|
||||
```n
|
||||
Base = {
|
||||
feature_hoge = {
|
||||
enable = Bool default true;
|
||||
fuga = Int default 10;
|
||||
};
|
||||
};
|
||||
|
||||
Patched = Base // {
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
`Patched` は以下に相当する。
|
||||
|
||||
```n
|
||||
{
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
fuga = Int default 10;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
ドットパスを使うと以下のようにも書ける。
|
||||
|
||||
```n
|
||||
Patched = Base // {
|
||||
feature_hoge.enable = false;
|
||||
};
|
||||
```
|
||||
|
||||
## object 全体の置換
|
||||
|
||||
`//` が deep patch である場合、object 全体を置き換えたいときの escape hatch が必要になる。
|
||||
|
||||
候補として、`replace(...)` を組み込み関数として提供する。
|
||||
|
||||
```n
|
||||
Replaced = Base // {
|
||||
feature_hoge = replace({
|
||||
enable = false;
|
||||
});
|
||||
};
|
||||
```
|
||||
|
||||
この場合、`feature_hoge` は再帰 patch されず、右辺の object に丸ごと置き換わる。
|
||||
|
||||
## `&` と `//` の使い分け
|
||||
|
||||
`&` は制約を満たす具体化に使う。
|
||||
|
||||
```n
|
||||
ValidConfig = MyConfig & {
|
||||
port = 8000;
|
||||
};
|
||||
```
|
||||
|
||||
`//` は既存構造の上書きや変形に使う。
|
||||
|
||||
```n
|
||||
ModifiedSchema = MyConfig // {
|
||||
port = Int default 9000;
|
||||
};
|
||||
```
|
||||
|
||||
`//` は右辺優先の patch であり、左辺の制約を常に保持するとは限らない。
|
||||
制約を保持したい場合は `&` を使う。
|
||||
@@ -0,0 +1,95 @@
|
||||
# 構文と字句
|
||||
|
||||
この章では、表層構文の方針をまとめる。
|
||||
厳密な EBNF は未確定であり、今後このファイルに詳細化する。
|
||||
|
||||
## コメント
|
||||
|
||||
コメントは `#` から行末までとする。
|
||||
|
||||
```n
|
||||
# comment
|
||||
host = "127.0.0.1"; # trailing comment
|
||||
```
|
||||
|
||||
## セミコロン
|
||||
|
||||
オブジェクトフィールド、let 束縛、match 分岐はセミコロンで区切る。
|
||||
末尾セミコロンは許可する。
|
||||
|
||||
```n
|
||||
{
|
||||
host = "127.0.0.1";
|
||||
port = 8000;
|
||||
}
|
||||
```
|
||||
|
||||
## 識別子
|
||||
|
||||
識別子の厳密な字句規則は未確定である。
|
||||
慣習としては `lower_snake`、`lowerCamel`、`UpperCamel` を使える想定とする。
|
||||
|
||||
```n
|
||||
my_config
|
||||
mkConfig
|
||||
IPv4Address
|
||||
```
|
||||
|
||||
## パス参照
|
||||
|
||||
ドットによるフィールド参照を許可する。
|
||||
|
||||
```n
|
||||
config.host
|
||||
config.feature_hoge.enable
|
||||
```
|
||||
|
||||
オブジェクト内では、ドットパスによるフィールド定義も許可する。
|
||||
|
||||
```n
|
||||
{
|
||||
feature_hoge.enable = false;
|
||||
}
|
||||
```
|
||||
|
||||
これは以下と同じ構造を表す。
|
||||
|
||||
```n
|
||||
{
|
||||
feature_hoge = {
|
||||
enable = false;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## 予約語候補
|
||||
|
||||
以下は予約語または予約構文として扱う候補である。
|
||||
|
||||
```text
|
||||
let
|
||||
in
|
||||
match
|
||||
import
|
||||
default
|
||||
true
|
||||
false
|
||||
rec
|
||||
```
|
||||
|
||||
`rec` の扱いは未確定である。
|
||||
|
||||
## 演算子
|
||||
|
||||
主要な演算子は以下である。
|
||||
|
||||
```text
|
||||
& 制約合成
|
||||
// patch 合成
|
||||
default fallback 指定
|
||||
=> 関数
|
||||
. フィールド参照 / ドットパス定義
|
||||
```
|
||||
|
||||
演算子の優先順位は未確定である。
|
||||
詳細は [合成演算子](./operators.md) で定義する。
|
||||
@@ -0,0 +1,19 @@
|
||||
# Bool
|
||||
|
||||
`Bool` は真偽値を表す primitive type constraint である。
|
||||
|
||||
## 例
|
||||
|
||||
```n
|
||||
enable = Bool default true;
|
||||
disable = Bool default false;
|
||||
```
|
||||
|
||||
## リテラル
|
||||
|
||||
`Bool` が受け入れるリテラルは以下である。
|
||||
|
||||
```n
|
||||
true
|
||||
false
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
# Float
|
||||
|
||||
`Float` は浮動小数値を表す primitive type constraint である。
|
||||
|
||||
## 例
|
||||
|
||||
```n
|
||||
ratio = Float;
|
||||
threshold = Float default 0.5;
|
||||
```
|
||||
|
||||
## 未確定事項
|
||||
|
||||
`Int` と `Float` の暗黙変換を許可するかは未確定である。
|
||||
軽量実装では、両者を明確に分ける方が単純である。
|
||||
@@ -0,0 +1,17 @@
|
||||
# Value
|
||||
|
||||
この章では、言語が扱う値の分類を定義する。
|
||||
|
||||
primitive type は、通常のデータ値ではなく、値が満たすべき組み込み制約として扱う。
|
||||
|
||||
```n
|
||||
name = String;
|
||||
retry = Int default 3;
|
||||
ratio = Float;
|
||||
enable = Bool default true;
|
||||
```
|
||||
|
||||
現在の primitive type は `String`、`Int`、`Float`、`Bool` である。
|
||||
各型の個別仕様へのリンクは [Manual Index](../../index.md) に集約する。
|
||||
|
||||
primitive type と制約合成の詳細は [制約と default](../constraints-and-defaults.md) も参照する。
|
||||
@@ -0,0 +1,18 @@
|
||||
# Int
|
||||
|
||||
`Int` は整数値を表す primitive type constraint である。
|
||||
|
||||
## 例
|
||||
|
||||
```n
|
||||
retry = Int default 3;
|
||||
port = Int & >= 1 & <= 65535;
|
||||
```
|
||||
|
||||
## 制約合成
|
||||
|
||||
数値比較制約と合成できる。
|
||||
|
||||
```n
|
||||
NarrowedPort = Int & >= 1 & <= 65535 & > 443;
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
# String
|
||||
|
||||
`String` は文字列値を表す primitive type constraint である。
|
||||
|
||||
## 例
|
||||
|
||||
```n
|
||||
name = String;
|
||||
greeting = String default "hello";
|
||||
```
|
||||
|
||||
## 制約合成
|
||||
|
||||
`String` は文字列制約と合成できる。
|
||||
|
||||
```n
|
||||
message = String & /Hello! .*/;
|
||||
```
|
||||
|
||||
正規表現制約を必須機能にするかは未確定である。
|
||||
Reference in New Issue
Block a user