debugcModerate
Simple Turing machine simulator
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
turing.h
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
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;
#endifturing.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
-
-
-
some noisey comments ("exit unsucessfully", "allocate memory" etc)
-
-
preferred. But as you use it only before dying, no risk.
-
-
do you prefer
whichever you use, consistency is best.
-
don't you get a warning (control may reach end of non-void function, or
similar) from
-
in some places you can define loop variables in the loop:
-
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
-
your
-
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
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
whereas checking for something that can only happen if an algorithm is wrongly
coded is a fair use of
But I've seen asserts sprinkled around like confettii, so opinions clearly
differ. Maybe other reviewers will be able to help.
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 ispreferred. 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, butwhichever 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.strlenreturnssize_tandmalloctakessize_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
strdupif you have it.
- but note that mallocing a copy of
inputis not necessary. You can just declare the string aschar input[] = "..."instead ofchar* input = "..."and it will be writable.
- if you use
char input[]you need not dostrlen(input)- justsizeof 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 ofchecking. 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 conditionwhereas 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 useassert 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.