commit 5d6a03b74f30f1beb4c627a0d4b94f984db8e869 Author: Hare Date: Tue Jun 16 00:45:10 2026 +0900 init diff --git a/doc/manual/souce/index.md b/doc/manual/souce/index.md new file mode 100644 index 0000000..b12c2dd --- /dev/null +++ b/doc/manual/souce/index.md @@ -0,0 +1,37 @@ +# Manual + +このディレクトリには、マニュアル文書を置く。 + +## 目次 + +1. [Introduction](./introduction.md) +2. [Language Specification](./language/index.md) + 1. [Lexical Structure and Syntax](./language/syntax.md) + 2. [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) + 3. [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. [Default](./language/expression/default.md) + 13. [String Interpolation](./language/expression/string-interpolation.md) + 4. [Constraints and Defaults](./language/constraints-and-defaults.md) + 5. [Composition Operators](./language/operators.md) + 6. [Functions](./language/functions.md) + 7. [Modules and Imports](./language/modules-and-imports.md) + 8. [Evaluation Semantics](./language/evaluation.md) + 9. [Materialization and Errors](./language/materialization-and-errors.md) + 10. [Naming Conventions](./language/naming.md) + 11. [Examples](./language/examples.md) +3. [Open Issues](./open-issues.md) diff --git a/doc/manual/souce/introduction.md b/doc/manual/souce/introduction.md new file mode 100644 index 0000000..9f62388 --- /dev/null +++ b/doc/manual/souce/introduction.md @@ -0,0 +1,86 @@ +# Introduction + +このマニュアルは、遅延評価データ記述言語の目的、設計方針、言語仕様をまとめる。 +この言語は、設定値・スキーマ・制約・派生設定を同じ式体系で扱い、組み込み環境でも実装しやすい小さな言語核を提供することを目指す。 + +## 目的 + +一般的な設定ファイルでは、値の記述、スキーマ定義、デフォルト値、派生設定、バリデーションが別々の仕組みとして扱われやすい。 +この言語では、それらを単一の式体系に寄せる。 + +例えば、以下のように制約と値を同じ構文で合成できる。 + +```n +Port = Int & >= 1 & <= 65535; +NarrowedPort = Port & > 443; + +MyConfig = { + host = String; + port = NarrowedPort default 8080; +}; + +Config = MyConfig & { + host = "127.0.0.1"; + port = 8000; +}; +``` + +`MyConfig` は設定の形と制約を表し、`Config` はそこへ具体値を合成した設定を表す。 +具体値は対応する制約を満たす必要がある。 + +## 設計目標 + +- データ記述とスキーマ記述を同じ構文で表現できる。 +- 制約を `&` で合成し、値が制約を満たすか検証できる。 +- 設定値やスキーマを `//` で構造的に patch できる。 +- `default` により、最終評価時の fallback 値を定義できる。 +- フィールド単位の遅延評価により、未使用値の評価を避ける。 +- import 循環があっても、必要なフィールド依存が循環していなければ評価できる。 +- 処理系を組み込み向けに小さく保てるよう、意味論を明示的かつ決定的にする。 + +## 非目標 + +初期仕様では以下を必須にしない。 + +- 高度な型推論。 +- match の完全な網羅性検査。 +- 到達不能分岐の静的検査。 +- 任意の関数呼び出し結果のグローバル memoize。 +- 正規表現エンジンの必須搭載。 +- `try / catch` の必須搭載。 +- 完全なプログラミング言語としての汎用性。 + +## 中心概念 + +この言語の中心概念は以下である。 + +- 値と制約を同じ式として扱う。 +- `&` で制約を保った合成を行う。 +- `//` で右辺優先の構造的 patch を行う。 +- `default` は制約ではなく、最終評価時の fallback として扱う。 +- フィールド単位で遅延評価する。 +- import 循環は、実際に必要なフィールド依存が循環しない限り許容する。 + +## 組み込み向けの方針 + +この言語は、汎用プログラミング言語を目指すものではない。 +主対象は、設定、スキーマ、制約、派生データの記述である。 + +そのため、言語核は「値・制約・構造の合成」と「遅延評価」に寄せる。 +便利な機能であっても、実装サイズ・評価モデル・エラー決定性を大きく複雑にするものは optional feature または将来拡張として扱う。 + +## ドキュメント構成 + +言語仕様の解説は [Language Specification](./language/index.md) にまとめる。 +`language/` 配下には、構文、値、式、制約、演算子、評価意味論など、言語仕様そのものの説明だけを置く。 + +主な章は以下である。 + +- [Value](./language/value/index.md): `String`、`Int`、`Float`、`Bool` などの値・プリミティブ制約。 +- [Expression](./language/expression/index.md): literal、object、array、function、let、match、import などの式。 +- [Constraints and Defaults](./language/constraints-and-defaults.md): 制約と `default` の意味。 +- [Composition Operators](./language/operators.md): `&` と `//` の意味。 +- [Evaluation Semantics](./language/evaluation.md): 遅延評価、thunk、循環検出。 +- [Materialization and Errors](./language/materialization-and-errors.md): 最終評価とエラー分類。 + +未確定事項は [Open Issues](./open-issues.md) に集約する。 diff --git a/doc/manual/souce/language/constraints-and-defaults.md b/doc/manual/souce/language/constraints-and-defaults.md new file mode 100644 index 0000000..f0f1573 --- /dev/null +++ b/doc/manual/souce/language/constraints-and-defaults.md @@ -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 + default: Option +} +``` + +## default の合成 + +同じフィールドに複数の `default` が合成された場合の詳細規則は未確定である。 +現時点の単純な方針は以下である。 + +- `&` による default 同士の衝突はエラーにする。 +- 同一 default 値は許可してよい。 +- `//` による patch では右辺 default が左辺 default を置き換える。 + +この方針により、`&` は制約を保った合成、`//` は上書き操作として説明できる。 diff --git a/doc/manual/souce/language/evaluation.md b/doc/manual/souce/language/evaluation.md new file mode 100644 index 0000000..d869c30 --- /dev/null +++ b/doc/manual/souce/language/evaluation.md @@ -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 される。 diff --git a/doc/manual/souce/language/examples.md b/doc/manual/souce/language/examples.md new file mode 100644 index 0000000..1c921b1 --- /dev/null +++ b/doc/manual/souce/language/examples.md @@ -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 は成立する。 diff --git a/doc/manual/souce/language/expression/array.md b/doc/manual/souce/language/expression/array.md new file mode 100644 index 0000000..065b390 --- /dev/null +++ b/doc/manual/souce/language/expression/array.md @@ -0,0 +1,15 @@ +# Array Expression + +array expression は、順序付きの値の列を表す。 + +```n +[1, 2, 3] +["a", "b", "c"] +``` + +## 未確定事項 + +- 配列要素の制約表現。 +- 異種配列を許可するか。 +- `//` による patch を右辺置換だけにするか。 +- append / prepend / remove などの操作を提供するか。 diff --git a/doc/manual/souce/language/expression/composition.md b/doc/manual/souce/language/expression/composition.md new file mode 100644 index 0000000..99a3711 --- /dev/null +++ b/doc/manual/souce/language/expression/composition.md @@ -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) に置く。 diff --git a/doc/manual/souce/language/expression/default.md b/doc/manual/souce/language/expression/default.md new file mode 100644 index 0000000..32c3554 --- /dev/null +++ b/doc/manual/souce/language/expression/default.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 は採用されない。 diff --git a/doc/manual/souce/language/expression/function-call.md b/doc/manual/souce/language/expression/function-call.md new file mode 100644 index 0000000..4ee581a --- /dev/null +++ b/doc/manual/souce/language/expression/function-call.md @@ -0,0 +1,17 @@ +# Function Call Expression + +function call expression は、関数値を引数に適用する式である。 + +```n +mk_hw({ + greet = "Hello"; + target = "World"; +}) +``` + +## 評価 + +引数は thunk として渡せる。 +関数本体内で引数が参照されたときに評価する。 + +任意の関数呼び出し結果をグローバルに memoize することは必須ではない。 diff --git a/doc/manual/souce/language/expression/function.md b/doc/manual/souce/language/expression/function.md new file mode 100644 index 0000000..d4f954e --- /dev/null +++ b/doc/manual/souce/language/expression/function.md @@ -0,0 +1,18 @@ +# Function Expression + +function expression は、引数を受け取り式を返す値である。 + +```n +(part: { + greet = String; + target = String; +}) => + "${part.greet}! ${part.target}" +``` + +関数仕様の詳細は [関数](../functions.md) に置く。 + +## 評価 + +関数は定義時の環境を参照として保持する。 +関数本体は、関数値の生成時ではなく呼び出し時に評価される。 diff --git a/doc/manual/souce/language/expression/identifier.md b/doc/manual/souce/language/expression/identifier.md new file mode 100644 index 0000000..8ecf1f6 --- /dev/null +++ b/doc/manual/souce/language/expression/identifier.md @@ -0,0 +1,14 @@ +# Identifier Expression + +identifier expression は、現在の環境に束縛された名前を参照する式である。 + +```n +Port +MyConfig +mkConfig +``` + +## 評価 + +識別子は、対応する束縛の thunk を参照する。 +束縛が存在しない場合は未定義識別子エラーになる。 diff --git a/doc/manual/souce/language/expression/import.md b/doc/manual/souce/language/expression/import.md new file mode 100644 index 0000000..01ecc47 --- /dev/null +++ b/doc/manual/souce/language/expression/import.md @@ -0,0 +1,15 @@ +# Import Expression + +import expression は、外部ファイルを読み込み、そのファイルの評価結果を返す。 + +```n +import ./config.n +import "./config.n" +``` + +import 仕様の詳細は [モジュールと import](../modules-and-imports.md) に置く。 + +## 評価 + +import 先はモジュール単位で読み込まれる。 +ただし、各 field は thunk として保持され、必要になるまで評価されない。 diff --git a/doc/manual/souce/language/expression/index.md b/doc/manual/souce/language/expression/index.md new file mode 100644 index 0000000..a5cb811 --- /dev/null +++ b/doc/manual/souce/language/expression/index.md @@ -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) に集約する。 diff --git a/doc/manual/souce/language/expression/let.md b/doc/manual/souce/language/expression/let.md new file mode 100644 index 0000000..041d13d --- /dev/null +++ b/doc/manual/souce/language/expression/let.md @@ -0,0 +1,18 @@ +# Let Expression + +let expression は、ローカル束縛を作る。 + +```n +let + part = { + greet = "Hello"; + target = "World"; + }; +in + "${part.greet}! ${part.target}" +``` + +## 評価 + +let 束縛は thunk として保持される。 +参照されない束縛は評価されない。 diff --git a/doc/manual/souce/language/expression/literal.md b/doc/manual/souce/language/expression/literal.md new file mode 100644 index 0000000..b886af2 --- /dev/null +++ b/doc/manual/souce/language/expression/literal.md @@ -0,0 +1,20 @@ +# Literal Expression + +literal expression は、ソース上に直接書かれる具体値である。 + +## 種類 + +```n +"hello" +123 +3.14 +true +false +``` + +## 対応する primitive type + +- 文字列リテラルは `String` を満たす。 +- 整数リテラルは `Int` を満たす。 +- 浮動小数リテラルは `Float` を満たす。 +- `true` / `false` は `Bool` を満たす。 diff --git a/doc/manual/souce/language/expression/match.md b/doc/manual/souce/language/expression/match.md new file mode 100644 index 0000000..295766d --- /dev/null +++ b/doc/manual/souce/language/expression/match.md @@ -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」を自動選択しない。 +広い条件を先に書くと、後続の狭い条件には到達しない。 diff --git a/doc/manual/souce/language/expression/object.md b/doc/manual/souce/language/expression/object.md new file mode 100644 index 0000000..8077751 --- /dev/null +++ b/doc/manual/souce/language/expression/object.md @@ -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; + }; +} +``` diff --git a/doc/manual/souce/language/expression/path-reference.md b/doc/manual/souce/language/expression/path-reference.md new file mode 100644 index 0000000..0c172cb --- /dev/null +++ b/doc/manual/souce/language/expression/path-reference.md @@ -0,0 +1,15 @@ +# Path Reference Expression + +path reference expression は、object のフィールドを参照する式である。 + +```n +config.host +config.feature_hoge.enable +``` + +## 評価 + +左側の式を object として評価し、指定された field を参照する。 +参照先 field は必要になるまで評価されない。 + +存在しない field への参照をエラーにするか、open schema として扱うかは未確定である。 diff --git a/doc/manual/souce/language/expression/string-interpolation.md b/doc/manual/souce/language/expression/string-interpolation.md new file mode 100644 index 0000000..c9a536d --- /dev/null +++ b/doc/manual/souce/language/expression/string-interpolation.md @@ -0,0 +1,12 @@ +# String Interpolation Expression + +string interpolation は、文字列内に式を埋め込む候補機能である。 + +```n +"${part.greet}! ${part.target}" +``` + +## 評価 + +補間式の評価タイミングは通常の遅延評価に従う。 +文字列補間を初期実装に含めるかは未確定である。 diff --git a/doc/manual/souce/language/functions.md b/doc/manual/souce/language/functions.md new file mode 100644 index 0000000..f0a89b0 --- /dev/null +++ b/doc/manual/souce/language/functions.md @@ -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 + body: ExprId + env: EnvRef +} +``` + +## 関数と重さ + +関数の文法・パーサー自体は大きくない。 +実装上の重さは、主に評価モデルと意味論から発生する。 + +注意点: + +- クロージャが環境を掴む。 +- 引数を lazy にするか strict にするかを決める必要がある。 +- 関数呼び出し結果をどこまで memoize するかを決める必要がある。 +- 再帰関数を許可するかを決める必要がある。 +- 関数値同士の `&` をどう扱うかを決める必要がある。 +- 関数値を object に入れたとき、materialize 可能かを決める必要がある。 +- import 循環と関数適用が絡んだときの cycle detection が必要になる。 + +軽量に保つため、初期仕様では以下の制限を検討できる。 + +- 関数は pure。 +- lexical closure は許可。 +- 引数は thunk として渡す。 +- 関数本体は必要になるまで評価しない。 +- 関数値は opaque。 +- 関数同士の `&` は、同一関数参照以外は conflict。 +- 関数値は最終データとして出力できない。 +- 再帰は cycle error として扱う、または v1 では禁止する。 diff --git a/doc/manual/souce/language/index.md b/doc/manual/souce/language/index.md new file mode 100644 index 0000000..1b3d391 --- /dev/null +++ b/doc/manual/souce/language/index.md @@ -0,0 +1,12 @@ +# 言語仕様 + +このディレクトリは、遅延評価データ記述言語の仕様本文を章ごとに分割して管理する。 + +目次はマニュアル直下の [Manual Index](../index.md) に集約する。 +このファイルは `Language Specification` 章の入口としてだけ使う。 + +## Language + +言語仕様は、構文、値、式、制約、合成演算子、関数、モジュール、評価意味論、materialize、エラーを定義する。 + +詳細な章構成と各ファイルへのリンクは [Manual Index](../index.md) を参照する。 diff --git a/doc/manual/souce/language/materialization-and-errors.md b/doc/manual/souce/language/materialization-and-errors.md new file mode 100644 index 0000000..a077b5a --- /dev/null +++ b/doc/manual/souce/language/materialization-and-errors.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。 diff --git a/doc/manual/souce/language/modules-and-imports.md b/doc/manual/souce/language/modules-and-imports.md new file mode 100644 index 0000000..f74141e --- /dev/null +++ b/doc/manual/souce/language/modules-and-imports.md @@ -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 循環に該当する。 diff --git a/doc/manual/souce/language/naming.md b/doc/manual/souce/language/naming.md new file mode 100644 index 0000000..6b23399 --- /dev/null +++ b/doc/manual/souce/language/naming.md @@ -0,0 +1,27 @@ +# 命名規約 + +具体値と抽象値がグラデーションになるため、大文字・小文字による厳密な意味分けは設けない。 + +ただし、読みやすさのために慣習を定める。 + +## 推奨規約 + +- object 値: `lower_snake` +- 関数: `lowerCamel` +- 組み込み型・抽象的な制約名: `UpperCamel` + +例: + +```n +IPv4Address +MyConfig +new_config +mkConfig +``` + +## 厳密な規則にしない理由 + +この言語では、値・制約・スキーマ・派生設定が同じ式体系に乗る。 +そのため、ある名前が「具体値」か「抽象的な制約」かは文脈によってグラデーションになる。 + +大文字なら型、小文字なら値、のような厳密な規則を設けると、実際の利用に対して過剰に硬くなる可能性がある。 diff --git a/doc/manual/souce/language/operators.md b/doc/manual/souce/language/operators.md new file mode 100644 index 0000000..d0914fb --- /dev/null +++ b/doc/manual/souce/language/operators.md @@ -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 であり、左辺の制約を常に保持するとは限らない。 +制約を保持したい場合は `&` を使う。 diff --git a/doc/manual/souce/language/syntax.md b/doc/manual/souce/language/syntax.md new file mode 100644 index 0000000..6ac5e3a --- /dev/null +++ b/doc/manual/souce/language/syntax.md @@ -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) で定義する。 diff --git a/doc/manual/souce/language/value/bool.md b/doc/manual/souce/language/value/bool.md new file mode 100644 index 0000000..d613196 --- /dev/null +++ b/doc/manual/souce/language/value/bool.md @@ -0,0 +1,19 @@ +# Bool + +`Bool` は真偽値を表す primitive type constraint である。 + +## 例 + +```n +enable = Bool default true; +disable = Bool default false; +``` + +## リテラル + +`Bool` が受け入れるリテラルは以下である。 + +```n +true +false +``` diff --git a/doc/manual/souce/language/value/float.md b/doc/manual/souce/language/value/float.md new file mode 100644 index 0000000..18b9731 --- /dev/null +++ b/doc/manual/souce/language/value/float.md @@ -0,0 +1,15 @@ +# Float + +`Float` は浮動小数値を表す primitive type constraint である。 + +## 例 + +```n +ratio = Float; +threshold = Float default 0.5; +``` + +## 未確定事項 + +`Int` と `Float` の暗黙変換を許可するかは未確定である。 +軽量実装では、両者を明確に分ける方が単純である。 diff --git a/doc/manual/souce/language/value/index.md b/doc/manual/souce/language/value/index.md new file mode 100644 index 0000000..fbacfb2 --- /dev/null +++ b/doc/manual/souce/language/value/index.md @@ -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) も参照する。 diff --git a/doc/manual/souce/language/value/int.md b/doc/manual/souce/language/value/int.md new file mode 100644 index 0000000..7db3ed6 --- /dev/null +++ b/doc/manual/souce/language/value/int.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; +``` diff --git a/doc/manual/souce/language/value/string.md b/doc/manual/souce/language/value/string.md new file mode 100644 index 0000000..ef465f7 --- /dev/null +++ b/doc/manual/souce/language/value/string.md @@ -0,0 +1,20 @@ +# String + +`String` は文字列値を表す primitive type constraint である。 + +## 例 + +```n +name = String; +greeting = String default "hello"; +``` + +## 制約合成 + +`String` は文字列制約と合成できる。 + +```n +message = String & /Hello! .*/; +``` + +正規表現制約を必須機能にするかは未確定である。 diff --git a/doc/manual/souce/open-issues.md b/doc/manual/souce/open-issues.md new file mode 100644 index 0000000..a37b3ad --- /dev/null +++ b/doc/manual/souce/open-issues.md @@ -0,0 +1,61 @@ +# 未確定事項 + +今後決める必要がある事項を管理する。 +詳細化するときは、各項目を該当する仕様ファイルへ移動または反映する。 + +## 構文 + +- 正式なファイル拡張子。 +- 正式な字句・構文仕様。 +- 演算子の優先順位。 +- `rec` の扱い。 +- コメント構文を `#` のみにするか。 +- パス import と文字列 import の扱い分け。 + +## 型・制約 + +- 配列要素の制約表現。 +- object の open/closed schema の扱い。 +- 正規表現を必須機能にするか optional feature にするか。 +- 代表的な組み込み述語の範囲。 + +## default + +- `default` 同士の conflict 解決規則。 +- `&` による default 合成の厳密な規則。 +- `//` による default 置換の厳密な規則。 +- default thunk の評価失敗をどの段階で報告するか。 + +## 演算子 + +- `//` による制約・default の置換詳細。 +- `replace(...)` を採用するか、別構文を設けるか。 +- 配列に対する patch 操作を右辺置換だけにするか。 +- 配列 append / prepend / remove などを提供するか。 + +## 関数 + +- 関数値の最終出力可否。 +- 再帰関数を許可するか。 +- 関数同士の `&` の扱い。 +- 関数値の等価性。 +- 関数呼び出し結果の memoize 範囲。 + +## 評価 + +- thunk のエラー memoize 方針。 +- import cache の単位。 +- 循環 import の診断メッセージ。 +- materialize 対象の範囲指定方法。 + +## match + +- match の網羅性チェックを行うか。 +- 到達不能分岐を警告するか。 +- パターン構文の範囲。 + +## エラー処理 + +- `try / catch` を言語核に入れるか。 +- エラー値を通常値として扱えるようにするか。 +- エラー報告に制約由来の説明をどこまで含めるか。