Skip to main content

chipi_build/
lib.rs

1//! Build script helper for chipi code generation in Rust projects.
2//!
3//! Provides builder APIs and config-driven generation for use in `build.rs` files.
4//!
5//! # Config-driven usage
6//!
7//! ```ignore
8//! // build.rs
9//! fn main() {
10//!     chipi_build::run_config("chipi.toml")
11//!         .expect("chipi codegen failed");
12//! }
13//! ```
14//!
15//! # Programmatic usage
16//!
17//! ```ignore
18//! // Decoder/disassembler generation
19//! chipi_build::generate("src/dsp/gcdsp.chipi")
20//!     .type_map("reg5", "crate::dsp::DspReg")
21//!     .dispatch_default(chipi_build::Dispatch::FnPtrLut)
22//!     .dispatch_for("GcDspExt", chipi_build::Dispatch::JumpTable)
23//!     .output("src/dsp/generated/gcdsp.rs")
24//!     .run()
25//!     .expect("chipi codegen failed");
26//!
27//! // Emulator dispatch LUT generation
28//! chipi_build::lut("cpu.chipi")
29//!     .handler_mod("crate::cpu::interpreter")
30//!     .ctx_type("crate::Cpu")
31//!     .output("out/cpu_lut.rs")
32//!     .run()
33//!     .expect("chipi lut failed");
34//! ```
35
36use std::collections::HashMap;
37use std::path::Path;
38
39pub use chipi_core::config::Dispatch;
40
41// ---------------------------------------------------------------------------
42// Config-driven entry point
43// ---------------------------------------------------------------------------
44
45/// Run all targets defined in a `chipi.toml` config file.
46///
47/// Processes all `[[gen]]` and `[[lut]]` targets. Emits `cargo:rerun-if-changed`
48/// for all input files and their includes.
49pub fn run_config(path: impl AsRef<Path>) -> Result<(), Box<dyn std::error::Error>> {
50    let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
51        .map(std::path::PathBuf::from)
52        .unwrap_or_default();
53    let path = manifest_dir.join(path.as_ref());
54    let mut cfg = chipi_core::config::load_config(&path)?;
55    let base_dir = path.parent().unwrap_or(Path::new("."));
56
57    println!("cargo:rerun-if-changed={}", path.display());
58
59    for target in &mut cfg.targets {
60        chipi_core::config::resolve_gen_paths(target, base_dir);
61        run_gen_target(target)?;
62    }
63
64    for target in &mut cfg.lut {
65        chipi_core::config::resolve_lut_paths(target, base_dir);
66        run_lut_target(target)?;
67    }
68
69    Ok(())
70}
71
72fn run_gen_target(target: &chipi_core::config::GenTarget) -> Result<(), Box<dyn std::error::Error>> {
73    let (def, deps) = chipi_core::parser::parse_file_with_deps(Path::new(&target.input))
74        .map_err(|errs| Box::new(chipi_core::error::Errors(errs)) as Box<dyn std::error::Error>)?;
75
76    for dep in &deps {
77        println!("cargo:rerun-if-changed={}", dep.display());
78    }
79
80    let validated = chipi_core::validate::validate(&def)
81        .map_err(|errs| Box::new(chipi_core::error::Errors(errs)) as Box<dyn std::error::Error>)?;
82
83    let backend = chipi_core::backend::get_backend(&target.lang).unwrap();
84    let code = backend.generate(&validated, target)?;
85    std::fs::write(&target.output, code)?;
86
87    if target.format {
88        if let Some(cmd) = backend.formatter_command() {
89            chipi_core::backend::run_formatter(cmd, &target.output);
90        }
91    }
92
93    Ok(())
94}
95
96fn run_lut_target(target: &chipi_core::config::LutTarget) -> Result<(), Box<dyn std::error::Error>> {
97    let (_, deps) = chipi_core::parser::parse_file_with_deps(Path::new(&target.input))
98        .map_err(|errs| Box::new(chipi_core::error::Errors(errs)) as Box<dyn std::error::Error>)?;
99
100    for dep in &deps {
101        println!("cargo:rerun-if-changed={}", dep.display());
102    }
103
104    chipi_core::LutBuilder::run_target(target)
105}
106
107// ---------------------------------------------------------------------------
108// Decoder/disassembler generation builder
109// ---------------------------------------------------------------------------
110
111/// Start building a decoder/disassembler generation target.
112pub fn generate(input: impl Into<String>) -> GenBuilder {
113    GenBuilder {
114        input: input.into(),
115        output: None,
116        type_map: HashMap::new(),
117        dispatch: Dispatch::FnPtrLut,
118        dispatch_overrides: HashMap::new(),
119        format: false,
120    }
121}
122
123/// Builder for decoder/disassembler code generation.
124pub struct GenBuilder {
125    input: String,
126    output: Option<String>,
127    type_map: HashMap<String, String>,
128    dispatch: Dispatch,
129    dispatch_overrides: HashMap<String, Dispatch>,
130    format: bool,
131}
132
133impl GenBuilder {
134    /// Map a chipi type name to a Rust type path.
135    pub fn type_map(mut self, chipi_type: &str, rust_path: &str) -> Self {
136        self.type_map
137            .insert(chipi_type.to_string(), rust_path.to_string());
138        self
139    }
140
141    /// Set the default dispatch strategy.
142    pub fn dispatch_default(mut self, dispatch: Dispatch) -> Self {
143        self.dispatch = dispatch;
144        self
145    }
146
147    /// Set a per-decoder dispatch strategy override.
148    pub fn dispatch_for(mut self, decoder_name: &str, dispatch: Dispatch) -> Self {
149        self.dispatch_overrides
150            .insert(decoder_name.to_string(), dispatch);
151        self
152    }
153
154    /// Enable running `rustfmt` on the generated output.
155    pub fn format(mut self, yes: bool) -> Self {
156        self.format = yes;
157        self
158    }
159
160    /// Set the output file path.
161    pub fn output(mut self, path: impl Into<String>) -> Self {
162        self.output = Some(path.into());
163        self
164    }
165
166    /// Run the code generation pipeline.
167    pub fn run(self) -> Result<(), Box<dyn std::error::Error>> {
168        let output = self.output.as_deref().ok_or("output path not set")?;
169        let target = chipi_core::config::GenTarget {
170            input: self.input,
171            lang: "rust".to_string(),
172            output: output.to_string(),
173            format: self.format,
174            dispatch: self.dispatch,
175            dispatch_overrides: self.dispatch_overrides,
176            type_map: self.type_map,
177            lang_options: None,
178        };
179        run_gen_target(&target)
180    }
181}
182
183// ---------------------------------------------------------------------------
184// Emulator dispatch LUT generation builder
185// ---------------------------------------------------------------------------
186
187/// Start building an emulator dispatch LUT generation target.
188pub fn lut(input: impl Into<String>) -> LutBuilder {
189    LutBuilder {
190        input: input.into(),
191        output: None,
192        handler_mod: String::new(),
193        ctx_type: String::new(),
194        dispatch: Dispatch::FnPtrLut,
195        groups: HashMap::new(),
196        lut_mod: None,
197        instr_type: None,
198        raw_expr: None,
199        instr_type_output: None,
200    }
201}
202
203/// Builder for emulator dispatch LUT generation.
204pub struct LutBuilder {
205    input: String,
206    output: Option<String>,
207    handler_mod: String,
208    ctx_type: String,
209    dispatch: Dispatch,
210    groups: HashMap<String, Vec<String>>,
211    lut_mod: Option<String>,
212    instr_type: Option<String>,
213    raw_expr: Option<String>,
214    instr_type_output: Option<String>,
215}
216
217impl LutBuilder {
218    pub fn output(mut self, path: impl Into<String>) -> Self {
219        self.output = Some(path.into());
220        self
221    }
222
223    pub fn handler_mod(mut self, m: impl Into<String>) -> Self {
224        self.handler_mod = m.into();
225        self
226    }
227
228    pub fn ctx_type(mut self, t: impl Into<String>) -> Self {
229        self.ctx_type = t.into();
230        self
231    }
232
233    pub fn dispatch(mut self, strategy: Dispatch) -> Self {
234        self.dispatch = strategy;
235        self
236    }
237
238    pub fn group(mut self, name: impl Into<String>, instrs: impl IntoIterator<Item = impl Into<String>>) -> Self {
239        self.groups.insert(name.into(), instrs.into_iter().map(|s| s.into()).collect());
240        self
241    }
242
243    pub fn lut_mod(mut self, path: impl Into<String>) -> Self {
244        self.lut_mod = Some(path.into());
245        self
246    }
247
248    pub fn instr_type(mut self, t: impl Into<String>) -> Self {
249        self.instr_type = Some(t.into());
250        self
251    }
252
253    pub fn raw_expr(mut self, expr: impl Into<String>) -> Self {
254        self.raw_expr = Some(expr.into());
255        self
256    }
257
258    /// Also generate an instruction newtype with field accessors.
259    pub fn instr_type_output(mut self, path: impl Into<String>) -> Self {
260        self.instr_type_output = Some(path.into());
261        self
262    }
263
264    /// Run the LUT generation pipeline.
265    pub fn run(self) -> Result<(), Box<dyn std::error::Error>> {
266        let output = self.output.as_deref().ok_or("output path not set")?;
267        let target = chipi_core::config::LutTarget {
268            input: self.input,
269            output: output.to_string(),
270            handler_mod: self.handler_mod,
271            ctx_type: self.ctx_type,
272            dispatch: self.dispatch,
273            groups: self.groups,
274            lut_mod: self.lut_mod,
275            instr_type: self.instr_type,
276            raw_expr: self.raw_expr,
277            instr_type_output: self.instr_type_output,
278        };
279        run_lut_target(&target)
280    }
281}