1/*2 * Copyright © 2016-2018 Sören Tempel3 *4 * This program is free software: you can redistribute it and/or5 * modify it under the terms of the GNU Affero General Public6 * License as published by the Free Software Foundation, either7 * 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 of11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU12 * Affero General Public License for more details.13 *14 * You should have received a copy of the GNU Affero General Public15 * License along with this program. If not, see16 * <http://www.gnu.org/licenses/>.17 */1819#include <assert.h>20#include <ctype.h>21#include <stdio.h>22#include <stdlib.h>23#include <string.h>2425#include <sys/types.h>2627#include "turing.h"28#include "util.h"2930/**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)4142/**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;5354 map = emalloc(sizeof(tmmap));55 map->size = size;56 map->entries = emalloc(sizeof(mapentry *) * size);5758 for (i = 0; i < size; i++)59 map->entries[i] = NULL;6061 return map;62}6364/**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_t72hash(tmmap *map, mapkey key)73{74 /* XXX: A more advanced hashing function could be75 * used here but this is good enough for now. */76 return (size_t)key % map->size;77}7879/**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;8990 ent = emalloc(sizeof(mapentry));91 ent->key = key;92 ent->next = NULL;93 return ent;94}9596/**97 * Frees allocated memory for a map entry.98 *99 * @param ent Pointer to map entry which should be freed.100 */101static void102freemapentry(mapentry *ent)103{104 mapentry *next;105106 assert(ent);107108 do {109 next = ent->next;110 free(ent);111 ent = next;112 } while (ent != NULL);113}114115/**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 int123setval(tmmap *map, mapentry *ent)124{125 size_t idx;126 mapentry *buck, *last, *next;127128 idx = hash(map, ent->key);129 if (!(buck = map->entries[idx])) {130 map->entries[idx] = ent;131 return 0;132 }133134 for (last = next = buck; next != NULL; next = next->next) {135 if (ent->key == next->key)136 return -1;137138 last = next;139 }140141 last->next = ent;142 return 0;143}144145/**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 int154getval(tmmap *map, mapkey key, mapentry **dest)155{156 size_t idx;157 mapentry *buck, *next;158159 idx = hash(map, key);160 if (!(buck = map->entries[idx]))161 return -1;162163 for (next = buck; next != NULL && next->key != key; next = next->next)164 ;165166 if (next == NULL)167 return -1;168169 *dest = next;170 return 0;171}172173/**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;185186 entr = emalloc(sizeof(tapeentry));187 entr->value = value;188 entr->next = next;189 entr->prev = prev;190 return entr;191}192193/**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;202203 state = emalloc(sizeof(tmstate));204 state->name = 0;205 state->trans = newtmmap(TRANSMAPSIZ);206 return state;207}208209/**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;218219 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}227228/**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 */234void235addaccept(dtm *tm, tmname state)236{237 size_t newsiz;238239 if (tm->acceptsiz && tm->acceptsiz % ACCEPTSTEP == 0) {240 newsiz = (tm->acceptsiz + ACCEPTSTEP) * sizeof(tmname);241 tm->accept = erealloc(tm->accept, newsiz);242 }243244 tm->accept[tm->acceptsiz++] = state;245}246247/**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 */254int255addstate(dtm *tm, tmstate *state)256{257 int ret;258 mapentry *entry;259260 entry = newmapentry(state->name);261 entry->data.state = state;262263 if ((ret = setval(tm->states, entry)))264 freemapentry(entry);265266 return ret;267}268269/**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 */277int278getstate(dtm *tm, tmname name, tmstate **dest)279{280 int ret;281 mapentry *entry;282283 if ((ret = getval(tm->states, name, &entry)))284 return ret;285286 *dest = entry->data.state;287 return ret;288}289290/**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 */297int298addtrans(tmstate *state, tmtrans *trans)299{300 int ret;301 mapentry *entry;302303 entry = newmapentry(trans->rsym);304 entry->data.trans = trans;305306 if ((ret = setval(state->trans, entry)))307 freemapentry(entry);308309 return ret;310}311312/**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 */320int321gettrans(tmstate *state, char rsym, tmtrans **dest)322{323 int ret;324 mapentry *entry;325326 if ((ret = getval(state->trans, rsym, &entry)))327 return ret;328329 *dest = entry->data.trans;330 return ret;331}332333/**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 */339void340writetape(dtm *tm, char *str)341{342 char c;343 tapeentry *ent, *last;344345 last = tm->tape;346 for (ent = tm->tape; ent; ent = ent->next)347 last = ent;348 assert(last);349350 while ((c = *str++)) {351 ent = newtapeentry(c, last, NULL);352 last->next = ent;353 last = ent;354 }355}356357/**358 * Writes the content of the tape to STDOUT. The output is always359 * terminated by a newline character.360 *361 * The tape is (theoretically speaking) infinite but since we don't have362 * infinite amounts of memory we only generate the blanks on the363 * beginning and end of the tape when you access them. Therefore, the364 * 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 access367 * the left-hand side of the tape since the turing machines tape is by368 * default initialized with a single blank character.369 *370 * @param tm Turing machine whose tape should be read.371 */372void373printtape(dtm *tm)374{375 tapeentry *c, *s;376377 for (s = tm->tape; s->prev; s = s->prev)378 ;379380 for (c = s; c; c = c->next)381 putchar(c->value);382 putchar('\n');383}384385/**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 int393isaccepting(dtm *tm, tmname name)394{395 size_t i;396397 for (i = 0; i < tm->acceptsiz; i++)398 if (tm->accept[i] == name)399 return 0;400401 return -1;402}403404/**405 * Performs transitions form the given state tail recursively until a state406 * 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 int413compute(dtm *tm, tmstate *state)414{415 char in;416 tmtrans *trans;417 static tmstate *next; /* static to enable tail call optimization. */418419 if (!tm->tape->next)420 tm->tape->next = newtapeentry(BLANKCHAR, tm->tape, NULL);421422 in = tm->tape->next->value;423 if (gettrans(state, in, &trans))424 return isaccepting(tm, state->name);425426 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 }441442 if (getstate(tm, trans->nextstate, &next))443 return isaccepting(tm, trans->nextstate);444445 return compute(tm, next);446}447448/**449 * Starts the turing machine. Meaning it will extract the initial state from450 * the given tm and will perform transitions (tail recursively) from this state451 * 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 */456int457runtm(dtm *tm)458{459 tmstate *start;460461 /* tm->tape->next is only NULL here if the user supplied the462 * empty word as an input for this turing maschine. In that463 * 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);466467 return compute(tm, start);468}469470/**471 * Iterates over each state of the given turing machine and472 * 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 */478void479eachstate(dtm *tm, void (*fn)(tmstate *, void *), void *arg)480{481 size_t i;482 mapentry *elem;483484 MAP_FOREACH (tm->states, elem, i)485 (*fn)(elem->data.state, arg);486}487488/**489 * Iterates over each transition of the given state and invokes490 * the given function for that transition491 *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 */496void497eachtrans(tmstate *state, void (*fn)(tmtrans *, tmstate *, void *), void *arg)498{499 size_t i;500 mapentry *elem;501502 MAP_FOREACH (state->trans, elem, i)503 (*fn)(elem->data.trans, state, arg);504}505506/**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 */512int513dirstr(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 }523524 /* Never reached. */525 return -1;526}527528/**529 * Verifies the given input string ensuring that it only consists of530 * alphanumeric characters and digits. Besides it ensures that it doesn't531 * 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 position535 * of the first character that wasn't valid input should be536 * 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 */539int540verifyinput(char *str, size_t *res)541{542 size_t pos;543 char ch;544545 pos = 0;546 while ((ch = *str++)) {547 if (!isalnum(ch) || ch == BLANKCHAR) {548 *res = pos;549 return 0;550 }551 pos++;552 }553554 return -1;555}