diff --git a/README.md b/README.md index bf7281b..d9006ee 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,13 @@ state: ```bash # Run a program -cargo run --package tur-cli -- -p examples/binary-addition.tur +cargo run -p tur-cli -- examples/binary-addition.tur + +# Pipe input to a program +echo '$011-' | cargo run -p tur-cli -- examples/binary-addition.tur + +# Chaining programs with pipes +echo '$011' | cargo run -p tur-cli -- examples/binary-addition.tur | cargo run -p tur-cli -- examples/binary-addition.tur ``` ### Terminal User Interface (TUI) diff --git a/examples/README.md b/examples/README.md index d0e4ac5..e66dd29 100644 --- a/examples/README.md +++ b/examples/README.md @@ -28,7 +28,7 @@ The `.tur` file format uses a structured syntax parsed by a Pest grammar with co - **Name**: Specified with `name:` followed by the program name - **Tape Configuration**: - **Single-tape**: `tape: symbol1, symbol2, symbol3` - - **Multi-tape**: + - **Multi-tape**: ```tur tapes: [a, b, c] @@ -47,7 +47,7 @@ The `.tur` file format uses a structured syntax parsed by a Pest grammar with co - **Write-only transitions**: If `-> new_symbol` is omitted, the read symbol is preserved - The first state defined is automatically the initial state - **Comments**: Use `#` for line comments and inline comments -- **Special Symbols**: +- **Special Symbols**: - `_` represents the blank symbol in program definitions - Any Unicode character can be used as tape symbols - The blank symbol can be customized with the `blank:` directive @@ -56,7 +56,6 @@ The `.tur` file format uses a structured syntax parsed by a Pest grammar with co ### Single-Tape Programs - **binary-addition.tur**: Adds two binary numbers -- **binary-counter.tur**: Increments a binary number - **busy-beaver-3.tur**: Classic 3-state busy beaver - **event-number-checker.tur**: Checks if a number is even - **palindrome.tur**: Checks if input is a palindrome diff --git a/examples/binary-addition.tur b/examples/binary-addition.tur index 47147ae..806f5d7 100644 --- a/examples/binary-addition.tur +++ b/examples/binary-addition.tur @@ -1,13 +1,16 @@ name: Binary addition -tape: $, 0, 0, 1, 1, 1, - +tape: $, 0, 0, 1, 1, 1 rules: start: $ -> $, R, s1 s1: 0 -> 0, R, s1 1 -> 1, R, s1 - - -> -, L, s2 + _ -> _, L, s2 s2: 1 -> 0, L, s2 0 -> 1, L, stop + $ -> 1, L, s3 + s3: + _ -> $, R, stop stop: diff --git a/examples/binary-counter.tur b/examples/binary-counter.tur deleted file mode 100644 index a46a367..0000000 --- a/examples/binary-counter.tur +++ /dev/null @@ -1,12 +0,0 @@ -name: Binary Counter -tape: 1, 0, 1, 1 -rules: - start: - 0 -> 0, R, start - 1 -> 1, R, start - _ -> _, L, inc # Reached end, go back to start incrementing - inc: - 0 -> 1, S, done # 0 becomes 1, no carry needed - 1 -> 0, L, inc # 1 becomes 0, carry to next position - _ -> 1, S, done # Reached beginning with carry, add new 1 - done: diff --git a/platforms/cli/src/main.rs b/platforms/cli/src/main.rs index 3c4fad1..cf02d04 100644 --- a/platforms/cli/src/main.rs +++ b/platforms/cli/src/main.rs @@ -9,7 +9,6 @@ use tur::ExecutionResult; #[clap(author, version, about, long_about = None, arg_required_else_help = true)] struct Cli { /// The Turing machine program file to execute - #[clap(short, long)] program: String, /// The input to the Turing machine @@ -111,7 +110,7 @@ fn read_tape_inputs(inputs: &[String]) -> Result, String> { for line in stdin.lock().lines() { match line { - Ok(content) => tape_inputs.push(content), + Ok(content) => tape_inputs.push(content.trim().to_string()), Err(e) => return Err(format!("Error reading from stdin: {}", e)), } } diff --git a/platforms/web/styles.css b/platforms/web/styles.css index 4f01cd8..df54e5c 100644 --- a/platforms/web/styles.css +++ b/platforms/web/styles.css @@ -470,6 +470,10 @@ body { border-color: var(--danger-color); } +.program-status.error pre { + white-space: pre-wrap; +} + .program-status svg { height: 1.25rem; width: 1.25rem; diff --git a/src/analyzer.rs b/src/analyzer.rs index 829d01b..1d1608d 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -21,6 +21,8 @@ pub enum AnalysisError { UnreachableStates(Vec), /// Indicates that the initial tape contains symbols for which no transitions are defined. InvalidTapeSymbols(Vec), + /// Indicates structural problems with the program (empty tapes, mismatched head positions, etc.). + StructuralError(String), } impl From for TuringMachineError { @@ -48,13 +50,16 @@ impl From for TuringMachineError { symbols )) } + AnalysisError::StructuralError(msg) => TuringMachineError::ValidationError(msg), } } } -/// Analyzes a given Turing Machine `Program` for common errors and inconsistencies. +/// Analyzes a given Turing Machine `Program` for structural and logical errors. /// -/// This function orchestrates a series of checks, collecting all detected `AnalysisError`s. +/// This function orchestrates a comprehensive series of checks, performing both +/// structural validation (basic consistency) and logical analysis (reachability, +/// symbol handling, etc.). /// /// # Arguments /// @@ -63,13 +68,12 @@ impl From for TuringMachineError { /// # Returns /// /// * `Ok(())` if no errors are found. -/// * `Err(Vec)` if one or more errors are detected, containing a list of all errors. -pub fn analyze(program: &Program) -> Result<(), Vec> { +/// * `Err(TuringMachineError::ValidationError)` if any validation rule is violated. +pub fn analyze(program: &Program) -> Result<(), TuringMachineError> { let errors = [ + check_structure, check_head, check_valid_start_state, - check_valid_stop_states, - check_undefined_next_states, check_unreachable_states, check_tape_symbols, ] @@ -78,10 +82,63 @@ pub fn analyze(program: &Program) -> Result<(), Vec> { .collect::>(); if !errors.is_empty() { - Err(errors) - } else { - Ok(()) + // Return the first error + if let Some(first_error) = errors.first() { + return Err((*first_error).clone().into()); + } } + + Ok(()) +} + +/// Checks basic structural requirements of the program. +/// +/// This validates fundamental structural consistency like: +/// - Tapes are defined (non-empty) +/// - Head positions match number of tapes +/// - Transitions have consistent tape counts +/// +/// # Arguments +/// +/// * `program` - A reference to the `Program` to check. +/// +/// # Returns +/// +/// * `Ok(())` if the structure is valid. +/// * `Err(AnalysisError::StructuralError)` if structural issues are found. +fn check_structure(program: &Program) -> Result<(), AnalysisError> { + // Check for empty tapes + if program.tapes.is_empty() { + return Err(AnalysisError::StructuralError( + "No tapes defined".to_string(), + )); + } + + // Check that head positions match number of tapes + if program.heads.len() != program.tapes.len() { + return Err(AnalysisError::StructuralError(format!( + "Number of head positions ({}) does not match number of tapes ({})", + program.heads.len(), + program.tapes.len() + ))); + } + + // Check that all transitions have consistent tape counts + for (state, transitions) in &program.rules { + for transition in transitions { + if transition.read.len() != program.tapes.len() + || transition.write.len() != program.tapes.len() + || transition.directions.len() != program.tapes.len() + { + return Err(AnalysisError::StructuralError(format!( + "Transition in state '{}' has inconsistent tape counts", + state + ))); + } + } + } + + Ok(()) } /// Checks if the initial head position(s) are valid for the program's tape(s). @@ -99,25 +156,15 @@ pub fn analyze(program: &Program) -> Result<(), Vec> { /// * `Ok(())` if the head position(s) are valid. /// * `Err(AnalysisError::InvalidHead)` if an invalid head position is found. fn check_head(program: &Program) -> Result<(), AnalysisError> { - // For single-tape programs, check the head position - if program.is_single_tape() { - let head_position = program.head_position(); - let tape_length = program.initial_tape().len(); - - if head_position >= tape_length && tape_length > 0 { - Err(AnalysisError::InvalidHead(head_position)) - } else { - Ok(()) - } - } else { - // For multi-tape programs, check all head positions - for (&head_pos, tape) in program.heads.iter().zip(&program.tapes) { - if head_pos >= tape.len() && !tape.is_empty() { - return Err(AnalysisError::InvalidHead(head_pos)); - } - } - Ok(()) - } + program + .heads + .iter() + .zip(&program.tapes) + .find_map(|(&head_pos, tape)| { + (head_pos >= tape.len() && !tape.is_empty()) + .then_some(AnalysisError::InvalidHead(head_pos)) + }) + .map_or(Ok(()), Err) } /// Checks whether the initial state is defined as a source state in any of the transition rules. @@ -154,6 +201,7 @@ fn check_valid_start_state(program: &Program) -> Result<(), AnalysisError> { /// /// * `Ok(())` if all stop states are properly referenced or if there are no unreferenced stop states. /// * `Err(AnalysisError::StopStatesNotFound)` if stop states are found that are not referenced. +#[allow(dead_code)] fn check_valid_stop_states(program: &Program) -> Result<(), AnalysisError> { // Collect all states that have no outgoing transitions (potential stop states) let stop_states: HashSet = program @@ -196,6 +244,7 @@ fn check_valid_stop_states(program: &Program) -> Result<(), AnalysisError> { /// /// * `Ok(())` if all next states are defined or are the "halt" state. /// * `Err(AnalysisError::UndefinedNextStates)` if transitions reference undefined states. +#[allow(dead_code)] fn check_undefined_next_states(program: &Program) -> Result<(), AnalysisError> { let defined_states: HashSet = program.rules.keys().cloned().collect(); @@ -628,25 +677,21 @@ mod tests { let result = analyze(&program); assert!(result.is_err()); - let errors = result.unwrap_err(); - - // Should have at least 3 errors: undefined next state, unreachable state, and invalid tape symbol - assert!(errors.len() >= 3); - - // Check for specific error types - let has_undefined = errors - .iter() - .any(|e| matches!(e, AnalysisError::UndefinedNextStates(_))); - let has_unreachable = errors - .iter() - .any(|e| matches!(e, AnalysisError::UnreachableStates(_))); - let has_invalid_tape = errors - .iter() - .any(|e| matches!(e, AnalysisError::InvalidTapeSymbols(_))); - assert!(has_undefined, "Missing UndefinedNextStates error"); - assert!(has_unreachable, "Missing UnreachableStates error"); - assert!(has_invalid_tape, "Missing InvalidTapeSymbols error"); + // Since analyze() now returns the first error found, we just check that an error occurred + if let Err(TuringMachineError::ValidationError(msg)) = result { + // Should contain one of the expected error types + let has_error = msg.contains("No tapes defined") + || msg.contains("Number of head positions") + || msg.contains("Invalid start state") + || msg.contains("Stop states not found") + || msg.contains("undefined state") + || msg.contains("Unreachable states") + || msg.contains("not handled by any transition"); + assert!(has_error, "Expected a validation error, got: {}", msg); + } else { + panic!("Expected ValidationError"); + } } #[test] @@ -682,4 +727,75 @@ mod tests { assert!(result.is_ok()); } + + #[test] + fn test_analyze_success() { + let mut rules = HashMap::new(); + rules.insert( + "start".to_string(), + vec![create_single_tape_transition( + 'a', + 'b', + Direction::Right, + "halt", + )], + ); + + let program = create_test_program("start", "a", rules); + let result = analyze(&program); + assert!(result.is_ok()); + } + + #[test] + fn test_analyze_structural_error() { + let mut rules = HashMap::new(); + // Initial state "start" is not defined in rules + rules.insert("other".to_string(), Vec::new()); + + let program = create_test_program("start", "a", rules); + let result = analyze(&program); + + assert!(result.is_err()); + if let Err(TuringMachineError::ValidationError(msg)) = result { + assert!(msg.contains("Invalid start state: start")); + } else { + panic!("Expected ValidationError"); + } + } + + #[test] + fn test_analyze_empty_tapes() { + let mut rules = HashMap::new(); + rules.insert("start".to_string(), Vec::new()); + + let mut program = create_test_program("start", "a", rules); + program.tapes.clear(); // Remove all tapes + + let result = analyze(&program); + + assert!(result.is_err()); + if let Err(TuringMachineError::ValidationError(msg)) = result { + assert!(msg.contains("No tapes defined")); + } else { + panic!("Expected ValidationError"); + } + } + + #[test] + fn test_analyze_inconsistent_head_positions() { + let mut rules = HashMap::new(); + rules.insert("start".to_string(), Vec::new()); + + let mut program = create_test_program("start", "a", rules); + program.heads.push(1); // Add extra head position + + let result = analyze(&program); + + assert!(result.is_err()); + if let Err(TuringMachineError::ValidationError(msg)) = result { + assert!(msg.contains("Number of head positions")); + } else { + panic!("Expected ValidationError"); + } + } } diff --git a/src/loader.rs b/src/loader.rs index 512eebe..39742fd 100644 --- a/src/loader.rs +++ b/src/loader.rs @@ -126,7 +126,7 @@ mod tests { let file_path = dir.path().join("test.tur"); let program_content = - "name: Test Program\ntape: a, b, c\nrules:\n start:\n a -> b, R, stop\n stop:"; + "name: Test Program\ntape: a\nrules:\n start:\n a -> b, R, stop\n stop:"; let mut file = File::create(&file_path).unwrap(); file.write_all(program_content.as_bytes()).unwrap(); @@ -136,7 +136,7 @@ mod tests { let program = result.unwrap(); assert_eq!(program.name, "Test Program"); - assert_eq!(program.initial_tape(), "abc"); + assert_eq!(program.initial_tape(), "a"); assert!(program.rules.contains_key("start")); assert!(program.rules.contains_key("stop")); } @@ -162,7 +162,7 @@ mod tests { // Create a valid program file let valid_path = dir.path().join("valid.tur"); let valid_content = - "name: Valid Program\ntape: a, b, c\nrules:\n start:\n a -> b, R, stop\n stop:"; + "name: Valid Program\ntape: a\nrules:\n start:\n a -> b, R, stop\n stop:"; let mut valid_file = File::create(&valid_path).unwrap(); valid_file.write_all(valid_content.as_bytes()).unwrap(); diff --git a/src/machine.rs b/src/machine.rs index 446cf10..a84c6e3 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -269,87 +269,6 @@ impl TuringMachine { .is_none_or(|transitions| transitions.is_empty()) } - /// Validates a `Program` before it is used to create a `TuringMachine`. - /// - /// This performs various checks, including: - /// - Ensuring the initial state is defined. - /// - Checking for empty tapes. - /// - Verifying that head positions match the number of tapes. - /// - Confirming that all referenced states in transitions exist. - /// - Ensuring consistency in tape counts for multi-tape transitions. - /// - For single-tape programs, it also leverages the `analyzer` module for more in-depth checks. - /// - /// # Arguments - /// - /// * `program` - A reference to the `Program` to validate. - /// - /// # Returns - /// - /// * `Ok(())` if the program is valid. - /// * `Err(TuringMachineError::ValidationError)` if any validation rule is violated. - pub fn validate_program(program: &Program) -> Result<(), TuringMachineError> { - // Check if initial state exists in rules - if !program.rules.contains_key(&program.initial_state) { - return Err(TuringMachineError::ValidationError(format!( - "Initial state '{}' not defined in transitions", - program.initial_state - ))); - } - - // Check for empty tapes - if program.tapes.is_empty() { - return Err(TuringMachineError::ValidationError( - "No tapes defined".to_string(), - )); - } - - // Check that head positions match number of tapes - if program.heads.len() != program.tapes.len() { - return Err(TuringMachineError::ValidationError(format!( - "Number of head positions ({}) does not match number of tapes ({})", - program.heads.len(), - program.tapes.len() - ))); - } - - // Validate that all referenced states exist - for (state, transitions) in &program.rules { - for transition in transitions { - if !program.rules.contains_key(&transition.next_state) - && transition.next_state != "halt" - { - return Err(TuringMachineError::ValidationError(format!( - "State '{}' references undefined state '{}'", - state, transition.next_state - ))); - } - - // Check that all transitions have consistent tape counts - if transition.read.len() != program.tapes.len() - || transition.write.len() != program.tapes.len() - || transition.directions.len() != program.tapes.len() - { - return Err(TuringMachineError::ValidationError(format!( - "Transition in state '{}' has inconsistent tape counts", - state - ))); - } - } - } - - // For single-tape programs, use the analyzer module - if program.is_single_tape() { - if let Err(errors) = crate::analyzer::analyze(program) { - // Return the first error for backward compatibility - if let Some(first_error) = errors.first() { - return Err((*first_error).clone().into()); - } - } - } - - Ok(()) - } - /// Returns a slice of the machine's tapes. pub fn tapes(&self) -> &[Vec] { &self.tapes @@ -648,106 +567,6 @@ mod multi_tape_tests { ); } - #[test] - fn test_multi_tape_validate_program_success() { - let program = create_simple_multi_tape_program(); - assert!(TuringMachine::validate_program(&program).is_ok()); - } - - #[test] - fn test_multi_tape_validate_program_initial_state_not_defined() { - let mut rules = HashMap::new(); - rules.insert("other".to_string(), Vec::new()); - - let program = Program { - name: "Invalid".to_string(), - initial_state: "nonexistent".to_string(), - tapes: vec!["a".to_string(), "x".to_string()], - heads: vec![0, 0], - blank: '-', - rules, - }; - - let result = TuringMachine::validate_program(&program); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("Initial state 'nonexistent' not defined in transitions")); - } - - #[test] - fn test_multi_tape_validate_program_empty_tapes() { - let mut rules = HashMap::new(); - rules.insert("start".to_string(), Vec::new()); - - let program = Program { - name: "Invalid".to_string(), - initial_state: "start".to_string(), - tapes: vec![], - heads: vec![], - blank: '-', - rules, - }; - - let result = TuringMachine::validate_program(&program); - assert!(result.is_err()); - assert!(result.unwrap_err().to_string().contains("No tapes defined")); - } - - #[test] - fn test_multi_tape_validate_program_inconsistent_head_positions() { - let mut rules = HashMap::new(); - rules.insert("start".to_string(), Vec::new()); - - let program = Program { - name: "Invalid".to_string(), - initial_state: "start".to_string(), - tapes: vec!["a".to_string(), "x".to_string()], - heads: vec![0], // Only one head position for two tapes - blank: '-', - rules, - }; - - let result = TuringMachine::validate_program(&program); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("Number of head positions")); - } - - #[test] - fn test_multi_tape_validate_program_undefined_state() { - let mut rules = HashMap::new(); - rules.insert( - "start".to_string(), - vec![Transition { - read: vec!['a', 'x'], - write: vec!['b'], // Only one write symbol for two tapes - directions: vec![Direction::Right, Direction::Right], - next_state: "halt".to_string(), - }], - ); - rules.insert("halt".to_string(), Vec::new()); - - let program = Program { - name: "Invalid".to_string(), - initial_state: "start".to_string(), - tapes: vec!["a".to_string(), "x".to_string()], - heads: vec![0, 0], - blank: '-', - rules, - }; - - let result = TuringMachine::validate_program(&program); - assert!(result.is_err()); - assert!(result - .unwrap_err() - .to_string() - .contains("inconsistent tape counts")); - } - #[test] fn test_multi_tape_stay_direction() { let mut rules = HashMap::new(); @@ -814,7 +633,7 @@ mod multi_tape_tests { r#" name: Custom Blank Write Test blank: {custom_blank} -tape: a, _, b +tape: a, _ rules: start: a -> a, R, halt diff --git a/src/parser.rs b/src/parser.rs index d808329..e559d90 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,8 +1,12 @@ //! This module provides the parser for Turing Machine programs, utilizing the `pest` crate. //! It defines the grammar for `.tur` files and functions to parse the input into a `Program` struct. -use crate::types::{ - Direction, Program, Transition, TuringMachineError, DEFAULT_BLANK_SYMBOL, INPUT_BLANK_SYMBOL, +use crate::{ + analyzer::analyze, + types::{ + Direction, Program, Transition, TuringMachineError, DEFAULT_BLANK_SYMBOL, + INPUT_BLANK_SYMBOL, + }, }; use pest::{ error::{Error, ErrorVariant}, @@ -23,7 +27,8 @@ pub struct TuringMachineParser; /// /// This is the main entry point for parsing Turing Machine program definitions. /// It trims the input, parses it using the `TuringMachineParser`, and then processes -/// the resulting parse tree into a structured `Program`. +/// the resulting parse tree into a structured `Program`. The parsed program is +/// automatically validated before being returned. /// /// # Arguments /// @@ -31,15 +36,21 @@ pub struct TuringMachineParser; /// /// # Returns /// -/// * `Ok(Program)` if the input is successfully parsed into a `Program`. -/// * `Err(TuringMachineError::ParseError)` if there are any syntax errors or structural issues. +/// * `Ok(Program)` if the input is successfully parsed and validated. +/// * `Err(TuringMachineError::ParseError)` if there are any syntax errors. +/// * `Err(TuringMachineError::ValidationError)` if the program fails validation. pub fn parse(input: &str) -> Result { let root = TuringMachineParser::parse(Rule::program, input.trim()) .map_err(|e| TuringMachineError::ParseError(e.into()))? // .next() .unwrap(); - parse_program(root) + let program = parse_program(root)?; + + // Analyze the parsed program + analyze(&program)?; + + Ok(program) } /// Parses the top-level structure of a Turing Machine program from a `Pair`. @@ -493,8 +504,8 @@ rules: name: Simple Multi-Tape heads: [0, 0] tapes: - [a, b, c] - [d, e, f] + [a] + [d] rules: start: [a, d] -> [b, e], [R, R], halt @@ -506,7 +517,7 @@ rules: let program = result.unwrap(); assert_eq!(program.name, "Simple Multi-Tape"); - assert_eq!(program.tapes, vec!["abc", "def"]); + assert_eq!(program.tapes, vec!["a", "d"]); assert_eq!( program.rules["start"][0], Transition { @@ -783,7 +794,7 @@ rules: fn test_parse_tape_with_blank_symbol() { let input = r#" name: Blank Tape Test -tape: a, _, b +tape: a, _ rules: start: a -> a, R, halt diff --git a/src/programs.rs b/src/programs.rs index 2a93879..9ac8b6c 100644 --- a/src/programs.rs +++ b/src/programs.rs @@ -3,10 +3,9 @@ use crate::types::{Program, TuringMachineError}; use std::sync::RwLock; // Default embedded programs -const PROGRAM_TEXTS: [&str; 9] = [ +const PROGRAM_TEXTS: [&str; 8] = [ include_str!("../examples/binary-addition.tur"), include_str!("../examples/palindrome.tur"), - include_str!("../examples/binary-counter.tur"), include_str!("../examples/event-number-checker.tur"), include_str!("../examples/subtraction.tur"), include_str!("../examples/busy-beaver-3.tur"), @@ -167,6 +166,7 @@ pub struct ProgramInfo { #[cfg(test)] mod tests { use super::*; + use crate::analyze; use crate::machine::TuringMachine; use std::fs::File; use std::io::Write; @@ -190,7 +190,7 @@ mod tests { let file_path = dir.path().join("custom.tur"); let content = r#" name: Custom Program -tape: x, y, z +tape: x rules: start: x -> y, R, stop @@ -205,7 +205,7 @@ rules: let program = program.unwrap(); assert_eq!(program.name, "Custom Program"); - assert_eq!(program.initial_tape(), "xyz"); + assert_eq!(program.initial_tape(), "x"); // Test that ProgramLoader can load from directory let results = crate::loader::ProgramLoader::load_programs(dir.path()); @@ -222,7 +222,7 @@ rules: for i in 0..count { let program = ProgramManager::get_program_by_index(i).unwrap(); assert!( - TuringMachine::validate_program(&program).is_ok(), + analyze(&program).is_ok(), "Program '{}' is invalid", program.name ); @@ -237,7 +237,6 @@ rules: let names = ProgramManager::list_program_names(); assert!(names.contains(&"Binary addition".to_string())); assert!(names.contains(&"Palindrome Checker".to_string())); - assert!(names.contains(&"Binary Counter".to_string())); assert!(names.contains(&"Subtraction".to_string())); } @@ -283,7 +282,7 @@ rules: let program = ProgramManager::get_program_by_name("Binary addition"); assert!(program.is_ok()); - assert_eq!(program.unwrap().initial_tape(), "$00111-"); + assert_eq!(program.unwrap().initial_tape(), "$00111"); let result = ProgramManager::get_program_by_name("Nonexistent"); assert!(result.is_err()); @@ -298,7 +297,6 @@ rules: assert!(names.len() >= 4); assert!(names.contains(&"Binary addition".to_string())); assert!(names.contains(&"Palindrome Checker".to_string())); - assert!(names.contains(&"Binary Counter".to_string())); assert!(names.contains(&"Subtraction".to_string())); } @@ -327,7 +325,7 @@ rules: let _ = ProgramManager::load(); let results = ProgramManager::search_programs("binary"); - assert!(results.len() >= 2); // "Binary addition" and "Binary counter" + assert!(!results.is_empty()); // "Binary addition" let results = ProgramManager::search_programs("palindrome"); assert!(!results.is_empty());