improve error handling

This commit is contained in:
Vinzenz Schroeter 2024-12-15 00:43:59 +01:00
parent 5ad6baa30d
commit a4a027b448
24 changed files with 238 additions and 112 deletions

21
Cargo.lock generated
View file

@ -165,6 +165,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"ariadne", "ariadne",
"chumsky", "chumsky",
"thiserror",
] ]
[[package]] [[package]]
@ -332,6 +333,26 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "thiserror"
version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.14" version = "1.0.14"

View file

@ -6,3 +6,4 @@ resolver = "2"
clap = { version = "4.5.23", features = ["derive"] } clap = { version = "4.5.23", features = ["derive"] }
shellwords = { version = "1.1.0" } shellwords = { version = "1.1.0" }
ariadne = { version = "0.5.0", features = ["auto-color"] } ariadne = { version = "0.5.0", features = ["auto-color"] }
thiserror = "2.0.6"

View file

@ -7,8 +7,6 @@ edition = "2021"
[dependencies] [dependencies]
chumsky = "0.9.3" chumsky = "0.9.3"
ariadne = {version = "0.5.0", optional = true} ariadne = {version = "0.5.0"}
thiserror.workspace = true
[features]
default = ["error-report"]
error-report = ["dep:ariadne"]

View file

@ -1,5 +1,6 @@
use crate::instructions::{ use crate::instructions::{
try_load_binary_op, Instruction, InstructionName, ParametersError, TryLoadInstruction, try_load_binary_op, ExecuteInstructionError, Instruction, InstructionName, InstructionResult,
ParametersError, TryLoadInstruction,
}; };
use crate::machine::{Machine, RegisterIndex, RegisterOrValue}; use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
use std::fmt::Display; use std::fmt::Display;
@ -16,9 +17,14 @@ pub struct Addition {
} }
impl Instruction for Addition { impl Instruction for Addition {
fn execute(&self, machine: &mut Machine) { fn execute(&self, machine: &mut Machine) -> InstructionResult {
let value = self.a.read(machine) + self.b.read(machine); let value = self
.a
.read(machine)
.checked_add(self.b.read(machine))
.ok_or(ExecuteInstructionError::ArithmeticError)?;
self.r.write(machine, value); self.r.write(machine, value);
Ok(())
} }
} }
@ -69,7 +75,8 @@ mod tests {
r: r0, r: r0,
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![65, 0]); assert_eq!(machine.registers.read_all(), vec![65, 0]);
Instruction::execute( Instruction::execute(
&Addition { &Addition {
@ -78,7 +85,8 @@ mod tests {
r: r1, r: r1,
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![65, 42]); assert_eq!(machine.registers.read_all(), vec![65, 42]);
Instruction::execute( Instruction::execute(
&Addition { &Addition {
@ -87,7 +95,8 @@ mod tests {
r: r1, r: r1,
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![65, 1379]); assert_eq!(machine.registers.read_all(), vec![65, 1379]);
} }

View file

@ -1,5 +1,6 @@
use crate::instructions::{ use crate::instructions::{
try_load_binary_op, Instruction, InstructionName, ParametersError, TryLoadInstruction, try_load_binary_op, ExecuteInstructionError, Instruction, InstructionName, InstructionResult,
ParametersError, TryLoadInstruction,
}; };
use crate::machine::{Machine, RegisterIndex, RegisterOrValue}; use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
@ -16,9 +17,14 @@ pub struct Division {
} }
impl Instruction for Division { impl Instruction for Division {
fn execute(&self, machine: &mut Machine) { fn execute(&self, machine: &mut Machine) -> InstructionResult {
let value = self.a.read(machine) / self.b.read(machine); let value = self
self.r.write(machine, value) .a
.read(machine)
.checked_div(self.b.read(machine))
.ok_or(ExecuteInstructionError::ArithmeticError)?;
self.r.write(machine, value);
Ok(())
} }
} }
@ -69,7 +75,8 @@ mod tests {
r: r0, r: r0,
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![4, 0]); assert_eq!(machine.registers.read_all(), vec![4, 0]);
Instruction::execute( Instruction::execute(
&Division { &Division {
@ -78,7 +85,8 @@ mod tests {
r: r1, r: r1,
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![4, -4]); assert_eq!(machine.registers.read_all(), vec![4, -4]);
Instruction::execute( Instruction::execute(
&Division { &Division {
@ -87,7 +95,8 @@ mod tests {
r: r1, r: r1,
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![4, -334]); assert_eq!(machine.registers.read_all(), vec![4, -334]);
} }

View file

@ -1,5 +1,6 @@
use crate::instructions::{ use crate::instructions::{
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction, check_param_count, Instruction, InstructionName, InstructionResult, ParametersError,
TryLoadInstruction,
}; };
use crate::machine::{Machine, RegisterOrValue}; use crate::machine::{Machine, RegisterOrValue};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
@ -12,8 +13,9 @@ pub struct Jump {
} }
impl Instruction for Jump { impl Instruction for Jump {
fn execute(&self, machine: &mut Machine) { fn execute(&self, machine: &mut Machine) -> InstructionResult {
machine.ip = self.to.read(machine) as usize; machine.ip = self.to.read(machine) as usize;
Ok(())
} }
} }
@ -55,7 +57,8 @@ mod tests {
to: RegisterOrValue::Value(42), to: RegisterOrValue::Value(42),
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![0]); assert_eq!(machine.registers.read_all(), vec![0]);
assert_eq!(machine.ip, 42); assert_eq!(machine.ip, 42);
machine.registers[0].write(19); machine.registers[0].write(19);
@ -65,7 +68,8 @@ mod tests {
to: RegisterOrValue::Register(r0), to: RegisterOrValue::Register(r0),
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![19]); assert_eq!(machine.registers.read_all(), vec![19]);
assert_eq!(machine.ip, 19); assert_eq!(machine.ip, 19);
} }

View file

@ -1,6 +1,8 @@
use crate::instructions::{ use crate::instructions::{
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction, check_param_count, Instruction, InstructionName, InstructionResult, ParametersError,
TryLoadInstruction,
}; };
use crate::isize_to_bool;
use crate::machine::{Machine, RegisterOrValue}; use crate::machine::{Machine, RegisterOrValue};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
use std::rc::Rc; use std::rc::Rc;
@ -12,10 +14,11 @@ pub struct JumpIfZero {
} }
impl Instruction for JumpIfZero { impl Instruction for JumpIfZero {
fn execute(&self, machine: &mut Machine) { fn execute(&self, machine: &mut Machine) -> InstructionResult {
if self.a.read(machine) == 0 { if !isize_to_bool(self.a.read(machine)) {
machine.ip = self.to.read(machine) as usize; machine.ip = self.to.read(machine) as usize;
} }
Ok(())
} }
} }
@ -62,7 +65,8 @@ mod tests {
to: Value(42), to: Value(42),
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![0]); assert_eq!(machine.registers.read_all(), vec![0]);
assert_eq!(machine.ip, 42); assert_eq!(machine.ip, 42);
machine.registers[0].write(19); machine.registers[0].write(19);
@ -73,7 +77,8 @@ mod tests {
to: RegisterOrValue::Register(r0), to: RegisterOrValue::Register(r0),
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![19]); assert_eq!(machine.registers.read_all(), vec![19]);
assert_eq!(machine.ip, 42); assert_eq!(machine.ip, 42);
} }

View file

@ -9,7 +9,6 @@ mod put_instruction;
mod sub_instruction; mod sub_instruction;
mod teq_instruction; mod teq_instruction;
mod tlt_instruction; mod tlt_instruction;
mod traits;
mod try_load; mod try_load;
mod tze_instruction; mod tze_instruction;
@ -24,6 +23,29 @@ pub use put_instruction::*;
pub use sub_instruction::*; pub use sub_instruction::*;
pub use teq_instruction::*; pub use teq_instruction::*;
pub use tlt_instruction::*; pub use tlt_instruction::*;
pub use traits::*;
pub use try_load::*; pub use try_load::*;
pub use tze_instruction::*; pub use tze_instruction::*;
use crate::machine::Machine;
use std::fmt::{Debug, Display};
pub trait Instruction: Debug + Display {
// TODO: this needs to be able to return errors, e.g. for division by zero
fn execute(&self, machine: &mut Machine) -> InstructionResult;
}
type InstructionResult = Result<(), ExecuteInstructionError>;
#[derive(Debug, thiserror::Error)]
pub enum ExecuteInstructionError {
#[error("A break instruction has reached")]
Break,
#[error("An arithmetic exception occurred")]
ArithmeticError,
#[error("the current instruction pointer does not point to an instruction. Has the end of file been reached?")]
InvalidIp,
}
pub trait InstructionName {
const INSTRUCTION_NAME: &'static str;
}

View file

@ -1,5 +1,6 @@
use crate::instructions::{ use crate::instructions::{
try_load_binary_op, Instruction, InstructionName, ParametersError, TryLoadInstruction, try_load_binary_op, ExecuteInstructionError, Instruction, InstructionName, InstructionResult,
ParametersError, TryLoadInstruction,
}; };
use crate::machine::{Machine, RegisterIndex, RegisterOrValue}; use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
@ -16,9 +17,14 @@ pub struct Modulus {
} }
impl Instruction for Modulus { impl Instruction for Modulus {
fn execute(&self, machine: &mut Machine) { fn execute(&self, machine: &mut Machine) -> InstructionResult {
let value = self.a.read(machine) % self.b.read(machine); let value = self
self.r.write(machine, value) .a
.read(machine)
.checked_rem(self.b.read(machine))
.ok_or(ExecuteInstructionError::ArithmeticError)?;
self.r.write(machine, value);
Ok(())
} }
} }
@ -69,7 +75,8 @@ mod tests {
r: r0, r: r0,
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![19, 0]); assert_eq!(machine.registers.read_all(), vec![19, 0]);
Instruction::execute( Instruction::execute(
&Modulus { &Modulus {
@ -78,7 +85,8 @@ mod tests {
r: r1, r: r1,
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![19, 19]); assert_eq!(machine.registers.read_all(), vec![19, 19]);
Instruction::execute( Instruction::execute(
&Modulus { &Modulus {
@ -87,7 +95,8 @@ mod tests {
r: r1, r: r1,
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![19, 7]); assert_eq!(machine.registers.read_all(), vec![19, 7]);
} }

View file

@ -1,5 +1,6 @@
use crate::instructions::{ use crate::instructions::{
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction, check_param_count, Instruction, InstructionName, InstructionResult, ParametersError,
TryLoadInstruction,
}; };
use crate::machine::{Machine, RegisterOrValue}; use crate::machine::{Machine, RegisterOrValue};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
@ -9,8 +10,9 @@ use std::rc::Rc;
pub struct NoOperation {} pub struct NoOperation {}
impl Instruction for NoOperation { impl Instruction for NoOperation {
fn execute(&self, _: &mut Machine) { fn execute(&self, _: &mut Machine) -> InstructionResult {
// intentionally left empty as this is the no operation instruction // intentionally left empty as this is the no operation instruction
Ok(())
} }
} }

View file

@ -1,5 +1,6 @@
use crate::instructions::{ use crate::instructions::{
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction, check_param_count, Instruction, InstructionName, InstructionResult, ParametersError,
TryLoadInstruction,
}; };
use crate::machine::{Machine, RegisterIndex, RegisterOrValue}; use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
@ -14,9 +15,10 @@ pub struct Put {
} }
impl Instruction for Put { impl Instruction for Put {
fn execute(&self, machine: &mut Machine) { fn execute(&self, machine: &mut Machine) -> InstructionResult {
let value = self.a.read(machine); let value = self.a.read(machine);
self.r.write(machine, value); self.r.write(machine, value);
Ok(())
} }
} }
@ -65,7 +67,8 @@ mod tests {
r: r0, r: r0,
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![42, 0]); assert_eq!(machine.registers.read_all(), vec![42, 0]);
Instruction::execute( Instruction::execute(
&Put { &Put {
@ -73,7 +76,8 @@ mod tests {
r: r1, r: r1,
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![42, 42]); assert_eq!(machine.registers.read_all(), vec![42, 42]);
} }
} }

View file

@ -1,5 +1,6 @@
use crate::instructions::{ use crate::instructions::{
try_load_binary_op, Instruction, InstructionName, ParametersError, TryLoadInstruction, try_load_binary_op, ExecuteInstructionError, Instruction, InstructionName, InstructionResult,
ParametersError, TryLoadInstruction,
}; };
use crate::machine::{Machine, RegisterIndex, RegisterOrValue}; use crate::machine::{Machine, RegisterIndex, RegisterOrValue};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
@ -16,9 +17,14 @@ pub struct Subtraction {
} }
impl Instruction for Subtraction { impl Instruction for Subtraction {
fn execute(&self, machine: &mut Machine) { fn execute(&self, machine: &mut Machine) -> InstructionResult {
let value = self.a.read(machine) - self.b.read(machine); let value = self
.a
.read(machine)
.checked_sub(self.b.read(machine))
.ok_or(ExecuteInstructionError::ArithmeticError)?;
self.r.write(machine, value); self.r.write(machine, value);
Ok(())
} }
} }
@ -69,7 +75,8 @@ mod tests {
r: r0, r: r0,
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![19, 0]); assert_eq!(machine.registers.read_all(), vec![19, 0]);
Instruction::execute( Instruction::execute(
&Subtraction { &Subtraction {
@ -78,7 +85,8 @@ mod tests {
r: r1, r: r1,
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![19, 42]); assert_eq!(machine.registers.read_all(), vec![19, 42]);
Instruction::execute( Instruction::execute(
&Subtraction { &Subtraction {
@ -87,7 +95,8 @@ mod tests {
r: r1, r: r1,
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers.read_all(), vec![19, 1295]); assert_eq!(machine.registers.read_all(), vec![19, 1295]);
} }

View file

@ -1,5 +1,7 @@
use crate::bool_to_isize;
use crate::instructions::{ use crate::instructions::{
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction, check_param_count, Instruction, InstructionName, InstructionResult, ParametersError,
TryLoadInstruction,
}; };
use crate::machine::{Machine, RegisterOrValue}; use crate::machine::{Machine, RegisterOrValue};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
@ -12,9 +14,10 @@ pub struct TestEqual {
} }
impl Instruction for TestEqual { impl Instruction for TestEqual {
fn execute(&self, machine: &mut Machine) { fn execute(&self, machine: &mut Machine) -> InstructionResult {
let result = self.a.read(machine) == self.b.read(machine); let result = self.a.read(machine) == self.b.read(machine);
machine.registers[0].write(if result { 1 } else { 0 }); machine.registers[0].write(bool_to_isize(result));
Ok(())
} }
} }
@ -59,7 +62,8 @@ mod tests {
b: RegisterOrValue::Value(23), b: RegisterOrValue::Value(23),
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers[0].read(), 0); assert_eq!(machine.registers[0].read(), 0);
machine.registers[0].write(-23); machine.registers[0].write(-23);
@ -69,7 +73,8 @@ mod tests {
b: RegisterOrValue::Value(-23), b: RegisterOrValue::Value(-23),
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers[0].read(), 1); assert_eq!(machine.registers[0].read(), 1);
} }

View file

@ -1,5 +1,7 @@
use crate::bool_to_isize;
use crate::instructions::{ use crate::instructions::{
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction, check_param_count, Instruction, InstructionName, InstructionResult, ParametersError,
TryLoadInstruction,
}; };
use crate::machine::{Machine, RegisterOrValue}; use crate::machine::{Machine, RegisterOrValue};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
@ -12,9 +14,10 @@ pub struct TestLessThan {
} }
impl Instruction for TestLessThan { impl Instruction for TestLessThan {
fn execute(&self, machine: &mut Machine) { fn execute(&self, machine: &mut Machine) -> InstructionResult {
let result = self.a.read(machine) < self.b.read(machine); let result = self.a.read(machine) < self.b.read(machine);
machine.registers[0].write(if result { 1 } else { 0 }); machine.registers[0].write(bool_to_isize(result));
Ok(())
} }
} }
@ -59,7 +62,8 @@ mod tests {
b: RegisterOrValue::Value(23), b: RegisterOrValue::Value(23),
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers[0].read(), 0); assert_eq!(machine.registers[0].read(), 0);
machine.registers[0].write(-24); machine.registers[0].write(-24);
@ -69,7 +73,8 @@ mod tests {
b: RegisterOrValue::Value(-23), b: RegisterOrValue::Value(-23),
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(machine.registers[0].read(), 1); assert_eq!(machine.registers[0].read(), 1);
} }

View file

@ -1,11 +0,0 @@
use crate::machine::Machine;
use std::fmt::{Debug, Display};
pub trait Instruction: Debug + Display {
// TODO: this needs to be able to return errors, e.g. for division by zero
fn execute(&self, machine: &mut Machine);
}
pub trait InstructionName {
const INSTRUCTION_NAME: &'static str;
}

View file

@ -5,9 +5,11 @@ pub(crate) trait TryLoadInstruction {
fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError>; fn try_load(params: &[RegisterOrValue]) -> Result<Rc<Self>, ParametersError>;
} }
#[derive(Debug)] #[derive(Debug, thiserror::Error)]
pub enum ParametersError { pub enum ParametersError {
#[error("the instruction expects {expected} parameters, but {found} were provided")]
UnexpectedCount { expected: usize, found: usize }, UnexpectedCount { expected: usize, found: usize },
#[error("unexpected type of parameter at index {index}")]
UnexpectedType { index: usize }, UnexpectedType { index: usize },
} }

View file

@ -1,5 +1,7 @@
use crate::bool_to_isize;
use crate::instructions::{ use crate::instructions::{
check_param_count, Instruction, InstructionName, ParametersError, TryLoadInstruction, check_param_count, Instruction, InstructionName, InstructionResult, ParametersError,
TryLoadInstruction,
}; };
use crate::machine::{Machine, RegisterOrValue}; use crate::machine::{Machine, RegisterOrValue};
use std::fmt::{Debug, Display}; use std::fmt::{Debug, Display};
@ -11,9 +13,10 @@ pub struct TestZero {
} }
impl Instruction for TestZero { impl Instruction for TestZero {
fn execute(&self, machine: &mut Machine) { fn execute(&self, machine: &mut Machine) -> InstructionResult {
let result = self.a.read(machine) == 0; let result = self.a.read(machine) == 0;
machine.registers[0].write(if result { 1 } else { 0 }); machine.registers[0].write(bool_to_isize(result));
Ok(())
} }
} }
@ -49,14 +52,15 @@ mod tests {
.unwrap(); .unwrap();
assert_eq!(0, machine.registers[0].read()); assert_eq!(0, machine.registers[0].read());
Instruction::execute(&TestZero { a: Value(42) }, &mut machine); Instruction::execute(&TestZero { a: Value(42) }, &mut machine).unwrap();
assert_eq!(0, machine.registers[0].read()); assert_eq!(0, machine.registers[0].read());
Instruction::execute( Instruction::execute(
&TestZero { &TestZero {
a: Register(RegisterIndex::R0), a: Register(RegisterIndex::R0),
}, },
&mut machine, &mut machine,
); )
.unwrap();
assert_eq!(1, machine.registers[0].read()); assert_eq!(1, machine.registers[0].read());
} }
} }

View file

@ -5,3 +5,15 @@ pub mod parser;
pub(crate) mod sealed { pub(crate) mod sealed {
pub trait Sealed {} pub trait Sealed {}
} }
pub fn isize_to_bool(value: isize) -> bool {
value != 0isize
}
pub fn bool_to_isize(value: bool) -> isize {
if value {
1isize
} else {
0isize
}
}

View file

@ -1,4 +1,4 @@
use crate::instructions::Instructions; use crate::instructions::{ExecuteInstructionError, Instructions};
use crate::machine::Registers; use crate::machine::Registers;
use crate::sealed::Sealed; use crate::sealed::Sealed;
use std::fmt::Display; use std::fmt::Display;
@ -11,15 +11,15 @@ pub struct Machine {
} }
impl Machine { impl Machine {
pub fn step(&mut self) -> bool { pub fn step(&mut self) -> Result<(), ExecuteInstructionError> {
if self.ip >= self.instructions.len() { if self.ip >= self.instructions.len() {
return false; return Err(ExecuteInstructionError::InvalidIp);
} }
let instruction = &self.instructions[self.ip].clone(); let instruction = &self.instructions[self.ip].clone();
instruction.execute(self); instruction.execute(self)?;
self.ip += 1; self.ip += 1;
true Ok(())
} }
} }
@ -115,7 +115,7 @@ pub enum MachineBuilderError {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::instructions::{Addition, Instructions}; use crate::instructions::{Addition, ExecuteInstructionError, Instructions};
use crate::instructions::{Instruction, Put}; use crate::instructions::{Instruction, Put};
use crate::machine::register::{RegisterIndex, RegisterOrValue}; use crate::machine::register::{RegisterIndex, RegisterOrValue};
use crate::machine::MachineBuilder; use crate::machine::MachineBuilder;
@ -145,15 +145,21 @@ mod tests {
assert_eq!(machine.ip, 0); assert_eq!(machine.ip, 0);
assert_eq!(reg_a.read(&machine), 0); assert_eq!(reg_a.read(&machine), 0);
assert!(machine.step()); assert!(matches!(machine.step(), Ok(())));
assert_eq!(machine.ip, 1); assert_eq!(machine.ip, 1);
assert_eq!(reg_a.read(&machine), 42); assert_eq!(reg_a.read(&machine), 42);
assert!(machine.step()); assert!(matches!(machine.step(), Ok(())));
assert_eq!(machine.ip, 2); assert_eq!(machine.ip, 2);
assert_eq!(reg_a.read(&machine), 65); assert_eq!(reg_a.read(&machine), 65);
assert!(!machine.step()); assert!(matches!(
assert!(!machine.step()); machine.step(),
Err(ExecuteInstructionError::InvalidIp)
));
assert!(matches!(
machine.step(),
Err(ExecuteInstructionError::InvalidIp)
));
} }
} }

View file

@ -4,10 +4,10 @@ use crate::instructions::{
TryLoadInstruction, TryLoadInstruction,
}; };
use crate::parser; use crate::parser;
use crate::parser::ei_ast::{Line}; use crate::parser::ei_ast::Line;
use crate::parser::ParseError;
use std::io::Write; use std::io::Write;
use std::rc::Rc; use std::rc::Rc;
use crate::parser::ParseError;
pub struct EiParser { pub struct EiParser {
file_name: String, file_name: String,
@ -84,10 +84,7 @@ impl EiParser {
illegal => return Err(TryLoadErrorReason::IllegalInstruction(illegal.to_string())), illegal => return Err(TryLoadErrorReason::IllegalInstruction(illegal.to_string())),
}) })
} }
}
#[cfg(feature = "error-report")]
impl EiParser {
pub fn write_error_report<W: Write>( pub fn write_error_report<W: Write>(
&self, &self,
ei_parser_error: EiParserError, ei_parser_error: EiParserError,
@ -115,7 +112,7 @@ impl EiParser {
use ariadne::{Color, Label, Report, ReportKind, Source}; use ariadne::{Color, Label, Report, ReportKind, Source};
let report = Report::build(ReportKind::Error, (&self.file_name, e.line.span.clone())) let report = Report::build(ReportKind::Error, (&self.file_name, e.line.span.clone()))
.with_message("Loading of instructions failed") .with_message(format!("{e}"))
.with_config(Self::get_ariadne_config()); .with_config(Self::get_ariadne_config());
let report = match e.reason { let report = match e.reason {
@ -142,14 +139,12 @@ impl EiParser {
.with_color(Color::Red) .with_color(Color::Red)
.with_message("not enough parameters") .with_message("not enough parameters")
}; };
report.with_label(label).with_note(format!( report.with_label(label)
"the instruction expects {expected} parameters, but {found} were provided."
))
} }
ParametersError::UnexpectedType { index } => { ParametersError::UnexpectedType { index } => {
let label = Label::new((&self.file_name, e.line.parameters[index].1.clone())) let label = Label::new((&self.file_name, e.line.parameters[index].1.clone()))
.with_color(Color::Red) .with_color(Color::Red)
.with_message("unexpected type of parameter"); .with_message(format!("{param_err}"));
report.with_label(label).with_help( report.with_label(label).with_help(
"check the parameter order and which ones can values and/or registers", "check the parameter order and which ones can values and/or registers",
) )
@ -162,7 +157,6 @@ impl EiParser {
.write((&self.file_name, Source::from(&self.file_content)), w) .write((&self.file_name, Source::from(&self.file_content)), w)
} }
#[cfg(feature = "error-report")]
pub fn write_parse_error_report<W: std::io::Write>( pub fn write_parse_error_report<W: std::io::Write>(
&self, &self,
e: ParseError, e: ParseError,
@ -246,21 +240,19 @@ pub enum EiParserError {
InstructionLoadError(TryLoadError), InstructionLoadError(TryLoadError),
} }
#[derive(Debug)] #[derive(Debug, thiserror::Error)]
#[error("The loading of the instruction at index {index} failed: {reason}")]
pub struct TryLoadError { pub struct TryLoadError {
pub index: usize, pub index: usize,
pub line: Line, pub line: Line,
#[source]
pub reason: TryLoadErrorReason, pub reason: TryLoadErrorReason,
} }
#[derive(Debug)] #[derive(Debug, thiserror::Error)]
pub enum TryLoadErrorReason { pub enum TryLoadErrorReason {
#[error("Illegal instruction \"{0}\"")]
IllegalInstruction(String), IllegalInstruction(String),
InvalidParameters(ParametersError), #[error(transparent)]
} InvalidParameters(#[from] ParametersError),
impl From<ParametersError> for TryLoadErrorReason {
fn from(reason: ParametersError) -> Self {
TryLoadErrorReason::InvalidParameters(reason)
}
} }

View file

@ -14,7 +14,7 @@ struct Cli {
fn main() { fn main() {
let args: Cli = clap::Parser::parse(); let args: Cli = clap::Parser::parse();
let parser = EiParser::from_file(&args.ei_file).unwrap(); let parser = EiParser::from_file(&args.ei_file).expect("failed to parse input file");
let instructions = match parser.parse() { let instructions = match parser.parse() {
Ok(i) => i, Ok(i) => i,
Err(e) => { Err(e) => {
@ -33,11 +33,19 @@ fn main() {
println!("done building machine: \n{machine}"); println!("done building machine: \n{machine}");
while machine.step() { loop {
if args.verbose { match machine.step() {
println!("executed instruction: {machine:?}"); Ok(_) => {
if args.verbose {
println!("executed instruction: {machine:?}");
}
}
Err(e) => {
eprintln!("{e}");
break;
}
} }
} }
println!("final state: \n{machine}"); println!("final state: \n{}", machine.registers);
} }

View file

@ -1,3 +1,4 @@
use echse::instructions::ExecuteInstructionError;
use echse::machine::{Machine, MachineBuilder}; use echse::machine::{Machine, MachineBuilder};
use echse::parser::EiParser; use echse::parser::EiParser;
use std::collections::HashSet; use std::collections::HashSet;
@ -129,7 +130,10 @@ impl App {
"{:03}: {}\n", "{:03}: {}\n",
self.machine.ip, self.machine.instructions[self.machine.ip] self.machine.ip, self.machine.instructions[self.machine.ip]
); );
self.machine.step(); match self.machine.step() {
Ok(_) => {}
Err(e) => Self::print_exec_err(e),
}
} else { } else {
println!("reached end of program\n"); println!("reached end of program\n");
} }
@ -153,7 +157,12 @@ impl App {
} }
DebugCommand::Continue => { DebugCommand::Continue => {
let mut count = 0; let mut count = 0;
while self.machine.step() { loop {
match self.machine.step() {
Ok(_) => {}
Err(e) => eprintln!("{e}"),
}
count += 1; count += 1;
if self.breakpoints.contains(&self.machine.ip) { if self.breakpoints.contains(&self.machine.ip) {
println!( println!(
@ -163,6 +172,7 @@ impl App {
break; break;
} }
} }
println!( println!(
"stepped {count} instructions, ip now at {}\n", "stepped {count} instructions, ip now at {}\n",
self.machine.ip self.machine.ip
@ -188,7 +198,7 @@ impl App {
fn main() { fn main() {
let args: Cli = clap::Parser::parse(); let args: Cli = clap::Parser::parse();
let parser = EiParser::from_file(&args.ei_file).unwrap(); let parser = EiParser::from_file(&args.ei_file).expect("failed to parse input file");
let instructions = match parser.parse() { let instructions = match parser.parse() {
Ok(i) => i, Ok(i) => i,
Err(e) => { Err(e) => {