Skip to content

Commit 3a322e1

Browse files
authored
Merge pull request #5 from rezigned/refactor
Refactor TuringMachine API design
2 parents 7c4a939 + 5741c6b commit 3a322e1

File tree

9 files changed

+302
-143
lines changed

9 files changed

+302
-143
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<img src=".github/tur-logo.png" width="400" />
77
</p>
88

9-
**Tur** is a language for defining and executing Turing machines, complete with parser, interpreter, and multi-platform visualization tools.
9+
**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.
1010

1111
<table>
1212
<thead>
@@ -133,16 +133,17 @@ state:
133133

134134
### Special Symbols
135135

136-
- The underscore (`"_"`) is a special symbol used to represent a blank character.
136+
- The underscore (`_`) is a special symbol used to represent a blank character in program definitions.
137137
- Any other character or unicode string can be used as tape symbols.
138+
- The blank symbol can be customized using the `blank:` directive in the program.
138139

139140
## Platforms
140141

141142
### Command Line Interface (CLI)
142143

143144
```bash
144145
# Run a program
145-
cargo run --package tur-cli -- examples/binary-addition.tur
146+
cargo run --package tur-cli -- -p examples/binary-addition.tur
146147
```
147148

148149
### Terminal User Interface (TUI)

examples/README.md

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Turing Machine Programs
22

3-
This directory contains example Turing machine programs in `.tur` format. These programs can be loaded and executed by the Turing machine simulator.
3+
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).
44

55
## File Format
66

@@ -12,38 +12,60 @@ Turing machine programs are defined in a simple text format with the `.tur` exte
1212

1313
### Example
1414

15-
```
15+
```tur
1616
name: Simple Test
1717
tape: a
18-
transitions:
18+
rules:
1919
start:
2020
a -> b, R, halt
2121
halt:
2222
```
2323

2424
### Syntax
2525

26-
The `.tur` file format uses a structured syntax parsed by a Pest grammar:
26+
The `.tur` file format uses a structured syntax parsed by a Pest grammar with comprehensive validation:
2727

2828
- **Name**: Specified with `name:` followed by the program name
29-
- **Tape**: Specified with `tape:` followed by comma-separated initial tape symbols (for single-tape machines)
30-
- **Tapes**: Specified with `tapes:` followed by multiple lines of tape definitions for multi-tape machines:
31-
```
32-
tapes:
33-
- [a, b, c]
34-
- [x, y, z]
35-
```
36-
- **Head**: Specified with `head:` followed by the initial head position (default: 0)
37-
- **Heads**: Specified with `heads:` followed by an array of initial head positions for multi-tape machines:
38-
```
39-
heads: [0, 0]
40-
```
41-
- **Blank**: Specified with `blank:` followed by the symbol to use for blank cells (default: '-')
42-
- **Transitions**: Specified with `transitions:` followed by state definitions
29+
- **Tape Configuration**:
30+
- **Single-tape**: `tape: symbol1, symbol2, symbol3`
31+
- **Multi-tape**:
32+
```tur
33+
tapes:
34+
[a, b, c]
35+
[x, y, z]
36+
```
37+
- **Head Positions** (optional):
38+
- **Single-tape**: `head: 0` (defaults to 0)
39+
- **Multi-tape**: `heads: [0, 0]` (defaults to all zeros)
40+
- **Blank Symbol**: `blank: _` (defaults to space character)
41+
- **Transition Rules**: Specified with `rules:` followed by state definitions
4342
- Each state is defined by its name followed by a colon
44-
- Each transition rule is indented (2 spaces or tabs) and specifies:
45-
- For single-tape machines: `symbol -> new_symbol, direction, next_state`
46-
- For multi-tape machines: `[symbol1, symbol2, ...] -> [new1, new2, ...], [dir1, dir2, ...], next_state`
47-
- The direction to move: `R`/`>` (right), `L`/`<` (left), `S`/`-` (stay)
48-
- The next state to transition to
49-
- If `-> new_symbol` is omitted, the read symbol is preserved
43+
- Each transition rule is indented and specifies:
44+
- **Single-tape**: `symbol -> new_symbol, direction, next_state`
45+
- **Multi-tape**: `[sym1, sym2] -> [new1, new2], [dir1, dir2], next_state`
46+
- **Directions**: `R`/`>` (right), `L`/`<` (left), `S`/`-` (stay)
47+
- **Write-only transitions**: If `-> new_symbol` is omitted, the read symbol is preserved
48+
- The first state defined is automatically the initial state
49+
- **Comments**: Use `#` for line comments and inline comments
50+
- **Special Symbols**:
51+
- `_` represents the blank symbol in program definitions
52+
- Any Unicode character can be used as tape symbols
53+
- The blank symbol can be customized with the `blank:` directive
54+
55+
## Available Examples
56+
57+
### Single-Tape Programs
58+
- **binary-addition.tur**: Adds two binary numbers
59+
- **binary-counter.tur**: Increments a binary number
60+
- **busy-beaver-3.tur**: Classic 3-state busy beaver
61+
- **event-number-checker.tur**: Checks if a number is even
62+
- **palindrome.tur**: Checks if input is a palindrome
63+
- **subtraction.tur**: Subtracts two numbers
64+
65+
### Multi-Tape Programs
66+
- **multi-tape-addition.tur**: Addition using multiple tapes
67+
- **multi-tape-compare.tur**: Compares content across tapes
68+
- **multi-tape-copy.tur**: Copies content from one tape to another
69+
- **multi-tape-example.tur**: Basic multi-tape demonstration
70+
71+
Each program includes comprehensive comments explaining the algorithm and state transitions.

