diff --git a/Cargo.lock b/Cargo.lock index 013bcb4..0d329cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1869,6 +1869,7 @@ dependencies = [ name = "tur-cli" version = "0.1.0" dependencies = [ + "atty", "clap", "tur", ] diff --git a/README.md b/README.md index f72dd32..bf7281b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

-**Tur** is a language for defining and executing Turing machines, complete with parser, interpreter, and multi-platform visualization tools. +**Tur** is a language for defining and executing Turing machines, complete with parser, interpreter, and multi-platform visualization tools. It supports both single-tape and multi-tape configurations. @@ -133,8 +133,9 @@ state: ### Special Symbols -- The underscore (`"_"`) is a special symbol used to represent a blank character. +- The underscore (`_`) is a special symbol used to represent a blank character in program definitions. - Any other character or unicode string can be used as tape symbols. +- The blank symbol can be customized using the `blank:` directive in the program. ## Platforms @@ -142,7 +143,7 @@ state: ```bash # Run a program -cargo run --package tur-cli -- examples/binary-addition.tur +cargo run --package tur-cli -- -p examples/binary-addition.tur ``` ### Terminal User Interface (TUI) diff --git a/examples/README.md b/examples/README.md index 5aef083..d0e4ac5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,6 +1,6 @@ # Turing Machine Programs -This directory contains example Turing machine programs in `.tur` format. These programs can be loaded and executed by the Turing machine simulator. +This directory contains example Turing machine programs in `.tur` format. These programs demonstrate both single-tape and multi-tape Turing machine capabilities and can be loaded and executed by the Turing machine simulator across all platforms (CLI, TUI, and Web). ## File Format @@ -12,10 +12,10 @@ Turing machine programs are defined in a simple text format with the `.tur` exte ### Example -``` +```tur name: Simple Test tape: a -transitions: +rules: start: a -> b, R, halt halt: @@ -23,27 +23,49 @@ transitions: ### Syntax -The `.tur` file format uses a structured syntax parsed by a Pest grammar: +The `.tur` file format uses a structured syntax parsed by a Pest grammar with comprehensive validation: - **Name**: Specified with `name:` followed by the program name -- **Tape**: Specified with `tape:` followed by comma-separated initial tape symbols (for single-tape machines) -- **Tapes**: Specified with `tapes:` followed by multiple lines of tape definitions for multi-tape machines: - ``` - tapes: - - [a, b, c] - - [x, y, z] - ``` -- **Head**: Specified with `head:` followed by the initial head position (default: 0) -- **Heads**: Specified with `heads:` followed by an array of initial head positions for multi-tape machines: - ``` - heads: [0, 0] - ``` -- **Blank**: Specified with `blank:` followed by the symbol to use for blank cells (default: '-') -- **Transitions**: Specified with `transitions:` followed by state definitions +- **Tape Configuration**: + - **Single-tape**: `tape: symbol1, symbol2, symbol3` + - **Multi-tape**: + ```tur + tapes: + [a, b, c] + [x, y, z] + ``` +- **Head Positions** (optional): + - **Single-tape**: `head: 0` (defaults to 0) + - **Multi-tape**: `heads: [0, 0]` (defaults to all zeros) +- **Blank Symbol**: `blank: _` (defaults to space character) +- **Transition Rules**: Specified with `rules:` followed by state definitions - Each state is defined by its name followed by a colon - - Each transition rule is indented (2 spaces or tabs) and specifies: - - For single-tape machines: `symbol -> new_symbol, direction, next_state` - - For multi-tape machines: `[symbol1, symbol2, ...] -> [new1, new2, ...], [dir1, dir2, ...], next_state` - - The direction to move: `R`/`>` (right), `L`/`<` (left), `S`/`-` (stay) - - The next state to transition to - - If `-> new_symbol` is omitted, the read symbol is preserved + - Each transition rule is indented and specifies: + - **Single-tape**: `symbol -> new_symbol, direction, next_state` + - **Multi-tape**: `[sym1, sym2] -> [new1, new2], [dir1, dir2], next_state` + - **Directions**: `R`/`>` (right), `L`/`<` (left), `S`/`-` (stay) + - **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**: + - `_` 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 + +## Available Examples + +### 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 +- **subtraction.tur**: Subtracts two numbers + +### Multi-Tape Programs +- **multi-tape-addition.tur**: Addition using multiple tapes +- **multi-tape-compare.tur**: Compares content across tapes +- **multi-tape-copy.tur**: Copies content from one tape to another +- **multi-tape-example.tur**: Basic multi-tape demonstration + +Each program includes comprehensive comments explaining the algorithm and state transitions. diff --git a/platforms/cli/Cargo.toml b/platforms/cli/Cargo.toml index 1d9da6e..8b3b9b6 100644 --- a/platforms/cli/Cargo.toml +++ b/platforms/cli/Cargo.toml @@ -17,3 +17,4 @@ path = "src/main.rs" [dependencies] tur = { path = "../../", version = "0.1.0" } clap = { version = "4.0", features = ["derive"] } +atty = "0.2.14" diff --git a/platforms/cli/src/main.rs b/platforms/cli/src/main.rs index 288c647..3c4fad1 100644 --- a/platforms/cli/src/main.rs +++ b/platforms/cli/src/main.rs @@ -1,4 +1,5 @@ use clap::Parser; +use std::io::{self, BufRead}; use std::path::Path; use tur::loader::ProgramLoader; use tur::machine::TuringMachine; @@ -30,53 +31,94 @@ fn main() { std::process::exit(1); } }; - let mut machine = TuringMachine::new(&program); + let mut machine = TuringMachine::new(program); - for (i, input_str) in cli.input.iter().enumerate() { - if i < machine.tapes.len() { - machine.tapes[i] = input_str.chars().collect(); + // Get tape inputs from either CLI args or stdin + let tapes = match read_tape_inputs(&cli.input) { + Ok(inputs) => inputs, + Err(e) => { + eprintln!("{}", e); + std::process::exit(1); + } + }; + + // Set tape contents if any inputs were provided + if !tapes.is_empty() { + if let Err(e) = machine.set_tapes_content(&tapes) { + eprintln!("Error setting tape content: {}", e); + std::process::exit(1); } } if cli.debug { - let print_state = |machine: &TuringMachine| { - let tapes_str = machine - .get_tapes_as_strings() - .iter() - .map(|s| s.to_string()) - .collect::>() - .join(", "); - - println!( - "Step: {}, State: {}, Tapes: [{}], Heads: {:?}", - machine.get_step_count(), - machine.get_state(), - tapes_str, - machine.get_head_positions() - ); - }; - - print_state(&machine); - - loop { - match machine.step() { - ExecutionResult::Continue => { - print_state(&machine); - } - ExecutionResult::Halt => { - println!("\nMachine halted."); - break; - } - ExecutionResult::Error(e) => { - println!("\nMachine error: {}", e); - break; - } - } - } - println!("\nFinal tapes:"); + run_with_debug(&mut machine); } else { machine.run_to_completion(); } println!("{}", machine.get_tapes_as_strings().join("\n")); } + +/// Runs the Turing machine with debug output, printing each step. +fn run_with_debug(machine: &mut TuringMachine) { + let print_state = |machine: &TuringMachine| { + let tapes_str = machine + .get_tapes_as_strings() + .iter() + .map(|s| s.to_string()) + .collect::>() + .join(", "); + + println!( + "Step: {}, State: {}, Tapes: [{}], Heads: {:?}", + machine.step_count(), + machine.state(), + tapes_str, + machine.head_positions() + ); + }; + + print_state(machine); + + loop { + match machine.step() { + ExecutionResult::Continue => { + print_state(machine); + } + ExecutionResult::Halt => { + println!("\nMachine halted."); + break; + } + ExecutionResult::Error(e) => { + println!("\nMachine error: {}", e); + break; + } + } + } + println!("\nFinal tapes:"); +} + +/// Gets tape input from either command line arguments or stdin. +/// Returns a vector of strings representing the content for each tape. +fn read_tape_inputs(inputs: &[String]) -> Result, String> { + if !inputs.is_empty() { + // Use command line inputs + Ok(inputs.to_vec()) + } else if !atty::is(atty::Stream::Stdin) { + // Read from stdin, each line represents a tape + let stdin = io::stdin(); + let mut tape_inputs = Vec::new(); + + for line in stdin.lock().lines() { + match line { + Ok(content) => tape_inputs.push(content), + Err(e) => return Err(format!("Error reading from stdin: {}", e)), + } + } + + Ok(tape_inputs) + } else { + // No input provided + Ok(Vec::new()) + } +} diff --git a/platforms/tui/src/app.rs b/platforms/tui/src/app.rs index 1f8af1e..f18ff5e 100644 --- a/platforms/tui/src/app.rs +++ b/platforms/tui/src/app.rs @@ -29,7 +29,7 @@ pub struct App { impl App { pub fn new_default() -> Self { let program = ProgramManager::get_program_by_index(0).unwrap(); - let machine = TuringMachine::new(&program); + let machine = TuringMachine::new(program); Self { machine, @@ -46,7 +46,7 @@ impl App { pub fn new_from_program_string(program_content: String) -> Result { let program: Program = ProgramLoader::load_program_from_string(&program_content) .map_err(|e| format!("Failed to load program: {}", e))?; - let machine = TuringMachine::new(&program); + let machine = TuringMachine::new(program); Ok(Self { machine, @@ -174,8 +174,8 @@ impl App { } fn render_tapes(&self, f: &mut Frame, area: Rect) { - let tapes = self.machine.get_tapes(); - let head_positions = self.machine.get_head_positions(); + let tapes = self.machine.tapes(); + let head_positions = self.machine.head_positions(); let tape_count = tapes.len(); let mut text_lines = Vec::new(); @@ -220,7 +220,7 @@ impl App { let current_symbol = if head_pos < tape.len() { tape[head_pos] } else { - self.machine.get_blank_symbol() + self.machine.blank_symbol() }; let head_indicator = format!( "Head at position: {} (symbol: '{}')", @@ -245,8 +245,8 @@ impl App { } fn render_machine_state(&self, f: &mut Frame, area: Rect) { - let state = self.machine.get_state(); - let step_count = self.machine.get_step_count(); + let state = self.machine.state(); + let step_count = self.machine.step_count(); let is_halted = self.machine.is_halted(); let (status_text, status_color) = if is_halted { @@ -291,8 +291,8 @@ impl App { } fn render_rules(&self, f: &mut Frame, area: Rect) { - let tape_count = self.machine.get_tapes().len(); - let current_state = self.machine.get_state(); + let tape_count = self.machine.tapes().len(); + let current_state = self.machine.state(); let current_symbols = self.machine.get_current_symbols(); let mut items = Vec::new(); @@ -346,7 +346,7 @@ impl App { } fn render_help(&self, f: &mut Frame, area: Rect) { - let tape_count = self.machine.get_tapes().len(); + let tape_count = self.machine.tapes().len(); let help_text = vec![ Line::from("Controls:"), @@ -408,7 +408,7 @@ impl App { match self.machine.step() { ExecutionResult::Continue => { - self.message = format!("Step {} completed", self.machine.get_step_count()); + self.message = format!("Step {} completed", self.machine.step_count()); } ExecutionResult::Halt => { self.message = "Machine halted".to_string(); @@ -470,12 +470,13 @@ impl App { fn load_current_program(&mut self) { let program = ProgramManager::get_program_by_index(self.current_program_index).unwrap(); - self.machine = TuringMachine::new(&program); + let tape_count = program.tapes.len(); + let program_name = program.name.clone(); + self.machine = TuringMachine::new(program); self.auto_play = false; self.scroll_offset = 0; - let tape_count = program.tapes.len(); - self.message = format!("Loaded {}-tape program: {}", tape_count, program.name); + self.message = format!("Loaded {}-tape program: {}", tape_count, program_name); } pub fn toggle_help(&mut self) { diff --git a/platforms/web/src/app.rs b/platforms/web/src/app.rs index 241cec3..47546a5 100644 --- a/platforms/web/src/app.rs +++ b/platforms/web/src/app.rs @@ -19,7 +19,6 @@ pub enum Msg { EditorError(String), UpdateEditorText(String), SetSpeed(u64), - HideProgramEditorHelp, } @@ -49,9 +48,10 @@ impl App { message: String, keyboard_listener: EventListener, ) -> Self { - let machine = TuringMachine::new(&program); let initial_state = program.initial_state.clone(); - let num_tapes = machine.get_tapes().len(); + let current_program_def = program.clone(); + let machine = TuringMachine::new(program); + let num_tapes = machine.tapes().len(); Self { machine, @@ -60,7 +60,7 @@ impl App { message, editor_text: program_text, is_program_ready: true, - current_program_def: program, + current_program_def, last_transition: None, previous_state: initial_state, speed: 500, @@ -185,20 +185,19 @@ impl Component for App { self.auto_play = false; self.last_transition = None; } else { - let last_head_positions = self.machine.get_head_positions().to_vec(); + let last_head_positions = self.machine.head_positions().to_vec(); let last_tape_lengths: Vec = - self.machine.get_tapes().iter().map(|t| t.len()).collect(); + self.machine.tapes().iter().map(|t| t.len()).collect(); // Store the current state as previous state before stepping - self.previous_state = self.machine.get_state().to_string(); + self.previous_state = self.machine.state().to_string(); // Get the transition that will be executed before stepping self.last_transition = self.machine.get_current_transition().cloned(); match self.machine.step() { ExecutionResult::Continue => { - self.message = - format!("Step {} completed", self.machine.get_step_count()); + self.message = format!("Step {} completed", self.machine.step_count()); } ExecutionResult::Halt => { self.message = "Machine halted".to_string(); @@ -211,9 +210,9 @@ impl Component for App { } } - // let new_head_positions = self.machine.get_head_positions().to_vec(); + // let new_head_positions = self.machine.head_positions().to_vec(); let new_tape_lengths: Vec = - self.machine.get_tapes().iter().map(|t| t.len()).collect(); + self.machine.tapes().iter().map(|t| t.len()).collect(); if let Some(transition) = &self.last_transition { for i in 0..last_head_positions.len() { @@ -241,8 +240,8 @@ impl Component for App { self.message = "Machine reset".to_string(); self.auto_play = false; self.last_transition = None; - self.tape_left_offsets = vec![0; self.machine.tapes.len()]; - self.previous_state = self.machine.get_initial_state().to_string(); + self.tape_left_offsets = vec![0; self.machine.tapes().len()]; + self.previous_state = self.machine.initial_state().to_string(); true } Msg::ToggleAutoPlay => { @@ -269,7 +268,6 @@ impl Component for App { if index != self.current_program { self.current_program = index; let program = ProgramManager::get_program_by_index(index).unwrap(); - self.machine = TuringMachine::new(&program); self.editor_text = ProgramManager::get_program_text_by_index(index) .unwrap_or("") .into(); @@ -279,6 +277,7 @@ impl Component for App { self.current_program_def = program.clone(); self.last_transition = None; self.previous_state = program.initial_state.clone(); + self.machine = TuringMachine::new(program); self.message = "".to_string(); true } else { @@ -299,13 +298,13 @@ impl Component for App { true } Msg::LoadCustomProgram(program) => { - self.machine = TuringMachine::new(&program); self.auto_play = false; self.is_program_ready = true; self.current_program = usize::MAX; // Indicate custom program self.current_program_def = program.clone(); self.last_transition = None; self.previous_state = program.initial_state.clone(); + self.machine = TuringMachine::new(program); self.message = "".to_string(); true } @@ -387,14 +386,14 @@ impl Component for App {
{"State Graph"}
diff --git a/src/machine.rs b/src/machine.rs index 15e4fd7..446cf10 100644 --- a/src/machine.rs +++ b/src/machine.rs @@ -15,7 +15,7 @@ use std::collections::HashMap; /// the blank symbol, and the set of transition rules. pub struct TuringMachine { state: String, - pub tapes: Vec>, + tapes: Vec>, head_positions: Vec, blank_symbol: char, rules: HashMap>, @@ -33,8 +33,8 @@ impl TuringMachine { /// /// # Arguments /// - /// * `program` - A reference to the `Program` defining the Turing Machine. - pub fn new(program: &Program) -> Self { + /// * `program` - The `Program` defining the Turing Machine. + pub fn new(program: Program) -> Self { let tapes: Vec> = program .tapes .iter() @@ -46,10 +46,10 @@ impl TuringMachine { tapes: tapes.clone(), head_positions: program.heads.clone(), blank_symbol: program.blank, - rules: program.rules.clone(), - initial_state: program.initial_state.clone(), + rules: program.rules, + initial_state: program.initial_state, initial_tapes: tapes, - initial_heads: program.heads.clone(), + initial_heads: program.heads, step_count: 0, } } @@ -236,12 +236,12 @@ impl TuringMachine { } /// Returns the current state of the Turing Machine. - pub fn get_state(&self) -> &str { + pub fn state(&self) -> &str { &self.state } /// Returns the initial state of the Turing Machine. - pub fn get_initial_state(&self) -> &str { + pub fn initial_state(&self) -> &str { &self.initial_state } @@ -255,7 +255,7 @@ impl TuringMachine { } /// Returns the total number of steps executed by the Turing Machine. - pub fn get_step_count(&self) -> usize { + pub fn step_count(&self) -> usize { self.step_count } @@ -351,12 +351,12 @@ impl TuringMachine { } /// Returns a slice of the machine's tapes. - pub fn get_tapes(&self) -> &[Vec] { + pub fn tapes(&self) -> &[Vec] { &self.tapes } /// Returns a slice of the machine's head positions for all tapes. - pub fn get_head_positions(&self) -> &[usize] { + pub fn head_positions(&self) -> &[usize] { &self.head_positions } @@ -423,9 +423,72 @@ impl TuringMachine { } /// Returns the blank symbol used by this Turing Machine. - pub fn get_blank_symbol(&self) -> char { + pub fn blank_symbol(&self) -> char { self.blank_symbol } + + /// Sets the content of a specific tape. + /// + /// # Arguments + /// + /// * `tape_index` - The index of the tape to modify (0-based) + /// * `content` - The new content for the tape as a string + /// + /// # Returns + /// + /// * `Ok(())` if the tape was successfully set + /// * `Err(TuringMachineError)` if the tape index is invalid + pub fn set_tape_content( + &mut self, + tape_index: usize, + content: &str, + ) -> Result<(), TuringMachineError> { + if tape_index >= self.tapes.len() { + return Err(TuringMachineError::ValidationError(format!( + "Tape index {} is out of bounds (machine has {} tapes)", + tape_index, + self.tapes.len() + ))); + } + + self.tapes[tape_index] = content + .chars() + .map(|c| { + if c == INPUT_BLANK_SYMBOL { + self.blank_symbol + } else { + c + } + }) + .collect(); + Ok(()) + } + + /// Sets the content of multiple tapes at once. + /// + /// # Arguments + /// + /// * `contents` - A vector of strings representing the new content for each tape + /// + /// # Returns + /// + /// * `Ok(())` if all tapes were successfully set + /// * `Err(TuringMachineError)` if there are more contents than tapes + pub fn set_tapes_content(&mut self, contents: &[String]) -> Result<(), TuringMachineError> { + if contents.len() > self.tapes.len() { + return Err(TuringMachineError::ValidationError(format!( + "Too many tape contents provided: {} contents for {} tapes", + contents.len(), + self.tapes.len() + ))); + } + + for (i, content) in contents.iter().enumerate() { + self.set_tape_content(i, content)?; + } + + Ok(()) + } } #[cfg(test)] @@ -464,32 +527,32 @@ mod multi_tape_tests { #[test] fn test_multi_tape_machine_creation() { let program = create_simple_multi_tape_program(); - let machine = TuringMachine::new(&program); + let machine = TuringMachine::new(program); - assert_eq!(machine.get_state(), "start"); - assert_eq!(machine.get_tapes(), &[vec!['a'], vec!['x']]); - assert_eq!(machine.get_head_positions(), &[0, 0]); - assert_eq!(machine.get_step_count(), 0); + assert_eq!(machine.state(), "start"); + assert_eq!(machine.tapes(), &[vec!['a'], vec!['x']]); + assert_eq!(machine.head_positions(), &[0, 0]); + assert_eq!(machine.step_count(), 0); } #[test] fn test_multi_tape_single_step() { let program = create_simple_multi_tape_program(); - let mut machine = TuringMachine::new(&program); + let mut machine = TuringMachine::new(program); let result = machine.step(); assert_eq!(result, ExecutionResult::Continue); - assert_eq!(machine.get_state(), "halt"); - assert_eq!(machine.get_tapes(), &[vec!['b', '-'], vec!['y', '-']]); // Tapes extended when moving right - assert_eq!(machine.get_head_positions(), &[1, 1]); - assert_eq!(machine.get_step_count(), 1); + assert_eq!(machine.state(), "halt"); + assert_eq!(machine.tapes(), &[vec!['b', '-'], vec!['y', '-']]); // Tapes extended when moving right + assert_eq!(machine.head_positions(), &[1, 1]); + assert_eq!(machine.step_count(), 1); } #[test] fn test_multi_tape_halt_state() { let program = create_simple_multi_tape_program(); - let mut machine = TuringMachine::new(&program); + let mut machine = TuringMachine::new(program); // First step should continue let result1 = machine.step(); @@ -503,10 +566,12 @@ mod multi_tape_tests { #[test] fn test_multi_tape_no_transition_error() { let program = create_simple_multi_tape_program(); - let mut machine = TuringMachine::new(&program); + let mut machine = TuringMachine::new(program); // Manually set tapes to symbols that have no transition - machine.tapes = vec![vec!['z'], vec!['z']]; + machine + .set_tapes_content(&["z".to_string(), "z".to_string()]) + .unwrap(); let result = machine.step(); @@ -525,25 +590,25 @@ mod multi_tape_tests { #[test] fn test_multi_tape_reset() { let program = create_simple_multi_tape_program(); - let mut machine = TuringMachine::new(&program); + let mut machine = TuringMachine::new(program); // Execute a step machine.step(); - assert_eq!(machine.get_state(), "halt"); - assert_eq!(machine.get_step_count(), 1); + assert_eq!(machine.state(), "halt"); + assert_eq!(machine.step_count(), 1); // Reset machine.reset(); - assert_eq!(machine.get_state(), "start"); - assert_eq!(machine.get_tapes(), &[vec!['a'], vec!['x']]); - assert_eq!(machine.get_head_positions(), &[0, 0]); - assert_eq!(machine.get_step_count(), 0); + assert_eq!(machine.state(), "start"); + assert_eq!(machine.tapes(), &[vec!['a'], vec!['x']]); + assert_eq!(machine.head_positions(), &[0, 0]); + assert_eq!(machine.step_count(), 0); } #[test] fn test_multi_tape_run_to_completion() { let program = create_simple_multi_tape_program(); - let mut machine = TuringMachine::new(&program); + let mut machine = TuringMachine::new(program); let steps = machine.run_to_completion(); @@ -556,7 +621,7 @@ mod multi_tape_tests { #[test] fn test_multi_tape_is_halted() { let program = create_simple_multi_tape_program(); - let mut machine = TuringMachine::new(&program); + let mut machine = TuringMachine::new(program); assert!(!machine.is_halted()); // Should not be halted initially @@ -567,7 +632,7 @@ mod multi_tape_tests { #[test] fn test_multi_tape_get_current_symbols() { let program = create_simple_multi_tape_program(); - let machine = TuringMachine::new(&program); + let machine = TuringMachine::new(program); assert_eq!(machine.get_current_symbols(), vec!['a', 'x']); } @@ -575,7 +640,7 @@ mod multi_tape_tests { #[test] fn test_multi_tape_get_tapes_as_strings() { let program = create_simple_multi_tape_program(); - let machine = TuringMachine::new(&program); + let machine = TuringMachine::new(program); assert_eq!( machine.get_tapes_as_strings(), @@ -708,12 +773,38 @@ mod multi_tape_tests { rules, }; - let mut machine = TuringMachine::new(&program); + let mut machine = TuringMachine::new(program); machine.step(); // First head should stay at position 0, second head should move right - assert_eq!(machine.get_head_positions(), &[0, 1]); - assert_eq!(machine.get_tapes(), &[vec!['b'], vec!['y', '-']]); + assert_eq!(machine.head_positions(), &[0, 1]); + assert_eq!(machine.tapes(), &[vec!['b'], vec!['y', '-']]); + } + + #[test] + fn test_set_tape_content_with_blank_symbol() { + let program = create_simple_multi_tape_program(); + let mut machine = TuringMachine::new(program); + + // Test setting tape content with INPUT_BLANK_SYMBOL ('_') + machine.set_tape_content(0, "a_b").unwrap(); + + // The '_' should be converted to the machine's blank symbol ('-') + assert_eq!(machine.tapes()[0], vec!['a', '-', 'b']); + } + + #[test] + fn test_set_tapes_content_with_blank_symbol() { + let program = create_simple_multi_tape_program(); + let mut machine = TuringMachine::new(program); + + // Test setting multiple tapes with INPUT_BLANK_SYMBOL ('_') + let contents = vec!["a_b".to_string(), "x_y".to_string()]; + machine.set_tapes_content(&contents).unwrap(); + + // The '_' should be converted to the machine's blank symbol ('-') + assert_eq!(machine.tapes()[0], vec!['a', '-', 'b']); + assert_eq!(machine.tapes()[1], vec!['x', '-', 'y']); } #[test] diff --git a/src/programs.rs b/src/programs.rs index ca1c54b..2a93879 100644 --- a/src/programs.rs +++ b/src/programs.rs @@ -249,7 +249,8 @@ rules: let count = ProgramManager::get_program_count(); for i in 0..count { let program = ProgramManager::get_program_by_index(i).unwrap(); - let mut machine = TuringMachine::new(&program); + let program_name = program.name.clone(); + let mut machine = TuringMachine::new(program); let result = machine.step(); // Should either continue or halt, but not error on first step @@ -257,7 +258,7 @@ rules: crate::types::ExecutionResult::Continue => {} crate::types::ExecutionResult::Halt => {} crate::types::ExecutionResult::Error(e) => { - panic!("Program '{}' failed on first step: {}", program.name, e); + panic!("Program '{}' failed on first step: {}", program_name, e); } } }