From 86194ae081dd419626be038e044c818a5160f405 Mon Sep 17 00:00:00 2001 From: Maurizio Porrato Date: Fri, 4 Sep 2020 11:05:16 +0100 Subject: [PATCH] First commit --- .gitignore | 4 + Makefile | 24 ++++++ program.c | 121 +++++++++++++++++++++++++++ program.h | 26 ++++++ readme.md | 9 ++ samples/binincr.program | 6 ++ samples/binincr.tape.1 | 1 + samples/busybeaver.program | 6 ++ samples/busybeaver.tape.1 | 1 + table.c | 151 +++++++++++++++++++++++++++++++++ table.h | 34 ++++++++ tape.c | 167 +++++++++++++++++++++++++++++++++++++ tape.h | 24 ++++++ turing.c | 164 ++++++++++++++++++++++++++++++++++++ 14 files changed, 738 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 program.c create mode 100644 program.h create mode 100644 readme.md create mode 100644 samples/binincr.program create mode 100644 samples/binincr.tape.1 create mode 100644 samples/busybeaver.program create mode 100644 samples/busybeaver.tape.1 create mode 100644 table.c create mode 100644 table.h create mode 100644 tape.c create mode 100644 tape.h create mode 100644 turing.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07b3c19 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*[~%] +*.o +core +turing diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..536ba34 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +.POSIX: +.PHONY: clean all pretty +DEFINES=-D_XOPEN_SOURCE=500 +CFLAGS=-std=c99 -Wall -pedantic $(DEFINES) +RM=-rm -f + +BINARIES=turing +OFILES=turing.o table.o program.o tape.o + +all: $(BINARIES) + +turing.o: turing.c table.h program.h tape.h +table.o: table.c table.h +program.o: program.c program.h table.h +tape.o: tape.c tape.h table.h + +turing: $(OFILES) + $(CC) $(LDFLAGS) -o $@ $(OFILES) $(LDLIBS) + +pretty: + astyle --style=mozilla *.c *.h + +clean: + $(RM) *% *~ *.o *.orig core $(BINARIES) diff --git a/program.c b/program.c new file mode 100644 index 0000000..61a99c7 --- /dev/null +++ b/program.c @@ -0,0 +1,121 @@ +#include +#include +#include +#include + +#include "table.h" +#include "program.h" + +static void *row_clone(void *p) +{ + struct instruction *row; + + row = (struct instruction *)malloc(sizeof(struct instruction)); + memcpy((void *)row, p, sizeof(struct instruction)); + + return (void *)row; +} + +static int row_cmp(void *a, void *b) +{ + struct instruction *ra, *rb; + + ra = (struct instruction *)a; + rb = (struct instruction *)b; + + if (ra->state < rb->state) + return -1; + if (ra->state > rb->state) + return 1; + if (ra->symbol < rb->symbol) + return -1; + if (ra->symbol > rb->symbol) + return 1; + + return 0; +} + +static void row_repr(void *p, char *buf, int size) +{ + struct instruction *row; + static char *moves = "LNR"; + + row = (struct instruction *)p; + + snprintf(buf, size, "[%d %d | %d %d %c]", + row->state, row->symbol, row->nextstate, row->write, + ((row->move >= -1) && (row->move <=1) ? moves[1+row->move] : '?')); + buf[size-1] = '\0'; +} + +static short int parse_move(char *m) +{ + char c; + + c = (char)tolower(m[0]); + if (c == 'l') + return -1; + if (c == 'r') + return 1; + if (c == 'n') + return 0; + + return atoi(m); +} + +struct table *program_new() +{ + return table_new(20, 10, row_clone, free, row_cmp, row_repr); +} + +int program_load(FILE *f, struct table *prg, struct table *st, struct table *sym) +{ + char line[LINE_LEN+1]; + char state[STATE_LEN+1]; + char symbol[SYMBOL_LEN+1]; + char write[SYMBOL_LEN+1]; + char move[8]; + char nextstate[STATE_LEN+1]; + int ll; + char *l; + char *p; + int r; + struct instruction row; + int cnt; + + line[LINE_LEN] = '\0'; + cnt = 0; + for (;;) { + l = fgets(line, sizeof(line)-1, f); + if (l == NULL) + break; + ll = strlen(l); + if (ll > 0) { + p = &line[ll-1]; + while ((ll > 0) && ((*p == '\r') || (*p == '\n'))) { + *p = '\0'; + ll--; + } + } + p = strchr(l, '#'); + if (p != NULL) + *p = '\0'; + r = sscanf(l, "%s %s %s %s %s", + state, symbol, nextstate, write, move); + if (r == 0) + continue; + if (r != 5) { + printf("Parsing error!\n"); + } else { + row.state = table_insert(st, state); + row.symbol = table_insert(sym, symbol); + row.write = table_insert(sym, write); + row.move = parse_move(move); + row.nextstate = table_insert(st, nextstate); + table_insert(prg, &row); + cnt++; + } + } + + return cnt; +} diff --git a/program.h b/program.h new file mode 100644 index 0000000..ee0b063 --- /dev/null +++ b/program.h @@ -0,0 +1,26 @@ +#ifndef _PROGRAM_H +#define _PROGRAM_H + +#include +#include + +#include "table.h" +#include "program.h" + +#define LINE_LEN 100 +#define SYMBOL_LEN 20 +#define STATE_LEN 20 + +struct instruction +{ + int state; + int symbol; + int write; + short int move; + int nextstate; +}; + +struct table *program_new(); +int program_load(FILE *f, struct table *prg, struct table *st, struct table *sym); + +#endif diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..825a64f --- /dev/null +++ b/readme.md @@ -0,0 +1,9 @@ +A simple and portable Turing Machine simulator +============================================== + +This code should build cleanly on any POSIX OS using a C99 compliant +C compiler. + +The goal of this project is to be easily understandable and hackable, +and is not meant to be efficient. + diff --git a/samples/binincr.program b/samples/binincr.program new file mode 100644 index 0000000..c70517f --- /dev/null +++ b/samples/binincr.program @@ -0,0 +1,6 @@ +start nul carry nul L +start 0 start 0 R +start 1 start 1 R +carry nul halt 1 N +carry 0 halt 1 N +carry 1 carry 0 L diff --git a/samples/binincr.tape.1 b/samples/binincr.tape.1 new file mode 100644 index 0000000..f1ab9ef --- /dev/null +++ b/samples/binincr.tape.1 @@ -0,0 +1 @@ +1 1 0 0 1 0 1 1 diff --git a/samples/busybeaver.program b/samples/busybeaver.program new file mode 100644 index 0000000..51febbd --- /dev/null +++ b/samples/busybeaver.program @@ -0,0 +1,6 @@ +A 0 B 1 R +A 1 C 1 L +B 0 A 1 L +B 1 B 1 R +C 0 B 1 L +C 1 halt 1 N diff --git a/samples/busybeaver.tape.1 b/samples/busybeaver.tape.1 new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/samples/busybeaver.tape.1 @@ -0,0 +1 @@ + diff --git a/table.c b/table.c new file mode 100644 index 0000000..d357f2e --- /dev/null +++ b/table.c @@ -0,0 +1,151 @@ +#include +#include +#include +#include + +#include "table.h" + +struct table *table_new(int capacity, int increment, + item_clone_f clone_f, item_free_f free_f, item_compare_f compare_f, item_repr_f repr_f) +{ + struct table *r; + + r = (struct table *)malloc(sizeof(*r)); + if (r != NULL) { + r->items = (void **)malloc(capacity*sizeof(void *)); + if (r->items == NULL) { + free(r); + return NULL; + } + r->indexes = (int *)malloc(capacity*sizeof(int)); + if (r->indexes == NULL) { + free(r->items); + free(r); + return NULL; + } + r->size = 0; + r->capacity = capacity; + r->increment = increment; + r->clone_f = clone_f; + r->free_f = free_f; + r->compare_f = compare_f; + r->repr_f = repr_f; + r->dirty = true; + r->lookup = NULL; + } + + return r; +} + +void table_free(struct table *t) +{ + if (t->lookup != NULL) + free(t->lookup); + if (t->free_f != NULL) + for (int i=0; i < t->size; i++) + t->free_f(t->items[i]); + free(t->indexes); + free(t->items); + free(t); +} + +static bool table_find_internal(struct table *t, void *entry, int *pos) +{ + int cmp; + + for (*pos=0; *pos < t->size; (*pos)++) { + cmp = t->compare_f(entry, t->items[*pos]); + if (cmp == 0) + return true; + if (cmp < 0) + return false; + } + + return false; +} + +int table_find(struct table *t, void *entry) +{ + int pos; + + if (table_find_internal(t, entry, &pos)) + return t->indexes[pos]; + else + return -1; +} + +int table_insert(struct table *t, void *entry) +{ + bool found; + int pos; + void **newitems; + int *newindexes; + void *swap; + int swapidx; + int r; + int newcapacity; + + found = table_find_internal(t, entry, &pos); + + if (!found) { + if (t->capacity <= t->size) { + newcapacity = t->capacity + t->increment; + newitems = (void **) realloc( + t->items, newcapacity*sizeof(void *)); + if (newitems == NULL) + return -1; + newindexes = (int *) realloc( + t->indexes, newcapacity*sizeof(int)); + if (newindexes == NULL) { + t->items = (void **) realloc( + newitems, t->capacity*sizeof(void *)); + return -1; + } + t->items = newitems; + t->indexes = newindexes; + t->capacity = newcapacity; + } + r = t->size; + if (t->clone_f != NULL) + entry = t->clone_f(entry); + for (int i=pos; i <= t->size; i++) { + swap = t->items[i]; + swapidx = t->indexes[i]; + t->items[i] = entry; + t->indexes[i] = r; + entry = swap; + r = swapidx; + } + t->size++; + t->dirty = true; + } + + return t->indexes[pos]; +} + +void *table_lookup(struct table *t, int idx) +{ + if (t->dirty) { + t->lookup = realloc(t->lookup, t->size*sizeof(void *)); + // TODO: check allocation + for (int i=0; isize; i++) + t->lookup[t->indexes[i]] = t->items[i]; + t->dirty = false; + } + + return t->lookup[idx]; +} + +void table_print(struct table *t) +{ + char buf[200]; + + printf("size=%d capacity=%d increment=%d\n", t->size, t->capacity, t->increment); + for (int i=0; isize; i++) { + if (t->repr_f != NULL) + t->repr_f(t->items[i], buf, sizeof(buf)); + printf(" %d (%d) %s\n", i, t->indexes[i], + (t->repr_f != NULL ? buf : "???")); + } +} + diff --git a/table.h b/table.h new file mode 100644 index 0000000..cc9a4c1 --- /dev/null +++ b/table.h @@ -0,0 +1,34 @@ +#ifndef __TABLE_H +#define __TABLE_H + +#include + +typedef void *(*item_clone_f)(void *); +typedef void (*item_free_f)(void *); +typedef int (*item_compare_f)(void *, void *); +typedef void (*item_repr_f)(void *, char *, int); + +struct table +{ + int size; // Number of entries actually used + int capacity; // Total allocated number of entries + int increment; // How many additional entries to allocate when growing the table + item_clone_f clone_f; + item_free_f free_f; + item_compare_f compare_f; + item_repr_f repr_f; + void **items; // Table entries, sorted + int *indexes; // Maps entried ids to their index inside the items array + void **lookup; // Cached lookup results + bool dirty; // Cached lookup entries are not up to date +}; + +struct table *table_new(int capacity, int increment, + item_clone_f clone_f, item_free_f free_f, item_compare_f compare_f, item_repr_f repr_f); +void table_free(struct table *t); +int table_find(struct table *t, void *entry); +int table_insert(struct table *t, void *entry); +void *table_lookup(struct table *t, int idx); +void table_print(struct table *t); + +#endif diff --git a/tape.c b/tape.c new file mode 100644 index 0000000..f3f0e46 --- /dev/null +++ b/tape.c @@ -0,0 +1,167 @@ +#include +#include +#include +#include "tape.h" + +struct tape *tape_new(int blank) +{ + struct tape *r; + + r = (struct tape *)malloc(sizeof(struct tape)); + + if (r != NULL) { + r->head = 0; + r->blank = blank; + r->ncapacity = r->pcapacity = 0; + r->nsyms = r->psyms = NULL; + } + + return r; +} + +int tape_read(struct tape *t, int pos) +{ + if (pos >= 0) { + if (pos >= t->pcapacity) + return t->blank; + else + return t->psyms[pos]; + } else { + pos = -1 - pos; + if (pos >= t->ncapacity) + return t->blank; + else + return t->nsyms[pos]; + } +} + +static bool tape_grow(struct tape *t, int pos) +{ + int **side; + int *capacity; + int newcapacity; + int *newside; + + if (pos >= 0) { + side = &t->psyms; + capacity = &t->pcapacity; + } else { + pos = -1 - pos; + side = &t->nsyms; + capacity = &t->ncapacity; + } + + if (pos + 1 < *capacity) + return true; + + newcapacity = pos + TAPE_CHUNK_SIZE; + printf("Grow tape to %d\n", newcapacity); + newside = (int *)realloc(*side, newcapacity*sizeof(int)); + + if (newside == NULL) + return false; + + *side = newside; + while (*capacity < newcapacity) + (*side)[(*capacity)++] = t->blank; + + return true; +} + +void tape_write(struct tape *t, int pos, int sym) +{ + if (pos >= 0) { + if (pos >= t->pcapacity) { + if (sym == t->blank) + return; + if (!tape_grow(t, pos)) // TODO: error out + return; + } + t->psyms[pos] = sym; + } else { + pos = -1 - pos; + if (pos >= t->ncapacity) { + if (sym == t->blank) + return; + if (!tape_grow(t, -1 - pos)) // TODO: error out + return; + } + t->nsyms[pos] = sym; + } +} + +int tape_load(struct tape *t, FILE *f, int offset, struct table *symbols) +{ + char line[1000]; + char *l; + int ll; + char *tok; + char *toksave; + static char *delim = " \t\f\n\r"; + int counter = 0; + + for (;;) { + l = fgets(line, sizeof(line)-1, f); + if (l == NULL) + break; + ll = strlen(l); + if (ll > 0) { + tok = strtok_r(line, delim, &toksave); + while (tok != NULL) { + tape_write(t, offset+counter, table_insert(symbols, tok)); + counter++; + tok = strtok_r(NULL, delim, &toksave); + } + } + } + + return counter; +} + +#define HL_ON "\x01b[7m" +#define HL_OFF "\x01b[0m" + +void tape_print(struct tape *t, struct table *sym) +{ + int si; + int first, last; + + printf("TAPE: "); + + if ((t->ncapacity == 0) && (t->pcapacity == 0)) { + printf("(empty)\n"); + return; + } + + first = t->pcapacity; + last = -1-t->ncapacity; + for (int i=-1-t->ncapacity; ipcapacity; i++) { + si = tape_read(t, i); + if ((si != t->blank) || (i == t->head)) { + if (i < first) + first = i; + if (i > last) + last = i; + } + } + + if ((first == last) && (tape_read(t, first) == t->blank)) { + printf("(empty)\n"); + return; + } + + for (int i=first; i <= last; i++) { + if (i == t->head) + printf(HL_ON); + si = tape_read(t, i); + if (sym == NULL) + printf("%d", si); + else + printf("%s", (char *)table_lookup(sym, si)); + if (i == t->head) + printf(HL_OFF); + printf(" "); + } + printf("\n"); +} + diff --git a/tape.h b/tape.h new file mode 100644 index 0000000..3046e62 --- /dev/null +++ b/tape.h @@ -0,0 +1,24 @@ +#ifndef _TAPE_H +#define _TAPE_H +#include +#include +#include +#include "table.h" + +#define TAPE_CHUNK_SIZE 100 + +struct tape +{ + int head; // Head position + int blank; // Blank symbol + int *nsyms, *psyms; // Halves of the infinite tape for negative and positive indices + int ncapacity, pcapacity; // Capacities of the two tape halves +}; + +struct tape *tape_new(int blank); +int tape_read(struct tape *t, int pos); +void tape_write(struct tape *t, int pos, int sym); +int tape_load(struct tape *t, FILE *f, int offset, struct table *symbols); +void tape_print(struct tape *t, struct table *sym); + +#endif diff --git a/turing.c b/turing.c new file mode 100644 index 0000000..8eba816 --- /dev/null +++ b/turing.c @@ -0,0 +1,164 @@ +#include +#include +#include +#include +#include + +#include "table.h" +#include "tape.h" +#include "program.h" + +struct turing +{ + struct table *states; + struct table *symbols; + struct table *program; +}; + +void *str_clone(void *p) +{ + return (void *)strdup((char *)p); +} + +int str_cmp(void *a, void *b) +{ + return strcmp((char *)a, (char *)b); +} + +void str_repr(void *p, char *buf, int size) +{ + strncpy(buf, (char *)p, size-1); + buf[size-1] = '\0'; +} + +void turing_free(struct turing *tm) +{ + if (tm == NULL) + return; + if (tm->program != NULL) + table_free(tm->program); + if (tm->symbols != NULL) + table_free(tm->symbols); + if (tm->states != NULL) + table_free(tm->states); + free(tm); +} + +struct turing *turing_new(char *programfn, char *start, char *blank) +{ + struct turing *r; + FILE *programf; + + programf = fopen(programfn, "r"); + if (programf == NULL) + return NULL; + + r = (struct turing *)malloc(sizeof(struct turing)); + + if (r == NULL) + return NULL; + + r->states = r->symbols = r->program = NULL; + r->states = table_new(20, 10, str_clone, free, str_cmp, str_repr); + r->symbols = table_new(20, 10, str_clone, free, str_cmp, str_repr); + r->program = program_new(); + if ((r->states == NULL) || (r->symbols == NULL) || (r->program == NULL)) { + turing_free(r); + return NULL; + } + + if (start != NULL) + table_insert(r->states, start); + if (blank != NULL) + table_insert(r->symbols, blank); + + program_load(programf, r->program, r->states, r->symbols); + fclose(programf); + + printf("STATES: "); + table_print(r->states); + printf("SYMBOLS: "); + table_print(r->symbols); + printf("PROGRAM: "); + table_print(r->program); + + return r; +} + +int turing_run(struct turing *tm, char *tapefn) +{ + FILE *tapef; + struct instruction row, *insn; + int iinsn; + int steps; + struct tape *tape; + + + tapef = fopen(tapefn, "r"); + if (tapef == NULL) + return EXIT_FAILURE; + + tape = tape_new(0); + tape_load(tape, tapef, 0, tm->symbols); + + fclose(tapef); + + steps = 0; + row.state = 0; + for (;;) { + tape_print(tape, tm->symbols); + row.symbol = tape_read(tape, tape->head); + iinsn = table_find(tm->program, &row); + if (iinsn < 0) + break; + insn = table_lookup(tm->program, iinsn); + row.state = insn->nextstate; + tape_write(tape, tape->head, insn->write); + tape->head += insn->move; + steps++; + } + + return steps; +} + +int main(int argc, char *argv[]) +{ + int opt; + char *start, *blank; + + struct turing *tm; + + start = blank = NULL; + + while ((opt = getopt(argc, argv, "s:b:")) != -1) { + switch (opt) { + case 's': + start = optarg; + break; + case 'b': + blank = optarg; + break; + default: + fprintf(stderr, "Usage: %s [-s start] [-b blank] program tape\n", + argv[0]); + return EXIT_FAILURE; + } + } + + if (optind >= argc - 1) { + fprintf(stderr, "Expected arguments\n"); + return EXIT_FAILURE; + } + + tm = turing_new(argv[optind], start, blank); + if (tm == NULL) { + fprintf(stderr, "Error loading program.\n"); + return EXIT_FAILURE; + } + + turing_run(tm, argv[optind+1]); + + turing_free(tm); + + return EXIT_SUCCESS; +}