HiveBrain v1.2.0
Get Started
← Back to all entries
debugcModerate

Simple Turing machine simulator

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
turingsimulatorsimplemachine

Problem

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
#include
#include
#include
#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->mo

Solution

AJ, I like it. Very nicely coded.

Here are some comments. I've omitted things already mentioned by @LokiAstari

-
forward declaration of struct Transition is unnecessary.

-
die, prefer to print errors to stderr

-
exit(1) prefer exit(EXIT_FAILURE)

-
some noisey comments ("exit unsucessfully", "allocate memory" etc)

-
State_create does not initialise transitions (probably benign)

-
sprintf is often used badly, resulting in buffer overflows. snprintf is
preferred. But as you use it only before dying, no risk.

-
Turing_create does not check malloc (other uses do).

-
do you prefer type var or type var? I prefer the latter, but
whichever you use, consistency is best.

-
don't you get a warning (control may reach end of non-void function, or
similar) from Turing_step?

-
in some places you can define loop variables in the loop: for( int i = 0; ...

-
make local functions static. This is good practice but for a small project
like this makes no difference.

-
your list of Transition_create calls in main would be more readable if aligned.

-
your main has some issues in the copying of input.

  • strlen returns size_t and malloc takes size_t. So best to use


size_t (-Wsign-conversion)

  • sizeof(char) is 1 by definition



  • you malloced one too few bytes for the string copy.



  • you could have used strdup if you have it.



  • but note that mallocing a copy of input is not necessary. You can just declare the string as char input[] = "..." instead of char* input = "..." and it will be writable.



  • if you use char input[] you need not do strlen(input) - just sizeof input -1



-
passing the length of the input string around seems unnecessary (users of
the string can check for the terminating '\0')

On error checking and use of assert, I think you have done a good job of
checking. I'm not a big user of asserts (so maybe my thoughts on them are not
of much use to you). I'm always uneasy using an assert - they are funny
things. They say, in effect, that the tested condition is so important that it
is worth exiting if it occurs during testing, but that if it occurs during
normal use (NDEBUG) it can be ignored. That is illogical unless you can be
sure that all possible assert failures can be detected during testing. To me
that implies that asserts should be used only to test for errors that are
intrinsic to the program and have no dependence upon the data or the runtime.
For example checking whether malloc failed would use an if condition
whereas checking for something that can only happen if an algorithm is wrongly
coded is a fair use of assert. In your case, I might be tempted to use
assert where you check array entries are not NULL:

Transition *trans = state->transitions[ i ];
    assert( trans );


But I've seen asserts sprinkled around like confettii, so opinions clearly
differ. Maybe other reviewers will be able to help.

Code Snippets

Transition *trans = state->transitions[ i ];
    assert( trans );

Context

StackExchange Code Review Q#19814, answer score: 12

Revisions (0)

No revisions yet.