1use std::{
5 error::Error,
6 ffi::OsString,
7 fmt::Display,
8 fs::{File, OpenOptions},
9 io::{BufRead, BufReader, BufWriter},
10 path::PathBuf,
11};
12
13use clap::ArgAction::Set;
14use clap::Parser;
15use regex::Regex;
16
17use assembler::{Binary, BinaryChunk, write_user_program};
18use base::prelude::{Address, Unsigned18Bit, Unsigned36Bit, join_halves};
19
20const OCTAL: u32 = 8;
21
22#[derive(Debug)]
23struct Fail(String);
24
25impl Display for Fail {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 self.0.fmt(f)
28 }
29}
30impl Error for Fail {}
31
32const AUTHOR: &str = "James Youngman <james@youngman.org>";
36
37#[derive(Parser, Debug)]
39#[clap(author = AUTHOR, version, about, long_about = None)]
40struct Cli {
41 #[clap(action=Set)]
43 input: OsString,
44
45 #[clap(action = Set, short = 'o', long)]
47 output: OsString,
48}
49
50fn read_binary(input_file: File) -> Result<Binary, Fail> {
51 let pattern: &'static str = r"^\s*[|](\d+) +(\d+)[|]\s*(\d+)\s*$";
52 let line_rx = match Regex::new(pattern) {
53 Ok(rx) => rx,
54 Err(e) => {
55 return Err(Fail(format!(
56 "internal error: failed to compile regular expression '{pattern}': {e}"
57 )));
58 }
59 };
60 let reader = BufReader::new(input_file);
61
62 let mut binary: Binary = Default::default();
63 let mut chunk: Option<BinaryChunk> = None;
64 let mut prev_addr: Option<Unsigned18Bit> = None;
65
66 for line_result in reader.lines() {
67 let line = match line_result {
68 Err(e) => {
69 return Err(Fail(format!("failed to read from input file: {e}")));
70 }
71 Ok(line) => line,
72 };
73 let line = line.trim();
74 if line.is_empty() {
75 continue; }
77 let matches = match line_rx.captures(line) {
78 Some(m) => m,
79 None => {
80 return Err(Fail(format!(
81 "input line '{line}' does not match required regular expression {pattern})"
82 )));
83 }
84 };
85 let (_, [left, right, address]) = matches.extract();
86 if left.len() != 6 {
87 return Err(Fail(format!(
88 "input line {line} has field {left} but it should be 6 characters long"
89 )));
90 }
91 let left: Unsigned18Bit = match u32::from_str_radix(left, OCTAL).map(|n| n.try_into()) {
92 Ok(Ok(n)) => n,
93 _ => {
94 return Err(Fail(format!("field {left} should be an octal number")));
95 }
96 };
97 if right.len() != 6 {
98 return Err(Fail(format!(
99 "input line {line} has field {right} but it should be 6 characters long"
100 )));
101 }
102 let right: Unsigned18Bit = match u32::from_str_radix(right, OCTAL).map(|n| n.try_into()) {
103 Ok(Ok(n)) => n,
104 _ => {
105 return Err(Fail(format!("field {right} should be an octal number")));
106 }
107 };
108 let prev_plus_one: Option<Unsigned18Bit> = match prev_addr {
109 None => None,
110 Some(p) => match p.checked_add(Unsigned18Bit::ONE) {
111 Some(n) => Some(n),
112 None => {
113 return Err(Fail("A block is too long to fit in memory".to_string()));
118 }
119 },
120 };
121
122 let word: Unsigned36Bit = join_halves(left, right);
123 let addr: Unsigned18Bit = match u32::from_str_radix(address, OCTAL).map(|n| n.try_into()) {
124 Ok(Ok(n)) => {
125 if address.len() == 3 {
126 match prev_plus_one {
127 Some(p) => (p & 0o777000) | (n & 0o777),
128 None => {
129 return Err(Fail(format!(
130 "The first address field {address} should be 6 characters long"
131 )));
132 }
133 }
134 } else {
135 n
136 }
137 }
138 _ => {
139 return Err(Fail(format!("field {address} should be an octal number")));
140 }
141 };
142
143 let non_contiuguous = match prev_plus_one {
144 None => true,
145 Some(expected) => expected != addr,
146 };
147
148 if non_contiuguous && let Some(old) = chunk.take() {
149 assert!(!old.is_empty());
150 binary.add_chunk(old);
151 }
152 let ch: &mut BinaryChunk = chunk.get_or_insert_with(|| BinaryChunk {
153 address: Address::from(addr),
154 words: Vec::new(),
155 });
156 prev_addr = Some(addr);
157 ch.push(word);
158 }
159 if let Some(current) = chunk.take()
160 && !current.is_empty()
161 {
162 binary.add_chunk(current);
163 }
164 Ok(binary)
165}
166
167fn run_utility() -> Result<(), Fail> {
168 let cli = Cli::parse();
169 let input_file = OpenOptions::new()
170 .read(true)
171 .open(cli.input)
172 .map_err(|e| Fail(format!("failed to open input file: {e}")))?;
173 let binary = read_binary(input_file)?;
174
175 let output_path = PathBuf::from(cli.output);
176 let output_file = OpenOptions::new()
177 .create(true)
178 .write(true)
179 .truncate(true)
180 .open(&output_path)
181 .map_err(|e| Fail(format!("failed to write output: {e}")))?;
182
183 let mut writer = BufWriter::new(output_file);
184 match write_user_program(&binary, &mut writer, &output_path) {
185 Ok(()) => Ok(()),
186 Err(e) => Err(Fail(e.to_string())),
187 }
188}
189
190fn main() {
191 match run_utility() {
192 Err(e) => {
193 eprintln!("{e}");
194 std::process::exit(1);
195 }
196 Ok(()) => {
197 std::process::exit(0);
198 }
199 }
200}