1use crate::bkend::{AssemblerBackend, AssemblyOutput};
2use crate::dis_uxndis::run_dis_file;
3use crate::{Assembler, AssemblerError};
4use std::fs;
5use std::path::Path;
6use std::process::Command;
7
8pub struct DebugAssembleResult {
9 pub uxntal_rom_path: String,
10 pub uxnasm_rom_path: String,
11 pub drifblim_rom_path: String,
12 pub uxntal_rom_bytes: Vec<u8>,
13 pub uxnasm_rom_bytes: Vec<u8>,
14 pub drifblim_rom_bytes: Vec<u8>,
15 pub uxntal_output: String,
16 pub uxnasm_output: String,
17 pub drifblim_output: String,
18 pub uxntal_dis_output: String,
19 pub uxnasm_dis_output: String,
20 pub drifblim_dis_output: String,
21 pub diff_uxntal_uxnasm: Option<(usize, String, String)>,
22 pub diff_uxntal_drifblim: Option<(usize, String, String)>,
23 pub diff_uxnasm_drifblim: Option<(usize, String, String)>,
24 pub backend_errors: Vec<(String, String)>,
25}
26
27#[derive(Default)]
28pub struct UxntalBackend {
29 pub drif_mode: bool,
30}
31
32impl UxntalBackend {
33 pub fn with_drif_mode(drif_mode: bool) -> Self {
34 Self { drif_mode }
35 }
36}
37
38impl AssemblerBackend for UxntalBackend {
39 fn name(&self) -> &'static str {
40 "uxntal"
41 }
42 fn assemble(&self, tal_file: &str, tal_source: &str) -> Result<AssemblyOutput, AssemblerError> {
43 let mut assembler = if self.drif_mode {
44 Assembler::with_drif_mode(true)
45 } else {
46 Assembler::new()
47 };
48 let rom = assembler.assemble(tal_source, Some(tal_file.to_string()))?;
49 let rom_path = format!("{}_{}.rom", tal_file, self.name());
50 fs::write(&rom_path, &rom).map_err(|e| io_err(&rom_path, e))?;
51 Ok(AssemblyOutput {
52 rom_path: rom_path.clone(),
53 rom_bytes: rom.clone(),
54 stdout: run_vm_last(&rom)?,
55 disassembly: run_dis_file(&rom_path)?,
56 })
57 }
58}
59
60pub struct UxnasmBackend;
61impl AssemblerBackend for UxnasmBackend {
62 fn name(&self) -> &'static str {
63 "uxnasm"
64 }
65 fn assemble(&self, tal_file: &str, _src: &str) -> Result<AssemblyOutput, AssemblerError> {
66 let input = if cfg!(windows) {
69 wslpath::windows_to_wsl(tal_file).map_err(|e| {
70 syntax_err(tal_file, &format!("Could not convert TAL path to WSL: {e}"))
71 })?
72 } else {
73 tal_file.to_string()
74 };
75 let rom_path = format!("{}_{}.rom", input, self.name());
87 let mut cmd = spawn_cmd("uxnasm", &["--verbose", &input, &rom_path]);
88 let output = cmd
89 .output()
90 .map_err(|e| syntax_err(&input, &format!("Failed to run uxnasm: {e}")))?;
91 if !output.status.success() {
92 return Err(syntax_err(
93 &input,
94 &format!(
95 "uxnasm failed {:?} {:?} stderr: {} stdout: {}",
96 output.status,
97 std::env::current_dir(),
98 String::from_utf8_lossy(&output.stderr),
99 String::from_utf8_lossy(&output.stdout)
100 .lines()
101 .rev()
102 .take(3)
103 .collect::<Vec<_>>()
104 .into_iter()
105 .rev()
106 .collect::<Vec<_>>()
107 .join("\n")
108 ),
109 ));
110 }
111 let rom_path = if cfg!(windows) {
113 wslpath::wsl_to_windows(&rom_path).map_err(|e| {
114 syntax_err(
115 &rom_path,
116 &format!("Could not convert ROM path to Windows: {e}"),
117 )
118 })?
119 } else {
120 rom_path
121 };
122 let bytes = fs::read(&rom_path).unwrap_or_default();
123 Ok(AssemblyOutput {
124 rom_path: rom_path.clone(),
125 rom_bytes: bytes.clone(),
126 stdout: run_uxncli_get_stdout(&rom_path)?,
127 disassembly: run_dis_file(&rom_path)?,
128 })
129 }
130}
131
132pub struct DrifblimBackend;
133impl AssemblerBackend for DrifblimBackend {
134 fn name(&self) -> &'static str {
135 "drifblim"
136 }
137 fn assemble(&self, tal_file: &str, _src: &str) -> Result<AssemblyOutput, AssemblerError> {
138 let input = tal_file.replace('\\', "/");
146 let input = input.replace("//?/C:", "/mnt/c"); let rom_path = format!("{}_{}.rom", input, self.name());
149 println!("rom_path: {}", rom_path);
150 println!("tal_file: {}", tal_file);
151 println!("input: {}", input);
152 let stdout = Self::run_drif(&input, &input, &rom_path)?;
153 let rom_path = rom_path.replace("/mnt/c/", "C:/"); let rom_path = rom_path.replace('/', "\\");
155 let bytes = fs::read(&rom_path).unwrap_or_default();
156 if bytes.is_empty() {
157 return Err(syntax_err(&rom_path, "drifblim produced empty ROM"));
158 }
159 Ok(AssemblyOutput {
160 rom_path: rom_path.clone(),
161 rom_bytes: bytes,
162 stdout,
163 disassembly: run_dis_file(&rom_path)?,
164 })
165 }
166}
167impl DrifblimBackend {
168 fn run_drif(tal_path: &str, _tal_file: &str, rom_path: &str) -> Result<String, AssemblerError> {
169 println!("tal_path: {}", tal_path);
170 println!("rom_path: {}", rom_path);
171 let drifblim_rom = crate::bkend_drif::drifblim_repo_get_drifblim();
172 let drifblim_rom = if cfg!(windows) {
173 wslpath::windows_to_wsl(&drifblim_rom.to_string_lossy())
174 .map_err(|_| syntax_err(rom_path, "Could not convert drifblim path to WSL"))?
175 } else {
176 drifblim_rom.to_string_lossy().to_string()
177 };
178 let output = if cfg!(windows) {
179 let in_wsl = detect_wsl();
180 if in_wsl {
181 Command::new("uxncli")
182 .arg(drifblim_rom)
183 .arg(tal_path)
184 .arg(rom_path)
185 .output()
186 } else {
187 Command::new("wsl")
188 .arg("uxncli")
189 .arg(drifblim_rom)
190 .arg(tal_path)
191 .arg(rom_path)
192 .output()
193 }
194 .map_err(|e| syntax_err(rom_path, &format!("drifblim failed: {e}")))?
195 } else {
196 Command::new("uxncli")
197 .arg(drifblim_rom)
198 .arg(tal_path)
199 .arg(rom_path)
200 .output()
201 .map_err(|e| syntax_err(rom_path, &format!("drifblim failed: {e}")))?
202 };
203 println!("drifblim stdout: {:?}", output);
204 if !output.stderr.is_empty() {
205 let stderr_str = String::from_utf8_lossy(&output.stderr);
206 if stderr_str.contains("Assembled ") {
207 println!("Path exceeded error from drifblim, retrying with short path");
209 } else {
220 return Err(syntax_err(
221 rom_path,
222 &format!(
223 "drifblim stderr: {}",
224 String::from_utf8_lossy(&output.stderr)
225 ),
226 ));
227 }
228 }
229 Ok(String::from_utf8_lossy(&output.stdout).to_string())
230 }
231}
232
233pub struct DrifblimSeedBackend;
234impl AssemblerBackend for DrifblimSeedBackend {
235 fn name(&self) -> &'static str {
236 "drifseed"
237 }
238 fn assemble(&self, tal_file: &str, _src: &str) -> Result<AssemblyOutput, AssemblerError> {
239 let tal_file = Path::new(tal_file)
246 .canonicalize()
247 .ok()
248 .and_then(|abs| {
249 std::env::current_dir()
250 .ok()
251 .and_then(|cwd| abs.strip_prefix(&cwd).ok().map(|rel| rel.to_path_buf()))
252 .or(Some(abs))
253 })
254 .unwrap_or_else(|| Path::new(tal_file).to_path_buf());
255 let cwd_wsl = std::env::current_dir()
256 .ok()
257 .and_then(|cwd| wslpath::windows_to_wsl(&cwd.display().to_string()).ok())
258 .unwrap_or_else(|| ".".to_string());
259 let input = if cfg!(windows) {
260 let input = wslpath::windows_to_wsl(&tal_file.display().to_string()).map_err(|e| {
261 syntax_err(
262 &tal_file.display().to_string(),
263 &format!("Could not convert TAL path to WSL: {e}"),
264 )
265 })?;
266 if input.starts_with(&cwd_wsl) {
267 input[cwd_wsl.len()..].trim_start_matches('/').to_string()
268 } else {
269 input
270 }
271 } else {
272 tal_file.display().to_string()
273 };
274 let rom_path = format!("{}_{}.rom", input, self.name());
276 println!("cwd_wsl: {}", cwd_wsl);
277 println!("rom_path: {}", rom_path);
278 println!("tal_file: {}", tal_file.display());
279 println!("input: {}", input);
280 let stdout = Self::run_drif(&input, &input, &rom_path)?;
282 let rom_path = if cfg!(windows) {
283 wslpath::wsl_to_windows(&rom_path).map_err(|e| {
284 syntax_err(
285 &rom_path,
286 &format!("Could not convert ROM path to Windows: {e}"),
287 )
288 })?
289 } else {
290 rom_path.to_string()
291 };
292 let bytes = fs::read(&rom_path).unwrap_or_default();
293 println!("rom_path: {}", rom_path);
294
295 if bytes.is_empty() {
296 return Err(syntax_err(&rom_path, "drifblim produced empty ROM"));
297 }
298
299 Ok(AssemblyOutput {
300 rom_path: rom_path.clone(),
301 rom_bytes: bytes,
302 stdout,
303 disassembly: run_dis_file(&rom_path)?,
304 })
305 }
306}
307impl DrifblimSeedBackend {
308 fn run_drif(tal_path: &str, _tal_file: &str, rom_path: &str) -> Result<String, AssemblerError> {
309 println!("tal_path: {}", tal_path);
310 println!("rom_path: {}", rom_path);
311 let drifblim_rom = crate::bkend_drif::drifblim_repo_get_drifblim_seed();
312 let drifblim_rom = if cfg!(windows) {
313 wslpath::windows_to_wsl(&drifblim_rom.to_string_lossy())
314 .map_err(|_| syntax_err(rom_path, "Could not convert drifblim path to WSL"))?
315 } else {
316 drifblim_rom.to_string_lossy().to_string()
317 };
318 let in_wsl = detect_wsl();
319 let output = if in_wsl || !cfg!(windows) {
320 Command::new("uxncli")
321 .arg(drifblim_rom)
322 .arg(tal_path)
323 .arg(rom_path)
324 .output()
325 } else {
326 Command::new("wsl")
327 .arg("uxncli")
328 .arg(drifblim_rom)
329 .arg(tal_path)
330 .arg(rom_path)
331 .output()
332 }
333 .map_err(|e| syntax_err(rom_path, &format!("drifblim failed: {e}")))?;
334 println!("drifblim stdout: {:?}", output);
335 if !output.stderr.is_empty() {
336 let stderr_str = String::from_utf8_lossy(&output.stderr);
337 if stderr_str.contains("Assembled ") {
338 } else {
351 return Err(syntax_err(
352 rom_path,
353 &format!(
354 "drifblim stderr: {}",
355 String::from_utf8_lossy(&output.stderr)
356 ),
357 ));
358 }
359 }
360 Ok(String::from_utf8_lossy(&output.stdout).to_string())
361 }
362}
363
364pub struct DrifloonBackend;
365impl AssemblerBackend for DrifloonBackend {
366 fn name(&self) -> &'static str {
367 "drifloon"
368 }
369 fn assemble(&self, tal_file: &str, _src: &str) -> Result<AssemblyOutput, AssemblerError> {
370 let input = tal_file.replace('\\', "/");
378 let input = input.replace("//?/C:", "/mnt/c"); let rom_path = format!("{}_{}.rom", input, self.name());
381 println!("rom_path: {}", rom_path);
382 println!("tal_file: {}", tal_file);
383 println!("input: {}", input);
384 let stdout = Self::run_drifloon(&input, _src, &rom_path)?;
385 let rom_path = rom_path.replace("/mnt/c/", "C:/"); let rom_path = rom_path.replace('/', "\\");
387 let bytes = fs::read(&rom_path).unwrap_or_default();
388 if bytes.is_empty() {
389 return Err(syntax_err(&rom_path, "drifloon produced empty ROM"));
390 }
391 Ok(AssemblyOutput {
392 rom_path: rom_path.clone(),
393 rom_bytes: bytes,
394 stdout,
395 disassembly: run_dis_file(&rom_path)?,
396 })
397 }
398}
399impl DrifloonBackend {
400 fn run_drifloon(
401 tal_path: &str,
402 tal_file: &str,
403 rom_path: &str,
404 ) -> Result<String, AssemblerError> {
405 println!("tal_path: {}", tal_path);
406 println!("rom_path: {}", rom_path);
407 let home_dir = dirs::home_dir()
408 .ok_or_else(|| syntax_err(rom_path, "Could not determine home directory"))?;
409 let uxntal_path = home_dir.join(".uxntal");
410 let drifblim_path = uxntal_path.join(".drifblim");
411 let drifloon_path = drifblim_path.join("src").join("drifloon.rom");
412 let drifloon_path = if cfg!(target_os = "windows") {
413 wslpath::windows_to_wsl(&drifloon_path.to_string_lossy())
414 .map_err(|_| syntax_err(rom_path, "Could not convert drifloon path to WSL"))?
415 } else {
416 drifloon_path.to_string_lossy().to_string()
417 };
418 let output = if cfg!(target_os = "windows") {
419 let in_wsl = detect_wsl();
420 if in_wsl {
421 let mut child = Command::new("uxncli")
422 .arg(&drifloon_path)
423 .stdin(std::process::Stdio::piped())
424 .stdout(std::process::Stdio::piped())
425 .stderr(std::process::Stdio::piped())
426 .spawn()
427 .map_err(|e| syntax_err(rom_path, &format!("drifloon failed to spawn: {e}")))?;
428
429 if let Some(mut stdin) = child.stdin.take() {
430 stdin.write_all(tal_file.as_bytes()).map_err(|e| {
431 syntax_err(rom_path, &format!("drifloon failed to write to stdin: {e}"))
432 })?;
433 }
434 child.wait_with_output()
435 } else {
436 let mut child = Command::new("wsl")
437 .arg("uxncli")
438 .arg(&drifloon_path)
439 .stdin(std::process::Stdio::piped())
440 .stdout(std::process::Stdio::piped())
441 .stderr(std::process::Stdio::piped())
442 .spawn()
443 .map_err(|e| syntax_err(rom_path, &format!("drifloon failed to spawn: {e}")))?;
444 if let Some(mut stdin) = child.stdin.take() {
445 stdin.write_all(tal_file.as_bytes()).map_err(|e| {
446 syntax_err(rom_path, &format!("drifloon failed to write to stdin: {e}"))
447 })?;
448 }
449 child.wait_with_output()
450 }
451 .map_err(|e| syntax_err(rom_path, &format!("drifloon failed: {e}")))?
452 } else {
453 let mut child = Command::new("uxncli")
454 .arg(&drifloon_path)
455 .stdin(std::process::Stdio::piped())
456 .stdout(std::process::Stdio::piped())
457 .stderr(std::process::Stdio::piped())
458 .spawn()
459 .map_err(|e| syntax_err(rom_path, &format!("drifloon failed to spawn: {e}")))?;
460 if let Some(mut stdin) = child.stdin.take() {
461 stdin.write_all(tal_file.as_bytes()).map_err(|e| {
462 syntax_err(rom_path, &format!("drifloon failed to write to stdin: {e}"))
463 })?;
464 }
465 child
466 .wait_with_output()
467 .map_err(|e| syntax_err(rom_path, &format!("drifloon failed: {e}")))?
468 };
469 println!("drifloon path: {}", &drifloon_path);
470 println!("drifloon stdout: {:?}", output);
471 println!("drifloon stderr: {:?}", output.stderr);
472 println!("rom_path: {}", rom_path);
473 if !output.stdout.is_empty() {
475 let rom_path = if cfg!(windows) {
476 wslpath::wsl_to_windows(rom_path).map_err(|e| {
477 syntax_err(
478 rom_path,
479 &format!("Could not convert ROM path to Windows: {e}"),
480 )
481 })?
482 } else {
483 rom_path.to_string()
484 };
485 fs::write(&rom_path, &output.stdout)
486 .map_err(|e| syntax_err(&rom_path, &format!("Failed to write ROM: {e}")))?;
487 }
488
489 if !output.stderr.is_empty() && output.stdout.is_empty() {
491 return Err(syntax_err(
492 rom_path,
493 &format!(
494 "drifloon stderr: {}",
495 String::from_utf8_lossy(&output.stderr)
496 ),
497 ));
498 }
499 Ok(String::from_utf8_lossy(&output.stdout).to_string())
500 }
501}
502
503fn detect_wsl() -> bool {
504 std::env::var("WSL_DISTRO_NAME").is_ok()
505 || std::env::var("WSLENV").is_ok()
506 || (Path::new("/proc/version").exists()
507 && fs::read_to_string("/proc/version")
508 .unwrap_or_default()
509 .to_lowercase()
510 .contains("microsoft"))
511}
512
513fn spawn_cmd(cmd: &str, args: &[&str]) -> Command {
514 if cfg!(not(windows)) {
515 let mut command = Command::new(cmd);
516 command.args(args);
517 return command;
518 }
519 let in_wsl = detect_wsl();
520 if in_wsl {
521 let mut command = Command::new(cmd);
522 command.args(args);
523 command
524 } else {
525 let mut all = vec![cmd];
526 all.extend(args);
527 let mut command = Command::new("wsl");
528 command.args(all);
529 command
530 }
531}
532
533fn emu_err(path: &str, msg: &str) -> AssemblerError {
534 AssemblerError::Backend {
535 message: format!("{path}: {msg}"),
536 }
537}
538
539fn run_vm_last(bytes: &[u8]) -> Result<String, AssemblerError> {
540 let tmp = ".__temp_uxntal_exec.rom";
541 fs::write(tmp, bytes).map_err(|e| io_err(tmp, e))?;
542 let out = run_uxncli_get_stdout(tmp)?;
543 let _ = fs::remove_file(tmp);
544 Ok(out)
545}
546
547pub fn run_uxncli_get_stdout(rom_path: &str) -> Result<String, AssemblerError> {
548 if cfg!(windows) {
549 let in_wsl = crate::wsl::detect_wsl();
550 let output = if in_wsl {
551 Command::new("uxncli").arg(rom_path).output()
552 } else {
553 Command::new("wsl").arg("uxncli").arg(rom_path).output()
554 }
555 .map_err(|e| emu_err(rom_path, &format!("uxncli failed: {e}")))?;
556 Ok(String::from_utf8_lossy(&output.stdout).to_string())
557 } else {
558 let output = Command::new("uxncli")
559 .arg(rom_path)
560 .output()
561 .map_err(|e| emu_err(rom_path, &format!("uxncli failed: {e}")))?;
562 Ok(String::from_utf8_lossy(&output.stdout).to_string())
563 }
564}
565
566fn syntax_err(path: &str, msg: &str) -> AssemblerError {
567 AssemblerError::SyntaxError {
568 path: path.to_string(),
569 line: 0,
570 position: 0,
571 message: msg.to_string(),
572 source_line: String::new(),
573 }
574}
575
576fn io_err(path: &str, e: std::io::Error) -> AssemblerError {
577 syntax_err(path, &format!("IO error: {e}"))
578}
579
580#[derive(Default)]
581pub struct DebugAssembler {
582 pub drif_mode: bool,
583}
584
585impl DebugAssembler {
586 pub fn new() -> Self {
587 Self { drif_mode: false }
588 }
589
590 pub fn with_drif_mode(drif_mode: bool) -> Self {
591 Self { drif_mode }
592 }
593
594 pub fn assemble_and_compare(
595 &self,
596 tal_file: &str,
597 tal_source: &str,
598 verbose: bool,
599 ) -> Result<DebugAssembleResult, AssemblerError> {
600 let backends: Vec<Box<dyn AssemblerBackend>> = vec![
601 Box::new(UxntalBackend::with_drif_mode(self.drif_mode)),
602 Box::new(DrifloonBackend),
603 Box::new(UxnasmBackend),
604 Box::new(DrifblimBackend),
605 Box::new(DrifblimSeedBackend),
606 Box::new(crate::bkend_buxn::UxnBuxnBackend),
607 Box::new(crate::bkend_uxn::UxnDUxnAsmBackend),
608 ];
609 let mut uxntal = None;
610 let mut uxnasm = None;
611 let mut drif = None;
612 let mut drifloon = None;
613 let mut drifblim_seed = None;
614 let mut buxn = None;
615 let mut uxn38 = None;
616 let mut duxnasm = None;
617 let mut backend_errors = Vec::new();
618
619 for b in backends {
620 match b.assemble(tal_file, tal_source) {
621 Ok(out) => {
622 let sym_path = format!("{}.sym", out.rom_path);
624 if !out.rom_bytes.is_empty() {
625 if b.name() == "uxntal" {
627 let mut assembler = if self.drif_mode {
628 Assembler::with_drif_mode(true)
629 } else {
630 Assembler::new()
631 };
632 if assembler
633 .assemble(tal_source, Some(tal_file.to_string()))
634 .is_ok()
635 {
636 let sym_bytes = assembler.generate_symbol_file();
637 let _ = fs::write(&sym_path, &sym_bytes);
638 }
639 }
640 }
641 match b.name() {
642 "uxntal" => uxntal = Some(out),
643 "uxnasm" => uxnasm = Some(out),
644 "drifblim" => drif = Some(out),
645 "drifloon" => drifloon = Some(out),
646 "drifseed" => drifblim_seed = Some(out),
647 "buxn" => buxn = Some(out),
648 "uxn38" => uxn38 = Some(out),
649 "duxnasm" => duxnasm = Some(out),
650 _ => {}
651 }
652 }
653 Err(e) => backend_errors.push((b.name().to_string(), e.to_string())),
654 }
655 }
656
657 if verbose {
659 println!("\n== Backend Summary ==");
660 println!(
661 " {:<9} {:<5} {:>8} output/summary",
662 "backend", "stat", "bytes"
663 );
664 for (name, out) in [
665 ("uxntal", &uxntal),
666 ("uxnasm", &uxnasm),
667 ("drifblim", &drif),
668 ("drifloon", &drifloon),
669 ("drifseed", &drifblim_seed),
670 ("buxn", &buxn),
671 ("uxn38", &uxn38),
672 ("duxnasm", &duxnasm),
673 ] {
674 if let Some(ref o) = out {
675 println!(
676 " {:<9} {:<5} {:>8} {}",
677 name,
678 "OK",
679 o.rom_bytes.len(),
680 o.rom_path
681 );
682 } else {
683 let summary = backend_errors
684 .iter()
685 .find(|(n, _)| n == name)
686 .map(|(_, e)| e.as_str())
687 .unwrap_or("-");
688 println!(" {:<9} {:<5} {:>8} {}", name, "FAIL", 0, summary);
689 }
690 }
691
692 println!("\n== Byte Diffs ==");
694 let pairs = [
695 ("uxntal", &uxntal, "uxnasm", &uxnasm),
696 ("uxntal", &uxntal, "drifblim", &drif),
697 ("uxnasm", &uxnasm, "drifblim", &drif),
698 ("uxntal", &uxntal, "drifloon", &drifloon),
699 ("uxnasm", &uxnasm, "drifloon", &drifloon),
700 ("drifblim", &drif, "drifloon", &drifloon),
701 ("drifblim", &drif, "drifseed", &drifblim_seed),
702 ("uxntal", &uxntal, "drifseed", &drifblim_seed),
703 ("uxnasm", &uxnasm, "drifseed", &drifblim_seed),
704 ("drifloon", &drifloon, "drifseed", &drifblim_seed),
705 ("buxn", &buxn, "uxntal", &uxntal),
706 ("buxn", &buxn, "uxnasm", &uxnasm),
707 ("buxn", &buxn, "drifblim", &drif),
708 ("buxn", &buxn, "drifloon", &drifloon),
709 ("buxn", &buxn, "drifseed", &drifblim_seed),
710 ("uxn38", &uxn38, "uxntal", &uxntal),
711 ("uxn38", &uxn38, "uxnasm", &uxnasm),
712 ("uxn38", &uxn38, "drifblim", &drif),
713 ("uxn38", &uxn38, "drifloon", &drifloon),
714 ("uxn38", &uxn38, "drifseed", &drifblim_seed),
715 ("uxn38", &uxn38, "buxn", &buxn),
716 ("duxnasm", &duxnasm, "uxntal", &uxntal),
717 ("duxnasm", &duxnasm, "uxnasm", &uxnasm),
718 ("duxnasm", &duxnasm, "drifblim", &drif),
719 ("duxnasm", &duxnasm, "drifloon", &drifloon),
720 ("duxnasm", &duxnasm, "drifseed", &drifblim_seed),
721 ("duxnasm", &duxnasm, "buxn", &buxn),
722 ("duxnasm", &duxnasm, "uxn38", &uxn38),
723 ];
724 for (a_name, a, b_name, b) in pairs {
725 print!("{:<8} vs {:<9}: ", a_name, b_name);
726 if let (Some(ref a), Some(ref b)) = (a, b) {
727 if let Some(d) = first_byte_diff(&a.rom_bytes, &b.rom_bytes) {
728 println!(
729 "first diff at 0x{:04X}: {:02X} != {:02X}",
730 d.index, d.a, d.b
731 );
732 } else {
733 println!("identical");
734 }
735 } else {
736 println!("skipped (missing backend)");
737 }
738 }
739
740 println!("\n== Disassembly Diffs (first differing line) ==");
742 let dis_pairs = [
743 ("uxntal", &uxntal, "uxnasm", &uxnasm),
744 ("uxntal", &uxntal, "drifblim", &drif),
745 ("uxnasm", &uxnasm, "drifblim", &drif),
746 ("uxntal", &uxntal, "drifloon", &drifloon),
747 ("uxnasm", &uxnasm, "drifloon", &drifloon),
748 ("drifblim", &drif, "drifloon", &drifloon),
749 ("drifblim", &drif, "drifseed", &drifblim_seed),
750 ("uxntal", &uxntal, "drifseed", &drifblim_seed),
751 ("uxnasm", &uxnasm, "drifseed", &drifblim_seed),
752 ("drifloon", &drifloon, "drifseed", &drifblim_seed),
753 ("buxn", &buxn, "uxntal", &uxntal),
754 ("buxn", &buxn, "uxnasm", &uxnasm),
755 ("buxn", &buxn, "drifblim", &drif),
756 ("buxn", &buxn, "drifloon", &drifloon),
757 ("buxn", &buxn, "drifseed", &drifblim_seed),
758 ("uxn38", &uxn38, "uxntal", &uxntal),
759 ("uxn38", &uxn38, "uxnasm", &uxnasm),
760 ("uxn38", &uxn38, "drifblim", &drif),
761 ("uxn38", &uxn38, "drifloon", &drifloon),
762 ("uxn38", &uxn38, "drifseed", &drifblim_seed),
763 ("uxn38", &uxn38, "buxn", &buxn),
764 ("duxnasm", &duxnasm, "uxntal", &uxntal),
765 ("duxnasm", &duxnasm, "uxnasm", &uxnasm),
766 ("duxnasm", &duxnasm, "drifblim", &drif),
767 ("duxnasm", &duxnasm, "drifloon", &drifloon),
768 ("duxnasm", &duxnasm, "drifseed", &drifblim_seed),
769 ("duxnasm", &duxnasm, "buxn", &buxn),
770 ("duxnasm", &duxnasm, "uxn38", &uxn38),
771 ];
772 for (a_name, a, b_name, b) in dis_pairs {
773 print!("{:<8} vs {:<9}: ", a_name, b_name);
774 if let (Some(ref a), Some(ref b)) = (a, b) {
775 if a.disassembly.is_empty() {
776 println!("skipped (empty disassembly from {})", a_name);
777 continue;
778 }
779 if b.disassembly.is_empty() {
780 println!("skipped (empty disassembly from {})", b_name);
781 continue;
782 }
783 match diff_first(&a.disassembly, &b.disassembly) {
784 Some((ln, la, lb)) => {
785 println!("line {}:\n {}: {}\n {}: {}", ln, a_name, la, b_name, lb);
786 }
787 None => {
788 let lines: Vec<_> = a.disassembly.lines().take(3).collect();
789 if lines.is_empty() {
790 println!("identical (no disassembly output)");
791 } else {
792 println!("identical");
793 }
804 }
805 }
806 } else {
807 println!("skipped (missing backend)");
808 }
809 }
810
811 if !backend_errors.is_empty() {
813 println!("\n== Backend Errors ==");
814 for (name, err) in &backend_errors {
815 println!("{}: {}", name, err);
816 }
817 }
818 }
819
820 let uxntal_rom_path = uxntal
822 .as_ref()
823 .map(|x| x.rom_path.clone())
824 .unwrap_or_default();
825 let uxnasm_rom_path = uxnasm
826 .as_ref()
827 .map(|x| x.rom_path.clone())
828 .unwrap_or_default();
829 let drifblim_rom_path = drif
830 .as_ref()
831 .map(|x| x.rom_path.clone())
832 .unwrap_or_default();
833 let uxntal_rom_bytes = uxntal
834 .as_ref()
835 .map(|x| x.rom_bytes.clone())
836 .unwrap_or_default();
837 let uxnasm_rom_bytes = uxnasm
838 .as_ref()
839 .map(|x| x.rom_bytes.clone())
840 .unwrap_or_default();
841 let drifblim_rom_bytes = drif
842 .as_ref()
843 .map(|x| x.rom_bytes.clone())
844 .unwrap_or_default();
845 let uxntal_output = uxntal
846 .as_ref()
847 .map(|x| x.stdout.clone())
848 .unwrap_or_default();
849 let uxnasm_output = uxnasm
850 .as_ref()
851 .map(|x| x.stdout.clone())
852 .unwrap_or_default();
853 let drifblim_output = drif.as_ref().map(|x| x.stdout.clone()).unwrap_or_default();
854 let uxntal_dis_output = uxntal
855 .as_ref()
856 .map(|x| x.disassembly.clone())
857 .unwrap_or_default();
858 let uxnasm_dis_output = uxnasm
859 .as_ref()
860 .map(|x| x.disassembly.clone())
861 .unwrap_or_default();
862 let drifblim_dis_output = drif
863 .as_ref()
864 .map(|x| x.disassembly.clone())
865 .unwrap_or_default();
866
867 let diff_uxntal_uxnasm = diff_first(&uxntal_dis_output, &uxnasm_dis_output);
869 let diff_uxntal_drifblim = diff_first(&uxntal_dis_output, &drifblim_dis_output);
870 let diff_uxnasm_drifblim = diff_first(&uxnasm_dis_output, &drifblim_dis_output);
871
872 Ok(DebugAssembleResult {
873 uxntal_rom_path,
874 uxnasm_rom_path,
875 drifblim_rom_path,
876 uxntal_rom_bytes,
877 uxnasm_rom_bytes,
878 drifblim_rom_bytes,
879 uxntal_output,
880 uxnasm_output,
881 drifblim_output,
882 uxntal_dis_output,
883 uxnasm_dis_output,
884 drifblim_dis_output,
885 diff_uxntal_uxnasm,
886 diff_uxntal_drifblim,
887 diff_uxnasm_drifblim,
888 backend_errors,
889 })
890 }
891}
892
893fn diff_first(a: &str, b: &str) -> Option<(usize, String, String)> {
894 for (i, (la, lb)) in a.lines().zip(b.lines()).enumerate() {
895 if la != lb {
896 if let (Some(a_without_comment), Some(b_without_comment)) =
898 (strip_symbol_comment(la), strip_symbol_comment(lb))
899 {
900 if a_without_comment == b_without_comment {
901 continue;
903 }
904 }
905 return Some((i + 1, la.to_string(), lb.to_string()));
906 }
907 }
908 let ac = a.lines().count();
909 let bc = b.lines().count();
910 if ac > bc {
911 a.lines()
912 .nth(bc)
913 .map(|extra| (bc + 1, extra.to_string(), String::new()))
914 } else if bc > ac {
915 b.lines()
916 .nth(ac)
917 .map(|extra| (ac + 1, String::new(), extra.to_string()))
918 } else {
919 None
920 }
921}
922
923fn strip_symbol_comment(line: &str) -> Option<&str> {
926 if let Some(comment_start) = line.find('(') {
927 if let Some(comment_end) = line.rfind(')') {
928 if comment_start < comment_end {
929 return Some(line[..comment_start].trim_end());
931 }
932 }
933 }
934 Some(line)
935}
936
937fn first_byte_diff(a: &[u8], b: &[u8]) -> Option<ByteDiff> {
938 let len = std::cmp::min(a.len(), b.len());
939 for i in 0..len {
940 if a[i] != b[i] {
941 let rom_address = i + 0x100; if rom_address == 0x0B48 {
944 eprintln!("DEBUG: [Byte comparison] At ROM address 0x{:04X} (file offset 0x{:04X}): a[{}]=0x{:02X}, b[{}]=0x{:02X}", rom_address, i, i, a[i], i, b[i]);
945 let start = (i as i32 - 8).max(0) as usize;
947 let end = (i + 8).min(len);
948 eprint!("DEBUG: [Context a]: ");
949 for (j, val) in a.iter().enumerate().take(end).skip(start) {
950 if j == i {
951 eprint!("[{:02X}] ", val);
952 } else {
953 eprint!("{:02X} ", val);
954 }
955 }
956 eprintln!();
957 eprint!("DEBUG: [Context b]: ");
958 for (j, val) in b.iter().enumerate().take(end).skip(start) {
959 if j == i {
960 eprint!("[{:02X}] ", val);
961 } else {
962 eprint!("{:02X} ", val);
963 }
964 }
965 eprintln!();
966 }
967 return Some(ByteDiff {
968 index: rom_address, a: a[i],
970 b: b[i],
971 });
972 }
973 }
974 if a.len() != b.len() {
975 Some(ByteDiff {
976 index: len,
977 a: if a.len() > len { a[len] } else { 0 },
978 b: if b.len() > len { b[len] } else { 0 },
979 })
980 } else {
981 None
982 }
983}
984
985struct ByteDiff {
986 index: usize,
987 a: u8,
988 b: u8,
989}
990
991use crate::chocolatal::{deluge_preprocess, preprocess};
997use std::io::{self, Write};
998use std::path::PathBuf;
999
1000pub fn compare_preprocessors(input_path: &str, root_dir: &PathBuf) -> io::Result<()> {
1002 let input = fs::read_to_string(input_path)?;
1004 let rust_out = match preprocess(&input, input_path, root_dir) {
1006 Ok(s) => s,
1007 Err(e) => format!("[chocolatal error: {:?}]\n", e),
1008 };
1009 let rust_path = "chocolatal.tal";
1010 let mut file = fs::File::create(rust_path)?;
1011 file.write_all(rust_out.as_bytes())?;
1012 let deluge_out = deluge_preprocess(input_path)?;
1014 let deluge_pp_path = "deluge_pp_cmp.tal";
1015 let mut file = fs::File::create(deluge_pp_path)?;
1016 file.write_all(deluge_out.as_bytes())?;
1017 let rust_lines: Vec<_> = rust_out.lines().collect();
1019 let deluge_lines: Vec<_> = deluge_out.lines().collect();
1020 let max = rust_lines.len().max(deluge_lines.len());
1021 let mut found = false;
1022 for i in 0..max {
1023 let r = rust_lines.get(i).map_or("", |v| v);
1024 let d = deluge_lines.get(i).map_or("", |v| v);
1025 if r != d {
1026 println!(
1027 "First difference at line {}:{}:\n chocolatal : {}\n deluge_pp : {}\n{}:{}:\n",
1028 rust_path,
1029 i + 1,
1030 r,
1031 d,
1032 deluge_pp_path,
1033 i + 1
1034 );
1035 found = true;
1036 break;
1037 }
1038 }
1039 if !found {
1040 println!("No differences found.");
1041 }
1042 Ok(())
1043}