1use 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#[derive(Debug)]
54pub enum PipelineError {
55 PerlConfig(PerlConfigError),
57 Compile(EnrichedCompileError),
59 Infer(InferError),
61 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#[derive(Debug, Clone)]
117pub struct PreprocessConfig {
118 pub input_file: PathBuf,
120 pub include_paths: Vec<PathBuf>,
122 pub defines: HashMap<String, Option<String>>,
124 pub target_dir: Option<PathBuf>,
126 pub emit_markers: bool,
128 pub wrapped_macros: Vec<String>,
130 pub collect_perlvars: bool,
137 pub debug_pp: bool,
139}
140
141impl PreprocessConfig {
142 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 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#[derive(Debug, Clone, Default)]
172pub struct InferConfig {
173 pub bindings_path: Option<PathBuf>,
175 pub apidoc_path: Option<PathBuf>,
177 pub apidoc_dir: Option<PathBuf>,
179 pub dump_apidoc_after_merge: Option<String>,
181 pub debug_type_inference: Vec<String>,
183 pub skip_codegen_lists: Vec<PathBuf>,
188 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#[derive(Debug, Clone)]
203pub struct CodegenConfig {
204 pub rust_edition: String,
206 pub strict_rustfmt: bool,
208 pub macro_comments: bool,
210 pub emit_inline_fns: bool,
212 pub emit_macros: bool,
214 pub use_statements: Vec<String>,
216 pub dump_ast_for: Option<String>,
218 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 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#[derive(Debug)]
257pub struct PipelineBuilder {
258 preprocess: PreprocessConfig,
259 infer: InferConfig,
260 codegen: CodegenConfig,
261}
262
263impl PipelineBuilder {
264 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 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 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 pub fn with_include(mut self, path: impl Into<PathBuf>) -> Self {
301 self.preprocess.include_paths.push(path.into());
302 self
303 }
304
305 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 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 pub fn with_emit_markers(mut self) -> Self {
319 self.preprocess.emit_markers = true;
320 self
321 }
322
323 pub fn with_perlvar_collection(mut self, enable: bool) -> Self {
328 self.preprocess.collect_perlvars = enable;
329 self
330 }
331
332 pub fn with_debug_pp(mut self) -> Self {
334 self.preprocess.debug_pp = true;
335 self
336 }
337
338 pub fn with_bindings(mut self, path: impl Into<PathBuf>) -> Self {
342 self.infer.bindings_path = Some(path.into());
343 self
344 }
345
346 pub fn with_apidoc(mut self, path: impl Into<PathBuf>) -> Self {
348 self.infer.apidoc_path = Some(path.into());
349 self
350 }
351
352 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 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 pub fn with_debug_type_inference(mut self, macros: Vec<String>) -> Self {
366 self.infer.debug_type_inference = macros;
367 self
368 }
369
370 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 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 pub fn with_strict_rustfmt(mut self) -> Self {
393 self.codegen.strict_rustfmt = true;
394 self
395 }
396
397 pub fn with_rust_edition(mut self, edition: impl Into<String>) -> Self {
399 self.codegen.rust_edition = edition.into();
400 self
401 }
402
403 pub fn with_macro_comments(mut self) -> Self {
405 self.codegen.macro_comments = true;
406 self
407 }
408
409 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 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 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 pub fn preprocess_config(self) -> PreprocessConfig {
434 self.preprocess
435 }
436
437 pub fn infer_config(&self) -> &InferConfig {
439 &self.infer
440 }
441
442 pub fn codegen_config(&self) -> &CodegenConfig {
444 &self.codegen
445 }
446}
447
448pub struct Pipeline {
454 preprocess_config: PreprocessConfig,
455 infer_config: InferConfig,
456 codegen_config: CodegenConfig,
457}
458
459impl Pipeline {
460 pub fn builder(input_file: impl Into<PathBuf>) -> PipelineBuilder {
462 PipelineBuilder::new(input_file)
463 }
464
465 pub fn preprocess_config(&self) -> &PreprocessConfig {
467 &self.preprocess_config
468 }
469
470 pub fn infer_config(&self) -> &InferConfig {
472 &self.infer_config
473 }
474
475 pub fn codegen_config(&self) -> &CodegenConfig {
477 &self.codegen_config
478 }
479
480 pub fn preprocess(self) -> Result<PreprocessedPipeline, PipelineError> {
482 let pp_config = self.preprocess_config.to_pp_config();
484
485 let mut pp = Preprocessor::new(pp_config);
487
488 for macro_name in &self.preprocess_config.wrapped_macros {
490 pp.add_wrapped_macro(macro_name);
491 }
492
493 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 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 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 pub fn infer(self) -> Result<InferredPipeline, PipelineError> {
527 self.preprocess()?.infer()
528 }
529
530 pub fn generate<W: Write>(self, writer: W) -> Result<GeneratedPipeline, PipelineError> {
532 self.infer()?.generate(writer)
533 }
534}
535
536pub struct PreprocessedPipeline {
542 preprocessor: Preprocessor,
543 infer_config: InferConfig,
544 codegen_config: CodegenConfig,
545 perlvar_dict: Option<std::rc::Rc<std::cell::RefCell<crate::perlvar_dict::PerlvarDict>>>,
549}
550
551impl PreprocessedPipeline {
552 pub fn preprocessor(&self) -> &Preprocessor {
554 &self.preprocessor
555 }
556
557 pub fn preprocessor_mut(&mut self) -> &mut Preprocessor {
559 &mut self.preprocessor
560 }
561
562 pub fn into_preprocessor(self) -> Preprocessor {
564 self.preprocessor
565 }
566
567 pub fn infer_config(&self) -> &InferConfig {
569 &self.infer_config
570 }
571
572 pub fn codegen_config(&self) -> &CodegenConfig {
574 &self.codegen_config
575 }
576
577 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 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 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 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 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 let apidoc_path = resolve_apidoc_path(
610 self.infer_config.apidoc_path.as_deref(),
611 true, self.infer_config.apidoc_dir.as_deref(),
613 ).map_err(|e| PipelineError::Infer(InferError::ApidocResolve(e)))?;
614
615 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 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 if let Some(rc) = self.perlvar_dict {
641 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 Err(PipelineError::Io(std::io::Error::new(
656 std::io::ErrorKind::Interrupted,
657 "Debug dump caused early exit",
658 )))
659 }
660 }
661 }
662
663 pub fn generate<W: Write>(self, writer: W) -> Result<GeneratedPipeline, PipelineError> {
665 self.infer()?.generate(writer)
666 }
667}
668
669pub struct InferredPipeline {
675 result: InferResult,
676 codegen_config: CodegenConfig,
677}
678
679impl InferredPipeline {
680 pub fn result(&self) -> &InferResult {
682 &self.result
683 }
684
685 pub fn into_result(self) -> InferResult {
687 self.result
688 }
689
690 pub fn codegen_config(&self) -> &CodegenConfig {
692 &self.codegen_config
693 }
694
695 pub fn with_strict_rustfmt(mut self) -> Self {
699 self.codegen_config.strict_rustfmt = true;
700 self
701 }
702
703 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 pub fn with_macro_comments(mut self) -> Self {
711 self.codegen_config.macro_comments = true;
712 self
713 }
714
715 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 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 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 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 Ok(GeneratedPipeline {
761 result: self.result,
762 stats,
763 })
764 }
765}
766
767pub struct GeneratedPipeline {
773 result: InferResult,
774 pub stats: CodegenStats,
776}
777
778impl GeneratedPipeline {
779 pub fn stats(&self) -> &CodegenStats {
781 &self.stats
782 }
783
784 pub fn result(&self) -> &InferResult {
786 &self.result
787 }
788
789 pub fn into_result(self) -> InferResult {
791 self.result
792 }
793}
794
795#[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}