use std::collections::HashMap;
use std::io::Write;
use std::path::PathBuf;
use crate::perl_config::{get_perl_config, PerlConfigError, get_default_target_dir};
use crate::preprocessor::{PPConfig, Preprocessor};
use crate::rust_codegen::{BindingsInfo, CodegenConfig as RustCodegenConfig, CodegenDriver, CodegenStats};
use crate::infer_api::{InferResult, InferError};
use crate::error::EnrichedCompileError;
#[derive(Debug)]
pub enum PipelineError {
PerlConfig(PerlConfigError),
Compile(EnrichedCompileError),
Infer(InferError),
Io(std::io::Error),
}
impl std::fmt::Display for PipelineError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PipelineError::PerlConfig(e) => write!(f, "Perl config error: {}", e),
PipelineError::Compile(e) => write!(f, "Compile error: {}", e),
PipelineError::Infer(e) => write!(f, "Inference error: {}", e),
PipelineError::Io(e) => write!(f, "I/O error: {}", e),
}
}
}
impl std::error::Error for PipelineError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
PipelineError::PerlConfig(e) => Some(e),
PipelineError::Compile(e) => Some(e),
PipelineError::Infer(e) => Some(e),
PipelineError::Io(e) => Some(e),
}
}
}
impl From<PerlConfigError> for PipelineError {
fn from(e: PerlConfigError) -> Self {
PipelineError::PerlConfig(e)
}
}
impl From<EnrichedCompileError> for PipelineError {
fn from(e: EnrichedCompileError) -> Self {
PipelineError::Compile(e)
}
}
impl From<InferError> for PipelineError {
fn from(e: InferError) -> Self {
PipelineError::Infer(e)
}
}
impl From<std::io::Error> for PipelineError {
fn from(e: std::io::Error) -> Self {
PipelineError::Io(e)
}
}
#[derive(Debug, Clone)]
pub struct PreprocessConfig {
pub input_file: PathBuf,
pub include_paths: Vec<PathBuf>,
pub defines: HashMap<String, Option<String>>,
pub target_dir: Option<PathBuf>,
pub emit_markers: bool,
pub wrapped_macros: Vec<String>,
pub collect_perlvars: bool,
pub debug_pp: bool,
}
impl PreprocessConfig {
pub fn new(input_file: impl Into<PathBuf>) -> Self {
Self {
input_file: input_file.into(),
include_paths: Vec::new(),
defines: HashMap::new(),
target_dir: None,
emit_markers: false,
wrapped_macros: Vec::new(),
collect_perlvars: true,
debug_pp: false,
}
}
pub(crate) fn to_pp_config(&self) -> PPConfig {
PPConfig {
include_paths: self.include_paths.clone(),
predefined: self.defines.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect(),
debug_pp: self.debug_pp,
target_dir: self.target_dir.clone(),
emit_markers: self.emit_markers,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct InferConfig {
pub bindings_path: Option<PathBuf>,
pub apidoc_path: Option<PathBuf>,
pub apidoc_dir: Option<PathBuf>,
pub dump_apidoc_after_merge: Option<String>,
pub debug_type_inference: Vec<String>,
pub skip_codegen_lists: Vec<PathBuf>,
pub perl_build_mode: Option<crate::perl_config::PerlBuildMode>,
}
impl InferConfig {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Debug, Clone)]
pub struct CodegenConfig {
pub rust_edition: String,
pub strict_rustfmt: bool,
pub macro_comments: bool,
pub emit_inline_fns: bool,
pub emit_macros: bool,
pub use_statements: Vec<String>,
pub dump_ast_for: Option<String>,
pub dump_types_for: Option<String>,
}
impl Default for CodegenConfig {
fn default() -> Self {
Self {
rust_edition: "2024".to_string(),
strict_rustfmt: false,
macro_comments: false,
emit_inline_fns: true,
emit_macros: true,
use_statements: Vec::new(),
dump_ast_for: None,
dump_types_for: None,
}
}
}
impl CodegenConfig {
pub(crate) fn to_rust_codegen_config(&self) -> RustCodegenConfig {
RustCodegenConfig {
emit_inline_fns: self.emit_inline_fns,
emit_macros: self.emit_macros,
include_source_location: self.macro_comments,
use_statements: self.use_statements.clone(),
dump_ast_for: self.dump_ast_for.clone(),
dump_types_for: self.dump_types_for.clone(),
}
}
}
#[derive(Debug)]
pub struct PipelineBuilder {
preprocess: PreprocessConfig,
infer: InferConfig,
codegen: CodegenConfig,
}
impl PipelineBuilder {
pub fn new(input_file: impl Into<PathBuf>) -> Self {
Self {
preprocess: PreprocessConfig::new(input_file),
infer: InferConfig::new(),
codegen: CodegenConfig::default(),
}
}
pub fn with_auto_perl_config(mut self) -> Result<Self, PipelineError> {
let perl_cfg = get_perl_config()?;
self.preprocess.include_paths = perl_cfg.include_paths;
self.preprocess.defines = perl_cfg.defines.into_iter().collect();
self.preprocess.target_dir = get_default_target_dir().ok();
Ok(self)
}
pub fn with_codegen_defaults(mut self) -> Self {
self.preprocess.wrapped_macros = vec![
"assert".to_string(),
"assert_".to_string(),
];
self
}
pub fn with_include(mut self, path: impl Into<PathBuf>) -> Self {
self.preprocess.include_paths.push(path.into());
self
}
pub fn with_define(mut self, name: impl Into<String>, value: Option<impl Into<String>>) -> Self {
self.preprocess.defines.insert(name.into(), value.map(|v| v.into()));
self
}
pub fn with_target_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.preprocess.target_dir = Some(path.into());
self
}
pub fn with_emit_markers(mut self) -> Self {
self.preprocess.emit_markers = true;
self
}
pub fn with_perlvar_collection(mut self, enable: bool) -> Self {
self.preprocess.collect_perlvars = enable;
self
}
pub fn with_debug_pp(mut self) -> Self {
self.preprocess.debug_pp = true;
self
}
pub fn with_bindings(mut self, path: impl Into<PathBuf>) -> Self {
self.infer.bindings_path = Some(path.into());
self
}
pub fn with_apidoc(mut self, path: impl Into<PathBuf>) -> Self {
self.infer.apidoc_path = Some(path.into());
self
}
pub fn with_apidoc_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.infer.apidoc_dir = Some(path.into());
self
}
pub fn with_dump_apidoc(mut self, filter: impl Into<String>) -> Self {
self.infer.dump_apidoc_after_merge = Some(filter.into());
self
}
pub fn with_debug_type_inference(mut self, macros: Vec<String>) -> Self {
self.infer.debug_type_inference = macros;
self
}
pub fn with_skip_codegen_list(mut self, path: impl Into<PathBuf>) -> Self {
self.infer.skip_codegen_lists.push(path.into());
self
}
pub fn with_perl_build_mode(mut self, mode: crate::perl_config::PerlBuildMode) -> Self {
self.infer.perl_build_mode = Some(mode);
self
}
pub fn with_strict_rustfmt(mut self) -> Self {
self.codegen.strict_rustfmt = true;
self
}
pub fn with_rust_edition(mut self, edition: impl Into<String>) -> Self {
self.codegen.rust_edition = edition.into();
self
}
pub fn with_macro_comments(mut self) -> Self {
self.codegen.macro_comments = true;
self
}
pub fn with_dump_ast_for(mut self, name: impl Into<String>) -> Self {
self.codegen.dump_ast_for = Some(name.into());
self
}
pub fn with_dump_types_for(mut self, name: impl Into<String>) -> Self {
self.codegen.dump_types_for = Some(name.into());
self
}
pub fn build(self) -> Result<Pipeline, PipelineError> {
Ok(Pipeline {
preprocess_config: self.preprocess,
infer_config: self.infer,
codegen_config: self.codegen,
})
}
pub fn preprocess_config(self) -> PreprocessConfig {
self.preprocess
}
pub fn infer_config(&self) -> &InferConfig {
&self.infer
}
pub fn codegen_config(&self) -> &CodegenConfig {
&self.codegen
}
}
pub struct Pipeline {
preprocess_config: PreprocessConfig,
infer_config: InferConfig,
codegen_config: CodegenConfig,
}
impl Pipeline {
pub fn builder(input_file: impl Into<PathBuf>) -> PipelineBuilder {
PipelineBuilder::new(input_file)
}
pub fn preprocess_config(&self) -> &PreprocessConfig {
&self.preprocess_config
}
pub fn infer_config(&self) -> &InferConfig {
&self.infer_config
}
pub fn codegen_config(&self) -> &CodegenConfig {
&self.codegen_config
}
pub fn preprocess(self) -> Result<PreprocessedPipeline, PipelineError> {
let pp_config = self.preprocess_config.to_pp_config();
let mut pp = Preprocessor::new(pp_config);
for macro_name in &self.preprocess_config.wrapped_macros {
pp.add_wrapped_macro(macro_name);
}
let perlvar_dict = if self.preprocess_config.collect_perlvars {
let (dict, c_var, c_init, c_array, c_const) =
crate::perlvar_dict::PerlvarCollector::new_set();
let interner = pp.interner_mut();
let id_var = interner.intern("PERLVAR");
let id_init = interner.intern("PERLVARI");
let id_array = interner.intern("PERLVARA");
let id_const = interner.intern("PERLVARIC");
pp.set_macro_called_callback(id_var, Box::new(c_var));
pp.set_macro_called_callback(id_init, Box::new(c_init));
pp.set_macro_called_callback(id_array, Box::new(c_array));
pp.set_macro_called_callback(id_const, Box::new(c_const));
Some(dict)
} else {
None
};
if let Err(e) = pp.add_source_file(&self.preprocess_config.input_file) {
return Err(PipelineError::Compile(e.with_files(pp.files())));
}
Ok(PreprocessedPipeline {
preprocessor: pp,
infer_config: self.infer_config,
codegen_config: self.codegen_config,
perlvar_dict,
})
}
pub fn infer(self) -> Result<InferredPipeline, PipelineError> {
self.preprocess()?.infer()
}
pub fn generate<W: Write>(self, writer: W) -> Result<GeneratedPipeline, PipelineError> {
self.infer()?.generate(writer)
}
}
pub struct PreprocessedPipeline {
preprocessor: Preprocessor,
infer_config: InferConfig,
codegen_config: CodegenConfig,
perlvar_dict: Option<std::rc::Rc<std::cell::RefCell<crate::perlvar_dict::PerlvarDict>>>,
}
impl PreprocessedPipeline {
pub fn preprocessor(&self) -> &Preprocessor {
&self.preprocessor
}
pub fn preprocessor_mut(&mut self) -> &mut Preprocessor {
&mut self.preprocessor
}
pub fn into_preprocessor(self) -> Preprocessor {
self.preprocessor
}
pub fn infer_config(&self) -> &InferConfig {
&self.infer_config
}
pub fn codegen_config(&self) -> &CodegenConfig {
&self.codegen_config
}
pub fn with_bindings(mut self, path: impl Into<PathBuf>) -> Self {
self.infer_config.bindings_path = Some(path.into());
self
}
pub fn with_apidoc(mut self, path: impl Into<PathBuf>) -> Self {
self.infer_config.apidoc_path = Some(path.into());
self
}
pub fn with_apidoc_dir(mut self, path: impl Into<PathBuf>) -> Self {
self.infer_config.apidoc_dir = Some(path.into());
self
}
pub fn with_skip_codegen_list(mut self, path: impl Into<PathBuf>) -> Self {
self.infer_config.skip_codegen_lists.push(path.into());
self
}
pub fn infer(self) -> Result<InferredPipeline, PipelineError> {
use crate::apidoc::resolve_apidoc_path;
use crate::infer_api::{run_inference_with_preprocessor, DebugOptions};
let apidoc_path = resolve_apidoc_path(
self.infer_config.apidoc_path.as_deref(),
true, self.infer_config.apidoc_dir.as_deref(),
).map_err(|e| PipelineError::Infer(InferError::ApidocResolve(e)))?;
let has_debug_opts = self.infer_config.dump_apidoc_after_merge.is_some()
|| !self.infer_config.debug_type_inference.is_empty();
let debug_opts = if has_debug_opts {
Some(DebugOptions {
dump_apidoc_after_merge: self.infer_config.dump_apidoc_after_merge.clone(),
debug_type_inference: self.infer_config.debug_type_inference.clone(),
})
} else {
None
};
let result = run_inference_with_preprocessor(
self.preprocessor,
apidoc_path.as_deref(),
self.infer_config.bindings_path.as_deref(),
debug_opts.as_ref(),
&self.infer_config.skip_codegen_lists,
self.infer_config.perl_build_mode,
)?;
match result {
Some(mut infer_result) => {
if let Some(rc) = self.perlvar_dict {
infer_result.perlvar_dict = rc.borrow().clone();
}
Ok(InferredPipeline {
result: infer_result,
codegen_config: self.codegen_config,
})
}
None => {
Err(PipelineError::Io(std::io::Error::new(
std::io::ErrorKind::Interrupted,
"Debug dump caused early exit",
)))
}
}
}
pub fn generate<W: Write>(self, writer: W) -> Result<GeneratedPipeline, PipelineError> {
self.infer()?.generate(writer)
}
}
pub struct InferredPipeline {
result: InferResult,
codegen_config: CodegenConfig,
}
impl InferredPipeline {
pub fn result(&self) -> &InferResult {
&self.result
}
pub fn into_result(self) -> InferResult {
self.result
}
pub fn codegen_config(&self) -> &CodegenConfig {
&self.codegen_config
}
pub fn with_strict_rustfmt(mut self) -> Self {
self.codegen_config.strict_rustfmt = true;
self
}
pub fn with_rust_edition(mut self, edition: impl Into<String>) -> Self {
self.codegen_config.rust_edition = edition.into();
self
}
pub fn with_macro_comments(mut self) -> Self {
self.codegen_config.macro_comments = true;
self
}
pub fn with_dump_ast_for(mut self, name: impl Into<String>) -> Self {
self.codegen_config.dump_ast_for = Some(name.into());
self
}
pub fn with_dump_types_for(mut self, name: impl Into<String>) -> Self {
self.codegen_config.dump_types_for = Some(name.into());
self
}
pub fn generate<W: Write>(self, mut writer: W) -> Result<GeneratedPipeline, PipelineError> {
let rust_codegen_config = self.codegen_config.to_rust_codegen_config();
let bindings_info = self.result.rust_decl_dict.as_ref()
.map(|d| BindingsInfo::from_rust_decl_dict(d))
.unwrap_or_default();
let mut driver = CodegenDriver::new(
&mut writer,
self.result.preprocessor.interner(),
&self.result.enum_dict,
&self.result.infer_ctx,
bindings_info,
rust_codegen_config,
);
driver.generate(&self.result)?;
let stats = driver.stats().clone();
crate::perlvar_emitter::emit_perlvar_section(
&mut writer,
&self.result.perlvar_dict,
self.result.perl_build_mode.is_threaded(),
)?;
Ok(GeneratedPipeline {
result: self.result,
stats,
})
}
}
pub struct GeneratedPipeline {
result: InferResult,
pub stats: CodegenStats,
}
impl GeneratedPipeline {
pub fn stats(&self) -> &CodegenStats {
&self.stats
}
pub fn result(&self) -> &InferResult {
&self.result
}
pub fn into_result(self) -> InferResult {
self.result
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pipeline_builder_basic() {
let builder = PipelineBuilder::new("test.h")
.with_include("/usr/include")
.with_define("FOO", Some("1"))
.with_bindings("bindings.rs");
assert_eq!(builder.preprocess.input_file, PathBuf::from("test.h"));
assert_eq!(builder.preprocess.include_paths.len(), 1);
assert_eq!(builder.preprocess.defines.get("FOO"), Some(&Some("1".to_string())));
assert_eq!(builder.infer.bindings_path, Some(PathBuf::from("bindings.rs")));
}
#[test]
fn test_pipeline_builder_codegen_defaults() {
let builder = PipelineBuilder::new("test.h")
.with_codegen_defaults();
assert_eq!(builder.preprocess.wrapped_macros, vec!["assert", "assert_"]);
}
#[test]
fn test_preprocess_config_to_pp_config() {
let mut config = PreprocessConfig::new("test.h");
config.include_paths.push(PathBuf::from("/usr/include"));
config.defines.insert("FOO".to_string(), Some("1".to_string()));
config.debug_pp = true;
let pp_config = config.to_pp_config();
assert_eq!(pp_config.include_paths.len(), 1);
assert_eq!(pp_config.predefined.len(), 1);
assert!(pp_config.debug_pp);
}
#[test]
fn test_codegen_config_default() {
let config = CodegenConfig::default();
assert_eq!(config.rust_edition, "2024");
assert!(!config.strict_rustfmt);
assert!(config.emit_inline_fns);
assert!(config.emit_macros);
}
}