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::rust_codegen::{CodegenReport, RequireCodegenError};
46use crate::infer_api::{InferResult, InferError};
47use crate::error::EnrichedCompileError;
48
49#[derive(Debug)]
55pub enum PipelineError {
56 PerlConfig(PerlConfigError),
58 Compile(EnrichedCompileError),
60 Infer(InferError),
62 Io(std::io::Error),
64 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#[derive(Debug, Clone)]
128pub struct PreprocessConfig {
129 pub input_file: PathBuf,
131 pub include_paths: Vec<PathBuf>,
133 pub defines: HashMap<String, Option<String>>,
135 pub target_dir: Option<PathBuf>,
137 pub emit_markers: bool,
139 pub wrapped_macros: Vec<String>,
141 pub collect_perlvars: bool,
148 pub debug_pp: bool,
150}
151
152impl PreprocessConfig {
153 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 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#[derive(Debug, Clone, Default)]
183pub struct InferConfig {
184 pub bindings_path: Option<PathBuf>,
186 pub apidoc_path: Option<PathBuf>,
188 pub apidoc_dir: Option<PathBuf>,
190 pub dump_apidoc_after_merge: Option<String>,
192 pub debug_type_inference: Vec<String>,
194 pub skip_codegen_lists: Vec<PathBuf>,
199 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#[derive(Debug, Clone)]
214pub struct CodegenConfig {
215 pub rust_edition: String,
217 pub strict_rustfmt: bool,
219 pub macro_comments: bool,
221 pub emit_inline_fns: bool,
223 pub emit_macros: bool,
225 pub use_statements: Vec<String>,
227 pub dump_ast_for: Option<String>,
229 pub dump_types_for: Option<String>,
231 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 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#[derive(Debug)]
273pub struct PipelineBuilder {
274 preprocess: PreprocessConfig,
275 infer: InferConfig,
276 codegen: CodegenConfig,
277}
278
279impl PipelineBuilder {
280 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 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 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 pub fn with_include(mut self, path: impl Into<PathBuf>) -> Self {
317 self.preprocess.include_paths.push(path.into());
318 self
319 }
320
321 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 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 pub fn with_emit_markers(mut self) -> Self {
335 self.preprocess.emit_markers = true;
336 self
337 }
338
339 pub fn with_perlvar_collection(mut self, enable: bool) -> Self {
344 self.preprocess.collect_perlvars = enable;
345 self
346 }
347
348 pub fn with_debug_pp(mut self) -> Self {
350 self.preprocess.debug_pp = true;
351 self
352 }
353
354 pub fn with_bindings(mut self, path: impl Into<PathBuf>) -> Self {
358 self.infer.bindings_path = Some(path.into());
359 self
360 }
361
362 pub fn with_apidoc(mut self, path: impl Into<PathBuf>) -> Self {
364 self.infer.apidoc_path = Some(path.into());
365 self
366 }
367
368 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 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 pub fn with_debug_type_inference(mut self, macros: Vec<String>) -> Self {
382 self.infer.debug_type_inference = macros;
383 self
384 }
385
386 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 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 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 pub fn with_strict_rustfmt(mut self) -> Self {
417 self.codegen.strict_rustfmt = true;
418 self
419 }
420
421 pub fn with_rust_edition(mut self, edition: impl Into<String>) -> Self {
423 self.codegen.rust_edition = edition.into();
424 self
425 }
426
427 pub fn with_macro_comments(mut self) -> Self {
429 self.codegen.macro_comments = true;
430 self
431 }
432
433 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 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 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 pub fn preprocess_config(self) -> PreprocessConfig {
458 self.preprocess
459 }
460
461 pub fn infer_config(&self) -> &InferConfig {
463 &self.infer
464 }
465
466 pub fn codegen_config(&self) -> &CodegenConfig {
468 &self.codegen
469 }
470}
471
472pub struct Pipeline {
478 preprocess_config: PreprocessConfig,
479 infer_config: InferConfig,
480 codegen_config: CodegenConfig,
481}
482
483impl Pipeline {
484 pub fn builder(input_file: impl Into<PathBuf>) -> PipelineBuilder {
486 PipelineBuilder::new(input_file)
487 }
488
489 pub fn preprocess_config(&self) -> &PreprocessConfig {
491 &self.preprocess_config
492 }
493
494 pub fn infer_config(&self) -> &InferConfig {
496 &self.infer_config
497 }
498
499 pub fn codegen_config(&self) -> &CodegenConfig {
501 &self.codegen_config
502 }
503
504 pub fn preprocess(self) -> Result<PreprocessedPipeline, PipelineError> {
506 let pp_config = self.preprocess_config.to_pp_config();
508
509 let mut pp = Preprocessor::new(pp_config);
511
512 for macro_name in &self.preprocess_config.wrapped_macros {
514 pp.add_wrapped_macro(macro_name);
515 }
516
517 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 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 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 pub fn infer(self) -> Result<InferredPipeline, PipelineError> {
551 self.preprocess()?.infer()
552 }
553
554 pub fn generate<W: Write>(self, writer: W) -> Result<GeneratedPipeline, PipelineError> {
556 self.infer()?.generate(writer)
557 }
558}
559
560pub struct PreprocessedPipeline {
566 preprocessor: Preprocessor,
567 infer_config: InferConfig,
568 codegen_config: CodegenConfig,
569 perlvar_dict: Option<std::rc::Rc<std::cell::RefCell<crate::perlvar_dict::PerlvarDict>>>,
573}
574
575impl PreprocessedPipeline {
576 pub fn preprocessor(&self) -> &Preprocessor {
578 &self.preprocessor
579 }
580
581 pub fn preprocessor_mut(&mut self) -> &mut Preprocessor {
583 &mut self.preprocessor
584 }
585
586 pub fn into_preprocessor(self) -> Preprocessor {
588 self.preprocessor
589 }
590
591 pub fn infer_config(&self) -> &InferConfig {
593 &self.infer_config
594 }
595
596 pub fn codegen_config(&self) -> &CodegenConfig {
598 &self.codegen_config
599 }
600
601 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 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 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 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 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 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 let apidoc_path = resolve_apidoc_path(
640 self.infer_config.apidoc_path.as_deref(),
641 true, self.infer_config.apidoc_dir.as_deref(),
643 ).map_err(|e| PipelineError::Infer(InferError::ApidocResolve(e)))?;
644
645 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 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 if let Some(rc) = self.perlvar_dict {
671 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 Err(PipelineError::Io(std::io::Error::new(
686 std::io::ErrorKind::Interrupted,
687 "Debug dump caused early exit",
688 )))
689 }
690 }
691 }
692
693 pub fn generate<W: Write>(self, writer: W) -> Result<GeneratedPipeline, PipelineError> {
695 self.infer()?.generate(writer)
696 }
697}
698
699pub struct InferredPipeline {
705 result: InferResult,
706 codegen_config: CodegenConfig,
707}
708
709impl InferredPipeline {
710 pub fn result(&self) -> &InferResult {
712 &self.result
713 }
714
715 pub fn into_result(self) -> InferResult {
717 self.result
718 }
719
720 pub fn codegen_config(&self) -> &CodegenConfig {
722 &self.codegen_config
723 }
724
725 pub fn with_strict_rustfmt(mut self) -> Self {
729 self.codegen_config.strict_rustfmt = true;
730 self
731 }
732
733 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 pub fn with_macro_comments(mut self) -> Self {
741 self.codegen_config.macro_comments = true;
742 self
743 }
744
745 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 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 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 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 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
809pub struct GeneratedPipeline {
815 result: InferResult,
816 pub stats: CodegenStats,
818 pub report: CodegenReport,
820}
821
822impl GeneratedPipeline {
823 pub fn stats(&self) -> &CodegenStats {
825 &self.stats
826 }
827
828 pub fn report(&self) -> &CodegenReport {
830 &self.report
831 }
832
833 pub fn result(&self) -> &InferResult {
835 &self.result
836 }
837
838 pub fn into_result(self) -> InferResult {
840 self.result
841 }
842}
843
844#[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}