platforms/cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ path = "src/main.rs"
1717
[dependencies]
1818
tur = { path = "../../", version = "0.1.0" }
1919
clap = { version = "4.0", features = ["derive"] }
20+
atty = "0.2.14"

platforms/cli/src/main.rs

Lines changed: 81 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use clap::Parser;
2+
use std::io::{self, BufRead};
23
use std::path::Path;
34
use tur::loader::ProgramLoader;
45
use tur::machine::TuringMachine;
@@ -30,53 +31,94 @@ fn main() {
3031
std::process::exit(1);
3132
}
3233
};
33-
let mut machine = TuringMachine::new(&program);
34+
let mut machine = TuringMachine::new(program);
3435

35-
for (i, input_str) in cli.input.iter().enumerate() {
36-
if i < machine.tapes.len() {
37-
machine.tapes[i] = input_str.chars().collect();
36+
// Get tape inputs from either CLI args or stdin
37+
let tapes = match read_tape_inputs(&cli.input) {
38+
Ok(inputs) => inputs,
39+
Err(e) => {
40+
eprintln!("{}", e);
41+
std::process::exit(1);
42+
}
43+
};
44+
45+
// Set tape contents if any inputs were provided
46+
if !tapes.is_empty() {
47+
if let Err(e) = machine.set_tapes_content(&tapes) {
48+
eprintln!("Error setting tape content: {}", e);
49+
std::process::exit(1);
3850
}
3951
}
4052

4153
if cli.debug {
42-
let print_state = |machine: &TuringMachine| {
43-
let tapes_str = machine
44-
.get_tapes_as_strings()
45-
.iter()
46-
.map(|s| s.to_string())
47-
.collect::<Vec<String>>()
48-
.join(", ");
49-
50-
println!(
51-
"Step: {}, State: {}, Tapes: [{}], Heads: {:?}",
52-
machine.get_step_count(),
53-
machine.get_state(),
54-
tapes_str,
55-
machine.get_head_positions()
56-
);
57-
};
58-
59-
print_state(&machine);
60-
61-
loop {
62-
match machine.step() {
63-
ExecutionResult::Continue => {
64-
print_state(&machine);
65-
}
66-
ExecutionResult::Halt => {
67-
println!("\nMachine halted.");
68-
break;
69-
}
70-
ExecutionResult::Error(e) => {
71-
println!("\nMachine error: {}", e);
72-
break;
73-
}
74-
}
75-
}
76-
println!("\nFinal tapes:");
54+
run_with_debug(&mut machine);
7755
} else {
7856
machine.run_to_completion();
7957
}
8058

8159
println!("{}", machine.get_tapes_as_strings().join("\n"));
8260
}
61+
62+
/// Runs the Turing machine with debug output, printing each step.
63+
fn run_with_debug(machine: &mut TuringMachine) {
64+
let print_state = |machine: &TuringMachine| {
65+
let tapes_str = machine
66+
.get_tapes_as_strings()
67+
.iter()
68+
.map(|s| s.to_string())
69+
.collect::<Vec<String>>()
70+
.join(", ");
71+
72+
println!(
73+
"Step: {}, State: {}, Tapes: [{}], Heads: {:?}",
74+
machine.step_count(),
75+
machine.state(),
76+
tapes_str,
77+
machine.head_positions()
78+
);
79+
};
80+
81+
print_state(machine);
82+
83+
loop {
84+
match machine.step() {
85+
ExecutionResult::Continue => {
86+
print_state(machine);
87+
}
88+
ExecutionResult::Halt => {
89+
println!("\nMachine halted.");
90+
break;
91+
}
92+
ExecutionResult::Error(e) => {
93+
println!("\nMachine error: {}", e);
94+
break;
95+
}
96+
}
97+
}
98+
println!("\nFinal tapes:");
99+
}
100+
101+
/// Gets tape input from either command line arguments or stdin.
102+
/// Returns a vector of strings representing the content for each tape.
103+
fn read_tape_inputs(inputs: &[String]) -> Result<Vec<String>, String> {
104+
if !inputs.is_empty() {
105+
// Use command line inputs
106+
Ok(inputs.to_vec())
107+
} else if !atty::is(atty::Stream::Stdin) {
108+
// Read from stdin, each line represents a tape
109+
let stdin = io::stdin();
110+
let mut tape_inputs = Vec::new();
111+
112+
for line in stdin.lock().lines() {
113+
match line {
114+
Ok(content) => tape_inputs.push(content),
115+
Err(e) => return Err(format!("Error reading from stdin: {}", e)),
116+
}
117+
}
118+
119+
Ok(tape_inputs)
120+
} else {
121+
// No input provided
122+
Ok(Vec::new())
123+
}
124+
}

platforms/tui/src/app.rs

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub struct App {
2929
impl App {
3030
pub fn new_default() -> Self {
3131
let program = ProgramManager::get_program_by_index(0).unwrap();
32-
let machine = TuringMachine::new(&program);
32+
let machine = TuringMachine::new(program);
3333

3434
Self {
3535
machine,
@@ -46,7 +46,7 @@ impl App {
4646
pub fn new_from_program_string(program_content: String) -> Result<Self, String> {
4747
let program: Program = ProgramLoader::load_program_from_string(&program_content)
4848
.map_err(|e| format!("Failed to load program: {}", e))?;
49-
let machine = TuringMachine::new(&program);
49+
let machine = TuringMachine::new(program);
5050

5151
Ok(Self {
5252
machine,
@@ -174,8 +174,8 @@ impl App {
174174
}
175175

176176
fn render_tapes(&self, f: &mut Frame, area: Rect) {
177-
let tapes = self.machine.get_tapes();
178-
let head_positions = self.machine.get_head_positions();
177+
let tapes = self.machine.tapes();
178+
let head_positions = self.machine.head_positions();
179179
let tape_count = tapes.len();
180180

181181
let mut text_lines = Vec::new();
@@ -220,7 +220,7 @@ impl App {
220220
let current_symbol = if head_pos < tape.len() {
221221
tape[head_pos]
222222
} else {
223-
self.machine.get_blank_symbol()
223+
self.machine.blank_symbol()
224224
};
225225
let head_indicator = format!(
226226
"Head at position: {} (symbol: '{}')",
@@ -245,8 +245,8 @@ impl App {
245245
}
246246

247247
fn render_machine_state(&self, f: &mut Frame, area: Rect) {
248-
let state = self.machine.get_state();
249-
let step_count = self.machine.get_step_count();
248+
let state = self.machine.state();
249+
let step_count = self.machine.step_count();
250250
let is_halted = self.machine.is_halted();
251251

252252
let (status_text, status_color) = if is_halted {
@@ -291,8 +291,8 @@ impl App {
291291
}
292292

293293
fn render_rules(&self, f: &mut Frame, area: Rect) {
294-
let tape_count = self.machine.get_tapes().len();
295-
let current_state = self.machine.get_state();
294+
let tape_count = self.machine.tapes().len();
295+
let current_state = self.machine.state();
296296
let current_symbols = self.machine.get_current_symbols();
297297

298298
let mut items = Vec::new();
@@ -346,7 +346,7 @@ impl App {
346346
}
347347

348348
fn render_help(&self, f: &mut Frame, area: Rect) {
349-
let tape_count = self.machine.get_tapes().len();
349+
let tape_count = self.machine.tapes().len();
350350

351351
let help_text = vec![
352352
Line::from("Controls:"),
@@ -408,7 +408,7 @@ impl App {
408408

409409
match self.machine.step() {
410410
ExecutionResult::Continue => {
411-
self.message = format!("Step {} completed", self.machine.get_step_count());
411+
self.message = format!("Step {} completed", self.machine.step_count());
412412
}
413413
ExecutionResult::Halt => {
414414
self.message = "Machine halted".to_string();
@@ -470,12 +470,13 @@ impl App {
470470

471471
fn load_current_program(&mut self) {
472472
let program = ProgramManager::get_program_by_index(self.current_program_index).unwrap();
473-
self.machine = TuringMachine::new(&program);
473+
let tape_count = program.tapes.len();
474+
let program_name = program.name.clone();
475+
self.machine = TuringMachine::new(program);
474476
self.auto_play = false;
475477
self.scroll_offset = 0;
476478

477-
let tape_count = program.tapes.len();
478-
self.message = format!("Loaded {}-tape program: {}", tape_count, program.name);
479+
self.message = format!("Loaded {}-tape program: {}", tape_count, program_name);
479480
}
480481

481482
pub fn toggle_help(&mut self) {

0 commit comments

Comments
 (0)