# モジュールと import `import` は外部ファイルを読み込み、そのファイルの評価結果を返す。 ## 構文 ```dcdl import "./config.dcdl" ``` import specifier は文字列リテラルとする。 パスリテラル構文は採用しない。 ## モジュール import 先はモジュール単位で読み込まれる。 ただし、モジュール全体を即時評価する必要はない。 各フィールドは thunk として保持され、必要になったときだけ評価される。 top-level に field 定義列を書いた module は、recursive module scope を作る。 つまり、top-level field は同じ module の他の top-level field から識別子として参照できる。 ```dcdl schema = { hoge = String; }; result = schema; ``` この場合、`result` の右辺の `schema` は同じ module の top-level field `schema` を参照する。 通常の object literal 内の field は、その object 内の sibling field を暗黙には識別子として参照できない。 object 内の値を参照する場合は、外側で束縛された値や明示的な path reference を使う。 ## ImportLoader `import` specifier の解決は処理系 core ではなく host 側の `ImportLoader` が行う。 CLI では、specifier を現在の module path からの相対 path として解決する。 組み込み利用では、resource table や static source map など、filesystem 以外の loader を使える。 module cache の key は loader が返す安定 key を使う。 CLI では canonical path を key とする。 ### 構造化 import `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 { 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 側から次のように扱える。 ```dcdl Post = { frontmatter = { title = String; draft = Bool default false; }; body = String; }; post = Post & import "./hello.md"; title = post.frontmatter.title; body = post.body; ``` `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 を直接返す。 ## 循環 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 は成立する。 ## import の評価単位 実装上は、以下の単位で管理するのが自然である。 ```text Module main schema -> thunk result -> thunk Module func root -> thunk ``` 各 thunk は一度だけ評価して memoize する。 評価中に同じ thunk へ戻った場合は循環依存としてエラーにする。 ## import 失敗 以下は import 失敗として扱う。 - ファイルが存在しない。 - ファイルが読めない。 - import 先の構文解析に失敗する。 - import 先の評価で必要な値がエラーになる。 - host による非 DCDL content の読み込みまたは構造化に失敗する。 - 実装が禁止する import 循環に該当する。