NEPLg2.1 コンパイラ構成設計

最終更新: 2026-03-17


1. 設計目標

1.1 現行(NEPLg2.0)の問題点

ファイル行数問題
typecheck.rs8,871型推論・trait 検査・effect 判定・HIR 生成が全部混在
compiler.rs933パイプライン統合とターゲットゲートパーサが同居
resolve.rs / name_resolve.rs279 / 55役割重複。両方ともスタブ状態
module_graph.rs / loader.rs583 / 738ファイル解決・パース・依存グラフが混在
hir.rs194TupleConstruct など NEPLg2.1 廃止構文が残る
passes/3ファイルResource IR パスが存在しない
nm.rs548NM フォーマットパーサがコアコンパイラに同居
トップレベル全体20+ ファイルフラット構造で関係が見えない

Resource IR(ownership/borrow/region/drop の中間表現)が存在しないことは NEPLg2.1 の中核仕様(doc/2.1spec/compiler.md)と直接矛盾する。

1.2 NEPLg2.1 での方針

  • パイプラインステージ = ディレクトリ階層。依存方向がディレクトリ構造から読み取れる。
  • 1 ファイルの目安は 800 行以下。巨大 typecheck.rs は役割ごとに分割する。
  • Resource IR を第一級モジュールとして配置resource/ ディレクトリに独立させる。
  • 型表現(ty/)と型検査(check/)を分離。型の定義と「その型を検査するロジック」は別。
  • モジュールシステムの 3 層(物理・構文・論理)をmodule/ 内の別ファイルで表現
  • nm/ はコアコンパイラから切り離す(ツールチェーン補助)。
  • セルフホスト(stdlib/neplg2/)との命名パリティを保つ。Rust 側のディレクトリ名が自然に NEPL 側のモジュール名に対応する。

1.3 括弧なし処理の基本アーキテクチャ

NEPLg2.1 は式・型式・パターンのいずれにも括弧によるグループ化を持たない。これは通常の LL/LR パーサが前提とする「パーサが構文木を決定する」設計とは根本的に異なるため、コンパイラパイプライン全体への影響を明示する。

原則:パーサは flat list を生成し、checker が境界を確定する

                 パーサの責務            checker の責務
                 ──────────────         ──────────────────────────────
式(expr)    :  PrefixList 生成  →  reduce_calls (arity + 期待型)  →  call tree (HIR)
型式(type)  :  TypePrefixList 生成 →  reduce_type_apps (kind)      →  TypeExpr (解決済み)
パターン      :  PatternList 生成  →  constructor arity 解決         →  pattern tree
  • PrefixList: juxtaposition を flat な Vec<PrefixItem> として表現。call tree への変換は check/expr_check.rsreduce_calls アルゴリズムが担う。
  • TypePrefixList: 型適用を flat な Vec<TypeToken> として表現。ty/kind.rsreduce_type_apps アルゴリズムが kind を使って境界を確定する。
  • PatternList: コンストラクタへの引数適用も flat。check/pat_check.rs が constructor の arity を参照して tree に変換する。

なぜパーサで解決できないか

型式の例: Vec Option .TVec の kind * -> * はユーザー定義型の場合、定義を読むまで不明。
式の例: add sub 1 2 3sub の arity は DefTable に名前解決した後に初めて確定する。

どちらも名前解決(resolve/)が完了してから境界確定が可能になる。パーサは flat list を生成するのみ。

型式の外側境界は構文で確定する

型式の「どこが終わりか」は構文マーカーで確定する(\where・改行+インデント・: など)。
不確定なのは「内部のどのトークンがどのコンストラクタの引数か」という内部適用構造のみ。
これが ty/kind.rsreduce_type_apps が解決する問題。

HIR が最初の「解決済み構造」

AST は flat/未解決。HIR(hir/hir.rs)が初めて完全に解決された call tree・type tree・pattern tree を持つ中間表現である。resource/ 以降はすべて HIR を入力とする。

NEPLg2.0(Zenn記事)との差分

参考: NEPLg2.0 における括弧なし式解決の実装詳細は Zenn 記事「括弧なし言語 NEPLg2 の実装」を参照。
NEPLg2.1 はその仕組みを型式・パターンにも拡張したもの。

