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(
73    target: &chipi_core::config::GenTarget,
74) -> Result<(), Box<dyn std::error::Error>> {
75    let (def, deps) = chipi_core::parser::parse_file_with_deps(Path::new(&target.input))
76        .map_err(|errs| Box::new(chipi_core::error::Errors(errs)) as Box<dyn std::error::Error>)?;
77
78    for dep in &deps {
79        println!("cargo:rerun-if-changed={}", dep.display());
80    }
81
82    let validated = chipi_core::validate::validate(&def)
83        .map_err(|errs| Box::new(chipi_core::error::Errors(errs)) as Box<dyn std::error::Error>)?;
84
85    let backend = chipi_core::backend::get_backend(&target.lang).unwrap();
86    let code = backend.generate(&validated, target)?;
87    std::fs::write(&target.output, code)?;
88
89    if target.format {
90        if let Some(cmd) = backend.formatter_command() {
91            chipi_core::backend::run_formatter(cmd, &target.output);
92        }
93    }
94
95    Ok(())
96}
97
98fn run_lut_target(
99    target: &chipi_core::config::LutTarget,
100) -> Result<(), Box<dyn std::error::Error>> {
101    let (_, deps) = chipi_core::parser::parse_file_with_deps(Path::new(&target.input))
102        .map_err(|errs| Box::new(chipi_core::error::Errors(errs)) as Box<dyn std::error::Error>)?;
103
104    for dep in &deps {
105        println!("cargo:rerun-if-changed={}", dep.display());
106    }
107
108    chipi_core::LutBuilder::run_target(target)
109}
110
111// ---------------------------------------------------------------------------
112// Decoder/disassembler generation builder
113// ---------------------------------------------------------------------------
114
115/// Start building a decoder/disassembler generation target.
116pub fn generate(input: impl Into<String>) -> GenBuilder {
117    GenBuilder {
118        input: input.into(),
119        output: None,
120        type_map: HashMap::new(),
121        dispatch: Dispatch::FnPtrLut,
122        dispatch_overrides: HashMap::new(),
123        format: false,
124    }
125}
126
127/// Builder for decoder/disassembler code generation.
128pub struct GenBuilder {
129    input: String,
130    output: Option<String>,
131    type_map: HashMap<String, String>,
132    dispatch: Dispatch,
133    dispatch_overrides: HashMap<String, Dispatch>,
134    format: bool,
135}
136
137impl GenBuilder {
138    /// Map a chipi type name to a Rust type path.
139    pub fn type_map(mut self, chipi_type: &str, rust_path: &str) -> Self {
140        self.type_map
141            .insert(chipi_type.to_string(), rust_path.to_string());
142        self
143    }
144
145    /// Set the default dispatch strategy.
146    pub fn dispatch_default(mut self, dispatch: Dispatch) -> Self {
147        self.dispatch = dispatch;
148        self
149    }
150
151    /// Set a per-decoder dispatch strategy override.
152    pub fn dispatch_for(mut self, decoder_name: &str, dispatch: Dispatch) -> Self {
153        self.dispatch_overrides
154            .insert(decoder_name.to_string(), dispatch);
155        self
156    }
157
158    /// Enable running `rustfmt` on the generated output.
159    pub fn format(mut self, yes: bool) -> Self {
160        self.format = yes;
161        self
162    }
163
164    /// Set the output file path.
165    pub fn output(mut self, path: impl Into<String>) -> Self {
166        self.output = Some(path.into());
167        self
168    }
169
170    /// Run the code generation pipeline.
171    pub fn run(self) -> Result<(), Box<dyn std::error::Error>> {
172        let output = self.output.as_deref().ok_or("output path not set")?;
173        let target = chipi_core::config::GenTarget {
174            input: self.input,
175            lang: "rust".to_string(),
176            output: output.to_string(),
177            format: self.format,
178            dispatch: self.dispatch,
179            dispatch_overrides: self.dispatch_overrides,
180            type_map: self.type_map,
181            lang_options: None,
182        };
183        run_gen_target(&target)
184    }
185}
186
187// ---------------------------------------------------------------------------
188// Emulator dispatch LUT generation builder
189// ---------------------------------------------------------------------------
190
191/// Start building an emulator dispatch LUT generation target.
192pub fn lut(input: impl Into<String>) -> LutBuilder {
193    LutBuilder {
194        input: input.into(),
195        output: None,
196        handler_mod: String::new(),
197        ctx_type: String::new(),
198        dispatch: Dispatch::FnPtrLut,
199        groups: HashMap::new(),
200        lut_mod: None,
201        instr_type: None,
202        raw_expr: None,
203        instr_type_output: None,
204    }
205}
206
207/// Builder for emulator dispatch LUT generation.
208pub struct LutBuilder {
209    input: String,
210    output: Option<String>,
211    handler_mod: String,
212    ctx_type: String,
213    dispatch: Dispatch,
214    groups: HashMap<String, Vec<String>>,
215    lut_mod: Option<String>,
216    instr_type: Option<String>,
217    raw_expr: Option<String>,
218    instr_type_output: Option<String>,
219}
220
221impl LutBuilder {
222    pub fn output(mut self, path: impl Into<String>) -> Self {
223        self.output = Some(path.into());
224        self
225    }
226
227    pub fn handler_mod(mut self, m: impl Into<String>) -> Self {
228        self.handler_mod = m.into();
229        self
230    }
231
232    pub fn ctx_type(mut self, t: impl Into<String>) -> Self {
233        self.ctx_type = t.into();
234        self
235    }
236
237    pub fn dispatch(mut self, strategy: Dispatch) -> Self {
238        self.dispatch = strategy;
239        self
240    }
241
242    pub fn group(
243        mut self,
244        name: impl Into<String>,
245        instrs: impl IntoIterator<Item = impl Into<String>>,
246    ) -> Self {
247        self.groups
248            .insert(name.into(), instrs.into_iter().map(|s| s.into()).collect());
249        self
250    }
251
252    pub fn lut_mod(mut self, path: impl Into<String>) -> Self {
253        self.lut_mod = Some(path.into());
254        self
255    }
256
257    pub fn instr_type(mut self, t: impl Into<String>) -> Self {
258        self.instr_type = Some(t.into());
259        self
260    }
261
262    pub fn raw_expr(mut self, expr: impl Into<String>) -> Self {
263        self.raw_expr = Some(expr.into());
264        self
265    }
266
267    /// Also generate an instruction newtype with field accessors.
268    pub fn instr_type_output(mut self, path: impl Into<String>) -> Self {
269        self.instr_type_output = Some(path.into());
270        self
271    }
272
273    /// Run the LUT generation pipeline.
274    pub fn run(self) -> Result<(), Box<dyn std::error::Error>> {
275        let output = self.output.as_deref().ok_or("output path not set")?;
276        let target = chipi_core::config::LutTarget {
277            input: self.input,
278            output: output.to_string(),
279            handler_mod: self.handler_mod,
280            ctx_type: self.ctx_type,
281            dispatch: self.dispatch,
282            groups: self.groups,
283            lut_mod: self.lut_mod,
284            instr_type: self.instr_type,
285            raw_expr: self.raw_expr,
286            instr_type_output: self.instr_type_output,
287        };
288        run_lut_target(&target)
289    }
290}