Skip to content

Commit 1a136d2

Browse files
Improve errors in rmc_assemble (#12)
* Include line numbers on assembler errors * Use .ok_or() instead of match statements * Return AssemblerErrors from main() instead of Strings Still not a massive fan of how we're handling errors but it's okay * Handle read errors instead of panicking * Improve WriteError error message
1 parent 4dc6f8f commit 1a136d2

2 files changed

Lines changed: 63 additions & 32 deletions

File tree

demos/tests/invalid.lmc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ABC 1

src/rmc_assemble.rs

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
use clap::Parser;
2-
use std::{
3-
collections::HashMap, fs, path::PathBuf
4-
};
2+
use std::{collections::HashMap, fmt, fs, io, path::PathBuf};
53

64
use rusty_man_computer::value::Value;
75

@@ -38,11 +36,36 @@ enum Line {
3836
}
3937

4038
#[derive(Debug)]
41-
enum ParseError {
39+
enum ParseErrorType {
4240
InvalidOpcode(String),
4341
OperandOutOfRange(i16),
4442
}
4543

44+
impl fmt::Display for ParseErrorType {
45+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46+
match self {
47+
ParseErrorType::InvalidOpcode(opcode) => {
48+
write!(f, "Invalid opcode: {}", opcode)
49+
}
50+
ParseErrorType::OperandOutOfRange(value) => {
51+
write!(f, "Operand out of range: {}", value)
52+
}
53+
}
54+
}
55+
}
56+
57+
#[derive(Debug)]
58+
struct ParseError {
59+
error: ParseErrorType,
60+
line: usize,
61+
}
62+
63+
impl fmt::Display for ParseError {
64+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
65+
write!(f, "Parse error on line {}: {}", self.line, self.error)
66+
}
67+
}
68+
4669
fn parse_opcode(string: &str) -> Option<Opcode> {
4770
match string {
4871
"HLT" => Some(Opcode::HLT),
@@ -64,8 +87,10 @@ fn parse_opcode(string: &str) -> Option<Opcode> {
6487
fn parse_assembly(program: &str) -> Vec<Result<Line, ParseError>> {
6588
program
6689
.lines()
67-
.map(|line| {
90+
.enumerate()
91+
.map(|(line_index, line)| {
6892
let line = line.trim();
93+
let line_number = line_index + 1;
6994
if line.is_empty() || line.starts_with("//") {
7095
return Ok(Line::Empty);
7196
}
@@ -84,15 +109,14 @@ fn parse_assembly(program: &str) -> Vec<Result<Line, ParseError>> {
84109
let opcode = match first_part_as_opcode {
85110
Some(opcode) => opcode,
86111
None => {
87-
let string = match parts.get(1) {
88-
Some(string) => string,
89-
// This means there's only one part: there's nothing to label, so it's just an invalid opcode
90-
None => return Err(ParseError::InvalidOpcode(parts[0].to_string())),
91-
};
92-
match parse_opcode(string) {
93-
Some(opcode) => opcode,
94-
None => return Err(ParseError::InvalidOpcode(string.to_string())),
95-
}
112+
let string = parts.get(1).ok_or(ParseError {
113+
error: ParseErrorType::InvalidOpcode(parts[0].to_string()),
114+
line: line_number,
115+
})?;
116+
parse_opcode(string).ok_or(ParseError {
117+
error: ParseErrorType::InvalidOpcode(string.to_string()),
118+
line: line_number,
119+
})?
96120
}
97121
};
98122
let operand_part = if label.is_some() {
@@ -106,7 +130,10 @@ fn parse_assembly(program: &str) -> Vec<Result<Line, ParseError>> {
106130
Some(string) => match string.parse::<i16>() {
107131
Ok(value) => Some(Operand::Value(
108132
// If the number doesn't fit within a Value, return an OperandOutOfRange error
109-
Value::new(value).map_err(|_| ParseError::OperandOutOfRange(value))?,
133+
Value::new(value).map_err(|_| ParseError {
134+
error: ParseErrorType::OperandOutOfRange(value),
135+
line: line_number,
136+
})?,
110137
)),
111138
Err(_) => Some(Operand::Label(string.to_string())),
112139
},
@@ -179,10 +206,22 @@ fn generate_machine_code(lines: Vec<Line>) -> Result<Vec<Value>, &'static str> {
179206
Ok(output)
180207
}
181208

182-
#[derive(Debug)]
183209
enum AssemblerError {
184210
ParseError(ParseError),
185211
MachineCodeError(&'static str),
212+
ReadError(io::Error),
213+
WriteError(io::Error),
214+
}
215+
216+
impl fmt::Debug for AssemblerError {
217+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218+
match self {
219+
AssemblerError::ParseError(e) => write!(f, "{}", e),
220+
AssemblerError::MachineCodeError(e) => write!(f, "Machine code error: {}", e),
221+
AssemblerError::WriteError(e) => write!(f, "Failed to write to output file: {}", e),
222+
AssemblerError::ReadError(e) => write!(f, "Failed to read input file: {}", e),
223+
}
224+
}
186225
}
187226

188227
fn assemble(program: &str) -> Result<Vec<Value>, AssemblerError> {
@@ -215,26 +254,17 @@ pub struct Args {
215254
output: PathBuf,
216255
}
217256

218-
fn main() -> Result<(), String> {
257+
fn main() -> Result<(), AssemblerError> {
219258
let args = Args::parse();
220-
let program = std::fs::read_to_string(args.program).expect("Failed to read program");
259+
let program =
260+
std::fs::read_to_string(args.program).map_err(|e| AssemblerError::ReadError(e))?;
221261
let assembler_result = assemble(&program);
222262
match assembler_result {
223-
Err(error) => match error {
224-
AssemblerError::ParseError(parse_error) => {
225-
return Err(format!("Parse error: {:?}", parse_error));
226-
}
227-
AssemblerError::MachineCodeError(message) => {
228-
return Err(message.to_string());
229-
}
230-
},
263+
Err(error) => Err(error),
231264
Ok(machine_code) => {
232-
let machine_code_bytes: Vec<u8> = machine_code
233-
.iter()
234-
.flat_map(|&i| i.to_be_bytes())
235-
.collect();
236-
fs::write(args.output, machine_code_bytes)
237-
.map_err(|e| format!("Failed to write output file: {}", e))
265+
let machine_code_bytes: Vec<u8> =
266+
machine_code.iter().flat_map(|&i| i.to_be_bytes()).collect();
267+
fs::write(args.output, machine_code_bytes).map_err(|e| AssemblerError::WriteError(e))
238268
}
239269
}
240270
}

0 commit comments

Comments
 (0)