diff --git a/mvn-assembler/Cargo.toml b/mvn-assembler/Cargo.toml index 1517dfc..7a42f5f 100644 --- a/mvn-assembler/Cargo.toml +++ b/mvn-assembler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mvn-assembler" -version = "0.1.1" +version = "0.1.2" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/mvn-assembler/src/processor/validator.rs b/mvn-assembler/src/processor/validator.rs index b33cdb6..adbf86a 100644 --- a/mvn-assembler/src/processor/validator.rs +++ b/mvn-assembler/src/processor/validator.rs @@ -1,6 +1,8 @@ +use std::collections::BTreeSet; + use crate::types::{mneumonic, Instruction, Line, Operand}; -use crate::processor::address::{Address, AddressedProgram, LabelMap}; +use crate::processor::address::{Address, AddressedLine, AddressedProgram, LabelMap}; use super::MvnReportError; @@ -10,6 +12,8 @@ pub fn validate<'a, 'b>( program: &'a AddressedProgram<'b>, label_map: &'a LabelMap<'b>, ) -> ValidatorResult<'b> { + let validator = ProgramValidator::new(program, label_map); + validator.validate()?; for line in program.lines.iter() { let validator = LineValidator::new(&line.line, &line.address, label_map); validator.validate()?; @@ -17,12 +21,52 @@ pub fn validate<'a, 'b>( Ok(()) } +// `label_map` is not required right now but it might be in future tests, +// so keep it in the struct despite being dead code +#[allow(dead_code)] +struct ProgramValidator<'a, 'b> { + program: &'a AddressedProgram<'b>, + label_map: &'a LabelMap<'b>, +} + struct LineValidator<'a, 'b> { line: &'a Line<'b>, address: &'a Address, label_map: &'a LabelMap<'b>, } +/* Every validator function's name should + * answer the question: "Does the program + * contain {name}?" + */ + +impl<'b> ProgramValidator<'_, 'b> { + pub fn validate(self) -> ValidatorResult<'b> { + self.labels_defined_more_than_once()?; + Ok(()) + } + + fn labels_defined_more_than_once(&self) -> ValidatorResult<'b> { + let mut label_set = BTreeSet::new(); + for AddressedLine { address: _, line } in self + .program + .lines + .iter() + .filter(|addressed_line| addressed_line.line.label.is_some()) + { + if let Some(label) = &line.label { + if !label_set.insert(label.value.clone()) { + return Err(MvnReportError::new( + label.position, + Some("label was already defined".to_string()), + )); + } + } + } + Ok(()) + } +} + impl<'b> LineValidator<'_, 'b> { pub fn validate(self) -> ValidatorResult<'b> { self.numeric_operand_on_import_export()?; @@ -33,11 +77,6 @@ impl<'b> LineValidator<'_, 'b> { Ok(()) } - /* Every validator function's name should - * answer the question: "Does the program - * contain {name}?" - */ - fn numeric_operand_on_import_export(&self) -> ValidatorResult<'b> { match &self.line.operation.instruction.value { Instruction::Relational(_) => match &self.line.operation.operand.value { @@ -133,6 +172,12 @@ impl<'b> LineValidator<'_, 'b> { } } +impl<'a, 'b> ProgramValidator<'a, 'b> { + fn new(program: &'a AddressedProgram<'b>, label_map: &'a LabelMap<'b>) -> Self { + Self { program, label_map } + } +} + impl<'a, 'b> LineValidator<'a, 'b> { fn new(line: &'a Line<'b>, address: &'a Address, label_map: &'a LabelMap<'b>) -> Self { Self { @@ -169,6 +214,7 @@ mod tests { */ struct TestProgram { + repeated_label: bool, import: Operand<'static>, constant: u32, position: Operand<'static>, @@ -179,6 +225,7 @@ mod tests { impl Default for TestProgram { fn default() -> Self { Self { + repeated_label: false, import: "IMPORT".into(), constant: 0x1, position: 0x100.into(), @@ -195,6 +242,11 @@ mod tests { } fn render(self) -> (AddressedProgram<'static>, LabelMap<'static>) { + let main_label: Label = if self.repeated_label { + "ONE".into() + } else { + "MAIN".into() + }; let main_position = if let Operand::Numeric(immediate) = self.position { immediate } else { @@ -256,7 +308,7 @@ mod tests { ..Default::default() }, Line::new( - Some(Token::new(Position::new(8, 1), "MAIN".into())), + Some(Token::new(Position::new(8, 1), main_label)), Operation::new( Token::new( Position::new(8, 9), @@ -318,10 +370,20 @@ mod tests { assert!(test_program.validate().is_ok()); } + #[test] + fn repeated_labels_should_fail() { + let test_program = TestProgram { + repeated_label: true, + ..Default::default() + }; + assert!(test_program.validate().is_err()); + } + // #[test] // fn symbolic_operand_on_import_export_should_pass() { // } + #[test] fn numeric_operand_on_import_export_should_fail() { let test_program = TestProgram { diff --git a/mvn-utils/src/types.rs b/mvn-utils/src/types.rs index 4b208e6..adb35de 100644 --- a/mvn-utils/src/types.rs +++ b/mvn-utils/src/types.rs @@ -64,3 +64,12 @@ impl fmt::UpperHex for Token { write!(f, "{:X}", &self.value) } } + +impl Clone for Token { + fn clone(&self) -> Self { + Self { + value: self.value.clone(), + position: self.position, + } + } +}