{"id":20483454,"url":"https://github.com/styczynski/automaton-state-machine","last_synced_at":"2026-06-01T04:31:47.861Z","repository":{"id":79817306,"uuid":"118683977","full_name":"styczynski/automaton-state-machine","owner":"styczynski","description":"Simple multi-process state machine for word verification. Original assignment description available on webpage:","archived":false,"fork":false,"pushed_at":"2018-01-23T23:35:33.000Z","size":747,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-05T16:16:23.354Z","etag":null,"topics":["c","c11"],"latest_commit_sha":null,"homepage":"https://www.mimuw.edu.pl/~mp249046/teaching/pw2017z/zadania/zadanie3/task3.html","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/styczynski.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2018-01-23T23:27:18.000Z","updated_at":"2020-01-14T08:22:34.000Z","dependencies_parsed_at":"2023-09-13T21:16:31.720Z","dependency_job_id":null,"html_url":"https://github.com/styczynski/automaton-state-machine","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/styczynski/automaton-state-machine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/styczynski%2Fautomaton-state-machine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/styczynski%2Fautomaton-state-machine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/styczynski%2Fautomaton-state-machine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/styczynski%2Fautomaton-state-machine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/styczynski","download_url":"https://codeload.github.com/styczynski/automaton-state-machine/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/styczynski%2Fautomaton-state-machine/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33760645,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-01T02:00:06.963Z","response_time":115,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["c","c11"],"created_at":"2024-11-15T16:17:24.448Z","updated_at":"2026-06-01T04:31:47.853Z","avatar_url":"https://github.com/styczynski.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Automaton\n\nThe automaton implementation as a part of the subject on the Warsaw University.\u003cbr\u003e\nThe document index:\n\n * Usage\n * Implementation\n   - Quick sources index\n   - General\n     * Communication model\n     * Sessions\n     * Locids\n     * Inter-process worker communication\n     * Errors\n     * Memory leaks\n     * Assumptions\n * Abstract\n   - Definitions\n   - Formal model\n     * Specification\n       - Proces validator\n       - Proces run\n       - Proces tester\n       - Input specification\n         * Program validator\n         * Program tester\n  \n## Usage\n\n```bash\n\n./validator [-v] \u003c \u003cautomaton_graph_file\u003e\n./tester    [-v] \u003c \u003ctester_input_file\u003e\n\n```\n\n**Important note:**\u003cbr\u003e\n**Note that *-v* switch can be used to enable verbosive debug mode!**\n\n**Server working with logging enabled:**\n\n![Screenshot of server logs with -v flag][screenshot]\n\nThere is also a helper to execute testers and server at one time:\n\n```bash\n\n./autovalidator [-v] \u003cautomaton_graph_file\u003e [\u003ctester_input_file1\u003e \u003ctester_input_file2\u003e ...]\n\n```\n\nWhich is equivalent to bash set of commands:\n\n```bash\n\n# Fork\n./validator [-v] \u003c \u003cautomaton_graph_file\u003e \u0026\n./tester    [-v] \u003c \u003ctester_input_file1\u003e   \u0026\n./tester    [-v] \u003c \u003ctester_input_file2\u003e   \u0026\n...\n# Wait\nwait\n\n```\n\n## Implementation\n\nThe implementation of automaton uses utility code that is:\n\n * Partially a code from JNP subject (Warsaw Univeristy) I have written (dynamic/array lists)\n * The huge part (written especially for this project) of code that tries to provide tidy API for low level C unix functions.\n\n### Quick sources index\n\nProject consists of the following files:\n\n * *array_lists.c* - Implementation of array lists (included in array_lists.h)\n * *automaton.h* - Implmentation of automaton data strucutres and machine itself\n * *autovalidator.c* - Helper program to launch server (validator) and clients (testers) automatically via one command\n * *dynamic_lists.h* - C99 bidirectional linked lists\n * *gc.h* - Interface to the GC (more info in GC section)\n * *generics.h* - Functions for handling void* (generic) data types\n * *hashmap.h* - Generic hashmap based on array/dynamic_lists\n * *msg_pipe.h* - Message pipes abstraction for UNIX pipes\n * *onexit.h* - Utilites to handle and manage process termination\n * *syslog.h* - Easy to use logging interface\n * *validator.c* - Automaton server\n * *array_lists.h* - Array lists (more or less like C++ std::vector's)\n * *automaton_config.h* - Config for automaton server/clients/workers (validator.c)\n * *dynamic_lists.c* - Implementation of C99 bidirectional linked lists (included in dynamic_lists.h)\n * *fork.h* - Utilities to manage fork/wait behaviour\n * *gcinit.h* - Implementation of GC interface (more info in GC section)\n * *getline.h* - Implementation of getline in C\n * *memalloc.h* - Tools for allocating memory\n * *msg_queue.h* - Message queues (mq) abstraction for UNIX message queues\n * *run.c* - Automaton server's worker process source code\n * *tester.c* - Automaton client source code\n\n### General\n\nThe automaton is divided into tree separate entities:\n \n * server - which dispatches clients' requests and send them results\n * tester - clients which sends words for verification from loaded input\n * run    - server's worker that is automatically spawned by the server when needed\n\n#### Communication model\n\nCommunication between these entities is implemented via several methods:\n\n * Message queues  - basic mean of communication\n * Pipes           - used to sending graph representation (that may be big!)\n * Via exec params - used in case of the server which passes the data into the spawned process via its cli parameters\n \nAs mentioned in the upper list, the message queues (unix mq) are the base mean of communication.\n\nAll the comunicates that are sent through mqueues are described on the diagram below:\n\n![Communication diagram][diagram]\n\nThe model of application is as follows:\n\n - Registration\n   - 2.0 When launched the `tester` process sends \"register\" event to the server via queue `\u003cserver_reg_in\u003e` (fig. 1.1)\n     * The message contains `\u003cpid\u003e` - the pid of the tester process\n     * The message contains `\u003cqn\u003e`  - name of the tester input queue `\u003ctester_ans_in\u003e`\n   - 2.1 The server receives the \"register\" events and saves new session for the client\n - Client data loading \n   - 3.0 The tester performs data loading\n - Parse requests \n   - 4.0 The tester send one or more verification requests - \"parse\" via server queue `\u003cserver_req_in\u003e` (fig. 1.2)\n     * The request must contain `\u003cpid\u003e`   - the pid of the tester process\n     * The request must contain `\u003cqn\u003e`    - name of the tester input queue `\u003ctester_ans_in\u003e`\n     * The request must contain `\u003clocid\u003e` - numerical identificator of the request\n     * The request must contain `\u003cword\u003e`  - text sequence to be parsed\n   - 4.1 The server reads the request and lanuches new worker process\n     * The worker process receives `\u003cworker_graph_in\u003e` pipe id (worker graph input pipe)\n     * The worker process receives `\u003cword\u003e` - word to be parsed\n   - 4.2 The worker receives automaton graph data using `\u003cworker_graph_in\u003e`\n - Worker internal calculations\n   - 5.0 The worker performs internal caluclations\n - Submiting worker results\n   - 6.0 After the worker has got its answer it sends \"run-termiante\" event to the server via server queue `\u003cserver_ans_in\u003e`\n     * The answer contains `\u003cpid\u003e` - the pid of the worker process\n     * The answer contains `\u003cans\u003e` - the generated answer\n   - 6.1 The server receives worker termination events and sends answer back to the client (via `\u003ctester_ans_in\u003e`)\n     * The answer contains previously sent   `\u003clocid\u003e`\n     * The answer contains the server answer `\u003cans\u003e`\n - Exitting\n   - 7.0 Optionally the client may want to stop the server with \"exit\" (fig. 1.3) message\n   - 7.1 When server receives the exit message it's broadcasted to all opened client sessions\n     * The broadcast happens after all unfinished server processes will terminate\n     * After registering exit request no other \"parse\" request will be executed\n   - 7.2 When client receives the exit message then it terminates\n\n*The server opens the following mqueues/pipes:*\n\n * `\u003cserver_reg_in\u003e` - fixed queue name; used for incoming tester registration events\n * `\u003cserver_req_in\u003e` - fixed queue name; used for incoming tester parse requests\n * `\u003cserver_ans_in\u003e` - fixed queue name; used for incoming worker termination events\n * Per each worker opened session:\n   * `\u003cworker_graph_in\u003e` - pipe to send the automaton graph data to the process\n * Per each tester opened session:\n   * `\u003ctester_ans_in\u003e` - used for sending answers to the tester (it's name is passed via \"register\" and \"parse\")\n\n*The client opens the following mqueues/pipes:*\n\n * `\u003ctester_ans_in\u003e` - queue used for incoming answers\n * `\u003cserver_reg_in\u003e` - queue used to register in the server\n * `\u003cserver_req_in\u003e` - queue to send the parse requests\n\n*The worker opens the following mqueues/pipes:*\n\n * `\u003cworker_graph_in\u003e` - pipe to receive graph data from the server\n * `\u003cserver_ans_in\u003e`   - queue to transmit calculated answers back to the server\n\nThe point **4.1** is implemented via passing-by-args method.\u003cbr\u003e\nThe worker is launched with the command:\n\n```\n  ./run \u003cworker_graph_in\u003e \u003cword\u003e\n```\n\nThe server side session-independent queues has got fixed names.\u003cbr\u003e\nAnd the tester has full flexibity to generate any input queue name `\u003ctester_ans_in\u003e`.\n\n#### Sessions\n\nWhen \"register\" event is received by the server (it has priority over any other client request),\nthe new session for the client is created and associated with it's pid passed in the message.\n\nThe \"register\" message is optional and sessions are opened automatically on normal \"parse\" events.\n\nHowever \"register\" event thanks to its priority will ensure that all clients launched before the server, will\nregister correctly.\u003cbr\u003e\nThe correct registration is crusial when it comes to the \"exit\" (point *7.0*) broadcasting (as we want to close all the clients).\n\nThe sessions are captured in the server inside HashMap indexed by process (client/worker) `pid`.\n\n#### Locids\n\nThe `\u003clocid\u003e` is abbreviation for local id.\n\nThe local id can be freely generated by the client.\u003cbr\u003e\nIt will be associated with the parse request.\u003cbr\u003e\nThe server identifies the worker answer by the locid value that is captured inside session data for the worker.\n\nThe communication model assumes that the client saves the information that the local id is associated with some word,\nbecause response from the server contains only local id and not the word itself.\n\nThe best approach in this situation is to generate local ids sequentially and save the words in array.\u003cbr\u003e\nBelow I attach some C-like pseudocode:\n\n```c++\nchar* words[100];\n\nint locid = 0;\nfor(int i=0;i\u003c100;++i) {\n    words[locid] = load_input_word();\n    send_request(locid, words[locid]);\n    ++locid;\n}\n\nint result = 0;\nwhile(1) {\n    int locid = wait_for_answer(\u0026result);\n    printf(\"asnwer for word %s is %d\", words[locid], result);\n}\n\n```\n\n#### Inter-process worker communication\n\nThe `acceptAsync` methods forks itself when needed (this is determined heuristically - for more details see `automaton.h`)\n\nThe communication between self-forked worker process is done via pipes.\u003cbr\u003e\nThe parent opens pipes for back-communication.\u003cbr\u003e\nThe child process calculates results and send the back via the parent pipe.\n\n#### Errors\n\nThe system functions inside utility functions are checked agains failures.\u003cbr\u003e\nIn most cases the function will trigger `syserr` which terminates program with -1 exit code immidiately.\n\nIn case when run forked itself and some of the children returned non-zero exit code or\nterminated abnormally then the process kills itself with -1 exit code.\n\nIn case when any worker process terminated with non-zero exit code, the server kills itself\ntrying to broadcast \"exit\" to all connected clients and return -1 exit code.\n\nIn case of server abnormal termination it will always try to broadcast the \"exit\" message if it's possible.\n\n#### Memory leaks\n\nThe application is designed to free all used resources.\n\nIn case of abnormal termination, exit(...) does not deallocate memory.\u003cbr\u003e\nIt's critical (specified in the study task) to prevent any memory leaks (also that with exit, even if OS handles this case for us).\n\nThat's why I wrote simple GC that uses HashMap to register all allocs/frees and free the memory on termination using `atexit`.\u003cbr\u003e\nThe GC registers only allocations done via `memalloc.h` functions and macros.\n\n\n#### Assumptions\n\nThe following assumptions were done during implementation:\n\n* Mamium line length of any input is `LINE_BUF_SIZE`\n* Maximum file input length (of graph representation) is `FILE_BUF_SIZE` and it fits into memory\n* Maximum number of states is `MAX_Q`\n* Letters are letter of ascii code `{'a', 'b', ..., 'a'+MAX_Q-1}`\n\nThis values as others settings can be changed in `automaton_config.h`.\n\nThe detailed code explenation is done in each file.\u003cbr\u003e\nPlease refer to `validator.c`, `tester.c` and `run.c`\nto obtain more implementation detailed info.\n\n## Abstract\n\n### Definitions\n\n\u003e **A finite automaton** -- a simple state machine which accepts or rejects words.\n\n\u003e **A word** is any finite sequence of letters from an alphabet.\n\n\u003e **An alphabet** is any finite set.\n\nIf *w* is a word , then by *|w|* we denote its length, and by *w[i]*, *0 \u003c= i \u003c |w|*, its *i-th* letter. \u003cbr\u003e\nMoreover, the symbol *ε* denotes an unique empty word, i.e. a word of length 0.\n\n### Formal model\n\nFormally, an automaton *A* is a tuple (*L, E, U, F, qI, T*), where:\n\n * *L* is an alphabet,\n * *E* is a set of existential states,\n * *U* is a set of universal states,\n * Q ≝ E ∪ *U* is the set of all states of *A*,\n * *F∈Q* is the set of final states,\n * *qI* is the unique initial state,\n * *T: Q × L → P(Q)* is the transition function.\n\nIntuitively, an automaton recognises a word in the following way: the automaton starts in the initial state and reads the word, letter by letter.\nWhen the automaton is in state *q* and sees a letter *a* then it *'chooses'* the next state from the set *T(q,a)* and proceeds.\nIf *q* is universal then every such choice has to lead to acceptance, if *q* is existential then only one choice has to lead to acceptance.\nAcceptance, in this context, requires that the above process after reading the last letter of the word ends up in a final state.\n\n**More formally:**\n\nthe above process can be described as a tree.\u003cbr\u003e\nA run of the automaton run on word *w* of length *d* is a tree of height *d+1*.\u003cbr\u003e\n(For instance, a run on the epty word has unique node -- the root -- at the depth 0.)\n\nThe root *ε* of the run is labelled *run[ε] = qI* and every node *u* in the tree satisfies the following:\n\n\nIf node *u* is at depth *i*, is labelled *q* and:\n\n * T(q, w[i]) is empty:                                     then u is a leaf;\n * T(q, w[i]) isn't empty and the state q is existential:   then u has exactly one child labelled with a state belonging to the set T(q, w[i]);\n * T(q, w[i]) isn't empty and q is universal:               then node u has exactly one p-labelled child for every state p ∈ T(q, w[i]) .\n\n\nWe say that a run is accepting if the leafs' labels on depth *d* are final states and the labels of the remaining leafs are universal states.\n\nA word is accepted by an automaton if there is an accepting run.\u003cbr\u003e\nIf there is none then the automaton rejects the word.\n\nThe acceptance of a word can also be described as a recursive function.\u003cbr\u003e\nAn automaton accepts a word *w* if the following `function bool accept(string w, string r)`\u003cbr\u003e\nreturns `true` when called as `accept(w,qI)`.\n\n```c++\n\n// This function accepts the word @w continuing the process from the last node of the path @r \n// belonging to some run of the automaton\nbool accept(string w, string r):\n\n  d := |r|-1;\n\n  if (d ≥ |w|)\n    // We have processed every letter in @w; accept if the last state is accepting\n    return (r[d] ∈ F);\n\n  if (r[d] ∈ E)\n    // The last state is existential so accept if there\n    // is an accepting continuation of the run @r\n    return (∃q∈T(r[d],w[d]) accept(w,rq))\n\n  // Otherwise, the last state is universal (r[d] ∈ U)\n  // and we have to accept every possible continuation\n  return (∀q∈T(r[d],w[d]) accept(w,rq))\n\n```\n  \n**Note:**\n\nVariables *w*, *r* are words and *rq* is the conctatenation of the word *r* and the letter (also a state) *q*.\u003cbr\u003e\nIntuitively, *r* can be seen as a path in a run.\n\n\n#### Specification\n\n  \u003e A sequence [wyr] denotes that the string wyr repeats a finite (greater than or equal to 0) number of times.\n\n **Proces validator**\n\n The validator is a server accepting, or rejecting, words. A word is accepted if it is accepted by\u003cbr\u003e\n specified automaton. The program validator begins by reading from the standard input\u003cbr\u003e\n the description of the automaton and then in an infinite loop waits for the words to verify.\n \n When a word is received he runs the program run which validates the word and vaidator waits\u003cbr\u003e\n for an another word or response from the program run. Afer receiving a message from one of the\u003cbr\u003e\n run processes validator forwords the message to the adequate tester.\n\n When a tester send an unique stop word ! the server stops , i.e. he does not accept new words,\u003cbr\u003e\n collects the responses from the run processes, forwards the answers, writes a report on stdout, and, finally, terminates.\n\n The validator report consist in three lines describing the numbers of received queries, sent answers and accepted words:\n\n```c\n\n     Rcd: x\\n\n     Snt: y\\n\n     Acc: z\\n\n\n```\n \n where x,y,z respectively are the numbers of received queries,\n sent answers and accepted words; and a sequence of summaries of the interactions\u003cbr\u003e\n with the programs tester from which validator received at least one query.\n \n A summary for a tester with PID pid consists in:\n\n```c\n\n     [PID: pid\\n\n     Rcd: y\\n\n     Acc: z\\n]\n\n```\n\n where *pid*, *y*, *z* respectively are:\n \n * the process' pid,\n * the number of messages received from this process\n * the number of acceped words sent by this process.\n\n\n **Proces run**\n \n Program run receives from validator a word to verify and the description of the automaton.\u003cbr\u003e\n Then he begins the verification. When the verification stops, the process sends a message to the server and terminates.\n \n **Proces tester**\n \n Queries in a form of words are sent by programs tester.\n\n A program tester in an infinite loop read words form the standard input stream and forwards them to the server.\u003cbr\u003e\n Every word is written in a single line and the line feed symbol \\n does not belong to any of the words.\u003cbr\u003e\n When the tester receives an answer from the server,\u003cbr\u003e\n he writes on the standard output stream the word he resecived answer for and the decision A if the word\u003cbr\u003e\n was accepted and N if not.\u003cbr\u003e\n The tester terminates when the server terminates of when tester receives the EOF symbol,\u003cbr\u003e\n i.e. the end of file symbol. When the tester terminates it sends no new queries,\u003cbr\u003e\n waits for the remaining answers from the server, and writes on the standard output a report.\n \n A Report of a tester consist of three lines\n\n```c\n\n     Snt: x\\n\n     Rcd: y\\n\n     Acc: z\\n\n\n```\n\n where *x*, *y*, *z* respectively are the numbers of:\n \n * queries,\n * received answers, \n * accepted words sent by this process\n\n\n **Input specification**\n\n  *Program validator*\n \n  Input of a validator consist in the automaton description:\n\n```c\n\n  N A Q U F\\n\n  q\\n\n  [q]\\n\n  [q a r [p]\\n]\n  \n```\n  \n  Where:\n\n  * *N* is the number of lines of the input\n  * *A* is the size of the alphabet: the alphabet is the set *{a,...,x}*, where *'x'-'a' = A-1*\n  * *Q* is the number of states: the states are the set *{0,...,Q-1}*\n  * *U* is the number of universal states: universal states = *{0, .., U-1}*, existential states = *{U, .., Q-1}*\n  * *F* is the number of final states\n  * *q*, *r*, *p* denotes some states\n  * *a* is a letter of the alphabet\n\n  The first line gives the cardinalities of the sets describing the automaton.\u003cbr\u003e\n  The second line gives the initial stare,\u003cbr\u003e\n  the third line is the list of final states.\n  \n  The remaining lines, of form: *q a r [p]\\n* , encode the transition function in the following way:\u003cbr\u003e\n  if there is a line *q a p1 p2 ... pk* then *T(q,a) = {p1, p2, ..., pk }* \u003cbr\u003e\n  If for any pair *(q,a)* there is no line in the encoding then one assumes that *T(q,a) = ∅*\n\n  One can assume that: *0 \u003c= F,U \u003c= Q \u003c 100, 0 \u003c= A \u003c= 'z'-'a'*\n\n  *Program tester*\n  \n  A tester on the standard input stream receives the following string.\n\n```c\n\n  [[a-z]\\n]\n  [!\\n]\n  [[a-z]\\n]\n  \n```\n  \n  Where\n  \n  * *a-z* are the alphabet letters;\n  * *!* is the unique terminate the server symbol.\n  \n  One can assume that the length of the supplied words will not exceed the constant *MAXLEN = 1000*.\n\n[screenshot]: https://raw.githubusercontent.com/styczynski/automaton-state-machine/master/static/screenshot1.png\n\n[diagram]: https://github.com/styczynski/automaton-state-machine/blob/master/static/automaton_com_sch1.png?raw=true\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstyczynski%2Fautomaton-state-machine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstyczynski%2Fautomaton-state-machine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstyczynski%2Fautomaton-state-machine/lists"}