Skip to main content

chipi/
lib.rs

1//! # chipi
2//!
3//! Generate instruction decoders and disassemblers from `.chipi` files.
4//!
5//! Write your CPU instruction encoding in a simple DSL, and chipi generates
6//! the Rust decoder and formatting code for you.
7//!
8//! ## Usage
9//!
10//! Add to `Cargo.toml`:
11//!
12//! ```toml
13//! [build-dependencies]
14//! chipi = "0.1.1"
15//! ```
16//!
17//! Create `build.rs`:
18//!
19//! ```ignore
20//! use std::env;
21//! use std::path::PathBuf;
22//!
23//! fn main() {
24//!     let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
25//!     chipi::generate("cpu.chipi", out_dir.join("cpu.rs").to_str().unwrap())
26//!         .expect("failed to generate decoder");
27//!     println!("cargo:rerun-if-changed=cpu.chipi");
28//! }
29//! ```
30//!
31//! Use the generated decoder:
32//!
33//! ```ignore
34//! mod cpu {
35//!     include!(concat!(env!("OUT_DIR"), "/cpu.rs"));
36//! }
37//!
38//! match cpu::CpuInstruction::decode(raw) {
39//!     Some(instr) => println!("{}", instr),
40//!     None => println!("invalid instruction"),
41//! }
42//! ```
43//!
44//! ## Example .chipi file
45//!
46//! ```text
47//! decoder Cpu {
48//!     width = 32
49//!     bit_order = msb0
50//! }
51//!
52//! type simm16 = i32 { sign_extend(16) }
53//! type simm24 = i32 { sign_extend(24), shift_left(2) }
54//!
55//! bx   [0:5]=010010 li:simm24[6:29] aa:bool[30] lk:bool[31]
56//!      | "b{lk ? l}{aa ? a} {li:#x}"
57//!
58//! addi [0:5]=001110 rd:u8[6:10] ra:u8[11:15] simm:simm16[16:31]
59//!      | ra == 0: "li {rd}, {simm}"
60//!      | "addi {rd}, {ra}, {simm}"
61//! ```
62//!
63//! ## Syntax
64//!
65//! ### Decoder block
66//!
67//! ```text
68//! decoder Name {
69//!     width = 32        # 8, 16, or 32 bits
70//!     bit_order = msb0  # msb0 or lsb0
71//!     max_units = 4     # optional: safety guard (validates bit ranges)
72//! }
73//! ```
74//!
75//! #### Variable-Length Instructions
76//!
77//! chipi automatically generates variable-length decoders when you use bit positions
78//! beyond `width-1`. Simply reference subsequent units in your bit ranges:
79//!
80//! ```text
81//! decoder Dsp {
82//!     width = 16
83//!     bit_order = msb0
84//!     max_units = 2     # Optional safety check: ensures bits don't exceed 32 (width * max_units)
85//! }
86//!
87//! nop    [0:15]=0000000000000000        # 1 unit (16 bits)
88//! lri    [0:10]=00000010000 rd:u5[11:15] imm:u16[16:31]  # 2 units (32 bits)
89//! ```
90//!
91//! Generated `decode` signature for `width = u16`:
92//! - Single-unit: `pub fn decode(opcode: u16) -> Option<Self>`
93//! - Variable-length: `pub fn decode(units: &[u16]) -> Option<(Self, usize)>`
94//!
95//! The variable-length decoder returns both the instruction and the number of units consumed.
96//!
97//! ### Instructions
98//!
99//! Each instruction is one line with a name, fixed bit patterns, and fields:
100//!
101//! ```text
102//! add [0:5]=011111 rd:u8[6:10] ra:u8[11:15]
103//! ```
104//!
105//! Fixed bits use `[range]=pattern`. Fields use `name:type[range]`.
106//!
107//! #### Overlapping Patterns
108//!
109//! chipi supports overlapping instruction patterns where one pattern is a subset of another.
110//! More specific patterns (with more fixed bits) are checked first:
111//!
112//! ```text
113//! # Generic instruction - matches 0x1X (any value in bits 4-7)
114//! load  [0:3]=0001 reg:u4[4:7]
115//!       | "load r{reg}"
116//!
117//! # Specific instruction - matches only 0x1F
118//! load_max [0:3]=0001 [4:7]=1111
119//!          | "load rmax"
120//! ```
121//!
122//! The decoder will check `load_max` first (all bits fixed), then fall back to `load`
123//! (bits 4-7 are wildcards). This works across all units in variable-length decoders.
124//!
125//! ### Types
126//!
127//! Builtin types:
128//! * `bool` (converts bit to true/false)
129//! * `u1` to `u7` (maps to u8)
130//! * `u8`, `u16`, `u32`
131//! * `i8`, `i16`, `i32`
132//!
133//! Custom types:
134//!
135//! ```text
136//! type simm = i32 { sign_extend(16) }
137//! type reg = u8 as Register
138//! ```
139//!
140//! Available transformations:
141//! * `sign_extend(n)` - sign extend from n bits
142//! * `zero_extend(n)` - zero extend from n bits
143//! * `shift_left(n)` - shift left by n bits
144//!
145//! Display format hints (controls how the field is printed in format strings):
146//! * `display(signed_hex)` - signed hex: `0x1A`, `-0x1A`, `0`
147//! * `display(hex)` - unsigned hex: `0x1A`, `0`
148//!
149//! ### Imports
150//!
151//! Import Rust types to wrap extracted values:
152//!
153//! ```text
154//! import crate::cpu::Register
155//! import std::num::Wrapping
156//! ```
157//!
158//! ### Format lines
159//!
160//! Format lines follow an instruction and define its disassembly output:
161//!
162//! ```text
163//! bx [0:5]=010010 li:simm24[6:29] aa:bool[30] lk:bool[31]
164//!    | "b{lk ? l}{aa ? a} {li:#x}"
165//! ```
166//!
167//! Features:
168//! * `{field}` - insert field value, with optional format spec: `{field:#x}`
169//! * `{field ? text}` - emit `text` if nonzero, `{field ? yes : no}` for else
170//! * `{a + b * 4}` - inline arithmetic (`+`, `-`, `*`, `/`, `%`)
171//! * `{-field}` - unary negation
172//! * `{map_name(arg)}` - call a map lookup
173//! * `{rotate_right(val, amt)}` - builtin functions
174//! * Guards: `| ra == 0: "li {rd}, {simm}"` - conditional format selection
175//! * Guard arithmetic: `| sh == 32 - mb : "srwi ..."` - arithmetic in guard operands
176//!
177//! ### Maps
178//!
179//! Lookup tables for use in format strings:
180//!
181//! ```text
182//! map spr_name(spr) {
183//!     1 => "xer"
184//!     8 => "lr"
185//!     9 => "ctr"
186//!     _ => "???"
187//! }
188//! ```
189//!
190//! ### Formatting trait
191//!
192//! chipi generates a `{Name}Format` trait with one method per instruction.
193//! Default implementations come from format lines. Override selectively:
194//!
195//! ```ignore
196//! struct MyFormat;
197//! impl cpu::CpuFormat for MyFormat {
198//!     fn fmt_bx(li: i32, aa: bool, lk: bool,
199//!               f: &mut std::fmt::Formatter) -> std::fmt::Result {
200//!         write!(f, "BRANCH {:#x}", li)
201//!     }
202//! }
203//!
204//! println!("{}", instr.display::<MyFormat>());
205//! ```
206//!
207//! ## API
208//!
209//! ```ignore
210//! // Parse and generate from file
211//! chipi::generate("cpu.chipi", "out.rs")?;
212//!
213//! // Generate from string
214//! let code = chipi::generate_from_str(source, "cpu.chipi")?;
215//!
216//! // Step by step
217//! let def = chipi::parse("cpu.chipi")?;
218//! chipi::emit(&def, "out.rs")?;
219//! ```
220
221pub mod codegen;
222pub mod error;
223pub mod format_parser;
224pub mod parser;
225pub mod tree;
226pub mod types;
227pub mod validate;
228
229use std::fs;
230use std::path::Path;
231
232use error::Errors;
233use types::DecoderDef;
234
235/// Parse a `.chipi` file from a file path and return the decoder definition.
236///
237/// # Errors
238///
239/// Returns an error if the file cannot be read or parsed.
240///
241/// # Example
242///
243/// ```ignore
244/// let def = chipi::parse("thumb.chipi")?;
245/// ```
246pub fn parse(input: &str) -> Result<DecoderDef, Box<dyn std::error::Error>> {
247    let path = Path::new(input);
248    let source = fs::read_to_string(path)?;
249    let filename = path
250        .file_name()
251        .and_then(|f| f.to_str())
252        .unwrap_or(input);
253
254    parser::parse(&source, filename).map_err(|errs| Box::new(Errors(errs)) as Box<dyn std::error::Error>)
255}
256
257/// Parse source text directly without reading from a file.
258///
259/// # Arguments
260///
261/// * `source`: `.chipi` source code
262/// * `filename`: name used in error messages
263pub fn parse_str(source: &str, filename: &str) -> Result<DecoderDef, Vec<error::Error>> {
264    parser::parse(source, filename)
265}
266
267/// Validate a parsed definition and write generated Rust code to a file.
268///
269/// # Errors
270///
271/// Returns validation or I/O errors.
272pub fn emit(def: &DecoderDef, output: &str) -> Result<(), Box<dyn std::error::Error>> {
273    let validated = validate::validate(def)
274        .map_err(|errs| Box::new(Errors(errs)) as Box<dyn std::error::Error>)?;
275
276    let tree = tree::build_tree(&validated);
277    let code = codegen::generate_code(&validated, &tree);
278
279    fs::write(output, code)?;
280    Ok(())
281}
282
283/// Full pipeline: parse a `.chipi` file and generate a Rust decoder.
284///
285/// # Example
286///
287/// ```ignore
288/// chipi::generate("thumb.chipi", "thumb_decoder.rs")?;
289/// ```
290pub fn generate(input: &str, output: &str) -> Result<(), Box<dyn std::error::Error>> {
291    let def = parse(input)?;
292    emit(&def, output)?;
293    Ok(())
294}
295
296/// Parse, validate, and generate code from source text. Returns the
297/// generated Rust code as a `String`.
298///
299/// # Errors
300///
301/// Returns parse or validation errors.
302pub fn generate_from_str(
303    source: &str,
304    filename: &str,
305) -> Result<String, Box<dyn std::error::Error>> {
306    let def = parser::parse(source, filename)
307        .map_err(|errs| Box::new(Errors(errs)) as Box<dyn std::error::Error>)?;
308
309    let validated = validate::validate(&def)
310        .map_err(|errs| Box::new(Errors(errs)) as Box<dyn std::error::Error>)?;
311
312    let tree = tree::build_tree(&validated);
313    let code = codegen::generate_code(&validated, &tree);
314
315    Ok(code)
316}