Skip to main content

libperl_macrogen/
pipeline.rs

1//! Pipeline API for libperl-macrogen
2//!
3//! 3フェーズ構成の Pipeline アーキテクチャを提供:
4//! 1. Preprocess: C ヘッダーファイルのプリプロセス
5//! 2. Infer: マクロと inline 関数の型推論
6//! 3. Generate: Rust コード生成
7//!
8//! # 使用例
9//!
10//! ```ignore
11//! use libperl_macrogen::Pipeline;
12//!
13//! // 一括実行
14//! let mut output = File::create("macro_fns.rs")?;
15//! Pipeline::builder("wrapper.h")
16//!     .with_auto_perl_config()?
17//!     .with_codegen_defaults()
18//!     .with_bindings("bindings.rs")
19//!     .build()?
20//!     .generate(&mut output)?;
21//!
22//! // 段階的実行
23//! let preprocessed = Pipeline::builder("wrapper.h")
24//!     .with_auto_perl_config()?
25//!     .with_codegen_defaults()
26//!     .build()?
27//!     .preprocess()?;
28//!
29//! let inferred = preprocessed
30//!     .with_bindings("bindings.rs")
31//!     .infer()?;
32//!
33//! let generated = inferred
34//!     .with_strict_rustfmt()
35//!     .generate(&mut output)?;
36//! ```
37
38use std::collections::HashMap;
39use std::io::Write;
40use std::path::PathBuf;
41
42use crate::perl_config::{get_perl_config, PerlConfigError, get_default_target_dir};
43use crate::preprocessor::{PPConfig, Preprocessor};
44use crate::rust_codegen::{BindingsInfo, CodegenConfig as RustCodegenConfig, CodegenDriver, CodegenStats};
45use crate::rust_codegen::{CodegenReport, RequireCodegenError};
46use crate::infer_api::{InferResult, InferError};
47use crate::error::EnrichedCompileError;
48
49// ============================================================================
50// Error types
51// ============================================================================
52
53/// Pipeline 実行時のエラー
54#[derive(Debug)]
55pub enum PipelineError {
56    /// Perl 設定取得エラー
57    PerlConfig(PerlConfigError),
58    /// プリプロセス/パースエラー(ファイルパスと該当行で強化済み)
59    Compile(EnrichedCompileError),
60    /// 推論エラー
61    Infer(InferError),
62    /// I/O エラー
63    Io(std::io::Error),
64    /// require-codegen-list 違反(出力書き込み後に検出)
65    RequireCodegen(RequireCodegenError),
66}
67
68impl std::fmt::Display for PipelineError {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        match self {
71            PipelineError::PerlConfig(e) => write!(f, "Perl config error: {}", e),
72            PipelineError::Compile(e) => write!(f, "Compile error: {}", e),
73            PipelineError::Infer(e) => write!(f, "Inference error: {}", e),
74            PipelineError::Io(e) => write!(f, "I/O error: {}", e),
75            PipelineError::RequireCodegen(e) => write!(f, "{}", e),
76        }
77    }
78}
79
80impl std::error::Error for PipelineError {
81    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
82        match self {
83            PipelineError::PerlConfig(e) => Some(e),
84            PipelineError::Compile(e) => Some(e),
85            PipelineError::Infer(e) => Some(e),
86            PipelineError::Io(e) => Some(e),
87            PipelineError::RequireCodegen(e) => Some(e),
88        }
89    }
90}
91
92impl From<RequireCodegenError> for PipelineError {
93    fn from(e: RequireCodegenError) -> Self {
94        PipelineError::RequireCodegen(e)
95    }
96}
97
98impl From<PerlConfigError> for PipelineError {
99    fn from(e: PerlConfigError) -> Self {
100        PipelineError::PerlConfig(e)
101    }
102}
103
104impl From<EnrichedCompileError> for PipelineError {
105    fn from(e: EnrichedCompileError) -> Self {
106        PipelineError::Compile(e)
107    }
108}
109
110impl From<InferError> for PipelineError {
111    fn from(e: InferError) -> Self {
112        PipelineError::Infer(e)
113    }
114}
115
116impl From<std::io::Error> for PipelineError {
117    fn from(e: std::io::Error) -> Self {
118        PipelineError::Io(e)
119    }
120}
121
122// ============================================================================
123// Phase-specific Config structs
124// ============================================================================
125
126/// Preprocessor フェーズの設定
127#[derive(Debug, Clone)]
128pub struct PreprocessConfig {
129    /// 入力ファイル
130    pub input_file: PathBuf,
131    /// インクルードパス (-I)
132    pub include_paths: Vec<PathBuf>,
133    /// プリプロセッサ定義 (-D)
134    pub defines: HashMap<String, Option<String>>,
135    /// ターゲットディレクトリ(Perl CORE)
136    pub target_dir: Option<PathBuf>,
137    /// マクロ展開マーカーを出力
138    pub emit_markers: bool,
139    /// ラップ対象マクロ(inline関数内で特別扱いするマクロ)
140    pub wrapped_macros: Vec<String>,
141    /// PERLVAR/PERLVARI/PERLVARA/PERLVARIC 呼び出しを観測して
142    /// `PerlvarDict` に集めるかどうか。
143    ///
144    /// デフォルト `true`(opt-out)。生成される `macro_bindings.rs` の
145    /// 末尾に `PL_xxx!()` 宣言マクロのセクションを追加する。
146    /// 必要なければ `with_perlvar_collection(false)` で無効化できる。
147    pub collect_perlvars: bool,
148    /// デバッグ出力
149    pub debug_pp: bool,
150}
151
152impl PreprocessConfig {
153    /// 入力ファイルのみを指定した最小構成
154    pub fn new(input_file: impl Into<PathBuf>) -> Self {
155        Self {
156            input_file: input_file.into(),
157            include_paths: Vec::new(),
158            defines: HashMap::new(),
159            target_dir: None,
160            emit_markers: false,
161            wrapped_macros: Vec::new(),
162            collect_perlvars: true,
163            debug_pp: false,
164        }
165    }
166
167    /// PPConfig に変換
168    pub(crate) fn to_pp_config(&self) -> PPConfig {
169        PPConfig {
170            include_paths: self.include_paths.clone(),
171            predefined: self.defines.iter()
172                .map(|(k, v)| (k.clone(), v.clone()))
173                .collect(),
174            debug_pp: self.debug_pp,
175            target_dir: self.target_dir.clone(),
176            emit_markers: self.emit_markers,
177        }
178    }
179}
180
181/// Inference フェーズの設定
182#[derive(Debug, Clone, Default)]
183pub struct InferConfig {
184    /// Rust バインディングファイル
185    pub bindings_path: Option<PathBuf>,
186    /// apidoc ファイルパス(省略時は自動検索)
187    pub apidoc_path: Option<PathBuf>,
188    /// apidoc ディレクトリ
189    pub apidoc_dir: Option<PathBuf>,
190    /// apidoc マージ後にダンプして終了
191    pub dump_apidoc_after_merge: Option<String>,
192    /// 型推論デバッグ対象のマクロ名リスト
193    pub debug_type_inference: Vec<String>,
194    /// codegen をスキップしたい関数名リストファイル。
195    /// 1 行 1 名、`#` コメント可。複数指定可。
196    /// JSON の apidoc patches (`skip_codegen`) にマージされる
197    /// (同名は既存が優先)。
198    pub skip_codegen_lists: Vec<PathBuf>,
199    /// 対象 perl の build mode(threaded / non-threaded)
200    ///
201    /// `None` の場合は `auto-detect`(実行時に `perl Config{usethreads}` を読む)。
202    /// `Some(...)` で明示指定(テスト用)。
203    pub perl_build_mode: Option<crate::perl_config::PerlBuildMode>,
204}
205
206impl InferConfig {
207    pub fn new() -> Self {
208        Self::default()
209    }
210}
211
212/// Codegen フェーズの設定
213#[derive(Debug, Clone)]
214pub struct CodegenConfig {
215    /// Rust edition for rustfmt
216    pub rust_edition: String,
217    /// rustfmt 失敗時にエラー
218    pub strict_rustfmt: bool,
219    /// マクロ定義位置コメント
220    pub macro_comments: bool,
221    /// inline 関数を出力
222    pub emit_inline_fns: bool,
223    /// マクロを出力
224    pub emit_macros: bool,
225    /// ヘッダーに出力する use 文(空ならデフォルト)
226    pub use_statements: Vec<String>,
227    /// AST ダンプ対象関数名(デバッグ用)
228    pub dump_ast_for: Option<String>,
229    /// 型推論ダンプ対象関数名(デバッグ用)
230    pub dump_types_for: Option<String>,
231    /// 必須生成関数名リストファイル(書式は skip-codegen-list と同じ)。
232    /// 生成後に検証し、emit されなかった名前があれば
233    /// `PipelineError::RequireCodegen`(出力自体は書き切った後)
234    pub require_codegen_lists: Vec<PathBuf>,
235}
236
237impl Default for CodegenConfig {
238    fn default() -> Self {
239        Self {
240            rust_edition: "2024".to_string(),
241            strict_rustfmt: false,
242            macro_comments: false,
243            emit_inline_fns: true,
244            emit_macros: true,
245            use_statements: Vec::new(),
246            dump_ast_for: None,
247            dump_types_for: None,
248            require_codegen_lists: Vec::new(),
249        }
250    }
251}
252
253impl CodegenConfig {
254    /// RustCodegenConfig に変換
255    pub(crate) fn to_rust_codegen_config(&self) -> RustCodegenConfig {
256        RustCodegenConfig {
257            emit_inline_fns: self.emit_inline_fns,
258            emit_macros: self.emit_macros,
259            include_source_location: self.macro_comments,
260            use_statements: self.use_statements.clone(),
261            dump_ast_for: self.dump_ast_for.clone(),
262            dump_types_for: self.dump_types_for.clone(),
263        }
264    }
265}
266
267// ============================================================================
268// PipelineBuilder
269// ============================================================================
270
271/// Pipeline を構築するための Builder
272#[derive(Debug)]
273pub struct PipelineBuilder {
274    preprocess: PreprocessConfig,
275    infer: InferConfig,
276    codegen: CodegenConfig,
277}
278
279impl PipelineBuilder {
280    /// 入力ファイルを指定して Builder を作成
281    pub fn new(input_file: impl Into<PathBuf>) -> Self {
282        Self {
283            preprocess: PreprocessConfig::new(input_file),
284            infer: InferConfig::new(),
285            codegen: CodegenConfig::default(),
286        }
287    }
288
289    // === Preprocess 設定 ===
290
291    /// Perl Config.pm から自動設定(--auto 相当)
292    ///
293    /// インクルードパス、プリプロセッサ定義、ターゲットディレクトリを
294    /// Perl の Config.pm から取得して設定する。
295    pub fn with_auto_perl_config(mut self) -> Result<Self, PipelineError> {
296        let perl_cfg = get_perl_config()?;
297        self.preprocess.include_paths = perl_cfg.include_paths;
298        self.preprocess.defines = perl_cfg.defines.into_iter().collect();
299        self.preprocess.target_dir = get_default_target_dir().ok();
300        Ok(self)
301    }
302
303    /// コード生成に推奨される設定を適用
304    ///
305    /// 以下を設定:
306    /// - wrapped_macros: ["assert", "assert_"] (inline関数内のassertを正しく変換)
307    pub fn with_codegen_defaults(mut self) -> Self {
308        self.preprocess.wrapped_macros = vec![
309            "assert".to_string(),
310            "assert_".to_string(),
311        ];
312        self
313    }
314
315    /// インクルードパスを追加 (-I)
316    pub fn with_include(mut self, path: impl Into<PathBuf>) -> Self {
317        self.preprocess.include_paths.push(path.into());
318        self
319    }
320
321    /// マクロ定義を追加 (-D)
322    pub fn with_define(mut self, name: impl Into<String>, value: Option<impl Into<String>>) -> Self {
323        self.preprocess.defines.insert(name.into(), value.map(|v| v.into()));
324        self
325    }
326
327    /// ターゲットディレクトリを設定
328    pub fn with_target_dir(mut self, path: impl Into<PathBuf>) -> Self {
329        self.preprocess.target_dir = Some(path.into());
330        self
331    }
332
333    /// マクロ展開マーカーを出力
334    pub fn with_emit_markers(mut self) -> Self {
335        self.preprocess.emit_markers = true;
336        self
337    }
338
339    /// PERLVAR/PERLVARI/PERLVARA/PERLVARIC 観測の有効/無効を指定。
340    ///
341    /// デフォルトは有効 (opt-out)。`false` を渡すと PERLVAR コレクションを
342    /// 無効化し、`PL_xxx!()` セクションは出力されなくなる。
343    pub fn with_perlvar_collection(mut self, enable: bool) -> Self {
344        self.preprocess.collect_perlvars = enable;
345        self
346    }
347
348    /// プリプロセッサデバッグ出力を有効化
349    pub fn with_debug_pp(mut self) -> Self {
350        self.preprocess.debug_pp = true;
351        self
352    }
353
354    // === Infer 設定 ===
355
356    /// Rust バインディングファイルを指定
357    pub fn with_bindings(mut self, path: impl Into<PathBuf>) -> Self {
358        self.infer.bindings_path = Some(path.into());
359        self
360    }
361
362    /// apidoc ファイルを指定
363    pub fn with_apidoc(mut self, path: impl Into<PathBuf>) -> Self {
364        self.infer.apidoc_path = Some(path.into());
365        self
366    }
367
368    /// apidoc ディレクトリを指定
369    pub fn with_apidoc_dir(mut self, path: impl Into<PathBuf>) -> Self {
370        self.infer.apidoc_dir = Some(path.into());
371        self
372    }
373
374    /// apidoc マージ後にダンプして終了(デバッグ用)
375    pub fn with_dump_apidoc(mut self, filter: impl Into<String>) -> Self {
376        self.infer.dump_apidoc_after_merge = Some(filter.into());
377        self
378    }
379
380    /// 型推論デバッグ対象のマクロを指定
381    pub fn with_debug_type_inference(mut self, macros: Vec<String>) -> Self {
382        self.infer.debug_type_inference = macros;
383        self
384    }
385
386    /// codegen をスキップする関数名リストファイルを追加
387    ///
388    /// ファイル形式: 1 行 1 名、`#` コメント可、空行無視。
389    /// 複数回呼び出してファイルを追加可能。JSON の apidoc patches
390    /// (`skip_codegen`) と同時指定できる(同名は patches 優先)。
391    pub fn with_skip_codegen_list(mut self, path: impl Into<PathBuf>) -> Self {
392        self.infer.skip_codegen_lists.push(path.into());
393        self
394    }
395
396    /// 必須生成関数名リストファイルを追加(書式は skip-codegen-list と同じ)。
397    /// 生成後に検証し、emit されなかった名前があれば
398    /// `PipelineError::RequireCodegen` を返す(出力は書き切られた後)
399    pub fn with_require_codegen_list(mut self, path: impl Into<PathBuf>) -> Self {
400        self.codegen.require_codegen_lists.push(path.into());
401        self
402    }
403
404    /// 対象 perl の build mode を明示指定する
405    ///
406    /// 省略時は実行時に `perl -V:usethreads` から auto-detect。
407    /// テストやクロスコンパイル用途で固定したい場合のみ呼び出す。
408    pub fn with_perl_build_mode(mut self, mode: crate::perl_config::PerlBuildMode) -> Self {
409        self.infer.perl_build_mode = Some(mode);
410        self
411    }
412
413    // === Codegen 設定 ===
414
415    /// rustfmt 失敗時にエラー終了
416    pub fn with_strict_rustfmt(mut self) -> Self {
417        self.codegen.strict_rustfmt = true;
418        self
419    }
420
421    /// Rust edition を指定
422    pub fn with_rust_edition(mut self, edition: impl Into<String>) -> Self {
423        self.codegen.rust_edition = edition.into();
424        self
425    }
426
427    /// マクロ定義位置コメントを有効化
428    pub fn with_macro_comments(mut self) -> Self {
429        self.codegen.macro_comments = true;
430        self
431    }
432
433    /// AST ダンプ対象関数名を指定(デバッグ用)
434    pub fn with_dump_ast_for(mut self, name: impl Into<String>) -> Self {
435        self.codegen.dump_ast_for = Some(name.into());
436        self
437    }
438
439    /// 型推論ダンプ対象関数名を指定(デバッグ用)
440    pub fn with_dump_types_for(mut self, name: impl Into<String>) -> Self {
441        self.codegen.dump_types_for = Some(name.into());
442        self
443    }
444
445    // === Build ===
446
447    /// Pipeline を構築
448    pub fn build(self) -> Result<Pipeline, PipelineError> {
449        Ok(Pipeline {
450            preprocess_config: self.preprocess,
451            infer_config: self.infer,
452            codegen_config: self.codegen,
453        })
454    }
455
456    /// PreprocessConfig のみを取り出す(Preprocessor 単独使用時)
457    pub fn preprocess_config(self) -> PreprocessConfig {
458        self.preprocess
459    }
460
461    /// InferConfig を取り出す
462    pub fn infer_config(&self) -> &InferConfig {
463        &self.infer
464    }
465
466    /// CodegenConfig を取り出す
467    pub fn codegen_config(&self) -> &CodegenConfig {
468        &self.codegen
469    }
470}
471
472// ============================================================================
473// Pipeline (Initial state)
474// ============================================================================
475
476/// 初期状態の Pipeline
477pub struct Pipeline {
478    preprocess_config: PreprocessConfig,
479    infer_config: InferConfig,
480    codegen_config: CodegenConfig,
481}
482
483impl Pipeline {
484    /// Builder を作成
485    pub fn builder(input_file: impl Into<PathBuf>) -> PipelineBuilder {
486        PipelineBuilder::new(input_file)
487    }
488
489    /// PreprocessConfig への参照を取得
490    pub fn preprocess_config(&self) -> &PreprocessConfig {
491        &self.preprocess_config
492    }
493
494    /// InferConfig への参照を取得
495    pub fn infer_config(&self) -> &InferConfig {
496        &self.infer_config
497    }
498
499    /// CodegenConfig への参照を取得
500    pub fn codegen_config(&self) -> &CodegenConfig {
501        &self.codegen_config
502    }
503
504    /// Phase 1: プリプロセスのみ実行
505    pub fn preprocess(self) -> Result<PreprocessedPipeline, PipelineError> {
506        // PPConfig を構築
507        let pp_config = self.preprocess_config.to_pp_config();
508
509        // Preprocessor を初期化
510        let mut pp = Preprocessor::new(pp_config);
511
512        // wrapped_macros を登録
513        for macro_name in &self.preprocess_config.wrapped_macros {
514            pp.add_wrapped_macro(macro_name);
515        }
516
517        // PERLVAR コレクション (opt-out、デフォルト有効)
518        let perlvar_dict = if self.preprocess_config.collect_perlvars {
519            let (dict, c_var, c_init, c_array, c_const) =
520                crate::perlvar_dict::PerlvarCollector::new_set();
521            // 借用衝突回避のため intern を先に済ませる
522            let interner = pp.interner_mut();
523            let id_var = interner.intern("PERLVAR");
524            let id_init = interner.intern("PERLVARI");
525            let id_array = interner.intern("PERLVARA");
526            let id_const = interner.intern("PERLVARIC");
527            pp.set_macro_called_callback(id_var, Box::new(c_var));
528            pp.set_macro_called_callback(id_init, Box::new(c_init));
529            pp.set_macro_called_callback(id_array, Box::new(c_array));
530            pp.set_macro_called_callback(id_const, Box::new(c_const));
531            Some(dict)
532        } else {
533            None
534        };
535
536        // ファイルを処理
537        if let Err(e) = pp.add_source_file(&self.preprocess_config.input_file) {
538            return Err(PipelineError::Compile(e.with_files(pp.files())));
539        }
540
541        Ok(PreprocessedPipeline {
542            preprocessor: pp,
543            infer_config: self.infer_config,
544            codegen_config: self.codegen_config,
545            perlvar_dict,
546        })
547    }
548
549    /// Phase 2: 推論まで実行(プリプロセスも含む)
550    pub fn infer(self) -> Result<InferredPipeline, PipelineError> {
551        self.preprocess()?.infer()
552    }
553
554    /// Phase 3: コード生成まで実行(全フェーズ)
555    pub fn generate<W: Write>(self, writer: W) -> Result<GeneratedPipeline, PipelineError> {
556        self.infer()?.generate(writer)
557    }
558}
559
560// ============================================================================
561// PreprocessedPipeline
562// ============================================================================
563
564/// プリプロセス完了状態
565pub struct PreprocessedPipeline {
566    preprocessor: Preprocessor,
567    infer_config: InferConfig,
568    codegen_config: CodegenConfig,
569    /// PERLVAR コレクション (有効時に Some)。
570    /// `Rc<RefCell<...>>` 経由でコールバックと共有しているので、
571    /// add_source_file 完了時点でコールバックが書き込み済み。
572    perlvar_dict: Option<std::rc::Rc<std::cell::RefCell<crate::perlvar_dict::PerlvarDict>>>,
573}
574
575impl PreprocessedPipeline {
576    /// Preprocessor への参照を取得
577    pub fn preprocessor(&self) -> &Preprocessor {
578        &self.preprocessor
579    }
580
581    /// Preprocessor への可変参照を取得
582    pub fn preprocessor_mut(&mut self) -> &mut Preprocessor {
583        &mut self.preprocessor
584    }
585
586    /// Preprocessor を消費して取得
587    pub fn into_preprocessor(self) -> Preprocessor {
588        self.preprocessor
589    }
590
591    /// InferConfig への参照を取得
592    pub fn infer_config(&self) -> &InferConfig {
593        &self.infer_config
594    }
595
596    /// CodegenConfig への参照を取得
597    pub fn codegen_config(&self) -> &CodegenConfig {
598        &self.codegen_config
599    }
600
601    // === Infer 設定を追加で指定可能 ===
602
603    /// Rust バインディングファイルを指定
604    pub fn with_bindings(mut self, path: impl Into<PathBuf>) -> Self {
605        self.infer_config.bindings_path = Some(path.into());
606        self
607    }
608
609    /// apidoc ファイルを指定
610    pub fn with_apidoc(mut self, path: impl Into<PathBuf>) -> Self {
611        self.infer_config.apidoc_path = Some(path.into());
612        self
613    }
614
615    /// apidoc ディレクトリを指定
616    pub fn with_apidoc_dir(mut self, path: impl Into<PathBuf>) -> Self {
617        self.infer_config.apidoc_dir = Some(path.into());
618        self
619    }
620
621    /// codegen をスキップする関数名リストファイルを追加
622    pub fn with_skip_codegen_list(mut self, path: impl Into<PathBuf>) -> Self {
623        self.infer_config.skip_codegen_lists.push(path.into());
624        self
625    }
626
627    /// 必須生成関数名リストファイルを追加(書式は skip-codegen-list と同じ)
628    pub fn with_require_codegen_list(mut self, path: impl Into<PathBuf>) -> Self {
629        self.codegen_config.require_codegen_lists.push(path.into());
630        self
631    }
632
633    /// Phase 2: 推論を実行
634    pub fn infer(self) -> Result<InferredPipeline, PipelineError> {
635        use crate::apidoc::resolve_apidoc_path;
636        use crate::infer_api::{run_inference_with_preprocessor, DebugOptions};
637
638        // apidoc パスを解決
639        let apidoc_path = resolve_apidoc_path(
640            self.infer_config.apidoc_path.as_deref(),
641            true, // auto_mode
642            self.infer_config.apidoc_dir.as_deref(),
643        ).map_err(|e| PipelineError::Infer(InferError::ApidocResolve(e)))?;
644
645        // デバッグオプションを構築
646        let has_debug_opts = self.infer_config.dump_apidoc_after_merge.is_some()
647            || !self.infer_config.debug_type_inference.is_empty();
648        let debug_opts = if has_debug_opts {
649            Some(DebugOptions {
650                dump_apidoc_after_merge: self.infer_config.dump_apidoc_after_merge.clone(),
651                debug_type_inference: self.infer_config.debug_type_inference.clone(),
652            })
653        } else {
654            None
655        };
656
657        // 推論を実行
658        let result = run_inference_with_preprocessor(
659            self.preprocessor,
660            apidoc_path.as_deref(),
661            self.infer_config.bindings_path.as_deref(),
662            debug_opts.as_ref(),
663            &self.infer_config.skip_codegen_lists,
664            self.infer_config.perl_build_mode,
665        )?;
666
667        match result {
668            Some(mut infer_result) => {
669                // PERLVAR コレクションを取り出して結果に転送
670                if let Some(rc) = self.perlvar_dict {
671                    // コールバックとの共有 Rc。Preprocessor 内にコールバックが
672                    // まだ生きている (= 参照カウント > 1) 想定なので、try_unwrap
673                    // ではなく素直に clone で取り出す。
674                    infer_result.perlvar_dict = rc.borrow().clone();
675                }
676                Ok(InferredPipeline {
677                    result: infer_result,
678                    codegen_config: self.codegen_config,
679                })
680            }
681            None => {
682                // デバッグダンプで早期終了
683                // 空の結果を返すか、専用のエラーを返すか検討が必要
684                // ここでは Io エラーとして扱う(暫定)
685                Err(PipelineError::Io(std::io::Error::new(
686                    std::io::ErrorKind::Interrupted,
687                    "Debug dump caused early exit",
688                )))
689            }
690        }
691    }
692
693    /// Phase 3: コード生成まで実行(推論も含む)
694    pub fn generate<W: Write>(self, writer: W) -> Result<GeneratedPipeline, PipelineError> {
695        self.infer()?.generate(writer)
696    }
697}
698
699// ============================================================================
700// InferredPipeline
701// ============================================================================
702
703/// 推論完了状態
704pub struct InferredPipeline {
705    result: InferResult,
706    codegen_config: CodegenConfig,
707}
708
709impl InferredPipeline {
710    /// InferResult への参照を取得
711    pub fn result(&self) -> &InferResult {
712        &self.result
713    }
714
715    /// InferResult を消費して取得
716    pub fn into_result(self) -> InferResult {
717        self.result
718    }
719
720    /// CodegenConfig への参照を取得
721    pub fn codegen_config(&self) -> &CodegenConfig {
722        &self.codegen_config
723    }
724
725    // === Codegen 設定を追加で指定可能 ===
726
727    /// rustfmt 失敗時にエラー終了
728    pub fn with_strict_rustfmt(mut self) -> Self {
729        self.codegen_config.strict_rustfmt = true;
730        self
731    }
732
733    /// Rust edition を指定
734    pub fn with_rust_edition(mut self, edition: impl Into<String>) -> Self {
735        self.codegen_config.rust_edition = edition.into();
736        self
737    }
738
739    /// マクロ定義位置コメントを有効化
740    pub fn with_macro_comments(mut self) -> Self {
741        self.codegen_config.macro_comments = true;
742        self
743    }
744
745    /// AST ダンプ対象関数名を指定(デバッグ用)
746    pub fn with_dump_ast_for(mut self, name: impl Into<String>) -> Self {
747        self.codegen_config.dump_ast_for = Some(name.into());
748        self
749    }
750
751    /// 型推論ダンプ対象関数名を指定(デバッグ用)
752    pub fn with_dump_types_for(mut self, name: impl Into<String>) -> Self {
753        self.codegen_config.dump_types_for = Some(name.into());
754        self
755    }
756
757    /// Phase 3: コード生成
758    pub fn generate<W: Write>(self, mut writer: W) -> Result<GeneratedPipeline, PipelineError> {
759        let rust_codegen_config = self.codegen_config.to_rust_codegen_config();
760
761        let bindings_info = self.result.rust_decl_dict.as_ref()
762            .map(|d| BindingsInfo::from_rust_decl_dict(d))
763            .unwrap_or_default();
764
765        let mut driver = CodegenDriver::new(
766            &mut writer,
767            self.result.preprocessor.interner(),
768            &self.result.enum_dict,
769            &self.result.infer_ctx,
770            bindings_info,
771            rust_codegen_config,
772        );
773
774        driver.generate(&self.result)?;
775
776        let stats = driver.stats().clone();
777        let report = driver.report().clone();
778
779        // PERLVAR section: emit at end of macro_bindings.rs.
780        // Empty dict (e.g. when collect_perlvars=false) is a no-op.
781        crate::perlvar_emitter::emit_perlvar_section(
782            &mut writer,
783            &self.result.perlvar_dict,
784            self.result.perl_build_mode.is_threaded(),
785        )?;
786
787        // TODO: strict_rustfmt の処理
788        // 現状は CodegenDriver が rustfmt を呼び出さないため、
789        // ここで別途 rustfmt を実行する必要がある
790
791        // require-codegen リストの検証。出力を書き切った後に行う
792        // (違反時も生成物を診断に使えるようにするため)
793        if !self.codegen_config.require_codegen_lists.is_empty() {
794            let mut required: Vec<String> = Vec::new();
795            for path in &self.codegen_config.require_codegen_lists {
796                required.extend(crate::apidoc_patches::load_name_list(path)?);
797            }
798            report.check_required(&required)?;
799        }
800
801        Ok(GeneratedPipeline {
802            result: self.result,
803            stats,
804            report,
805        })
806    }
807}
808
809// ============================================================================
810// GeneratedPipeline
811// ============================================================================
812
813/// コード生成完了状態
814pub struct GeneratedPipeline {
815    result: InferResult,
816    /// コード生成の統計情報
817    pub stats: CodegenStats,
818    /// 関数別の生成結果(emit 済み名と skip 理由)
819    pub report: CodegenReport,
820}
821
822impl GeneratedPipeline {
823    /// 統計情報を取得
824    pub fn stats(&self) -> &CodegenStats {
825        &self.stats
826    }
827
828    /// 関数別の生成結果を取得
829    pub fn report(&self) -> &CodegenReport {
830        &self.report
831    }
832
833    /// InferResult への参照を取得
834    pub fn result(&self) -> &InferResult {
835        &self.result
836    }
837
838    /// InferResult を消費して取得
839    pub fn into_result(self) -> InferResult {
840        self.result
841    }
842}
843
844// ============================================================================
845// Tests
846// ============================================================================
847
848#[cfg(test)]
849mod tests {
850    use super::*;
851
852    #[test]
853    fn test_pipeline_builder_basic() {
854        let builder = PipelineBuilder::new("test.h")
855            .with_include("/usr/include")
856            .with_define("FOO", Some("1"))
857            .with_bindings("bindings.rs");
858
859        assert_eq!(builder.preprocess.input_file, PathBuf::from("test.h"));
860        assert_eq!(builder.preprocess.include_paths.len(), 1);
861        assert_eq!(builder.preprocess.defines.get("FOO"), Some(&Some("1".to_string())));
862        assert_eq!(builder.infer.bindings_path, Some(PathBuf::from("bindings.rs")));
863    }
864
865    #[test]
866    fn test_pipeline_builder_codegen_defaults() {
867        let builder = PipelineBuilder::new("test.h")
868            .with_codegen_defaults();
869
870        assert_eq!(builder.preprocess.wrapped_macros, vec!["assert", "assert_"]);
871    }
872
873    #[test]
874    fn test_preprocess_config_to_pp_config() {
875        let mut config = PreprocessConfig::new("test.h");
876        config.include_paths.push(PathBuf::from("/usr/include"));
877        config.defines.insert("FOO".to_string(), Some("1".to_string()));
878        config.debug_pp = true;
879
880        let pp_config = config.to_pp_config();
881        assert_eq!(pp_config.include_paths.len(), 1);
882        assert_eq!(pp_config.predefined.len(), 1);
883        assert!(pp_config.debug_pp);
884    }
885
886    #[test]
887    fn test_codegen_config_default() {
888        let config = CodegenConfig::default();
889        assert_eq!(config.rust_edition, "2024");
890        assert!(!config.strict_rustfmt);
891        assert!(config.emit_inline_fns);
892        assert!(config.emit_macros);
893    }
894}