Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<img src=".github/tur-logo.png" width="400" />
</p>

**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.

<table>
<thead>
Expand Down Expand Up @@ -133,16 +133,17 @@ 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

### Command Line Interface (CLI)

```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)
Expand Down
70 changes: 46 additions & 24 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -12,38 +12,60 @@ 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:
```

### 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.
1 change: 1 addition & 0 deletions platforms/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
120 changes: 81 additions & 39 deletions platforms/cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use clap::Parser;
use std::io::{self, BufRead};
use std::path::Path;
use tur::loader::ProgramLoader;
use tur::machine::TuringMachine;
Expand Down Expand Up @@ -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::<Vec<String>>()
.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::<Vec<String>>()
.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<Vec<String>, 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())
}
}
29 changes: 15 additions & 14 deletions platforms/tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -46,7 +46,7 @@ impl App {
pub fn new_from_program_string(program_content: String) -> Result<Self, String> {
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,
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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: '{}')",
Expand All @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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:"),
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down
Loading