1pub mod ast;
35pub mod builtin;
36pub mod error;
37pub mod eval;
38pub mod parser;
39pub mod types;
40pub mod utils;
41
42pub use error::{DelbinError, DelbinWarning, ErrorCode, Result, WarningCode};
43pub use types::{Endian, ScalarType, Value};
44pub use utils::{
45 create_env, create_sections, env_insert_int, env_insert_str, from_hex_string, hex_dump,
46 to_hex_string,
47};
48
49use std::collections::HashMap;
50
51#[derive(Debug)]
53pub struct GenerateResult {
54 pub data: Vec<u8>,
56 pub warnings: Vec<DelbinWarning>,
58}
59
60pub fn generate(
92 dsl: &str,
93 env: &HashMap<String, Value>,
94 sections: &HashMap<String, Vec<u8>>,
95) -> Result<GenerateResult> {
96 let file = parser::parse(dsl)?;
98
99 let mut evaluator = eval::Evaluator::new(env.clone(), sections.clone());
101 let data = evaluator.eval(&file)?;
102
103 Ok(GenerateResult {
104 data,
105 warnings: evaluator.warnings().to_vec(),
106 })
107}
108
109pub fn generate_hex(
121 dsl: &str,
122 env: &HashMap<String, Value>,
123 sections: &HashMap<String, Vec<u8>>,
124) -> Result<String> {
125 let result = generate(dsl, env, sections)?;
126 Ok(to_hex_string(&result.data))
127}
128
129pub fn validate(
133 dsl: &str,
134 env: &HashMap<String, Value>,
135) -> Result<Vec<DelbinWarning>> {
136 let file = parser::parse(dsl)?;
137 let mut evaluator = eval::Evaluator::new(env.clone(), HashMap::new());
138 evaluator.eval(&file)?;
139 Ok(evaluator.warnings().to_vec())
140}
141
142pub fn parse(
156 dsl: &str,
157 env: &HashMap<String, Value>,
158 data: &[u8],
159) -> Result<HashMap<String, Value>> {
160 let file = parser::parse(dsl)?;
161 let mut evaluator = eval::Evaluator::new(env.clone(), HashMap::new());
162 evaluator.parse_bytes(&file, data)
163}
164
165
166pub fn merge(
176 dsl: &str,
177 env: &HashMap<String, Value>,
178 image_data: &[u8],
179) -> Result<GenerateResult> {
180 let mut sections = HashMap::new();
181 sections.insert("image".to_string(), image_data.to_vec());
182
183 let result = generate(dsl, env, §ions)?;
184
185 let mut merged = result.data;
187 merged.extend_from_slice(image_data);
188
189 Ok(GenerateResult {
190 data: merged,
191 warnings: result.warnings,
192 })
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_generate_simple() {
201 let dsl = r#"
202 @endian = little;
203 struct header @packed {
204 magic: [u8; 4] = @bytes("fpk\0");
205 version: u32 = 0x0100;
206 }
207 "#;
208
209 let env = HashMap::new();
210 let sections = HashMap::new();
211
212 let result = generate(dsl, &env, §ions).unwrap();
213 assert_eq!(result.data.len(), 8);
214 assert_eq!(&result.data[..4], b"fpk\0");
215 assert_eq!(&result.data[4..8], &[0x00, 0x01, 0x00, 0x00]);
216 }
217
218 #[test]
219 fn test_generate_with_env() {
220 let dsl = r#"
221 @endian = little;
222 struct header @packed {
223 version: u32 = (${MAJOR} << 24) | (${MINOR} << 16) | ${PATCH};
224 }
225 "#;
226
227 let mut env = HashMap::new();
228 env.insert("MAJOR".to_string(), Value::U64(1));
229 env.insert("MINOR".to_string(), Value::U64(2));
230 env.insert("PATCH".to_string(), Value::U64(3));
231
232 let sections = HashMap::new();
233
234 let result = generate(dsl, &env, §ions).unwrap();
235 assert_eq!(result.data, vec![0x03, 0x00, 0x02, 0x01]);
236 }
237
238 #[test]
239 fn test_generate_with_sizeof() {
240 let dsl = r#"
241 @endian = little;
242 struct header @packed {
243 img_size: u32 = @sizeof(image);
244 }
245 "#;
246
247 let env = HashMap::new();
248 let mut sections = HashMap::new();
249 sections.insert("image".to_string(), vec![0u8; 1024]);
250
251 let result = generate(dsl, &env, §ions).unwrap();
252 assert_eq!(result.data, vec![0x00, 0x04, 0x00, 0x00]); }
254
255 #[test]
256 fn test_generate_with_crc32() {
257 let dsl = r#"
258 @endian = little;
259 struct header @packed {
260 crc: u32 = @crc32(image);
261 }
262 "#;
263
264 let env = HashMap::new();
265 let mut sections = HashMap::new();
266 sections.insert("image".to_string(), b"hello world".to_vec());
267
268 let result = generate(dsl, &env, §ions).unwrap();
269 assert_eq!(result.data, vec![0x85, 0x11, 0x4A, 0x0D]);
271 }
272
273 #[test]
274 fn test_generate_with_self_sizeof() {
275 let dsl = r#"
276 @endian = little;
277 struct header @packed {
278 magic: [u8; 4] = @bytes("TEST");
279 header_size: u32 = @sizeof(@self);
280 }
281 "#;
282
283 let env = HashMap::new();
284 let sections = HashMap::new();
285
286 let result = generate(dsl, &env, §ions).unwrap();
287 assert_eq!(result.data.len(), 8);
288 assert_eq!(&result.data[4..8], &[0x08, 0x00, 0x00, 0x00]);
290 }
291
292 #[test]
293 fn test_generate_with_padding() {
294 let dsl = r#"
295 @endian = little;
296 struct header @packed {
297 magic: [u8; 4] = @bytes("TEST");
298 _pad: [u8; 64 - @offsetof(_pad)];
299 }
300 "#;
301
302 let env = HashMap::new();
303 let sections = HashMap::new();
304
305 let result = generate(dsl, &env, §ions).unwrap();
306 assert_eq!(result.data.len(), 64);
307 }
308
309 #[test]
310 fn test_generate_full_header() {
311 let dsl = r#"
312 @endian = little;
313 struct header @packed {
314 magic: [u8; 4] = @bytes("fpk\0");
315 image_type: u32 = 0;
316 header_ver: u16 = 0x0100;
317 header_size: u16 = @sizeof(@self);
318 fw_version: u32 = (${VERSION_MAJOR} << 24) | (${VERSION_MINOR} << 16) | ${VERSION_PATCH};
319 build_number: u32 = ${BUILD_NUMBER};
320 version_str: [u8; 16] = @bytes(${VERSION_STRING});
321 flags: u32 = 0;
322 img_size: u32 = @sizeof(image);
323 packed_size: u32 = @sizeof(image);
324 timestamp: u32 = ${UNIX_STAMP};
325 partition: [u8; 16] = @bytes("app");
326 watermark: [u8; 16] = @bytes("DELBIN_DEMO");
327 reserved: [u8; 32];
328 img_crc32: u32 = @crc32(image);
329 img_sha256: [u8; 32] = @sha256(image);
330 header_crc32: u32 = @crc32(@self[..header_crc32]);
331 _padding: [u8; 256 - @offsetof(_padding)];
332 }
333 "#;
334
335 let mut env = HashMap::new();
336 env.insert("VERSION_MAJOR".to_string(), Value::U64(1));
337 env.insert("VERSION_MINOR".to_string(), Value::U64(2));
338 env.insert("VERSION_PATCH".to_string(), Value::U64(3));
339 env.insert("BUILD_NUMBER".to_string(), Value::U64(100));
340 env.insert("VERSION_STRING".to_string(), Value::String("1.2.3".to_string()));
341 env.insert("UNIX_STAMP".to_string(), Value::U64(1705574400));
342
343 let mut sections = HashMap::new();
344 sections.insert("image".to_string(), vec![0xABu8; 1024]);
345
346 let result = generate(dsl, &env, §ions).unwrap();
347
348 assert_eq!(result.data.len(), 256);
350
351 assert_eq!(&result.data[0..4], b"fpk\0");
353
354 assert_eq!(result.data[10], 0x00); assert_eq!(result.data[11], 0x01); println!("Generated header ({} bytes):", result.data.len());
359 println!("{}", hex_dump(&result.data, 16));
360 }
361
362 #[test]
365 fn test_string_direct_assign_to_array_is_error() {
366 let dsl = r#"
367 @endian = little;
368 struct header @packed {
369 magic: [u8; 4] = "bad";
370 }
371 "#;
372 let result = generate(dsl, &HashMap::new(), &HashMap::new());
373 assert!(result.is_err(), "expected error for string literal directly assigned to array");
374 let msg = result.unwrap_err().message;
375 assert!(msg.contains("@bytes"), "error should mention @bytes, got: {}", msg);
376 }
377
378 #[test]
379 fn test_bytes_to_non_u8_array_is_error() {
380 let dsl = r#"
381 @endian = little;
382 struct header @packed {
383 data: [u16; 2] = @bytes("AB");
384 }
385 "#;
386 let result = generate(dsl, &HashMap::new(), &HashMap::new());
387 assert!(result.is_err(), "expected error for @bytes() on non-u8 array");
388 let msg = result.unwrap_err().message;
389 assert!(msg.contains("u8"), "error should mention u8, got: {}", msg);
390 }
391
392 #[test]
393 fn test_integer_truncation_emits_warning() {
394 let dsl = r#"
395 @endian = little;
396 struct header @packed {
397 small: u8 = 0x1FF;
398 }
399 "#;
400 let result = generate(dsl, &HashMap::new(), &HashMap::new()).unwrap();
401 assert_eq!(result.data, vec![0xFF]); assert!(!result.warnings.is_empty(), "expected truncation warning");
403 }
404
405 #[test]
408 fn test_range_field_to_end() {
409 let dsl = r#"
411 @endian = little;
412 struct header @packed {
413 magic: [u8; 4] = @bytes("TEST");
414 crc: u32 = @crc32(@self[magic..]);
415 }
416 "#;
417 let env = HashMap::new();
418 let sections = HashMap::new();
419 let result = generate(dsl, &env, §ions).unwrap();
420 assert_eq!(result.data.len(), 8);
421 let crc_bytes = &result.data[4..8];
423 assert_ne!(crc_bytes, &[0u8; 4], "CRC should not be zero");
424 }
425
426 #[test]
427 fn test_range_field_to_field() {
428 let dsl = r#"
430 @endian = little;
431 struct header @packed {
432 magic: [u8; 4] = @bytes("TEST");
433 reserved: u32 = 0;
434 body_crc: u32 = @crc32(@self[magic..body_crc]);
435 }
436 "#;
437 let env = HashMap::new();
438 let sections = HashMap::new();
439 let result = generate(dsl, &env, §ions).unwrap();
440 assert_eq!(result.data.len(), 12);
441 let crc_bytes = &result.data[8..12];
442 assert_ne!(crc_bytes, &[0u8; 4], "CRC should not be zero");
443 }
444
445 #[test]
448 fn test_undefined_env_var_is_error() {
449 let dsl = r#"
450 @endian = little;
451 struct header @packed {
452 ver: u8 = ${MISSING_VAR};
453 }
454 "#;
455 let result = generate(dsl, &HashMap::new(), &HashMap::new());
456 assert!(result.is_err(), "expected Err for undefined env var");
457 assert_eq!(result.unwrap_err().code, ErrorCode::E02001);
458 }
459
460 #[test]
461 fn test_shift_by_64_emits_warning_and_returns_zero() {
462 let dsl = r#"
464 @endian = little;
465 struct header @packed {
466 val: u64 = 1 << 64;
467 }
468 "#;
469 let result = generate(dsl, &HashMap::new(), &HashMap::new()).unwrap();
470 assert_eq!(result.data, vec![0u8; 8], "result should be 0 when shift >= 64");
471 assert!(
472 result.warnings.iter().any(|w| w.code == WarningCode::W04001),
473 "expected W04001 ShiftOverflow warning"
474 );
475 }
476
477 #[test]
478 fn test_crc_unified_equals_crc32() {
479 let env = HashMap::new();
481 let sects = HashMap::new();
482
483 let dsl_unified = r#"
484 @endian = little;
485 struct header @packed {
486 magic: [u8; 4] = @bytes("TEST");
487 crc: u32 = @crc("crc32", @self[magic..crc]);
488 }
489 "#;
490 let dsl_legacy = r#"
491 @endian = little;
492 struct header @packed {
493 magic: [u8; 4] = @bytes("TEST");
494 crc: u32 = @crc32(@self[magic..crc]);
495 }
496 "#;
497
498 let unified = generate(dsl_unified, &env, §s).unwrap();
499 let legacy = generate(dsl_legacy, &env, §s).unwrap();
500 assert_eq!(unified.data, legacy.data, "@crc(\"crc32\",...) must equal @crc32(...)");
501 }
502
503 #[test]
504 fn test_crc_unified_crc16_modbus() {
505 let mut sections = HashMap::new();
506 sections.insert("fw".to_string(), vec![0x01u8, 0x02, 0x03, 0x04]);
507
508 let dsl = r#"
509 @endian = little;
510 struct header @packed {
511 crc16: u16 = @crc("crc16-modbus", fw);
512 }
513 "#;
514 let result = generate(dsl, &HashMap::new(), §ions).unwrap();
515 assert_eq!(result.data.len(), 2);
516 let crc = u16::from_le_bytes([result.data[0], result.data[1]]);
517 assert_ne!(crc, 0, "CRC16-MODBUS should not be zero for non-empty input");
518 }
519
520 #[test]
521 fn test_crc_unknown_algorithm_is_error() {
522 let mut sections = HashMap::new();
523 sections.insert("fw".to_string(), vec![0xAAu8]);
524
525 let dsl = r#"
526 @endian = little;
527 struct header @packed {
528 crc: u32 = @crc("nonexistent-algo", fw);
529 }
530 "#;
531 let result = generate(dsl, &HashMap::new(), §ions);
532 assert!(result.is_err(), "unknown CRC algorithm should return Err");
533 assert_eq!(result.unwrap_err().code, ErrorCode::E04003);
534 }
535
536 #[test]
539 fn test_align_4_pads_to_boundary() {
540 let dsl = r#"
542 @endian = little;
543 struct header @align(4) {
544 tag: u8 = 0xAB;
545 val: u16 = 0x1234;
546 }
547 "#;
548 let result = generate(dsl, &HashMap::new(), &HashMap::new()).unwrap();
549 assert_eq!(result.data.len(), 4, "aligned struct should be 4 bytes");
550 assert_eq!(result.data[0], 0xAB);
551 assert_eq!(result.data[1], 0x34); assert_eq!(result.data[2], 0x12); assert_eq!(result.data[3], 0x00); }
555
556 #[test]
557 fn test_align_already_aligned_no_extra_padding() {
558 let dsl = r#"
560 @endian = little;
561 struct header @align(4) {
562 val: u32 = 0xDEADBEEF;
563 }
564 "#;
565 let result = generate(dsl, &HashMap::new(), &HashMap::new()).unwrap();
566 assert_eq!(result.data.len(), 4);
567 }
568
569 #[test]
572 fn test_validate_valid_dsl_returns_ok() {
573 let dsl = r#"
574 @endian = little;
575 struct header @packed {
576 version: u8 = 1;
577 }
578 "#;
579 let result = validate(dsl, &HashMap::new());
580 assert!(result.is_ok(), "valid DSL should pass validate()");
581 }
582
583 #[test]
584 fn test_validate_invalid_syntax_returns_error() {
585 let result = validate("this is not valid dsl", &HashMap::new());
586 assert!(result.is_err(), "invalid syntax should fail validate()");
587 }
588
589 #[test]
590 fn test_validate_undefined_env_var_returns_error() {
591 let dsl = r#"
592 @endian = little;
593 struct header @packed {
594 ver: u8 = ${NO_SUCH_VAR};
595 }
596 "#;
597 let result = validate(dsl, &HashMap::new());
598 assert!(result.is_err(), "undefined env var should fail validate()");
599 assert_eq!(result.unwrap_err().code, ErrorCode::E02001);
600 }
601
602 #[test]
603 fn test_validate_returns_warnings_for_truncation() {
604 let dsl = r#"
605 @endian = little;
606 struct header @packed {
607 small: u8 = 0x1FF;
608 }
609 "#;
610 let warnings = validate(dsl, &HashMap::new()).unwrap();
611 assert!(!warnings.is_empty(), "truncation should produce a warning");
612 assert!(warnings.iter().any(|w| w.code == WarningCode::W03002));
613 }
614
615 #[test]
618 fn test_parse_scalar_fields_little_endian() {
619 let dsl = "@endian = little; struct h @packed { ver: u8; flags: u16; size: u32; }";
620 let data: &[u8] = &[0x01, 0x34, 0x12, 0x78, 0x56, 0x34, 0x12];
621 let result = parse(dsl, &HashMap::new(), data).unwrap();
622 assert_eq!(result["ver"].as_u64().unwrap(), 0x01);
623 assert_eq!(result["flags"].as_u64().unwrap(), 0x1234);
624 assert_eq!(result["size"].as_u64().unwrap(), 0x12345678);
625 }
626
627 #[test]
628 fn test_parse_scalar_fields_big_endian() {
629 let dsl = "@endian = big; struct h @packed { val: u32; }";
630 let data: &[u8] = &[0x12, 0x34, 0x56, 0x78];
631 let result = parse(dsl, &HashMap::new(), data).unwrap();
632 assert_eq!(result["val"].as_u64().unwrap(), 0x12345678);
633 }
634
635 #[test]
636 fn test_parse_array_field_returns_bytes() {
637 let dsl = "@endian = little; struct h @packed { magic: [u8; 4]; }";
638 let data: &[u8] = b"TEST";
639 let result = parse(dsl, &HashMap::new(), data).unwrap();
640 assert_eq!(result["magic"].as_bytes().unwrap(), b"TEST");
641 }
642
643 #[test]
644 fn test_parse_data_too_short_is_error() {
645 let dsl = "@endian = little; struct h @packed { size: u32; }";
646 let data: &[u8] = &[0x01, 0x02]; let result = parse(dsl, &HashMap::new(), data);
648 assert!(result.is_err(), "short data should return Err");
649 }
650
651 #[test]
652 fn test_parse_roundtrip() {
653 let dsl = r#"
654 @endian = little;
655 struct h @packed {
656 version: u8 = 3;
657 flags: u16 = 0x1234;
658 size: u32 = 0xDEADBEEF;
659 }
660 "#;
661 let generated = generate(dsl, &HashMap::new(), &HashMap::new()).unwrap();
662 let parsed = parse(dsl, &HashMap::new(), &generated.data).unwrap();
663 assert_eq!(parsed["version"].as_u64().unwrap(), 3);
664 assert_eq!(parsed["flags"].as_u64().unwrap(), 0x1234);
665 assert_eq!(parsed["size"].as_u64().unwrap(), 0xDEAD_BEEF);
666 }
667}