NEPLg2.0 の typecheck.rsreduce_calls アルゴリズムを実装していたが、型式には angle bracket があったためパーサが構造を確定できた。NEPLg2.1 はこの前提を崩す:

課題NEPLg2.0NEPLg2.1担当モジュール
式の call 境界typecheck.rs 内 reduce_callscheck/expr_check.rs✓ 同じ仕組み
型式の境界パーサが angle bracket で確定reduce_type_apps(新規)ty/kind.rs
パターン境界arity 依存(暗黙)明示的に分離check/pat_check.rs
let の pattern/expr 分割記述なしPatternList から arity で分割check/pat_check.rs
クロージャ一級値制限あり(記事注記)完全対応。キャプチャ解析が必要check/expr_check.rs + resource/
@ident 強制値モード該当なしreduce_calls への値ヒントexpr_parser.rs + expr_check.rs

2. ブートストラップ Rust コンパイラ (nepl-core-2.1)

2.1 ファイル構成

nepl-core-2.1/
    src/
        lib.rs                  # crate root、公開 API の re-export

        // ── 共通基盤 ────────────────────────────────────
        infra/
            mod.rs
            span.rs             # SourceSpan, FileId
            source.rs           # SourceFile, SourceMap
            diag/
                mod.rs
                diag.rs         # Diagnostic, Severity, DiagLabel, DiagNote
                ids.rs          # DiagnosticId 定数一覧
                outcome.rs      # Outcome<T, E>(診断付き Result)
                emit.rs         # テキスト出力(人間向け表示)

        // ── フロントエンド ───────────────────────────────
        syntax/
            mod.rs
            token.rs            # Token, TokenKind
            lexer.rs            # インデント aware トークナイザ(オフサイド規則)
            ast/
                mod.rs
                item.rs         # Item(let / struct / enum / trait / impl / use / merge)
                expr.rs         # Expr — juxtaposition は PrefixList(flat Vec<PrefixItem>、未解決)
                                #   call tree への変換は check/expr_check.rs の reduce_calls が行う
                                #   block / if / while / match / let / set / borrow は構文的に確定
                pattern.rs      # Pattern — コンストラクタ適用は PatternList(flat、未解決)
                                #   constructor arity による tree 変換は check/pat_check.rs が行う
                                #   リテラル・ワイルドカード・OR・@ は構文的に確定
                typexpr.rs      # TypeExpr — 型適用は TypePrefixList(flat Vec<TypeToken>、未解決)
                                #   kind-directed な境界確定は ty/kind.rs の reduce_type_apps が行う
                                #   fn/fn*/&/&mut・型変数 .T・外側境界(\、where、: 等)は構文的に確定
                module_ast.rs   # ModuleAst(ファイル単位 AST)・FileHeader(#module/#entry/#part)
            parser/
                mod.rs
                item_parser.rs  # 宣言パーサ(let 束縛 / struct / enum / trait / impl)
                                #   型パラメータ列・%TypeExpr の外側境界を構文的に確定させ TypePrefixList を収集
                expr_parser.rs  # 式パーサ(前置 juxtaposition → PrefixList 生成・block・if/while/match)
                                #   call 境界は確定させない。PrefixList を flat なまま AST に格納する
                                #   @ ident(強制値モード)はパーサが認識し PrefixItem::ForcedValue として格納
                                #     → reduce_calls がこの識別子を「呼び出し対象」ではなく「値」として扱う
                                #   \x body / \x: body_block / \(): body_block(クロージャ)は特殊形式として直接 AST 化
                                #     ただし body は PrefixList のままで call 境界は check/ に委譲
                type_parser.rs  # 型式パーサ(TypePrefixList 生成)
                                #   kind-directed 境界確定はここでは行わない。ty/kind.rs に委譲する
                                #   fn/fn* の kind-directed 境界・% 終端・\ / where 等の外側境界のみ構文的に確定
                pat_parser.rs   # パターンパーサ(PatternList 生成)
                                #   constructor 境界は確定させない。check/pat_check.rs に委譲する

        // ── モジュールシステム ──────────────────────────
        module/
            mod.rs
            physical.rs         # 物理層:ファイルパス解決・ハッシュ計算
            syntax_graph.rs     # 構文層:AST + merge/use 辺の DAG
            logical.rs          # 論理層:Canonical Module Path・merge 結合後モジュールツリー
            use_resolver.rs     # use/pub use パス解決・循環検出(pub use cycle)
            cache.rs            # パースキャッシュ・セマンティックキャッシュ

        // ── 名前解決 ────────────────────────────────────
        resolve/
            mod.rs
            def_id.rs           # DefId, DefKind, DefTable
            scope.rs            # スコープチェーン(ネスト・巻き上げ対応)
            item_resolver.rs    # トップレベル item 解決・定義テーブル構築
            use_import.rs       # use 文の item 引き当て・エクスポートテーブル
            variant_resolver.rs # EnumType::Variant / bare variant 解決(type context 判定)

        // ── 型システム ──────────────────────────────────
        ty/
            mod.rs
            ty.rs               # TypeId, TypeKind(Prim / Fn / Struct / Enum / Apply / Var …)
            arena.rs            # TypeArena(型インターン)
            kind.rs             # Kind(*、* -> * …)・kind 推論
                                # ★ reduce_type_apps: TypePrefixList → TypeExpr の kind-directed 境界確定
                                #   式の reduce_calls に対応する型式版アルゴリズム
                                #   KindEnv(DefTable 由来)を受け取り、flat な型トークン列を解決済み木に変換
            unify.rs            # 単一化・型変数インスタンス化
            subst.rs            # 型変数代入(Substitution)
            effect.rs           # Effect(Pure/Impure)・InternalAlloc / ExternalIO 分類

        // ── 型検査 ──────────────────────────────────────
        check/
            mod.rs
            checker.rs          # 型検査オーケストレーション・ContextStack
            expr_check.rs       # 式の型検査
                                # ★ reduce_calls: PrefixList → call tree への arity-directed 縮約
                                #   DefTable から各識別子の arity を取得し stack-based reduction を行う
                                #   infer_expected_from_outer_consumer で期待型を双方向伝播させ曖昧性を解消
                                #   オーバーロード候補の絞り込みも同一パスで行う
                                # ★ クロージャ型検査(NEPLg2.0 では不完全だった一級値としての closure)
                                #   期待型 fn A B / fn* A B に基づいて \x body / \x: body_block を型検査
                                #   キャプチャ変数の種別を判定(Copy はコピー、Owned は move、Linear はエラー)
                                #   Linear キャプチャ禁止の enforcement はここで行う
                                #   move キャプチャした変数は以降 Moved 状態(resource/ownership.rs と連携)
                                # ★ @ident の処理: PrefixItem::ForcedValue を「関数適用の対象」としてではなく
                                #   「値そのもの」として reduce_calls に渡す。arity 消費をスキップ
            pat_check.rs        # パターン型検査・constructor arity 解決・網羅性検査
                                # ★ PatternList → pattern tree への変換(expr_check の reduce_calls に対応)
                                #   コンストラクタが何個のサブパターンを取るかは DefTable から取得
                                #   match arms だけでなく let 文の "let <pattern> <expr>" も同一アルゴリズムで処理
                                #     例: let Point x: a y: b p を
                                #           pattern=Point{x:a,y:b}、expr=p に分割
                                #   match exhaustiveness(全バリアントカバレッジ)はコンパイル時に静的検査
            decl_check.rs       # 宣言検査(let / struct / enum / trait / impl)
            hoist.rs            # let 束縛の巻き上げ(相互再帰対応)
            overload.rs         # オーバーロード解決 3 段階(制約フィルタ→期待型→修飾名)
            trait_check.rs      # impl 一意性・Orphan Rule・Global Coherence
            effect_check.rs     # Pure/Impure 伝播・InternalAlloc Escape Analysis

        // ── HIR ─────────────────────────────────────────
        hir/
            mod.rs
            hir.rs              # HirModule, HirExpr, HirPattern, HirItem
            lower.rs            # AST + 型検査結果 → HIR lowering
            pretty.rs           # デバッグ表示

        // ── Resource IR(NEPLg2.1 新規)────────────────
        resource/
            mod.rs
            ir.rs               # ResourceIr ノード
                                #   move x → y
                                #   borrow_shared x → b
                                #   borrow_unique x → b
                                #   region_new ρ / region_alloc ρ n / region_end ρ
                                #   drop x
                                #   io_open/write/close
            lower.rs            # HIR → Resource IR lowering
            ownership.rs        # use-after-move 検査(診断 5001)
            borrow.rs           # Borrow check / NLL last-use(診断 5007/5008)
            region.rs           # Region Inference(Pure persistent value の bulk free)
            drop_elab.rs        # Drop Elaboration(Owned/Linear の自動 drop 挿入)
            linear.rs           # Linear resource 消費検査(診断 5005/5006)

        // ── パス ────────────────────────────────────────
        passes/
            mod.rs
            target_gate.rs      # #if[target=...] / #target 評価
            codegen_pre.rs      # コード生成前整合検査

        // ── 単相化 ──────────────────────────────────────
        mono/
            mod.rs
            mono.rs             # 単相化メインロジック
            instance.rs         # インスタンス化キャッシュ・重複排除

        // ── コード生成 ──────────────────────────────────
        codegen/
            mod.rs
            wasm/
                mod.rs
                emit.rs         # Wasm バイナリ出力(wasm-encoder)
                intrinsic.rs    # Wasm 組み込み命令
                runtime.rs      # runtime helper 関数注入
                layout.rs       # 型の Wasm メモリレイアウト
            llvm/
                mod.rs
                emit.rs         # LLVM IR テキスト出力
                intrinsic.rs    # LLVM 組み込み命令
                layout.rs       # 型のネイティブレイアウト

        // ── 組み込み ────────────────────────────────────
        builtins/
            mod.rs
            primitives.rs       # プリミティブ型・演算(i32/u8/f32/bool/str/()/never)
            intrinsics.rs       # コンパイラ組み込み操作(load/store/mem_copy 等)
            prelude.rs          # 暗黙 prelude(core::prelude から自動 use されるもの)

        // ── パイプライン API ────────────────────────────
        pipeline.rs             # compile_xxx 公開関数・パイプライン統合・エラー集約
        options.rs              # CompileOptions, BuildProfile, CompileTarget

        // ── ツールチェーン補助(コアコンパイラとは分離)──
        nm/
            mod.rs
            parser.rs           # .n.md NM 拡張 Markdown パーサ
            extractor.rs        # doctest・テストケース抽出

