1use crate::data::DataSource;
2use crate::error::MintError;
3use crate::layout;
4use crate::layout::block::Config;
5use crate::layout::error::LayoutError;
6use crate::layout::used_values::{NoopValueSink, ValueCollector};
7use crate::output;
8use crate::output::error::OutputError;
9use crate::output::{DataRange, OutputFile, OutputFormat};
10use rayon::prelude::*;
11use std::collections::{HashMap, HashSet};
12use std::time::{Duration, Instant};
13
14#[derive(Debug, Clone)]
15pub struct BlockStat {
16 pub name: String,
17 pub start_address: u32,
18 pub allocated_size: u32,
19 pub used_size: u32,
20 pub checksum_values: Vec<u32>,
21}
22
23#[derive(Debug)]
24pub struct BuildStats {
25 pub blocks_processed: usize,
26 pub total_allocated: usize,
27 pub total_used: usize,
28 pub total_duration: Duration,
29 pub block_stats: Vec<BlockStat>,
30}
31
32impl Default for BuildStats {
33 fn default() -> Self {
34 Self::new()
35 }
36}
37
38impl BuildStats {
39 pub fn new() -> Self {
40 Self {
41 blocks_processed: 0,
42 total_allocated: 0,
43 total_used: 0,
44 total_duration: Duration::from_secs(0),
45 block_stats: Vec::new(),
46 }
47 }
48
49 pub fn add_block(&mut self, stat: BlockStat) {
50 self.blocks_processed += 1;
51 self.total_allocated += stat.allocated_size as usize;
52 self.total_used += stat.used_size as usize;
53 self.block_stats.push(stat);
54 }
55
56 pub fn space_used_pct(&self) -> f64 {
57 if self.total_allocated == 0 {
58 0.0
59 } else {
60 (self.total_used as f64 / self.total_allocated as f64) * 100.0
61 }
62 }
63}
64
65#[derive(Clone)]
66pub struct BuildRequest<'a> {
67 pub blocks: Vec<BlockNames>,
68 pub data_source: Option<&'a dyn DataSource>,
69 pub strict: bool,
70 pub capture_values: bool,
71}
72
73#[derive(Debug)]
74pub struct NamedLayout {
75 pub name: String,
76 pub config: Config,
77}
78
79pub struct BuildFromLayoutsRequest<'a> {
80 pub layouts: Vec<NamedLayout>,
81 pub blocks: Vec<BlockNames>,
82 pub data_source: Option<&'a dyn DataSource>,
83 pub strict: bool,
84 pub capture_values: bool,
85}
86
87#[derive(Debug)]
88pub struct BuildArtifact {
89 pub ranges: Vec<DataRange>,
90 pub stats: BuildStats,
91 pub used_values: Option<serde_json::Value>,
92}
93
94impl BuildArtifact {
95 pub fn output_file(&self, format: OutputFormat, record_width: usize) -> OutputFile {
96 OutputFile {
97 ranges: self.ranges.clone(),
98 format,
99 record_width,
100 }
101 }
102
103 pub fn render(&self, format: OutputFormat, record_width: usize) -> Result<String, OutputError> {
104 self.output_file(format, record_width).render()
105 }
106}
107
108#[derive(Debug, Clone)]
109pub struct BlockNames {
110 pub name: String,
111 pub file: String,
112}
113
114#[derive(Debug, Clone)]
115struct ResolvedBlock {
116 name: String,
117 file: String,
118}
119
120struct BlockBuildResult {
121 block_names: BlockNames,
122 data_range: DataRange,
123 stat: BlockStat,
124 used_values: Option<serde_json::Value>,
125}
126
127pub fn build(request: BuildRequest<'_>) -> Result<BuildArtifact, MintError> {
128 if request.blocks.is_empty() {
129 return Err(LayoutError::NoBlocksProvided.into());
130 }
131
132 let (resolved_blocks, layouts) = resolve_blocks(&request.blocks)?;
133 build_resolved(
134 resolved_blocks,
135 &layouts,
136 request.data_source,
137 request.strict,
138 request.capture_values,
139 )
140}
141
142pub fn build_from_layouts(
143 request: BuildFromLayoutsRequest<'_>,
144) -> Result<BuildArtifact, MintError> {
145 if request.blocks.is_empty() {
146 return Err(LayoutError::NoBlocksProvided.into());
147 }
148
149 let layouts = collect_named_layouts(request.layouts)?;
150 let resolved_blocks = resolve_blocks_from_layouts(&request.blocks, &layouts)?;
151 build_resolved(
152 resolved_blocks,
153 &layouts,
154 request.data_source,
155 request.strict,
156 request.capture_values,
157 )
158}
159
160fn build_resolved(
161 resolved_blocks: Vec<ResolvedBlock>,
162 layouts: &HashMap<String, Config>,
163 data_source: Option<&dyn DataSource>,
164 strict: bool,
165 capture_values: bool,
166) -> Result<BuildArtifact, MintError> {
167 let start_time = Instant::now();
168 let mut results = build_bytestreams(
169 &resolved_blocks,
170 layouts,
171 data_source,
172 strict,
173 capture_values,
174 )?;
175
176 let used_values = if capture_values {
177 Some(take_used_values_report(&mut results)?)
178 } else {
179 None
180 };
181
182 let (ranges, mut stats) = collect_results(results)?;
183 stats.total_duration = start_time.elapsed();
184
185 Ok(BuildArtifact {
186 ranges,
187 stats,
188 used_values,
189 })
190}
191
192fn collect_named_layouts(
193 layouts: Vec<NamedLayout>,
194) -> Result<HashMap<String, Config>, LayoutError> {
195 let mut out = HashMap::with_capacity(layouts.len());
196 for layout in layouts {
197 if out.insert(layout.name.clone(), layout.config).is_some() {
198 return Err(LayoutError::FileError(format!(
199 "duplicate layout name '{}'",
200 layout.name
201 )));
202 }
203 }
204 Ok(out)
205}
206
207fn resolve_blocks_from_layouts(
208 block_args: &[BlockNames],
209 layouts: &HashMap<String, Config>,
210) -> Result<Vec<ResolvedBlock>, LayoutError> {
211 let mut resolved = Vec::new();
212 for arg in block_args {
213 let layout = layouts.get(&arg.file).ok_or_else(|| {
214 let available = layouts.keys().cloned().collect::<Vec<_>>().join(", ");
215 LayoutError::BlockNotFound(format!(
216 "layout '{}'. Available layouts: {}",
217 arg.file, available
218 ))
219 })?;
220
221 if arg.name.is_empty() {
222 for block_name in layout.blocks.keys() {
223 resolved.push(ResolvedBlock {
224 name: block_name.clone(),
225 file: arg.file.clone(),
226 });
227 }
228 } else if layout.blocks.contains_key(&arg.name) {
229 resolved.push(ResolvedBlock {
230 name: arg.name.clone(),
231 file: arg.file.clone(),
232 });
233 } else {
234 let available_blocks = layout.blocks.keys().cloned().collect::<Vec<_>>().join(", ");
235 return Err(LayoutError::BlockNotFound(format!(
236 "'{}' in '{}'. Available blocks: {}",
237 arg.name, arg.file, available_blocks
238 )));
239 }
240 }
241
242 let mut seen = HashSet::new();
243 Ok(resolved
244 .into_iter()
245 .filter(|b| seen.insert((b.file.clone(), b.name.clone())))
246 .collect())
247}
248
249fn resolve_blocks(
250 block_args: &[BlockNames],
251) -> Result<(Vec<ResolvedBlock>, HashMap<String, Config>), LayoutError> {
252 let unique_files: HashSet<String> = block_args.iter().map(|b| b.file.clone()).collect();
253
254 let layouts: Result<HashMap<String, Config>, LayoutError> = unique_files
255 .par_iter()
256 .map(|file| layout::load_layout(file).map(|cfg| (file.clone(), cfg)))
257 .collect();
258
259 let layouts = layouts?;
260
261 let mut resolved = Vec::new();
262 for arg in block_args {
263 if arg.name.is_empty() {
264 let layout = &layouts[&arg.file];
265 for block_name in layout.blocks.keys() {
266 resolved.push(ResolvedBlock {
267 name: block_name.clone(),
268 file: arg.file.clone(),
269 });
270 }
271 } else {
272 let layout = &layouts[&arg.file];
273 if !layout.blocks.contains_key(&arg.name) {
274 let available_blocks = layout.blocks.keys().cloned().collect::<Vec<_>>().join(", ");
275 return Err(LayoutError::BlockNotFound(format!(
276 "'{}' in '{}'. Available blocks: {}",
277 arg.name, arg.file, available_blocks
278 )));
279 }
280 resolved.push(ResolvedBlock {
281 name: arg.name.clone(),
282 file: arg.file.clone(),
283 });
284 }
285 }
286
287 let mut seen = HashSet::new();
288 let deduplicated: Vec<ResolvedBlock> = resolved
289 .into_iter()
290 .filter(|b| seen.insert((b.file.clone(), b.name.clone())))
291 .collect();
292
293 Ok((deduplicated, layouts))
294}
295
296fn build_bytestreams(
297 blocks: &[ResolvedBlock],
298 layouts: &HashMap<String, Config>,
299 data_source: Option<&dyn DataSource>,
300 strict: bool,
301 capture_values: bool,
302) -> Result<Vec<BlockBuildResult>, MintError> {
303 blocks
304 .par_iter()
305 .map(|resolved| {
306 build_single_bytestream(resolved, layouts, data_source, strict, capture_values)
307 })
308 .collect()
309}
310
311fn build_single_bytestream(
312 resolved: &ResolvedBlock,
313 layouts: &HashMap<String, Config>,
314 data_source: Option<&dyn DataSource>,
315 strict: bool,
316 capture_values: bool,
317) -> Result<BlockBuildResult, MintError> {
318 let result = (|| {
319 let layout = layouts.get(&resolved.file).ok_or_else(|| {
320 LayoutError::FileError(format!(
321 "resolved layout missing from build map: {}",
322 resolved.file
323 ))
324 })?;
325 let block = layout.blocks.get(&resolved.name).ok_or_else(|| {
326 let available_blocks = layout.blocks.keys().cloned().collect::<Vec<_>>().join(", ");
327 LayoutError::BlockNotFound(format!(
328 "'{}' in '{}'. Available blocks: {}",
329 resolved.name, resolved.file, available_blocks
330 ))
331 })?;
332 let mut collector = ValueCollector::new();
333 let mut noop = NoopValueSink;
334 let value_sink = if capture_values {
335 &mut collector as &mut dyn crate::layout::used_values::ValueSink
336 } else {
337 &mut noop as &mut dyn crate::layout::used_values::ValueSink
338 };
339
340 let build_output = block.build_bytestream(data_source, &layout.mint, strict, value_sink)?;
341
342 let data_range = output::bytestream_to_datarange(
343 build_output.bytestream,
344 &block.header,
345 build_output.padding_count,
346 )?;
347
348 let stat = BlockStat {
349 name: resolved.name.clone(),
350 start_address: data_range.start_address,
351 allocated_size: data_range.allocated_size,
352 used_size: data_range.used_size,
353 checksum_values: build_output.checksum_values,
354 };
355
356 Ok(BlockBuildResult {
357 block_names: BlockNames {
358 name: resolved.name.clone(),
359 file: resolved.file.clone(),
360 },
361 data_range,
362 stat,
363 used_values: capture_values.then(|| collector.into_value()),
364 })
365 })();
366
367 result.map_err(|e| MintError::InBlock {
368 block_name: resolved.name.clone(),
369 layout_file: resolved.file.clone(),
370 source: Box::new(e),
371 })
372}
373
374fn collect_results(
375 results: Vec<BlockBuildResult>,
376) -> Result<(Vec<DataRange>, BuildStats), MintError> {
377 let mut stats = BuildStats::new();
378 let named_ranges: Vec<(String, DataRange)> = results
379 .into_iter()
380 .map(|r| {
381 stats.add_block(r.stat);
382 (r.block_names.name, r.data_range)
383 })
384 .collect();
385
386 check_overlaps(&named_ranges)?;
387 let ranges = named_ranges.into_iter().map(|(_, r)| r).collect();
388 Ok((ranges, stats))
389}
390
391fn check_overlaps(named_ranges: &[(String, DataRange)]) -> Result<(), MintError> {
392 for i in 0..named_ranges.len() {
393 for j in (i + 1)..named_ranges.len() {
394 let (ref name_a, ref range_a) = named_ranges[i];
395 let (ref name_b, ref range_b) = named_ranges[j];
396 let a_start = range_a.start_address;
397 let a_end = a_start + range_a.allocated_size;
398 let b_start = range_b.start_address;
399 let b_end = b_start + range_b.allocated_size;
400
401 let overlap_start = a_start.max(b_start);
402 let overlap_end = a_end.min(b_end);
403
404 if overlap_start < overlap_end {
405 let overlap_size = overlap_end - overlap_start;
406 let msg = format!(
407 "Block '{}' (0x{:08X}-0x{:08X}) overlaps with block '{}' (0x{:08X}-0x{:08X}). Overlap: 0x{:08X}-0x{:08X} ({} bytes)",
408 name_a,
409 a_start,
410 a_end - 1,
411 name_b,
412 b_start,
413 b_end - 1,
414 overlap_start,
415 overlap_end - 1,
416 overlap_size
417 );
418 return Err(OutputError::BlockOverlapError(msg).into());
419 }
420 }
421 }
422 Ok(())
423}
424
425fn take_used_values_report(
426 results: &mut [BlockBuildResult],
427) -> Result<serde_json::Value, MintError> {
428 let mut report = serde_json::Map::new();
429 for result in results {
430 let value = result.used_values.take().ok_or_else(|| {
431 OutputError::FileError("JSON export requested but values were not captured.".to_owned())
432 })?;
433 let file_entry = report
434 .entry(result.block_names.file.clone())
435 .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
436 let serde_json::Value::Object(blocks) = file_entry else {
437 return Err(OutputError::FileError(
438 "JSON export contains unexpected non-object entry.".to_owned(),
439 )
440 .into());
441 };
442 if blocks.contains_key(&result.block_names.name) {
443 return Err(OutputError::FileError(format!(
444 "Duplicate block '{}' in JSON export for file '{}'.",
445 result.block_names.name, result.block_names.file
446 ))
447 .into());
448 }
449 blocks.insert(result.block_names.name.clone(), value);
450 }
451 Ok(serde_json::Value::Object(report))
452}