Add host-structured imports

This commit is contained in:
2026-08-11 18:33:49 +09:00
parent 46b2b3b1b6
commit 165daada23
12 changed files with 402 additions and 46 deletions
+25 -4
View File
@@ -1,7 +1,7 @@
# Embedding API
Decodal core can be embedded without giving the core crate access to a filesystem.
The host supplies imported sources through `SourceLoader` and may also provide global bindings through the host prelude API.
The host supplies imports through `ImportLoader` and may also provide global bindings through the host prelude API.
## Host prelude
@@ -131,11 +131,32 @@ Supported field attributes are intentionally small:
The derive does not add host callbacks or reflection.
It only generates schema construction and typed decoding code.
## SourceLoader and prelude together
## ImportLoader and prelude together
`SourceLoader` and host prelude bindings are independent mechanisms.
`ImportLoader` and host prelude bindings are independent mechanisms.
- Use `SourceLoader` when user sources should explicitly import host-provided modules.
- Use `ImportLoader` when user sources should explicitly import host-provided sources or values.
- Use prelude bindings when host-provided schemas or constants should be globally available.
Both mechanisms share the same runtime evaluator, thunk model, and materialization rules.
## Structured imports
`ImportLoader::load` returns either `LoadedImport::Source` or `LoadedImport::Value`.
The value variant carries a `HostValue`, allowing a host to parse non-Decodal resources such as Markdown into an application-specific structure.
```text
import "./post.md"
-> host parses content
-> LoadedImport::Value(
{ frontmatter: {...}, body: "..." }
)
-> Engine internalizes HostValue
-> normal composition and materialization
```
The core does not select content types or bundle Markdown/frontmatter parsers.
The loader owns path resolution, media or extension dispatch, parsing rules, and parse diagnostics.
The stable loader key is also used to cache structured imports.
`load` is the single import hook: loaders dispatch by extension, media type, or another host-defined rule and return the appropriate variant directly.
@@ -66,14 +66,16 @@ ModuleRegistry:
処理系は、まず root module を parse / desugar して registry に登録する。
import 先 module は、この段階で全て読み込む必要はない。
import expression が評価されたとき、処理系は `SourceLoader` に現在の module key と import specifier を渡す。
loader は module key、表示名、source text を返す。
module registry は key が未登録なら対象 module を parse / desugar して登録する。
登録された module は module root thunk を持つ。
同じ module が複数回 import された場合は、同じ `ModuleId` を返す
import expression が評価されたとき、処理系は `ImportLoader::load` に現在の module key と import specifier を渡す。
loader は module key、表示名、および DCDL source text または構造化済み `HostValue` を返す。
DCDL source の場合、module registry は key が未登録なら対象 module を parse / desugar して登録する。
登録された source module は module root thunk を持つ。
構造化値の場合、host value を runtime value に internalize した root thunk を key でキャッシュする
同じ key が複数回 import された場合は、同じ source module または structured root thunk を使う。
つまり import は module を即時評価しない。
つまり source import は module を即時評価しない。
module を読み込み、module root を thunk として登録するだけにする。
structured import も root value を thunk として保持し、object field や array item の既存 thunk model を利用する。
AST の `ExprId` は module-local である。
そのため runtime が保持する式参照は `ExprRef { module, expr }` として module-qualified にする。
+2 -2
View File
@@ -104,7 +104,7 @@ Concrete(Int(8000))
## Runtime scope
Decodal runtime は application runtime ではなく、pure value evaluator である。
同じ source、同じ import sources、同じ host globals が与えられた場合、評価結果は決定的である。
同じ source、同じ import results、同じ host globals が与えられた場合、評価結果は決定的である。
runtime が扱う責務は以下に限る。
@@ -114,7 +114,7 @@ runtime が扱う責務は以下に限る。
- materialize 時に constraint を検証する。
runtime は filesystem、network、environment variable、time、random、mutation を扱わない。
core における import は host supplied source を受け取る境界であり、filesystem access ではない。
core における import は host supplied source または structured value を受け取る境界であり、filesystem access ではない。
## Constraint
@@ -32,15 +32,77 @@ result = schema;
通常の object literal 内の field は、その object 内の sibling field を暗黙には識別子として参照できない。
object 内の値を参照する場合は、外側で束縛された値や明示的な path reference を使う。
## SourceLoader
## ImportLoader
`import` specifier の解決は処理系 core ではなく host 側の `SourceLoader` が行う。
`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 が構築した `HostValue` を import 結果として返す。
Markdown、JSON、TOML などの解釈規則は core に固定せず、loader がファイル種別を判定して構造化する。
```rust
use decodal::{HostValue, 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,
HostValue::object([
("frontmatter", parsed.frontmatter),
("body", HostValue::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
モジュール間に循環参照があっても、必要なフィールドの依存関係が循環していなければ評価できる。
@@ -93,4 +155,5 @@ Module func
- ファイルが読めない。
- import 先の構文解析に失敗する。
- import 先の評価で必要な値がエラーになる。
- host による非 DCDL content の読み込みまたは構造化に失敗する。
- 実装が禁止する import 循環に該当する。