Yesterday I got a sudden urge to go from writing Python to something, well, more UNIX core-ish. I read some examples on here and decided I might as well put some of that stuff to use to test something else I'm working on: computability theory. Ergo: I wanted to write a basic, one tape, Turing machine simulator.
It works, as far as I know. It's not brilliant, and the Turing machine is hard coded in this early version, but it is functional.
I'd really like some review on the code. One thing I'm particularly curious about is whether I'm doing appropriate error checking and handling. e.g. are there specific cases where assert
is more appropriate or where I should do more error checking?
turing.h
#ifndef __turing_h__
#define __turing_h__
#define MAX_TRANSITIONS 5
#define MAX_STATES 25
// forward declare structs
struct State;
struct Transition;
typedef enum {
LEFT, RIGHT
} Direction;
typedef enum {
FALSE, TRUE
} Bool;
struct Transition {
char input;
char write;
Direction move;
struct State *next;
};
typedef struct Transition Transition;
struct State {
int id;
int trans_count;
struct Transition* transitions[ MAX_TRANSITIONS ];
Bool accept;
Bool reject;
};
typedef struct State State;
struct Turing {
int state_count;
State* states[ MAX_STATES ];
State* current;
int head;
};
typedef struct Turing Turing;
#endif
turing.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "turing.h"
// disable debug mode
#define NDEBUG
#include "debug.h"
// used to hand out ids
int state_id = 0;
void die( char *message )
{
if( message )
{
printf( "Error: %s.\n", message );
}
// exit unsuccesfully
exit(1);
}
Transition* Transition_create( char input, char write, Direction move, State *next )
{
// allocate memory
Transition *trans = malloc( sizeof( Transition ));
if( ! trans ) die( "Memory error" );
trans->input = input;
trans->write = write;
trans->move = move;
trans->next = next;
return trans;
}
void Transition_destroy( Transition* trans )
{
free( trans );
}
State* State_create( Bool accept, Bool reject )
{
// allocate mem
State *state = malloc( sizeof( State ));
if( ! state ) die( "Memory error" );
state->id = state_id++;
state->accept = accept;
state->reject = reject;
state->trans_count = 0;
return state;
}
void State_add_transition( State *state, Transition *trans )
{
// check if we can still add another transition
if( state->trans_count == MAX_TRANSITIONS ) {
char buffer[ 50 ];
sprintf( buffer, "State %d already has the maximum amount of transitions.", state->id );
die( buffer );
}
// add the transition
state->transitions[ state->trans_count ] = trans;
state->trans_count++;
}
void State_destroy( State *state )
{
int i = 0;
// loop over its transitions
for( i = 0; i < state->trans_count; i++ ) {
Transition *trans = state->transitions[ i ];
if( !trans ) die( "Could not fetch transition." );
Transition_destroy( trans );
}
free( state );
}
Turing* Turing_create()
{
// allocate mem
Turing *machine = malloc( sizeof( Turing ));
machine->state_count = 0;
machine->current = NULL;
machine->head = 0;
return machine;
}
void Turing_destroy( Turing *machine )
{
int i = 0;
// loop over it's states
for( i = 0; i < machine->state_count; i++ ) {
State *state = machine->states[ i ];
if( !state ) die( "Could not fetch turing state" );
State_destroy( state );
}
free( machine );
}
void Turing_add_state( Turing *machine, State *state )
{
if( machine->state_count == MAX_STATES ) {
die( "The turing machine already has the maximum amount of states" );
}
// add the state
machine->states[ machine->state_count++ ] = state;
}
State* Turing_step( Turing *machine, char* tape, int tape_len )
{
int i = 0;
char input = tape[ machine->head ];
State* state = machine->current;
// look for a transition on the given input
for( i = 0; i < state->trans_count; i++ ) {
Transition* trans = state->transitions[ i ];
if( !trans ) die( "Transition retrieval error" );
// check if this is a transition in the given char input
if( trans->input == input ) {
debug( "Found transition for input %c", input );
State *next = trans->next;
if( !next ) die( "Transitions to NULL state" );
// write if nescesary
if( trans->write != '\0' ) {
debug( "Writing %c", trans->write );
tape[ machine->head ] = trans->write;
debug( "Writing done" );
}
// move the head
if( trans->move == LEFT ) {
if( machine->head > 0 ) {
machine->head--;
debug( "Moved head left" );
}
} else {
if( machine->head + 1 >= tape_len ) {
die( "Machine walked of tape on right side" );
}
machine->head++;
debug( "Moved head right" );
}
// move the machine to the next state
debug( "Setting current state" );
machine->current = next;
return next;
}
}
char buffer[ 50 ];
sprintf( buffer, "Turing machine blocked: state %d for input %c", state->id, input );
die( buffer );
}
void Turing_run( Turing *machine, char *tape, int tapelen )
{
// check if the start state is configured properly
if( !machine->current ) die( "Turing machine has now start state" );
while( TRUE ) {
State* state = Turing_step( machine, tape, tapelen );
if( state->accept ) {
printf( "Input accepted in state: %d\n", state->id );
break;
} else if( state->reject ) {
printf( "Input rejected in state: %d\n", state->id );
break;
} else {
printf( "Moved to state: %d\n", state->id );
}
}
}
int main( int argc, char* argv[] )
{
Turing* machine = Turing_create();
State* q1 = State_create( FALSE, FALSE );
State* q2 = State_create( FALSE, FALSE );
State* q3 = State_create( FALSE, FALSE );
State* q4 = State_create( FALSE, FALSE );
State* q5 = State_create( FALSE, FALSE );
State* qaccept = State_create( TRUE, FALSE );
State* qreject = State_create( FALSE, TRUE );
Transition* q1_r_space = Transition_create( ' ', '\0', RIGHT, qreject );
Transition* q1_r_x = Transition_create( 'x', '\0', RIGHT, qreject );
Transition* q1_q2_zero = Transition_create( '0', ' ', RIGHT, q2 );
Transition* q2_q2_x = Transition_create( 'x', '\0', RIGHT, q2 );
Transition* q2_a_space = Transition_create( ' ', '\0', RIGHT, qaccept );
Transition* q2_q3_zero = Transition_create( '0', 'x', RIGHT, q3 );
Transition* q3_q3_x = Transition_create( 'x', '\0', RIGHT, q3 );
Transition* q3_q4_zero = Transition_create( '0', '\0', RIGHT, q4 );
Transition* q3_q5_space = Transition_create( ' ', '\0', LEFT, q5 );
Transition* q4_q3_zero = Transition_create( '0', 'x', RIGHT, q3 );
Transition* q4_q4_x = Transition_create( 'x', '\0', RIGHT, q4 );
Transition* q4_r_space = Transition_create( ' ', '\0', RIGHT, qreject );
Transition* q5_q5_zero = Transition_create( '0', '\0', LEFT, q5 );
Transition* q5_q5_x = Transition_create( 'x', '\0', LEFT, q5 );
Transition* q5_q2_space = Transition_create( ' ', '\0', RIGHT, q2 );
State_add_transition( q1, q1_r_space );
State_add_transition( q1, q1_r_x );
State_add_transition( q1, q1_q2_zero );
State_add_transition( q2, q2_q2_x );
State_add_transition( q2, q2_a_space );
State_add_transition( q2, q2_q3_zero );
State_add_transition( q3, q3_q3_x );
State_add_transition( q3, q3_q4_zero );
State_add_transition( q3, q3_q5_space );
State_add_transition( q4, q4_q3_zero );
State_add_transition( q4, q4_q4_x );
State_add_transition( q4, q4_r_space );
State_add_transition( q5, q5_q5_zero );
State_add_transition( q5, q5_q5_x );
State_add_transition( q5, q5_q2_space );
Turing_add_state( machine, q1 );
Turing_add_state( machine, q2 );
Turing_add_state( machine, q3 );
Turing_add_state( machine, q4 );
Turing_add_state( machine, q5 );
Turing_add_state( machine, qaccept );
Turing_add_state( machine, qreject );
machine->current = q1;
char* input = "0000000000000000 ";
int len = strlen( input );
char* tape = malloc( len * sizeof( char ));
strcpy( tape, input );
Turing_run( machine, tape, len );
// clean up
Turing_destroy( machine );
free( tape );
}
debug.h
#ifndef __debug_h__
#define __debug_h__
#ifndef NDEBUG
#define debug(M, ... ) fprintf( stderr, "DEBUG: %s:%d: " M "\n", __FILE__, __LINE__, ##__VA_ARGS__ )
#else
#define debug(M, ... )
#endif
#endif
For now the simulator is configured to accept on input strings from the language:
L = { 0^2^n | n > 0 }
Or: all strings of 0s whose length is a power of 2.
If you happen to have Sipser Introduction to the Theory of Computation. It's on page 145 of the 2nd edition.
GIT repo up and documentation on the way (we have a tiny wiki with basic usage)