From d48c0665986252c737d4fff0b81df682c0349a4f Mon Sep 17 00:00:00 2001
From: Marut Khumtong
Date: Tue, 12 Aug 2025 15:26:07 +0700
Subject: [PATCH 1/2] refactor: change TuringMachine::new to take ownership of
Program
---
Cargo.lock | 1 +
examples/README.md | 8 +--
platforms/cli/Cargo.toml | 1 +
platforms/cli/src/main.rs | 14 +++--
platforms/tui/src/app.rs | 6 +-
platforms/web/src/app.rs | 6 +-
src/machine.rs | 125 ++++++++++++++++++++++++++++++++------
src/programs.rs | 5 +-
8 files changed, 133 insertions(+), 33 deletions(-)
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/examples/README.md b/examples/README.md
index 5aef083..bc98a7d 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -15,7 +15,7 @@ Turing machine programs are defined in a simple text format with the `.tur` exte
```
name: Simple Test
tape: a
-transitions:
+rules:
start:
a -> b, R, halt
halt:
@@ -30,8 +30,8 @@ The `.tur` file format uses a structured syntax parsed by a Pest grammar:
- **Tapes**: Specified with `tapes:` followed by multiple lines of tape definitions for multi-tape machines:
```
tapes:
- - [a, b, c]
- - [x, y, z]
+ [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:
@@ -39,7 +39,7 @@ The `.tur` file format uses a structured syntax parsed by a Pest grammar:
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
+- **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`
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..d2c04c2 100644
--- a/platforms/cli/src/main.rs
+++ b/platforms/cli/src/main.rs
@@ -30,12 +30,18 @@ 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();
+ if !cli.input.is_empty() {
+ for (i, input_str) in cli.input.iter().enumerate() {
+ if i < machine.tapes.len() {
+ machine.tapes[i] = input_str.chars().collect();
+ }
}
+ } else if !atty::is(atty::Stream::Stdin) {
+ let mut input = String::new();
+ std::io::stdin().read_line(&mut input).unwrap();
+ machine.tapes[0] = input.trim().chars().collect();
}
if cli.debug {
diff --git a/platforms/tui/src/app.rs b/platforms/tui/src/app.rs
index 1f8af1e..2bf2360 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,
@@ -470,7 +470,7 @@ 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);
+ self.machine = TuringMachine::new(program);
self.auto_play = false;
self.scroll_offset = 0;
diff --git a/platforms/web/src/app.rs b/platforms/web/src/app.rs
index 241cec3..5cbdcdf 100644
--- a/platforms/web/src/app.rs
+++ b/platforms/web/src/app.rs
@@ -49,7 +49,7 @@ impl App {
message: String,
keyboard_listener: EventListener,
) -> Self {
- let machine = TuringMachine::new(&program);
+ let machine = TuringMachine::new(program);
let initial_state = program.initial_state.clone();
let num_tapes = machine.get_tapes().len();
@@ -269,7 +269,7 @@ 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.machine = TuringMachine::new(program);
self.editor_text = ProgramManager::get_program_text_by_index(index)
.unwrap_or("")
.into();
@@ -299,7 +299,7 @@ impl Component for App {
true
}
Msg::LoadCustomProgram(program) => {
- self.machine = TuringMachine::new(&program);
+ self.machine = TuringMachine::new(program);
self.auto_play = false;
self.is_program_ready = true;
self.current_program = usize::MAX; // Indicate custom program
diff --git a/src/machine.rs b/src/machine.rs
index 15e4fd7..4ceebc5 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,
}
}
@@ -426,6 +426,69 @@ impl TuringMachine {
pub fn get_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,7 +527,7 @@ 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']]);
@@ -475,7 +538,7 @@ mod multi_tape_tests {
#[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();
@@ -489,7 +552,7 @@ mod multi_tape_tests {
#[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,7 +590,7 @@ 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();
@@ -543,7 +608,7 @@ mod multi_tape_tests {
#[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,7 +773,7 @@ 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
@@ -716,6 +781,32 @@ mod multi_tape_tests {
assert_eq!(machine.get_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.get_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.get_tapes()[0], vec!['a', '-', 'b']);
+ assert_eq!(machine.get_tapes()[1], vec!['x', '-', 'y']);
+ }
+
#[test]
fn test_write_input_blank_symbol_with_custom_blank() {
let custom_blank = 'X';
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);
}
}
}
From 5741c6b821f53ea8c8d354e8f6297d394517d265 Mon Sep 17 00:00:00 2001
From: Marut Khumtong
Date: Tue, 12 Aug 2025 16:29:46 +0700
Subject: [PATCH 2/2] refactor: improve TuringMachine API design and
consistency
---
README.md | 7 ++-
examples/README.md | 68 ++++++++++++++-------
platforms/cli/src/main.rs | 124 ++++++++++++++++++++++++--------------
platforms/tui/src/app.rs | 23 +++----
platforms/web/src/app.rs | 43 +++++++------
src/machine.rs | 50 +++++++--------
6 files changed, 187 insertions(+), 128 deletions(-)
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 bc98a7d..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,7 +12,7 @@ Turing machine programs are defined in a simple text format with the `.tur` exte
### Example
-```
+```tur
name: Simple Test
tape: a
rules:
@@ -23,27 +23,49 @@ rules:
### 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: '-')
-- **Rules**: Specified with `rules:` 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/src/main.rs b/platforms/cli/src/main.rs
index d2c04c2..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;
@@ -32,57 +33,92 @@ fn main() {
};
let mut machine = TuringMachine::new(program);
- if !cli.input.is_empty() {
- 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);
}
- } else if !atty::is(atty::Stream::Stdin) {
- let mut input = String::new();
- std::io::stdin().read_line(&mut input).unwrap();
- machine.tapes[0] = input.trim().chars().collect();
}
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 2bf2360..f18ff5e 100644
--- a/platforms/tui/src/app.rs
+++ b/platforms/tui/src/app.rs
@@ -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();
+ 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 5cbdcdf..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 {
diff --git a/src/machine.rs b/src/machine.rs
index 4ceebc5..446cf10 100644
--- a/src/machine.rs
+++ b/src/machine.rs
@@ -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,7 +423,7 @@ 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
}
@@ -529,10 +529,10 @@ mod multi_tape_tests {
let program = create_simple_multi_tape_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]
@@ -543,10 +543,10 @@ mod multi_tape_tests {
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]
@@ -594,15 +594,15 @@ mod multi_tape_tests {
// 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]
@@ -777,8 +777,8 @@ mod multi_tape_tests {
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]
@@ -790,7 +790,7 @@ mod multi_tape_tests {
machine.set_tape_content(0, "a_b").unwrap();
// The '_' should be converted to the machine's blank symbol ('-')
- assert_eq!(machine.get_tapes()[0], vec!['a', '-', 'b']);
+ assert_eq!(machine.tapes()[0], vec!['a', '-', 'b']);
}
#[test]
@@ -803,8 +803,8 @@ mod multi_tape_tests {
machine.set_tapes_content(&contents).unwrap();
// The '_' should be converted to the machine's blank symbol ('-')
- assert_eq!(machine.get_tapes()[0], vec!['a', '-', 'b']);
- assert_eq!(machine.get_tapes()[1], vec!['x', '-', 'y']);
+ assert_eq!(machine.tapes()[0], vec!['a', '-', 'b']);
+ assert_eq!(machine.tapes()[1], vec!['x', '-', 'y']);
}
#[test]