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