1type AssembleDirectoryResult = (
52 std::path::PathBuf,
53 std::path::PathBuf,
54 Option<std::path::PathBuf>,
55 usize,
56);
57pub use util::{RealRomCache, RealRomEntryResolver};
58pub use uxn_tal_common::{get_or_write_cached_rom, hash_url};
60pub mod assembler;
61pub mod bkend;
62pub mod bkend_buxn;
63pub mod bkend_drif;
64pub mod bkend_uxn;
65pub mod bkend_uxn38;
66pub mod chocolatal;
67pub mod debug;
68pub mod devicemap;
69pub mod dis_uxndis;
70pub mod error;
71pub mod hexrev;
72pub mod lexer;
73pub mod opcode_table;
74pub mod opcodes;
75pub mod parser;
76pub mod rom;
77pub mod runes;
78pub mod wsl;
79pub use assembler::Assembler;
80pub use error::AssemblerError;
81pub mod emulator_utils;
82pub mod fetch;
83pub mod mode_basic;
84pub mod mode_orca;
85pub mod paths;
86pub mod util;
87pub use fetch::parse_uxntal_url;
88pub use fetch::resolver::resolve_entry_from_url;
89pub mod probe_runtime;
90pub mod probe_tal;
91pub mod protocol_parser;
92
93pub use uxn_tal_defined::*;
94pub use protocol_parser::ProtocolParser;
96
97pub fn assemble(source: &str) -> Result<Vec<u8>, AssemblerError> {
98 let mut a = Assembler::new();
99 a.assemble(source, None)
100}
101
102pub fn assemble_with_path(source: &str, path: &str) -> Result<Vec<u8>, AssemblerError> {
103 let mut a = Assembler::new();
104 a.assemble(source, Some(path.to_string()))
105}
106
107pub fn assemble_file<P: AsRef<std::path::Path>>(input_path: P) -> Result<Vec<u8>, AssemblerError> {
109 let source = std::fs::read_to_string(&input_path)?;
110 let mut assembler = Assembler::new();
111 let path_str = input_path.as_ref().to_string_lossy().into_owned();
112 assembler.assemble(&source, Some(path_str))
113}
114
115pub fn assemble_file_to_rom<P: AsRef<std::path::Path>, Q: AsRef<std::path::Path>>(
117 input_path: P,
118 output_path: Q,
119) -> Result<usize, AssemblerError> {
120 let rom = assemble_file(input_path)?;
121 std::fs::write(&output_path, &rom)?;
122 Ok(rom.len())
123}
124
125pub fn assemble_file_auto<P: AsRef<std::path::Path>>(
127 input_path: P,
128) -> Result<(std::path::PathBuf, usize), AssemblerError> {
129 let input_path = input_path.as_ref();
130 let output_path = input_path.with_extension("rom");
131 let size = assemble_file_to_rom(input_path, &output_path)?;
132 Ok((output_path, size))
133}
134
135pub fn assemble_file_with_symbols<P: AsRef<std::path::Path>>(
137 input_path: P,
138) -> Result<(std::path::PathBuf, std::path::PathBuf, usize), AssemblerError> {
139 let input_path = input_path.as_ref();
140 let source = std::fs::read_to_string(input_path)?;
141 let mut assembler = Assembler::new();
142 let path_str = input_path.to_string_lossy().into_owned();
143 let rom = assembler.assemble(&source, Some(path_str))?;
144
145 let rom_path = input_path.with_extension("rom");
147 std::fs::write(&rom_path, &rom)?;
148
149 let sym_path = input_path.with_extension("sym");
151 let symbols = assembler.generate_symbol_file();
152 std::fs::write(&sym_path, &symbols)?;
153
154 Ok((rom_path, sym_path, rom.len()))
155}
156
157pub fn assemble_directory<P: AsRef<std::path::Path>>(
159 dir_path: P,
160 generate_symbols: bool,
161) -> Result<Vec<AssembleDirectoryResult>, AssemblerError> {
162 let dir_path = dir_path.as_ref();
163 let mut results = Vec::new();
164
165 for entry in std::fs::read_dir(dir_path)? {
166 let entry = entry?;
167 let path = entry.path();
168
169 if path.extension().and_then(|s| s.to_str()) == Some("tal") {
170 if generate_symbols {
171 let (rom_path, sym_path, size) = assemble_file_with_symbols(&path)?;
172 results.push((path, rom_path, Some(sym_path), size));
173 } else {
174 let (rom_path, size) = assemble_file_auto(&path)?;
175 results.push((path, rom_path, None, size));
176 }
177 }
178 }
179
180 Ok(results)
181}
182
183pub fn assemble_with_rust_interface_module(
184 source: &str,
185 module_name: &str,
186) -> Result<(Vec<u8>, String), AssemblerError> {
187 let mut a = Assembler::new();
188 let rom = a.assemble(source, None)?;
189 let module = generate_rust_interface_module(&a, module_name);
190 Ok((rom, module))
191}
192
193pub fn generate_rust_interface_module(
194 assembler: &crate::assembler::Assembler,
195 module_name: &str,
196) -> String {
197 let mut out = String::new();
198 out.push_str("#![allow(clippy::module_inception)]\n");
199 out.push_str(&format!("pub mod {} {{\n", module_name));
200 out.push_str(" #![allow(non_upper_case_globals)]\n");
201 out.push_str(" // Auto-generated: label address & size constants\n");
202 for name in &assembler.symbol_order {
204 if let Some(sym) = assembler.symbols.get(name) {
205 let id = {
206 let mut s: String = name
207 .chars()
208 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
209 .collect();
210 if s.chars()
211 .next()
212 .map(|c| c.is_ascii_digit())
213 .unwrap_or(false)
214 {
215 s.insert(0, '_');
216 }
217 s.to_ascii_uppercase()
218 };
219 out.push_str(&format!(
220 " pub const _c{}: usize = 0x{:04X};\n",
221 id, sym.address
222 ));
223 let next_addr = assembler
225 .symbol_order
226 .iter()
227 .skip_while(|n| *n != name)
228 .skip(1)
229 .filter_map(|n| assembler.symbols.get(n))
230 .map(|s| s.address)
231 .find(|&a| a > sym.address)
232 .unwrap_or(assembler.effective_length as u16);
233 let size = next_addr.saturating_sub(sym.address);
234 out.push_str(&format!(
235 " pub const _c{}_SIZE: usize = 0x{:04X};\n",
236 id, size
237 ));
238 }
239 }
240 out.push_str(
242 r#"
243 /// Returns a slice of RAM for a label by name (address, size)
244 pub fn get_slice<'a>(ram: &'a [u8], label: &str) -> Option<&'a [u8]> {
245 match label {
246"#,
247 );
248 for name in &assembler.symbol_order {
249 if let Some(_sym) = assembler.symbols.get(name) {
250 let id = {
251 let mut s: String = name
252 .chars()
253 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
254 .collect();
255 if s.chars()
256 .next()
257 .map(|c| c.is_ascii_digit())
258 .unwrap_or(false)
259 {
260 s.insert(0, '_');
261 }
262 s.to_ascii_uppercase()
263 };
264 out.push_str(&format!(
265 " \"{name}\" => Some(&ram[_c{}.._c{}+_c{}_SIZE]),\n",
266 id, id, id
267 ));
268 }
269 }
270 out.push_str(
271 r#" _ => None,
272 }
273 }
274"#,
275 );
276 out.push_str("}\n");
277 out
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_simple_assembly() {
286 let source = r#"
287 |0100
288 #42 #43 ADD BRK
289 "#;
290
291 let mut assembler = Assembler::new();
292 let rom = assembler.assemble(source, None).expect("Assembly failed");
293
294 assert_eq!(rom.len(), 6);
297 assert_eq!(rom[0], 0x80); assert_eq!(rom[1], 0x42); assert_eq!(rom[2], 0x80); assert_eq!(rom[3], 0x43); assert_eq!(rom[4], 0x18); }
303
304 #[test]
305 fn test_label_reference() {
306 let source = r#"
307 |0100 @start
308 ;data LDA2
309 BRK
310 @data #1234
311 "#;
312
313 let mut assembler = Assembler::new();
314 let rom = assembler
315 .assemble(source, Some("(test_label_reference)".to_string()))
316 .expect("Assembly failed");
317
318 assert!(rom.len() > 4);
321 assert_eq!(rom[0], 0xa0); let expected_addr = 0x0105_u16;
325 assert_eq!(rom[1], (expected_addr >> 8) as u8); assert_eq!(rom[2], (expected_addr & 0xff) as u8); }
328
329 fn _test_instruction_modes() {
330 let source = r#"
331 |0100
332 ADD ( base instruction )
333 ADD2 ( short mode )
334 ADDr ( return mode )
335 ADDk ( keep mode )
336 ADD2rk ( all modes )
337 BRK
338 "#;
339
340 let mut assembler = Assembler::new();
341 let rom = assembler
343 .assemble(source, Some("(test_instruction_modes)".to_string()))
344 .expect("Assembly failed");
345 assert_eq!(rom[1], 0x18 | 0x20); assert_eq!(rom[2], 0x18 | 0x40); assert_eq!(rom[3], 0x18 | 0x80); assert_eq!(rom[4], 0x18 | 0x20 | 0x40 | 0x80); assert_eq!(rom[5], 0x00); }
351
352 #[test]
353 fn test_hex_literals() {
354 let source = r#"
355 #12 #3456 #ab #cdef
356 "#;
357
358 let mut assembler = Assembler::new();
359 let rom = assembler
360 .assemble(source, Some("(test_hex_literals1)".to_string()))
361 .expect("Assembly failed");
362
363 assert_eq!(rom[0], 0x80); assert_eq!(rom[1], 0x12); assert_eq!(rom[2], 0xa0); assert_eq!(rom[3], 0x34); assert_eq!(rom[4], 0x56); assert_eq!(rom[5], 0x80); assert_eq!(rom[6], 0xab); assert_eq!(rom[7], 0xa0); assert_eq!(rom[8], 0xcd); assert_eq!(rom[9], 0xef); }
378
379 #[test]
380 fn test_character_literals() {
381 let source = r#"
382 |0100
383 'A 'B 'C
384 "#;
385
386 let mut assembler = Assembler::new();
387 let rom = assembler.assemble(source, None).expect("Assembly failed");
388
389 assert_eq!(rom[0], b'A');
391 assert_eq!(rom[1], b'B');
392 assert_eq!(rom[2], b'C');
393 }
394
395 #[test]
396 fn test_raw_strings() {
397 let source = r#"
398 |0100
399 "Hello"
400 "#;
401
402 let mut assembler = Assembler::new();
403 let rom = assembler.assemble(source, None).expect("Assembly failed");
404
405 assert_eq!(&rom[0..5], b"Hello");
407 }
408
409 #[test]
410 #[ignore = "reason: not sure why it fails, tbd"]
411 fn test_undefined_label_error() {
412 let source = r#"
413 |0100
414 ;undefined-label LDA2
415 "#;
416
417 let mut assembler = Assembler::new();
418 let result = assembler.assemble(source, None);
419
420 assert!(matches!(result, Err(AssemblerError::UndefinedLabel { .. })));
421 }
422
423 #[test]
424 #[ignore = "reason: not sure why it fails, tbd"]
425 fn test_duplicate_label_error() {
426 let source = r#"
427 |0100 @label
428 @label
429 "#;
430
431 let mut assembler = Assembler::new();
432 let result = assembler.assemble(source, Some("(test_duplicate_label_error)".to_owned()));
433
434 assert!(matches!(result, Err(AssemblerError::DuplicateLabel { .. })));
435 }
436
437 #[test]
438 #[ignore = "reason: not sure why it fails, tbd"]
439 fn test_unknown_opcode_error() {
440 let source = r#"
441 |0100
442 UNKNOWN
443 "#;
444
445 let mut assembler = Assembler::new();
446 let result = assembler.assemble(source, None);
447
448 assert!(matches!(result, Err(AssemblerError::UnknownOpcode { .. })));
449 }
450
451 #[test]
452 fn test_skip_directive() {
453 let source = r#"
454 |0100
455 #12
456 $04
457 #34
458 "#;
459
460 let mut assembler = Assembler::new();
461 let data = assembler
462 .assemble(source, Some("(test_skip_directive)".to_string()))
463 .unwrap();
464
465 assert_eq!(data[0], 0x80); assert_eq!(data[1], 0x12); let _rom = assembler
470 .assemble(source, Some("(test_hex_literals)".to_string()))
471 .expect("Assembly failed");
472 assert_eq!(data[3], 0x00); assert_eq!(data[4], 0x00); assert_eq!(data[5], 0x00); assert_eq!(data[6], 0x80); assert_eq!(data[7], 0x34); }
478
479 #[test]
480 fn test_device_access() {
481 let source = r#"
482 |00 @System &r $2
483 |0100 @main
484 #ff .System/r DEO
485 "#;
486
487 let mut assembler = Assembler::new();
488 let data = assembler
489 .assemble(source, Some("(test_device_access)".to_string()))
490 .unwrap();
491
492 assert_eq!(data.len(), 5);
494 assert_eq!(data[0], 0x80); assert_eq!(data[1], 0xff); assert_eq!(data[2], 0x80); assert_eq!(data[3], 0x00); assert_eq!(data[4], 0x17); }
500
501 #[test]
502 fn test_macros() {
503 let source = r#"
504 %DOUBLE { DUP ADD }
505 |0100 @main
506 #05 DOUBLE
507 "#;
508
509 let mut assembler = Assembler::new();
510 let data = assembler
511 .assemble(source, Some("(test_macros)".to_string()))
512 .unwrap();
513
514 assert_eq!(data.len(), 4);
516 assert_eq!(data[0], 0x80); assert_eq!(data[1], 0x05); assert_eq!(data[2], 0x06); assert_eq!(data[3], 0x18); }
521
522 #[test]
523 fn test_inline_assembly() {
524 let source = r#"
525 |0100 @main
526 [ #05 DUP ADD ]
527 "#;
528
529 let mut assembler = Assembler::new();
530 let data = assembler
531 .assemble(source, Some("(test_inline_assembly)".to_string()))
532 .unwrap();
533
534 assert_eq!(data.len(), 4);
536 assert_eq!(data[0], 0x80); assert_eq!(data[1], 0x05); assert_eq!(data[2], 0x06); assert_eq!(data[3], 0x18); }
541
542 #[test]
543 fn test_complete_tal_features() {
544 let source = r#"
545 |0100 @main
546 #41 #18 DEO
547 BRK
548 "#;
549
550 let mut assembler = Assembler::new();
551 let result = assembler.assemble(source, Some("(test_complete_tal_features)".to_string()));
552 if let Err(ref e) = result {
553 println!("Assembly error: {}", e);
554 }
555 assert!(result.is_ok(), "Complete TAL assembly should succeed");
556
557 let data = result.unwrap();
558 assert!(data.len() == 6, "Should generate some ROM data");
559
560 assert_eq!(data[0], 0x80); assert_eq!(data[1], 0x41); assert_eq!(data[2], 0x80); assert_eq!(data[3], 0x18); assert_eq!(data[4], 0x17); }
567}
568
569#[test]
570fn test_tal_strings_error() {
571 use crate::Assembler;
572 let source = r#"&guide "TYPE 20 ""HELP" 20 "FOR 20 "INFO 20 7f 20 $1 &bytes-free 20 "BYTES 20 "FREE 0a $1"#;
576
577 let mut assembler = Assembler::new();
579 let _ = assembler
580 .assemble(source, None)
581 .expect("Assembly should succeed");
582
583 let guide_offset = assembler.symbols["guide"].address as usize - 0x0100;
585 let bytes_free_offset = assembler.symbols["bytes-free"].address as usize - 0x0100;
586
587 let rom = assembler.rom.data();
589
590 let expected_guide: &[u8] = b"TYPE \"HELP\" FOR INFO \x7f ";
592 let expected_bytes_free: &[u8] = b" BYTES FREE\n";
593
594 assert_eq!(
595 &rom[guide_offset..guide_offset + expected_guide.len()],
596 expected_guide
597 );
598 assert_eq!(
599 &rom[bytes_free_offset..bytes_free_offset + expected_bytes_free.len()],
600 expected_bytes_free
601 );
602}