tmsim

A fast turing machine simulator with graphviz export functionality

git clone https://git.8pit.net/tmsim.git

  1/*
  2 * Copyright © 2016-2018 Sören Tempel
  3 *
  4 * This program is free software: you can redistribute it and/or
  5 * modify it under the terms of the GNU Affero General Public
  6 * License as published by the Free Software Foundation, either
  7 * version 3 of the License, or (at your option) any later version.
  8 *
  9 * This program is distributed in the hope that it will be useful,
 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 12 * Affero General Public License for more details.
 13 *
 14 * You should have received a copy of the GNU Affero General Public
 15 * License along with this program. If not, see
 16 * <http://www.gnu.org/licenses/>.
 17 */
 18
 19#include <assert.h>
 20#include <ctype.h>
 21#include <stdio.h>
 22#include <stdlib.h>
 23#include <string.h>
 24
 25#include <sys/types.h>
 26
 27#include "turing.h"
 28#include "util.h"
 29
 30/**
 31 * Macro used to iterate over all entries of a tmmap.
 32 *
 33 * @param MAP Pointer to a tmmap to iterate over.
 34 * @param VARNAME Variable name used for current item.
 35 * @param IDXVARNAME Variable used to iterate through buckets.
 36 */
 37#define MAP_FOREACH(MAP, VARNAME, IDXVARNAME) \
 38	for (IDXVARNAME = 0; IDXVARNAME < MAP->size; IDXVARNAME++) \
 39		for (VARNAME = MAP->entries[IDXVARNAME]; VARNAME; \
 40		     VARNAME = VARNAME->next)
 41
 42/**
 43 * Allocates memory for a tmmap and initializes it.
 44 *
 45 * @param size Amount of buckets that should be used.
 46 * @returns Pointer to the initialized tmmap.
 47 */
 48static tmmap *
 49newtmmap(size_t size)
 50{
 51	size_t i;
 52	tmmap *map;
 53
 54	map = emalloc(sizeof(tmmap));
 55	map->size = size;
 56	map->entries = emalloc(sizeof(mapentry *) * size);
 57
 58	for (i = 0; i < size; i++)
 59		map->entries[i] = NULL;
 60
 61	return map;
 62}
 63
 64/**
 65 * Hash function for bucket hashing.
 66 *
 67 * @param map Map to calculate hash for.
 68 * @param key Key that should be hashed.
 69 * @returns Hash of the given key.
 70 */
 71static size_t
 72hash(tmmap *map, mapkey key)
 73{
 74	/* XXX: A more advanced hashing function could be
 75	 * used here but this is good enough for now. */
 76	return (size_t)key % map->size;
 77}
 78
 79/**
 80 * Allocates memory and initializes a new map entry / bucket for the tmmap.
 81 *
 82 * @param key Key which should be used for this entry.
 83 * @returns Pointer to initialized mapentry.
 84 */
 85static mapentry *
 86newmapentry(mapkey key)
 87{
 88	mapentry *ent;
 89
 90	ent = emalloc(sizeof(mapentry));
 91	ent->key = key;
 92	ent->next = NULL;
 93	return ent;
 94}
 95
 96/**
 97 * Frees allocated memory for a map entry.
 98 *
 99 * @param ent Pointer to map entry which should be freed.
100 */
101static void
102freemapentry(mapentry *ent)
103{
104	mapentry *next;
105
106	assert(ent);
107
108	do {
109		next = ent->next;
110		free(ent);
111		ent = next;
112	} while (ent != NULL);
113}
114
115/**
116 * Adds a new value to the tmmap, if the key is not already present.
117 *
118 * @param map Map to which a key should be added.
119 * @param ent Entry which should be added.
120 * @returns -1 if the key was already present, 0 otherwise.
121 */
122static int
123setval(tmmap *map, mapentry *ent)
124{
125	size_t idx;
126	mapentry *buck, *last, *next;
127
128	idx = hash(map, ent->key);
129	if (!(buck = map->entries[idx])) {
130		map->entries[idx] = ent;
131		return 0;
132	}
133
134	for (last = next = buck; next != NULL; next = next->next) {
135		if (ent->key == next->key)
136			return -1;
137
138		last = next;
139	}
140
141	last->next = ent;
142	return 0;
143}
144
145/**
146 * Reads the associated value for a given key from the map.
147 *
148 * @param map Map to use for key lookup.
149 * @param key Key which should be looked up.
150 * @param dest Pointer to mapentry used to store the associated value.
151 * @returns -1 if a value for the given key didn't exist, 0 otherwise.
152 */
153static int
154getval(tmmap *map, mapkey key, mapentry **dest)
155{
156	size_t idx;
157	mapentry *buck, *next;
158
159	idx = hash(map, key);
160	if (!(buck = map->entries[idx]))
161		return -1;
162
163	for (next = buck; next != NULL && next->key != key; next = next->next)
164		;
165
166	if (next == NULL)
167		return -1;
168
169	*dest = next;
170	return 0;
171}
172
173/**
174 * Allocates memory for a new tape entry and initializes it.
175 *
176 * @param value Symbol of this tape entry.
177 * @param prev Entry on the left hand side of this one.
178 * @param next Entry on the right hand side of this one.
179 * @returns Pointer to the newly created tape entry.
180 */
181static tapeentry *
182newtapeentry(char value, tapeentry *prev, tapeentry *next)
183{
184	tapeentry *entr;
185
186	entr = emalloc(sizeof(tapeentry));
187	entr->value = value;
188	entr->next = next;
189	entr->prev = prev;
190	return entr;
191}
192
193/**
194 * Allocates memory for a new state and initializes it.
195 *
196 * @returns Pointer to the newly created state.
197 */
198tmstate *
199newtmstate(void)
200{
201	tmstate *state;
202
203	state = emalloc(sizeof(tmstate));
204	state->name = 0;
205	state->trans = newtmmap(TRANSMAPSIZ);
206	return state;
207}
208
209/**
210 * Allocates memory for a new turing maschine and initializes it.
211 *
212 * @returns Pointer to the newly created turing maschine.
213 */
214dtm *
215newtm(void)
216{
217	dtm *tm;
218
219	tm = emalloc(sizeof(dtm));
220	tm->states = newtmmap(STATEMAPSIZ);
221	tm->start = 0;
222	tm->tape = newtapeentry(BLANKCHAR, NULL, NULL);
223	tm->accept = emalloc(ACCEPTSTEP * sizeof(tmname));
224	tm->acceptsiz = 0;
225	return tm;
226}
227
228/**
229 * Adds an accepting state (identified by name) to a turing maschine.
230 *
231 * @param tm Turing maschine to which the state should be added.
232 * @param state Name of the state.
233 */
234void
235addaccept(dtm *tm, tmname state)
236{
237	size_t newsiz;
238
239	if (tm->acceptsiz && tm->acceptsiz % ACCEPTSTEP == 0) {
240		newsiz = (tm->acceptsiz + ACCEPTSTEP) * sizeof(tmname);
241		tm->accept = erealloc(tm->accept, newsiz);
242	}
243
244	tm->accept[tm->acceptsiz++] = state;
245}
246
247/**
248 * Adds a new state to an existing turing maschine.
249 *
250 * @param tm Turing maschine to which a state should be added.
251 * @param state State which should be added to the turing maschine.
252 * @returns -1 if a state with the given name already exists, 0 otherwise.
253 */
254int
255addstate(dtm *tm, tmstate *state)
256{
257	int ret;
258	mapentry *entry;
259
260	entry = newmapentry(state->name);
261	entry->data.state = state;
262
263	if ((ret = setval(tm->states, entry)))
264		freemapentry(entry);
265
266	return ret;
267}
268
269/**
270 * Retrieves a state form an exsting turing maschine.
271 *
272 * @param tm Turing machine from which a state should be retrieved.
273 * @param name Name of the state that should be retrieved.
274 * @param dest Pointer to a state which should be used for storing the result.
275 * @returns -1 if the state doesn't exist, 0 otherwise.
276 */
277int
278getstate(dtm *tm, tmname name, tmstate **dest)
279{
280	int ret;
281	mapentry *entry;
282
283	if ((ret = getval(tm->states, name, &entry)))
284		return ret;
285
286	*dest = entry->data.state;
287	return ret;
288}
289
290/**
291 * Adds a transition to an existing turing maschine state.
292 *
293 * @param state State to which a new transition should be added.
294 * @param trans Pointer to the transition which should be added to the state.
295 * @returns -1 if a state with the given symbol already exists, 0 otherwise.
296 */
297int
298addtrans(tmstate *state, tmtrans *trans)
299{
300	int ret;
301	mapentry *entry;
302
303	entry = newmapentry(trans->rsym);
304	entry->data.trans = trans;
305
306	if ((ret = setval(state->trans, entry)))
307		freemapentry(entry);
308
309	return ret;
310}
311
312/**
313 * Retrieves a transition from an existing turing state.
314 *
315 * @param state State from which a transition should be extracted.
316 * @param rsym Symbol which triggers the tranisition.
317 * @param dest Double pointer used for storing the result.
318 * @returns -1 if a transition with the given symbol doesn't exist, 0 otherwise.
319 */
320int
321gettrans(tmstate *state, char rsym, tmtrans **dest)
322{
323	int ret;
324	mapentry *entry;
325
326	if ((ret = getval(state->trans, rsym, &entry)))
327		return ret;
328
329	*dest = entry->data.trans;
330	return ret;
331}
332
333/**
334 * Writes the given string to the tape of the given turing maschine.
335 *
336 * @param tm Turing machine to modify tape of.
337 * @param str String which should be written to the tape.
338 */
339void
340writetape(dtm *tm, char *str)
341{
342	char c;
343	tapeentry *ent, *last;
344
345	last = tm->tape;
346	for (ent = tm->tape; ent; ent = ent->next)
347		last = ent;
348	assert(last);
349
350	while ((c = *str++)) {
351		ent = newtapeentry(c, last, NULL);
352		last->next = ent;
353		last = ent;
354	}
355}
356
357/**
358 * Writes the content of the tape to STDOUT. The output is always
359 * terminated by a newline character.
360 *
361 * The tape is (theoretically speaking) infinite but since we don't have
362 * infinite amounts of memory we only generate the blanks on the
363 * beginning and end of the tape when you access them. Therefore, the
364 * output will contain as many blanks as accessed by the caller.
365 *
366 * However, the output might start with a blank even if you didn't access
367 * the left-hand side of the tape since the turing machines tape is by
368 * default initialized with a single blank character.
369 *
370 * @param tm Turing machine whose tape should be read.
371 */
372void
373printtape(dtm *tm)
374{
375	tapeentry *c, *s;
376
377	for (s = tm->tape; s->prev; s = s->prev)
378		;
379
380	for (c = s; c; c = c->next)
381		putchar(c->value);
382	putchar('\n');
383}
384
385/**
386 * Whether or not the given state name maps to an accepting state.
387 *
388 * @param tm Turing machine which defines the accepting states.
389 * @param name State name to check.
390 * @returns 0 if it does, -1 if it doesn't.
391 */
392static int
393isaccepting(dtm *tm, tmname name)
394{
395	size_t i;
396
397	for (i = 0; i < tm->acceptsiz; i++)
398		if (tm->accept[i] == name)
399			return 0;
400
401	return -1;
402}
403
404/**
405 * Performs transitions form the given state tail recursively until a state
406 * without any new transitions for the current tape symbol is reached.
407 *
408 * @param tm Turing machine to perform transitions on.
409 * @param state State to perform transitions from.
410 * @return 0 if the reached state is an accepting state, -1 otherwise.
411 */
412static int
413compute(dtm *tm, tmstate *state)
414{
415	char in;
416	tmtrans *trans;
417	static tmstate *next; /* static to enable tail call optimization. */
418
419	if (!tm->tape->next)
420		tm->tape->next = newtapeentry(BLANKCHAR, tm->tape, NULL);
421
422	in = tm->tape->next->value;
423	if (gettrans(state, in, &trans))
424		return isaccepting(tm, state->name);
425
426	tm->tape->next->value = trans->wsym;
427	switch (trans->headdir) {
428	case RIGHT:
429		tm->tape = tm->tape->next;
430		break;
431	case LEFT:
432		if (!tm->tape->prev)
433			tm->tape->prev = newtapeentry(BLANKCHAR, NULL,
434			                              tm->tape);
435		tm->tape = tm->tape->prev;
436		break;
437	case STAY:
438		/* Nothing to do here. */
439		break;
440	}
441
442	if (getstate(tm, trans->nextstate, &next))
443		return isaccepting(tm, trans->nextstate);
444
445	return compute(tm, next);
446}
447
448/**
449 * Starts the turing machine. Meaning it will extract the initial state from
450 * the given tm and will perform transitions (tail recursively) from this state
451 * until a state without any further transitions is reached.
452 *
453 * @param tm Turing machine which should be started.
454 * @return 0 if the reached state is an accepting state, -1 otherwise.
455 */
456int
457runtm(dtm *tm)
458{
459	tmstate *start;
460
461	/* tm->tape->next is only NULL here if the user supplied the
462	 * empty word as an input for this turing maschine. In that
463	 * case we don't want to perform any further transitions. */
464	if (getstate(tm, tm->start, &start) || !tm->tape->next)
465		return isaccepting(tm, tm->start);
466
467	return compute(tm, start);
468}
469
470/**
471 * Iterates over each state of the given turing machine and
472 * invokes the given function for that state.
473 *
474 * @param tm Turing machine to iterate over.
475 * @param fn Function to invoke for each state.
476 * @param arg Additional argument to passed to the function.
477 */
478void
479eachstate(dtm *tm, void (*fn)(tmstate *, void *), void *arg)
480{
481	size_t i;
482	mapentry *elem;
483
484	MAP_FOREACH (tm->states, elem, i)
485		(*fn)(elem->data.state, arg);
486}
487
488/**
489 * Iterates over each transition of the given state and invokes
490 * the given function for that transition
491 *
492 * @param state Turing state to iterate over.
493 * @param fn Function to invoke for each state.
494 * @param arg Additional argument passed to the function.
495 */
496void
497eachtrans(tmstate *state, void (*fn)(tmtrans *, tmstate *, void *), void *arg)
498{
499	size_t i;
500	mapentry *elem;
501
502	MAP_FOREACH (state->trans, elem, i)
503		(*fn)(elem->data.trans, state, arg);
504}
505
506/**
507 * Returns a char representation for a head direction.
508 *
509 * @param dir Head direction which should be converted.
510 * @returns Char describing the direction.
511 */
512int
513dirstr(direction dir)
514{
515	switch (dir) {
516	case RIGHT:
517		return 'r';
518	case LEFT:
519		return 'l';
520	case STAY:
521		return 'n';
522	}
523
524	/* Never reached. */
525	return -1;
526}
527
528/**
529 * Verifies the given input string ensuring that it only consists of
530 * alphanumeric characters and digits. Besides it ensures that it doesn't
531 * contain the special blank symbol.
532 *
533 * @param str Pointer to a string which should be verified.
534 * @param res Pointer to an address where the position
535 * 	of the first character that wasn't valid input should be
536 * 	stored. The first element is assigned position 0.
537 * @returns 0 if the input isn't valid or a non-zero number if it is.
538 */
539int
540verifyinput(char *str, size_t *res)
541{
542	size_t pos;
543	char ch;
544
545	pos = 0;
546	while ((ch = *str++)) {
547		if (!isalnum(ch) || ch == BLANKCHAR) {
548			*res = pos;
549			return 0;
550		}
551		pos++;
552	}
553
554	return -1;
555}