Implement module loading and imports

This commit is contained in:
2026-06-16 10:01:31 +09:00
parent fead194ba6
commit bd3da1aeee
13 changed files with 586 additions and 124 deletions
+10 -3
View File
@@ -54,23 +54,30 @@ desugar は、意味論を単純にするための表層構文変換を行う。
## module registry
module registry は、読み込んだ module を canonical path で管理する。
module registry は、読み込んだ module を loader が返す安定 key で管理する。
CLI では canonical path を key とする。
組み込み利用では、resource name や static source table の key を使える。
```text
ModuleRegistry:
CanonicalPath -> ModuleId
ModuleKey -> ModuleId
```
処理系は、まず root module を parse / desugar して registry に登録する。
import 先 module は、この段階で全て読み込む必要はない。
import expression が評価されたとき、module registry は path を解決し、未登録なら対象 module を parse / desugar して登録する
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 は module を即時評価しない。
module を読み込み、module root を thunk として登録するだけにする。
AST の `ExprId` は module-local である。
そのため runtime が保持する式参照は `ExprRef { module, expr }` として module-qualified にする。
## demand-driven evaluation
評価器は、必要になった thunk だけを force する。
@@ -7,11 +7,16 @@ thunk は、まだ評価していない式をあとで評価できるように
```text
Thunk {
expr: ExprId
expr: ExprRef
env: EnvId
state: ThunkState
}
ExprRef {
module: ModuleId
expr: ExprId
}
ThunkState =
Unevaluated
Evaluating
@@ -19,7 +24,8 @@ ThunkState =
Error(Diagnostic)
```
`expr` は評価対象の AST node を指す。
`expr` は評価対象の AST node を module-qualified に指す。
`ExprId` は module-local な ID なので、runtime では `ModuleId` と組み合わせた `ExprRef` を保持する。
`env` は、その式を評価するときに使う lexical environment を指す。
式だけではなく environment も保持するのは、遅延評価された式が定義時の名前解決文脈を必要とするためである。
@@ -17,6 +17,29 @@ 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 を暗黙に recursive scope にするかは別仕様とする。
## SourceLoader
`import` specifier の解決は処理系 core ではなく host 側の `SourceLoader` が行う。
CLI では、specifier を現在の module path からの相対 path として解決する。
組み込み利用では、resource table や static source map など、filesystem 以外の loader を使える。
module cache の key は loader が返す安定 key を使う。
CLI では canonical path を key とする。
## 循環 import
モジュール間に循環参照があっても、必要なフィールドの依存関係が循環していなければ評価できる。
@@ -42,7 +65,7 @@ import 先はモジュール単位で読み込まれる。
}
```
`func.n``main.n` を import しているが、参照しているのは `main.schema` である。
`func.dcdl``main.dcdl` を import しているが、参照しているのは `main.schema` である。
`main.schema``main.result` に依存していなければ、この循環 import は成立する。
## import の評価単位