2.2 依存方向

infra
  ↑
syntax  →  module
  ↑           ↑
resolve  ←  (module)
  ↑    ╲
  │     ╲ KindEnv・DefTable を ty/kind.rs と check/ に渡す
  │      ↘
ty  ←────── resolve(KindEnv 受け取り)
  ↑
check  ←── resolve(DefTable・arity 参照)
  ↑
hir       ← ここで初めて flat AST → 解決済み tree に変換される
  ↑
resource
  ↑
passes  →  mono  →  codegen/{wasm, llvm}
                        ↑
                    builtins

pipeline.rs(全体を統合)
nm/(独立。pipeline からは参照しない)

上位ほど下位に依存する(矢印 = 依存先)。
ty/kind.rsresolve:: が構築した KindEnv(型コンストラクタ名 → Kind の対応表)を受け取って reduce_type_apps を実行する。check/expr_check.rs も同様に DefTable から arity を参照する。nm/ はコアコンパイラ本体からは参照しない独立ツール。

2.3 ファイルサイズの目安

モジュール推奨上限根拠
lexer.rs1,000 行複雑なインデント規則があるため
parser/*_parser.rs800 行役割単位で分割済み
ty/unify.rs600 行単一化アルゴリズムに集中
check/expr_check.rs800 行typecheck.rs の主要部分を切り出し
check/checker.rs400 行オーケストレーションのみ
resource/*500 行解析フェーズごとに独立
codegen/wasm/emit.rs1,200 行wasm エンコードの複雑さを許容
codegen/llvm/emit.rs1,000 行LLVM IR テキスト生成

3. CLI クレート (nepl-cli)

nepl-cli/
    src/
        main.rs             # CLI エントリポイント・引数パース・exit コード
        args.rs             # コマンドライン引数定義(clap または手書き)
        file_io.rs          # ソースファイル読み込み・出力ファイル書き込み
        runner/
            mod.rs
            wasm_runner.rs  # WASM/WASI 実行(wasmtime / wasm3)
            llvm_runner.rs  # clang 呼び出し・native バイナリ生成
        repl.rs             # 将来の REPL サポート(Phase 後半)

nepl-clinepl-core-2.1 に依存する。WASI / OS 依存の操作はすべてここに集中させ、nepl-core-2.1no_stdextern crate alloc を維持する。


4. セルフホストコンパイラ (stdlib/neplg2/)

セルフホストは NEPLg2.1 自身で書かれたコンパイラ。Rust 側のクレート構造とディレクトリ名を一致させることで、移植の対応関係を明確にする。

stdlib/neplg2/
    core/                   # 純粋コンパイラコア(Wasm、WASI 不使用)
        #module             # anchor ファイル
        infra/
            span.nepl       # SourceSpan, FileId(core::pair/option に依存)
            diag.nepl       # Diagnostic, DiagnosticId, Outcome
        syntax/
            token.nepl      # Token, TokenKind
            lexer.nepl      # インデント aware トークナイザ
            ast/
                item.nepl
                expr.nepl
                pattern.nepl
                typexpr.nepl
                module_ast.nepl
            parser/
                item_parser.nepl
                expr_parser.nepl
                type_parser.nepl
                pat_parser.nepl
        module/
            physical.nepl
            syntax_graph.nepl
            logical.nepl
            use_resolver.nepl
            cache.nepl
        resolve/
            def_id.nepl
            scope.nepl
            item_resolver.nepl
            variant_resolver.nepl
        ty/
            ty.nepl
            arena.nepl
            kind.nepl
            unify.nepl
            effect.nepl
        check/
            checker.nepl
            expr_check.nepl
            pat_check.nepl
            decl_check.nepl
            trait_check.nepl
            effect_check.nepl
        hir/
            hir.nepl
            lower.nepl
        resource/
            ir.nepl
            ownership.nepl
            borrow.nepl
            region.nepl
            drop_elab.nepl
            linear.nepl
        passes/
            target_gate.nepl
            codegen_pre.nepl
        mono/
            mono.nepl
            instance.nepl
        codegen/
            wasm/
                emit.nepl
                layout.nepl
            llvm/
                emit.nepl
                layout.nepl
        builtins/
            primitives.nepl
            intrinsics.nepl
        pipeline.nepl
        options.nepl

    cli/                    # WASI CLI(ファイル I/O・プロセス操作を含む)
        #entry
        args.nepl
        file_io.nepl
        runner.nepl

core/ は Pure 関数のみで構成し、WASI syscall を一切含まない。cli/ が WASI を通じて外界と接続し、core/pipeline.nepl を呼び出す。これは現行の nepl-core(no_std)と nepl-cli(std)の分離を NEPL 言語で再現したもの。


4.5 その他のワークスペースクレート

NEPLg2.1 ワークスペースには nepl-core-2.1 / nepl-cli 以外にも次のクレートが存在する。

クレート責務状態
nepl-lspLanguage Server Protocol 実装。補完・診断・ホバーを提供。nepl-core-2.1 の型検査結果をインクリメンタルに利用開発中(NEPLg2.1 対応は Stage 3 以降)
webWebAssembly バインディング。ブラウザ上のプレイグラウンド向け。nepl-core-2.1 を wasm-bindgen でラップNEPLg2.1 対応は Stage 5 以降

どちらも nepl-core-2.1 に依存し、OS/ブラウザ固有の I/O は各クレート内に閉じ込める。nepl-core-2.1 本体は no_stdextern crate alloc を維持する。


5. 現行ファイルと NEPLg2.1 ファイルの対応表

現行(nepl-core/src/)NEPLg2.1(nepl-core-2.1/src/)変更内容
span.rsinfra/span.rs移動のみ
diagnostic.rs + diagnostic_ids.rsinfra/diag/diag.rs + ids.rs統合・Outcome 追加
error.rsinfra/diag/diag.rs に統合CoreError → Diagnostic に一本化
log.rs廃止(eprintln! → Diag emitter へ)11 行のみ、不要
lexer.rssyntax/lexer.rs移動のみ
ast.rssyntax/ast/ (分割)TypeExpr → typexpr.rs、Expr → expr.rs、Pattern → pattern.rs
parser.rssyntax/parser/ (分割)役割別に 4 ファイルへ
module_graph.rs + loader.rsmodule/ (分割)3 層モデルに再設計
resolve.rs + name_resolve.rsresolve/ (統合・拡張)variant 解決追加
types.rsty/ty.rs + ty/arena.rsTuple 廃止、ResourceUsage 追加
effects.rsty/effect.rsInternalAlloc/ExternalIO 分類を充実
typecheck.rs (8,871 行)check/ (7 ファイルに分割)役割別分割が最大の変更
hir.rshir/hir.rsTupleConstruct 廃止
compiler.rspipeline.rs + options.rsターゲットゲートは passes/target_gate.rs
builtins.rsbuiltins/primitives.rs分割・整理
runtime_helpers.rscodegen/wasm/runtime.rsWasm 固有のため移動
wasm_shared.rscodegen/wasm/layout.rs + emit.rs に統合
codegen_wasm.rscodegen/wasm/emit.rs移動・整理
codegen_llvm.rscodegen/llvm/emit.rs移動・整理
monomorphize.rsmono/mono.rs + mono/instance.rs分割
passes/move_check.rsresource/ownership.rs に統合Resource IR の一部として再実装
passes/drop_insertion.rsresource/drop_elab.rs に統合Resource IR の一部として再実装
passes/codegen_precheck.rspasses/codegen_pre.rs移動
target_precheck.rspasses/target_gate.rs に統合
nm.rsnm/parser.rs + nm/extractor.rsコアから分離
―(存在しない)resource/ir.rs / borrow.rs / region.rs / linear.rsNEPLg2.1 新規

6. パイプライン統合(pipeline.rs

pipeline.rs は各ステージを順に呼び出す統合関数を提供する。

pub fn compile(opts: CompileOptions, source_map: &SourceMap)
    -> Result<CompilationArtifact, Vec<Diagnostic>>

内部処理順:
  1. syntax::lexer          トークナイズ
  2. syntax::parser         パース → ModuleAst(flat/未解決 AST)
                              expr: PrefixList(call 境界未確定)
                              type: TypePrefixList(kind-directed 境界未確定)
                              pattern: PatternList(constructor arity 未確定)
  3. module::               物理→構文→論理の 3 層解決。import 先モジュールの DefTable・KindEnv を収集
  4. resolve::              DefId 付与・スコープ構築・use 引き当て・KindEnv 構築
                              ※ この段階で arity / kind 情報が初めて確定する
  5. ty:: + check::         ① ty/kind.rs: TypePrefixList → TypeExpr(reduce_type_apps)
                              ② check/expr_check.rs: PrefixList → call tree(reduce_calls)
                              ③ check/pat_check.rs: PatternList → pattern tree(constructor arity 解決)
                              ④ 型推論・単一化・effect 検査・trait 検査・overload 解決
                              ※ ①〜④は相互依存するため反復収束(固定点)で解く
  6. hir::lower             AST(flat/未解決) + 型検査結果 → HIR(解決済み call tree)
                              HIR が最初の「完全解決済み構造」。以降のパスはすべて HIR を入力とする
  7. resource::             Resource IR 生成・ownership/borrow/region/drop 検査
  8. passes::               target gate 評価・codegen 前検査
  9. mono::                 単相化
 10. codegen::{wasm,llvm}   コード生成

各ステージは独立した Result<_, Vec<Diagnostic>> を返す。エラーがあっても可能な限り後段まで続行して診断をまとめる(エラー回復)。


7. 移行戦略

NEPLg2.1 コンパイラは現行 nepl-core を置き換えるのではなく、並行して新規クレート nepl-core-2.1 として開発する

フェーズ内容
Stage 1infra/ + syntax/ + module/ の骨格を作る。現行 lexer/parser をリファクタして移植
Stage 2resolve/ + ty/ の基礎を移植。effect 分類を充実させる
Stage 3check/typecheck.rs から分割移植。巨大ファイルを解消
Stage 4hir/ + resource/ を新規実装。Resource IR を導入
Stage 5mono/ + codegen/ を移植。既存テストが通ることを確認
Stage 6nepl-corenepl-core-2.1 に切り替え。テスト全通過を確認してから旧クレートを廃止

セルフホスト (stdlib/neplg2/) はブートストラップコンパイラが Stage 4 以降(Resource IR 利用可能)になってから本格着手する。

On this page