summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authornoodle <shawtynoodle@gmail.com>2023-07-10 15:40:08 +0300
committernoodle <shawtynoodle@gmail.com>2023-07-10 15:40:08 +0300
commitb7ac144cd2d242791938b51569effb7a1378a332 (patch)
tree0db39dc6d72a96697707c662c32f4dcdb99372b7
parent35eacac40f265aad47bf25d10f3ecd3670b79b2f (diff)
Add files
-rwxr-xr-xbuild7
-rw-r--r--cycle.c77
-rw-r--r--cycle.h34
-rw-r--r--fun_menu.c1055
-rw-r--r--fun_menu.h28
-rw-r--r--richwin.c85
-rw-r--r--richwin.h68
-rw-r--r--stack.c179
-rw-r--r--stack.h68
-rw-r--r--utils.c25
-rw-r--r--utils.h11
11 files changed, 1637 insertions, 0 deletions
diff --git a/build b/build
new file mode 100755
index 0000000..2c66a2d
--- /dev/null
+++ b/build
@@ -0,0 +1,7 @@
+#!/bin/sh -e
+options=""
+options="${options} -g3 -O0"
+options="${options} -Weverything -Wno-declaration-after-statement -Wno-padded -Wno-disabled-macro-expansion"
+options="${options} -std=c99 -Wpedantic -pedantic-errors"
+options="${options} $(pkg-config --cflags --libs ncurses)"
+clang $options -lm -o fun_menu fun_menu.c utils.c richwin.c stack.c cycle.c
diff --git a/cycle.c b/cycle.c
new file mode 100644
index 0000000..6bdf994
--- /dev/null
+++ b/cycle.c
@@ -0,0 +1,77 @@
+#include <stddef.h> // for size_t
+#include <stdlib.h> // for malloc() and free()
+#include <assert.h>
+#include "utils.h"
+#include "cycle.h"
+
+/* cycling iterable */
+struct Cycle {
+ const void *base;
+ size_t i;
+ size_t memb_n;
+ size_t memb_size;
+};
+
+/*
+ * cycle_new: create a new cycle of array startinng at 'base' with memb_n
+ * elements each of size 'memb_size'
+ */
+struct Cycle *
+cycle_new(const void *base, size_t memb_n, size_t memb_size)
+{
+ assert(base != NULL);
+ assert(memb_n > 0);
+ assert(memb_size > 0);
+ struct Cycle *cycle = malloc(sizeof *cycle);
+
+ if (cycle == NULL) {
+ return NULL;
+ }
+ cycle->base = base;
+ cycle->i = 0;
+ cycle->memb_n = memb_n;
+ cycle->memb_size = memb_size;
+ return cycle;
+}
+
+/* cycle_free: free memmory used by cycle */
+void
+cycle_free(struct Cycle *cycle)
+{
+ assert(cycle != NULL);
+ free(cycle);
+}
+
+/* cycle_index: get index of current element of cycle */
+size_t
+cycle_index(struct Cycle *cycle)
+{
+ assert(cycle != NULL);
+ return cycle->i;
+}
+
+/* cycle_current: get current element of cycle */
+const void *
+cycle_current(struct Cycle *cycle)
+{
+ assert(cycle != NULL);
+ return (const char *)cycle->base + cycle->memb_size*cycle->i;
+}
+
+/* cycle_next: point cycle to next element and return it */
+const void *
+cycle_next(struct Cycle *cycle)
+{
+ assert(cycle != NULL);
+ cycle->i = (cycle->i >= cycle->memb_n-1) ? 0 : cycle->i+1;
+ return cycle_current(cycle);
+}
+
+/* cycle_prev: point cycle to previous elemetn and return it */
+const void *
+cycle_prev(struct Cycle *cycle)
+{
+ assert(cycle != NULL);
+ cycle->i = (cycle->i <= 0) ? cycle->memb_n-1 : cycle->i-1;
+ return cycle_current(cycle);
+}
diff --git a/cycle.h b/cycle.h
new file mode 100644
index 0000000..96d98c1
--- /dev/null
+++ b/cycle.h
@@ -0,0 +1,34 @@
+#ifndef CYCLE_H
+#define CYCLE_H
+
+// cycle
+typedef struct Cycle Cycle;
+
+/*
+ * cycle_new: create a new cycling iterator
+ * parameters:
+ * base (void *) -> pointer to start of array to cycle through
+ * memb_n (size_t) -> number of elements of array 'base'
+ * memb_size (size_t) -> size of an element of array 'base'
+ * returns:
+ * Cycle * -> the created cylce
+ * NULL -> failed to allocate memory for cycle
+ */
+Cycle *cycle_new(const void *base, size_t memb_n, size_t memb_size);
+
+/* cycle_free: free the cycle. doesn't free buffer pointed to by cycle->base */
+void cycle_free(Cycle *cycle);
+
+/* cycle_index: get index of current element of cycle */
+size_t cycle_index(Cycle *cycle);
+
+/* cycle_current: get current element of cycle */
+const void *cycle_current(Cycle *cycle);
+
+/* cycle_next: move cycle one element forward */
+const void *cycle_next(Cycle *cycle);
+
+/* cycle_free: move cycle one element backward*/
+const void *cycle_prev(Cycle *cycle);
+
+#endif /* CYCLE_H */
diff --git a/fun_menu.c b/fun_menu.c
new file mode 100644
index 0000000..36fed4d
--- /dev/null
+++ b/fun_menu.c
@@ -0,0 +1,1055 @@
+#include <stdio.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <locale.h> // for setlocale()
+#include <string.h> // for strncpy() and strlen()
+#include <ctype.h> // for isalnum()
+#include <stdlib.h> // for malloc() and free()
+
+#include <curses.h>
+#include "fun_menu.h"
+#include "utils.h"
+#include "richwin.h"
+#include "stack.h"
+#include "cycle.h"
+
+// macros
+#define ARRAY_LENGTH(arr) (sizeof (arr) / sizeof ((arr)[0]))
+#define MEMBER_SIZE(arr) (sizeof ((arr)[0]))
+
+// default strings/characters
+#define DEFAULT_WORD "hello"
+#define DEFAULT_PROMPT "Enter word to draw"
+#define INDICATOR_CHAR_LEFT '>'
+#define INDICATOR_CHAR_RIGHT '<'
+#define INPUT_PLACEHOLDER '_'
+#define TEXT_CONTINUATION '-'
+#define INPUT_CONTINUATION '+'
+
+// default numbers
+#define WORD_MAXLEN 20
+#define INDICATORS 2
+#define BORDERS 2
+#define PADDINGS 2
+#define INDICATOR INDICATORS/2
+#define BORDER BORDERS/2
+#define PADDING PADDINGS/2
+
+enum WindowType { BOARD_WIN, MENU_WIN, PROMPT_WIN };
+enum AppActionType { APP_DRAW, APP_CLEAN, APP_CHANGE_COLOR, APP_CHANGE_WORD,
+ APP_END};
+
+/* structures */
+
+// MainWin: pointer to one of the following windows and it's type. used for
+// converting to/from void *
+struct MainWin {
+ void *sub;
+ enum WindowType type;
+ struct RichWin *richwin;
+ struct RichWin *(*new_richwin)(void *sub);
+ void *(*draw_window)(void *sub);
+};
+// BoardWin: a resizable drawing board with stats of the word to draw,
+// background color, whether it's enables/disabled and whether drawing on it is
+// done
+struct BoardWin {
+ struct MainWin base;
+ char *word;
+ Cycle *colors;
+ bool on;
+ bool done;
+};
+// MenuWin: a resizable menu with a list of options, and the currently
+// choosen option from that list
+struct MenuWin {
+ struct MainWin base;
+ Cycle *options;
+};
+// PromptWin: a resizable user prompt with prompt text, prompt input buffer,
+// and it's size
+struct PromptWin {
+ struct MainWin base;
+ const char *prompt;
+ char *input;
+ const int input_maxlen;
+ const int content_minlen;
+};
+// MenuOption: text for a menu option used in a MenuWin and accociated function
+// to call on choosing that option
+struct MenuOption {
+ struct BoardWin *(*func)(struct BoardWin *boardwin);
+ enum AppActionType type;
+ const char *text;
+};
+
+/* function declarations */
+
+// misc functions
+static int prompt_user(const char *prompt, char *buf, size_t len);
+static int mvwaddcstr(WINDOW *win, int y, int x, const char *str, int len,
+ const char break_char);
+// cleanup functions
+static void cleanup_main(Stack *win_stack, struct BoardWin *boardwin,
+ struct MenuWin *menuwin);
+static void cleanup_prompt_user(struct PromptWin *promptwin, bool on_stack);
+// error handling functions
+static void write_error(enum ErrorCode code, int line, const char *func_name);
+static int print_error(void);
+// window methds:
+// MainWin methods
+static void *mainwin_resize(void *window);
+static void mainwin_cleanup(struct MainWin *mainwin);
+// BoardWin methods
+static struct RichWin *boardwin_new_richwin(void *window);
+static void *boardwin_draw(void *window);
+static void boardwin_cleanup(struct BoardWin *boardwin);
+// MenuWin methods
+static struct RichWin *menuwin_new_richwin(void *window);
+static void *menuwin_draw(void *window);
+static void menuwin_cleanup(struct MenuWin *menuwin);
+// PromptWin methods
+static struct RichWin *promptwin_new_richwin(void *window);
+static void *promptwin_draw(void *window);
+static void promptwin_cleanup(struct PromptWin *promptwin);
+
+// application actions
+static struct BoardWin *app_draw(struct BoardWin *boardwin);
+static struct BoardWin *app_clean(struct BoardWin *boardwin);
+static struct BoardWin *app_change_word(struct BoardWin *boardwin);
+static struct BoardWin *app_change_color(struct BoardWin *boardwin);
+static struct BoardWin *app_end(struct BoardWin *boardwin);
+
+
+/* globals */
+
+// holds curses errors
+static int err;
+// remembers whether the terminal supports color
+static bool colorize;
+// keeps track of every window for responding to a terminal resize
+static Stack *window_stack;
+
+/* constants */
+
+// holds error info
+static struct ErrorInfo {
+ enum ErrorCode code;
+ int line;
+ const char *func_name;
+} error_info = {
+ .code = ERROR_OK,
+ .line = 0,
+ .func_name = NULL,
+};
+// text representation of error codes
+static const char *error_texts[ERROR_MAX] = {
+ [ERROR_LOCALE_SETTING] = "Failed to set locale!",
+ [ERROR_COLOR_STARTING] = "Failed to start colors!",
+ [ERROR_CURSES_SETTING] = "Failed to set curses settings!",
+ [ERROR_MEMORY_ALLOCATION] = "Failed to allocate memory!",
+ [ERROR_CHARACTER_INPUT] = "Failed to recieve character from window!",
+ [ERROR_CHARACTER_OUTPUT] = "Failed to print character to window!",
+ [ERROR_WINDOW_CREATION] = "Failed to create a window!",
+ [ERROR_WINDOW_DRAWING] = "Failed to write to a window!",
+ [ERROR_WINDOW_RENDERING] = "Failed to render a window!",
+ [ERROR_COLOR_PAIR_DEFINITION] = "Failed to define a color pair!",
+ [ERROR_OPTION_SETTING] = "Failed to set an option for a window!",
+ [ERROR_UNKNOWN] = "Unknown error!",
+};
+// holds BoardWin's colors to cycle through
+static const short colors[] = {
+ COLOR_RED,
+ COLOR_GREEN,
+ COLOR_YELLOW,
+ COLOR_BLUE,
+ COLOR_MAGENTA,
+ COLOR_CYAN,
+};
+// holds an iterable vtable
+static const struct MenuOption menu_options[] = {
+ {
+ .func = app_draw,
+ .type = APP_DRAW,
+ .text = "draw",
+ },
+ {
+ .func = app_clean,
+ .type = APP_CLEAN,
+ .text = "clean",
+ },
+ {
+ .func = app_change_color,
+ .type = APP_CHANGE_COLOR,
+ .text = "change color",
+ },
+ {
+ .func = app_change_word,
+ .type = APP_CHANGE_WORD,
+ .text = "change word",
+ },
+ {
+ .func = app_end,
+ .type = APP_END,
+ .text = "exit",
+ },
+};
+
+/* a "little" arrow-driven/hjkl menu practice */
+int
+main(void)
+{
+ struct BoardWin *boardwin = NULL;
+ struct MenuWin *menuwin = NULL;
+
+ // set locale from environment
+ if (setlocale(LC_ALL, "") == NULL) {
+ write_error(ERROR_LOCALE_SETTING, __LINE__, __func__);
+ goto error;
+ }
+ // start curses
+ (void) initscr();
+ // start color
+ colorize = has_colors();
+ if (colorize) {
+ if (start_color() == ERR) {
+ write_error(ERROR_COLOR_STARTING, __LINE__, __func__);
+ goto error;
+ }
+ }
+ // set options
+ if (noecho() == ERR
+ || raw() == ERR
+ || nl() == ERR
+ || leaveok(stdscr, true) == ERR
+ || curs_set(0) == ERR)
+ {
+ write_error(ERROR_CURSES_SETTING, __LINE__, __func__);
+ goto error;
+ }
+
+ // set color pairs
+ if (colorize) {
+ if (init_pair(1, COLOR_BLACK, COLOR_WHITE) == ERR
+ ||init_pair(2, COLOR_BLACK, colors[0]) == ERR)
+ {
+ write_error(ERROR_COLOR_PAIR_DEFINITION, __LINE__, __func__);
+ goto error;
+ }
+ }
+
+ // create window stack
+ if ((window_stack = stack_new()) == NULL) {
+ write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__);
+ goto error;
+ }
+
+ // create board window
+ struct BoardWin boardwin_struct = {
+ .word = malloc(WORD_MAXLEN),
+ .colors = cycle_new(colors, ARRAY_LENGTH(colors), MEMBER_SIZE(colors)),
+ .on = false,
+ .done = false,
+ };
+ boardwin = &boardwin_struct;
+ // verify allocations
+ if (boardwin->word == NULL || boardwin->colors == NULL) {
+ write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__);
+ goto error;
+ }
+ // create base
+ struct MainWin boardwin_base = {
+ .sub = boardwin,
+ .type = BOARD_WIN,
+ .richwin = boardwin_new_richwin(NULL),
+ .new_richwin = boardwin_new_richwin,
+ .draw_window = boardwin_draw,
+ };
+ boardwin->base = boardwin_base;
+ // error checking
+ if (boardwin->base.richwin == NULL) goto error;
+ // push boardwin to window stack
+ if (stack_push(window_stack, boardwin) == NULL) {
+ write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__);
+ goto error;
+ }
+ // write default word into it
+ strncpy(boardwin->word, DEFAULT_WORD, WORD_MAXLEN);
+ boardwin->word[WORD_MAXLEN-1] = '\0';
+
+ // create menu window
+ struct MenuWin menuwin_struct = {
+ .options = cycle_new(menu_options, ARRAY_LENGTH(menu_options),
+ MEMBER_SIZE(menu_options)),
+ };
+ menuwin = &menuwin_struct;
+ // verify allocations
+ if (menuwin->options == NULL) {
+ write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__);
+ goto error;
+ }
+ // create base
+ struct MainWin menuwin_base = {
+ .sub = menuwin,
+ .type = MENU_WIN,
+ .richwin = menuwin_new_richwin(NULL),
+ .new_richwin = menuwin_new_richwin,
+ .draw_window = menuwin_draw,
+ };
+ menuwin->base = menuwin_base;
+ // error checking
+ if (menuwin->base.richwin == NULL) goto error;
+ // push it to window stack
+ if (stack_push(window_stack, menuwin) == NULL) {
+ write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__);
+ goto error;
+ }
+
+ // main loop
+ if (menuwin_draw(menuwin) == NULL) goto error;
+ if (doupdate() == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ goto error;
+ }
+ int ch;
+ while (!boardwin->done) {
+ ch = wgetch(menuwin->base.richwin->win);
+ if (ch == ERR) {
+ write_error(ERROR_CHARACTER_INPUT, __LINE__, __func__);
+ goto error;
+ }
+ switch(ch) {
+ case KEY_RESIZE:
+ // resize all windows
+ if (stack_iter_reverse(window_stack, mainwin_resize) == NULL) {
+ goto error;
+ }
+ // refresh
+ if (doupdate() == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ goto error;
+ }
+ break;
+ case 'j':
+ case KEY_DOWN:
+ // go down
+ cycle_next(menuwin->options);
+ break;
+ case 'k':
+ case KEY_UP:
+ // go up
+ cycle_prev(menuwin->options);
+ break;
+ case '\n':
+ {
+ // execute an option
+ const struct MenuOption *menu_option = cycle_current(
+ menuwin->options);
+ if (menu_option->func(boardwin) == NULL) goto error;
+ break;
+ }
+ }
+ // render
+ if (menuwin_draw(menuwin) == NULL) goto error;
+ if (doupdate() == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ goto error;
+ }
+ }
+
+ // cleanup and exit
+ cleanup_main(window_stack, boardwin, menuwin);
+ return 0;
+error:
+ cleanup_main(window_stack, boardwin, menuwin);
+ print_error();
+ return 1;
+}
+
+/*== misc functions =========================================================*/
+
+/*
+ * prompt_user: open a prompt window with text 'prompt' and store the result
+ * in buffer 'buffer' of size 'buffer_size'. number of characters read from
+ * user is returned or -1 if prompt failed
+ */
+static int
+prompt_user(const char *prompt, char *buffer, size_t buffer_size)
+{
+ assert(prompt != NULL);
+ assert(buffer != NULL);
+ assert(buffer_size > 0);
+ struct PromptWin *promptwin = NULL;
+ bool on_stack = false;
+ const int input_maxlen = (int)buffer_size - 1,
+ content_minlen = (int) max_size_t(strlen(prompt), buffer_size);
+
+ // allocate buffer for user input
+ char *input = malloc(buffer_size);
+ if (input == NULL) {
+ write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__);
+ goto error;
+ }
+ // initialize buffer to placholders
+ {
+ int i;
+ for (i = 0; i < input_maxlen; i++) {
+ input[i] = INPUT_PLACEHOLDER;
+ }
+ input[i] = '\0';
+ }
+
+ // create prompt window
+ struct PromptWin promptwin_struct = {
+ .prompt = prompt,
+ .input = input,
+ .input_maxlen = input_maxlen,
+ .content_minlen = content_minlen,
+ };
+ promptwin = &promptwin_struct;
+ // create base
+ struct MainWin promptwin_base = {
+ .sub = promptwin,
+ .type = PROMPT_WIN,
+ .richwin = promptwin_new_richwin(promptwin),
+ .new_richwin = promptwin_new_richwin,
+ .draw_window = promptwin_draw,
+ };
+ promptwin->base = promptwin_base;
+ // verify data allocation
+ if (promptwin->base.richwin == NULL) goto error;
+ if (stack_push(window_stack, promptwin) == NULL) {
+ cleanup_prompt_user(promptwin, on_stack);
+ write_error(ERROR_MEMORY_ALLOCATION, __LINE__, __func__);
+ return -1;
+ }
+ on_stack = true;
+
+ // prompt loop
+ // draw promptwin
+ if (promptwin_draw(promptwin) == NULL) goto error;
+ // refresh
+ if (doupdate() == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ goto error;
+ }
+ // keep reading into buffer until a newline is read
+ int ch, i = 0;
+ while ((ch = wgetch(promptwin->base.richwin->win)) != '\n') {
+ if (ch == ERR) {
+ // failed to read character
+ write_error(ERROR_CHARACTER_INPUT, __LINE__, __func__);
+ goto error;
+ } else if (ch == KEY_RESIZE) {
+ // resize all windows from first to last
+ if (stack_iter_reverse(window_stack, mainwin_resize) == NULL) {
+ goto error;
+ }
+ // refresh
+ if (doupdate() == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ goto error;
+ }
+ } else if ((ch == '\b' || ch == KEY_BACKSPACE) && i > 0) {
+ // remove current character and go back by one
+ input[--i] = INPUT_PLACEHOLDER;
+ } else if (i < input_maxlen && isalnum(ch)) {
+ // read character into input buffer
+ input[i++] = (char) ch;
+ }
+ // draw promptwin
+ if (promptwin_draw(promptwin) == NULL) goto error;
+ if (doupdate() == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ goto error;
+ }
+ }
+ if (i != 0) {
+ // copy visual input to the user's buffer
+ input[i] = '\0';
+ strncpy(buffer, input, buffer_size);
+ buffer[buffer_size-1] = '\0';
+ }
+
+ // remove promptwin from program
+ wclear(promptwin->base.richwin->win);
+ wrefresh(promptwin->base.richwin->win);
+
+ // cleanup
+ cleanup_prompt_user(promptwin, on_stack);
+ return i;
+error:
+ cleanup_prompt_user(promptwin, on_stack);
+ return -1;
+}
+
+/* mvwaddcstr: like mvwaddstr but also centers 'str' on area of length 'n' */
+static int
+mvwaddcstr(WINDOW *win, int y, int x, const char *str, int n, char break_char)
+{
+ assert(win != NULL);
+ assert(y >= 0);
+ assert(x >= 0);
+ assert(str != NULL);
+ const int text_len = (int) strlen(str);
+
+ if (text_len <= 0) {
+ // there's no text
+ return x;
+ }
+ if (n <= 0) {
+ // cant write on that area
+ return x;
+ }
+ if (text_len <= n) {
+ // text fits in area
+ n -= x;
+ x = x + (n-text_len) / 2;
+ // print text centered
+ err = mvwaddnstr(win, y, x, str, n);
+ if (err == ERR) return -1;
+ } else {
+ // text is longer than area:
+ // print text truncated to length of area
+ err = mvwaddnstr(win, y, x, str, n-1);
+ if (err == ERR) return -1;
+ // replace last character of text with specified text break character
+ err = waddch(win, (const chtype) break_char);
+ if (err == ERR) return -1;
+ }
+ return x;
+}
+
+/*
+ * highlight_str: change background color or write indicators on edges of area
+ * on screen starting at 'y' and 'x' of length 'n'
+ */
+static int
+highlight_str(WINDOW *win, int y, int x, int n, int pair)
+{
+ assert(win != NULL);
+ assert(y >= 0);
+ assert(x >= 0);
+ assert(n >= 1);
+ assert(pair >= 0);
+
+ if (colorize) {
+ // colorize area of length 'n'
+ err = mvwchgat(win, y, x, n, A_NORMAL, 1, NULL);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return -1;
+ }
+ } else {
+ // add indicators on edges of area of length 'n'
+ err = mvwaddch(win, y, x, INDICATOR_CHAR_LEFT);
+ if (err == ERR) {
+ write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__);
+ return -1;
+ }
+ err = mvwaddch(win, y, x+n-1, INDICATOR_CHAR_RIGHT);
+ if (err == ERR) {
+ write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__);
+ return -1;
+ }
+ }
+ return 0;
+}
+
+
+/*== cleanup functions ======================================================*/
+
+/* cleanup_main: free resources allocated by main() */
+static void
+cleanup_main(Stack *win_stack, struct BoardWin *boardwin,
+ struct MenuWin *menuwin)
+{
+ if (win_stack != NULL) stack_delete(window_stack);
+ if (boardwin != NULL) boardwin_cleanup(boardwin);
+ if (menuwin != NULL) menuwin_cleanup(menuwin);
+ endwin();
+}
+
+/* cleanup_prompt_user: cleanup resources allocated by prompt_user() */
+static void
+cleanup_prompt_user(struct PromptWin *promptwin, bool on_stack)
+{
+ assert(window_stack != NULL);
+ if (promptwin != NULL) promptwin_cleanup(promptwin);
+ if (on_stack == true) stack_pop(window_stack);
+}
+
+/*== error handling functions ===============================================*/
+
+/* error: write error information to global struct 'error_info' */
+static void
+write_error(enum ErrorCode code, int line, const char *func_name)
+{
+ error_info.code = code;
+ error_info.line = line;
+ error_info.func_name = func_name;
+}
+
+/* print_error: print error stored in global struct 'error_info' */
+static int
+print_error(void)
+{
+ assert(error_info.code >= 1 && error_info.code < ERROR_MAX);
+ return fprintf((stderr), "%s:%d:%s(): %s\n",
+ __FILE__,
+ error_info.line,
+ error_info.func_name,
+ (error_texts[error_info.code] == NULL)
+ ? error_texts[ERROR_UNKNOWN]
+ : error_texts[error_info.code]);
+}
+
+/* dump_core: crashes the program to generate debugging information */
+void
+dump_core(void)
+{
+ // suppress -Wunused-function
+ (void) dump_core;
+ int *crash = NULL;
+ *crash = 7;
+}
+
+
+/*== window methods =========================================================*/
+
+/*-- MainWin methods --------------------------------------------------------*/
+
+/* mainwin_resize: resize any main window pointed to by 'window' */
+static void *
+mainwin_resize(void *window)
+{
+ assert(window != NULL);
+ struct MainWin *mainwin = window;
+
+ // clear richwin
+ err = wclear(mainwin->richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+ // delete richwin
+ err = richwin_del(mainwin->richwin);
+ if (err == ERR) return NULL;
+ // create new richwin
+ mainwin->richwin = mainwin->new_richwin(mainwin->sub);
+ if (mainwin->richwin == NULL) return NULL;
+ // redraw window
+ if (mainwin->draw_window(mainwin->sub) == NULL) return NULL;
+ return mainwin;
+}
+
+/* mainwin_cleanup: free resources used by a MainWin window */
+static void
+mainwin_cleanup(struct MainWin *mainwin)
+{
+ assert(mainwin != NULL);
+ richwin_del(mainwin->richwin);
+ mainwin->richwin = NULL;
+}
+
+/*-- BoardWin methods -------------------------------------------------------*/
+
+/* boardwin_new_richwin: defaults for board rich window */
+static struct RichWin *
+boardwin_new_richwin(void *window)
+{
+ (void) window;
+ struct RichWin *richwin;
+
+ // create window
+ richwin = richwin_new(LINES, COLS, 0, 0, NULL);
+ if (richwin == NULL) {
+ write_error(richwin_error_code, __LINE__, __func__);
+ return NULL;
+ }
+ // set options
+ err = leaveok(richwin->win, true);
+ if (err == ERR) {
+ richwin_del(richwin);
+ write_error(ERROR_OPTION_SETTING, __LINE__, __func__);
+ return NULL;
+ }
+ return richwin;
+}
+
+/* boardwin_draw: draw cool stuff on 'boardwin' in the color choosen by user */
+static void *
+boardwin_draw(void *window)
+{
+ assert(window != NULL);
+ struct BoardWin *boardwin = window;
+ struct RichWin *richwin = boardwin->base.richwin;
+ const int colstep = (int) strlen(boardwin->word);
+
+ // clear previous stuff
+ err = wclear(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+
+ if (!boardwin->on) {
+ // save current screen and exit if board is turned off
+ // write to virtual screen
+ err = wnoutrefresh(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ return NULL;
+ }
+ return boardwin;
+ }
+ if (colstep <= 0) {
+ // no appropriate word to draw
+ return boardwin;
+ }
+
+ // set color
+ if (colorize) {
+ err = wbkgd(richwin->win, COLOR_PAIR(2));
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+ }
+ // write 'word' all over 'boardwin'
+ const int left = (richwin->cols%colstep) / 2,
+ colmax = richwin->cols - colstep;
+ for (int line = 0; line < richwin->lines; line++) {
+ // print 'word' repeatedly until end of line
+ for (int col = left; col <= colmax; col+=colstep) {
+ err = mvwaddnstr(richwin->win, line, col, boardwin->word, colstep);
+ /* error is ignored when last character of word is written at the
+ * lower right margin of screen (addch succeeds but returns an
+ * error) */
+ if (err == ERR && richwin->cols-col != colstep) {
+ dump_core();
+ write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__);
+ return NULL;
+ }
+ }
+ }
+
+ // write to virtual screen
+ err = wnoutrefresh(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ return NULL;
+ }
+ return boardwin;
+}
+
+/* boardwin_cleanup: free resources used by a BoardWin window */
+static void
+boardwin_cleanup(struct BoardWin *boardwin)
+{
+ assert(boardwin != NULL);
+ free(boardwin->word);
+ boardwin->word = NULL;
+ cycle_free(boardwin->colors);
+ boardwin->colors = NULL;
+ mainwin_cleanup(&boardwin->base);
+}
+
+/*-- MenuWin methods --------------------------------------------------------*/
+
+/* menuwin_new_richwin: defaults for menu rich window */
+static struct RichWin *
+menuwin_new_richwin(void *window)
+{
+ (void) window;
+ int lines = min_int(ARRAY_LENGTH(menu_options)+BORDERS, LINES),
+ cols = COLS/2;
+ // ser borders
+ struct WinBorders winborders = {
+ .ls = '|', .rs = '|', .ts = '-', .bs = '-',
+ .tl = '+', .tr = '+', .bl = '+', .br = '+',
+ };
+ struct RichWin *richwin;
+
+ // create window
+ richwin = richwin_new_centered(lines, cols, &winborders);
+ if (richwin == NULL) {
+ write_error(richwin_error_code, __LINE__, __func__);
+ return NULL;
+ }
+ // set options
+ if (keypad(richwin->win, true) == ERR
+ ||leaveok(richwin->win, true) == ERR)
+ {
+ richwin_del(richwin);
+ write_error(ERROR_OPTION_SETTING, __LINE__, __func__);
+ return NULL;
+ }
+ return richwin;
+}
+
+/*
+ * menuwin_draw: draw 'menuwin' and it's options each centered on their own
+ * line, then highlight the selected option
+ */
+static void *
+menuwin_draw(void *window)
+{
+ assert(window != NULL);
+ static const int option_left = BORDER + INDICATOR,
+ highlight_left = BORDER;
+ struct MenuWin *menuwin = window;
+ struct RichWin *richwin = menuwin->base.richwin;
+ // calculate number of menu options to draw and width of each one
+ const int option_maxnum = richwin->lines - BORDERS,
+ option_num = min_int(option_maxnum, ARRAY_LENGTH(menu_options)),
+ option_width = richwin->cols - BORDERS - INDICATORS;
+
+ // clear menuwin
+ err = werase(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+
+ // border menuwin
+ err = richwin_border(richwin);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+
+ if (option_width > 0 && option_num > 0) {
+ // draw menu options centered on window
+ for (int i = 0; i < option_num; i++) {
+ const int option_top = BORDER + i;
+ const char *option_text = menu_options[i].text;
+ // write centered menu option
+ err = mvwaddcstr(richwin->win, option_top, option_left,
+ option_text, option_width, TEXT_CONTINUATION);
+ if (err == ERR) {
+ write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__);
+ return NULL;
+ }
+ }
+
+ // highlight selected option
+ const int highlight_top = BORDER + (int) cycle_index(menuwin->options),
+ highlight_width = option_width + INDICATORS;
+ if (highlight_top < BORDER+option_maxnum) {
+ highlight_str(richwin->win, highlight_top, highlight_left,
+ highlight_width, 1);
+ }
+ }
+
+ // write to virtual screen
+ err = wnoutrefresh(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ return NULL;
+ }
+ return menuwin;
+}
+
+/* menuwin_cleanup: free resources used by a MenuWin window */
+static void
+menuwin_cleanup(struct MenuWin *menuwin)
+{
+ assert(menuwin != NULL);
+ cycle_free(menuwin->options);
+ menuwin->options = NULL;
+ mainwin_cleanup(&menuwin->base);
+}
+
+/*-- PromptWin methods ------------------------------------------------------*/
+
+/* promptwin_new_richwin: defaults for prompt rich window */
+static struct RichWin *
+promptwin_new_richwin(void *window)
+{
+ assert(window != NULL);
+ struct PromptWin *promptwin = window;
+ const int lines = min_int(BORDERS+2, LINES),
+ needed_cols = promptwin->content_minlen + PADDINGS + BORDERS,
+ cols = min_int(needed_cols, COLS/2);
+ // set borders
+ struct WinBorders winborders = {
+ .ls = '|', .rs = '|', .ts = '-', .bs = '-',
+ .tl = '+', .tr = '+', .bl = '+', .br = '+',
+ };
+ struct RichWin *richwin;
+
+ // create window
+ richwin = richwin_new_centered(lines, cols, &winborders);
+ if (richwin == NULL) {
+ write_error(richwin_error_code, __LINE__, __func__);
+ return NULL;
+ }
+ // set options
+ err = keypad(richwin->win, true);
+ if (err == ERR) {
+ richwin_del(richwin);
+ write_error(ERROR_OPTION_SETTING, __LINE__, __func__);
+ return NULL;
+ }
+ return richwin;
+}
+
+/* promptwin_draw: draw 'propmtwin', it's prompt, and input field */
+static void *
+promptwin_draw(void *window)
+{
+ assert(window != NULL);
+ struct PromptWin *promptwin = window;
+ struct RichWin *richwin = promptwin->base.richwin;
+ static const int content_left = BORDER + PADDING,
+ prompt_top = BORDER;
+ const int content_width = richwin->cols - BORDERS - PADDING,
+ input_top = prompt_top + 1;
+
+ // clear window
+ err = werase(richwin->win) == ERR;
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+
+ // border window
+ err = richwin_border(richwin) == ERR;
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+
+ if (content_width > 0 && richwin->lines > input_top) {
+ // print prompt
+ err = mvwaddcstr(richwin->win, prompt_top, content_left,
+ promptwin->prompt, content_width, TEXT_CONTINUATION);
+ if (err == ERR) {
+ dump_core();
+ write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__);
+ return NULL;
+ }
+ // print input field
+ err = mvwaddcstr(richwin->win, input_top, content_left,
+ promptwin->input, content_width, INPUT_CONTINUATION);
+ if (err == ERR) {
+ write_error(ERROR_CHARACTER_OUTPUT, __LINE__, __func__);
+ return NULL;
+ }
+ }
+
+ // write to virtual screen
+ err = wnoutrefresh(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ return NULL;
+ }
+ return promptwin;
+}
+
+/* promptwin_cleanup: free resources used by a PromptWin window */
+static void
+promptwin_cleanup(struct PromptWin *promptwin)
+{
+ assert(promptwin != NULL);
+ free(promptwin->input);
+ promptwin->input = NULL;
+ mainwin_cleanup(&promptwin->base);
+}
+
+
+/*== application actions ====================================================*/
+
+/* app_draw: enable drawing and draw board */
+static struct BoardWin *
+app_draw(struct BoardWin *boardwin)
+{
+ assert(boardwin != NULL);
+
+ // turn board on and draw board
+ boardwin->on = true;
+ if (boardwin_draw(boardwin) == NULL) return NULL;
+ return boardwin;
+}
+
+/* app_clean: erase stuff drwan on 'boardwin' */
+static struct BoardWin *
+app_clean(struct BoardWin *boardwin)
+{
+ assert(boardwin != NULL);
+ struct RichWin *richwin = boardwin->base.richwin;
+
+ // turn off board and clear it
+ boardwin->on = false;
+ err = wclear(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+ // restore background color to original
+ if (colorize) {
+ err = wbkgd(richwin->win, COLOR_PAIR(0));
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_DRAWING, __LINE__, __func__);
+ return NULL;
+ }
+ }
+
+ // write to virtual screen
+ err = wnoutrefresh(richwin->win);
+ if (err == ERR) {
+ write_error(ERROR_WINDOW_RENDERING, __LINE__, __func__);
+ return NULL;
+ }
+ return boardwin;
+}
+
+/* app_change_word: replace word to draw with new one from user */
+static struct BoardWin *
+app_change_word(struct BoardWin *boardwin)
+{
+ assert(boardwin != NULL);
+
+ // prompt user for new word
+ if (prompt_user(DEFAULT_PROMPT, boardwin->word, WORD_MAXLEN) < 0) {
+ return NULL;
+ }
+ // draw board
+ if (boardwin->on) {
+ if (boardwin_draw(boardwin) == NULL) return NULL;
+ }
+ return boardwin;
+}
+
+/* app_change_color: change the color to use when drawing 'boardwin' */
+static struct BoardWin *
+app_change_color(struct BoardWin *boardwin)
+{
+ assert(boardwin != NULL);
+ if (!colorize) {
+ return boardwin;
+ }
+
+ // modify board drawing color pair
+ const short bg_color = *(const short *) cycle_next(boardwin->colors);
+ err = init_pair(2, COLOR_BLACK, bg_color);
+ if (err == ERR) {
+ write_error(ERROR_COLOR_PAIR_DEFINITION, __LINE__, __func__);
+ return NULL;
+ }
+ // draw board
+ if (boardwin->on) {
+ if (boardwin_draw(boardwin) == NULL) return NULL;
+ }
+ return boardwin;
+}
+
+/* app_end: mark 'boardwin' done being used so program can exit */
+static struct BoardWin *
+app_end(struct BoardWin *boardwin)
+{
+ assert(boardwin != NULL);
+ boardwin->done = true;
+ return boardwin;
+}
diff --git a/fun_menu.h b/fun_menu.h
new file mode 100644
index 0000000..27bce8b
--- /dev/null
+++ b/fun_menu.h
@@ -0,0 +1,28 @@
+#ifndef FUN_MENU_H
+#define FUN_MENU_H
+
+// dump core
+void dump_core(void);
+
+// define error codes
+enum ErrorCode {
+ ERROR_OK,
+ ERROR_LOCALE_SETTING,
+ ERROR_COLOR_STARTING,
+ ERROR_CURSES_SETTING,
+ ERROR_MEMORY_ALLOCATION,
+ ERROR_CHARACTER_INPUT,
+ ERROR_CHARACTER_OUTPUT,
+ ERROR_WINDOW_CREATION,
+ ERROR_WINDOW_DRAWING,
+ ERROR_WINDOW_RENDERING,
+ ERROR_COLOR_PAIR_DEFINITION,
+ ERROR_OPTION_SETTING,
+ ERROR_UNKNOWN,
+ ERROR_MAX,
+};
+
+// hold errors of richwin.c
+static enum ErrorCode richwin_error_code = ERROR_OK;
+
+#endif /* FUN_MENU_H */
diff --git a/richwin.c b/richwin.c
new file mode 100644
index 0000000..07af571
--- /dev/null
+++ b/richwin.c
@@ -0,0 +1,85 @@
+#include <stdlib.h> // for malloc() and free()
+#include <string.h> // for memcpy()
+#include <assert.h>
+
+#include <curses.h>
+#include "fun_menu.h"
+#include "richwin.h"
+
+/* richwin_new */
+struct RichWin *
+richwin_new(int lines, int cols, int begy, int begx,
+ struct WinBorders *winborders)
+{
+ assert(lines >= 0);
+ assert(cols >= 0);
+ assert(begy >= 0);
+ assert(begx >= 0);
+ // create a temporary struct for initializing const members of 'richwin'
+ struct RichWin richwin_init = {
+ .lines = lines, .cols = cols,
+ .begy = begy, .begx = begx,
+ };
+
+ // ask curses for a window
+ richwin_init.win = newwin(lines, cols, begy, begx);
+ if (richwin_init.win == NULL) {
+ richwin_error_code = ERROR_WINDOW_CREATION;
+ return NULL;
+ }
+ // allocate window borders struct if user asked for it
+ if (winborders != NULL) {
+ richwin_init.winborders = malloc(sizeof *richwin_init.winborders);
+ if (richwin_init.winborders == NULL) {
+ richwin_error_code = ERROR_MEMORY_ALLOCATION;
+ return NULL;
+ }
+ *richwin_init.winborders = *winborders;
+ }
+
+ // allocate rich window struct
+ struct RichWin *richwin = malloc(sizeof *richwin);
+ if (richwin == NULL) {
+ richwin_error_code = ERROR_MEMORY_ALLOCATION;
+ return NULL;
+ }
+ // initialize 'richwin' from 'richwin_init'
+ memcpy(richwin, &richwin_init, sizeof *richwin);
+ return richwin;
+}
+
+/* richwin_new_centered */
+struct RichWin *
+richwin_new_centered(int lines, int cols, struct WinBorders *winborders)
+{
+ assert(lines <= LINES);
+ assert(cols <= COLS);
+ // no need for asserting because this is a convenience wrapper
+ return richwin_new(lines, cols, (LINES-lines)/2, (COLS-cols)/2,
+ winborders);
+}
+
+/* richwin_border */
+int
+richwin_border(struct RichWin *richwin)
+{
+ assert(richwin != NULL);
+ assert(richwin->winborders != NULL);
+ struct WinBorders *winborders = richwin->winborders;
+ return wborder(richwin->win,
+ winborders->ls, winborders->rs, winborders->ts, winborders->bs,
+ winborders->tl, winborders->tr, winborders->bl, winborders->br);
+}
+
+/* richwin_del */
+int
+richwin_del(struct RichWin *richwin)
+{
+ assert(richwin != NULL);
+ if (delwin(richwin->win) == ERR) {
+ return ERR;
+ }
+ free(richwin->winborders);
+ free(richwin);
+ return OK;
+}
diff --git a/richwin.h b/richwin.h
new file mode 100644
index 0000000..5a9bfb2
--- /dev/null
+++ b/richwin.h
@@ -0,0 +1,68 @@
+#ifndef RICHWIN_H
+#define RICHWIN_H
+
+// a rich window: represents a ncurses window with stats
+struct RichWin {
+ WINDOW *win;
+ struct WinBorders *winborders;
+ const int lines, cols;
+ const int begy, begx;
+};
+
+// window borders: holds each character for an ncurses window border
+struct WinBorders {
+ chtype ls, rs, ts, bs,
+ tl, tr, bl, br;
+};
+
+/*
+ * richwin_new: create a new rich window
+ * parameters:
+ * lines (int) -> window line number
+ * colums (int) -> window column number
+ * begy (int) -> y-coordinate of upper-left corner of window
+ * begx (int) -> x-coordinate of upper-left corner of window
+ * winborders (struct WinBorders *)
+ * returns:
+ * struct RichWin * -> pointer to the new window
+ * NULL -> failure
+ */
+struct RichWin *richwin_new(int lines, int cols, int begy, int begx,
+ struct WinBorders *winborders);
+
+/*
+ * richwin_new_centered create a new centered rich window
+ * parameters:
+ * lines (int) -> window line number
+ * colums (int) -> window column number
+ * begy (int) -> y-coordinate of upper-left corner of window
+ * begx (int) -> x-coordinate of upper-left corner of window
+ * winborders (struct WinBorders *)
+ * returns:
+ * struct RichWin * -> pointer to the new window
+ * NULL -> failure
+ */
+struct RichWin *richwin_new_centered(int lines, int cols,
+ struct WinBorders *winborders);
+
+/*
+ * richwin_border: border a rich window
+ * parameters:
+ * winborders (struct WinBorders *)
+ * returns:
+ * ERR -> if wborder() returns ERR
+ * OK -> on success
+ */
+int richwin_border(struct RichWin *richwin);
+
+/*
+ * richwin_del: delete a rich window
+ * parameters:
+ * richwin (struct RichWin *) -> window to delete
+ * returns:
+ * ERR -> if delwin() returns ERR
+ * OK -> on success
+ */
+int richwin_del(struct RichWin *richwin);
+
+#endif /* RICHWIN_H */
diff --git a/stack.c b/stack.c
new file mode 100644
index 0000000..178ed6e
--- /dev/null
+++ b/stack.c
@@ -0,0 +1,179 @@
+#include <assert.h>
+#include <stdlib.h> // for malloc() and free()
+#include "stack.h"
+
+/* node of a doubly-linked list */
+struct Node {
+ struct Node *next, *prev;
+ void *data;
+};
+
+/*
+ * stack implemented using a doubly-linked list to allow traversal from the top
+ * or the bottom
+ */
+struct Stack {
+ struct Node *top, *bottom;
+ size_t size;
+};
+
+/* node_new: create a new doubly-linked list node */
+static struct Node *
+node_new(void)
+{
+ struct Node *node = malloc(sizeof *node);
+ if (node == NULL) {
+ return NULL;
+ }
+ node->next = node->prev = node->data = NULL;
+ return node;
+}
+
+/* node_free: free a doubly-linked list node */
+static void
+node_free(struct Node *node)
+{
+ assert(node != NULL);
+ free(node);
+}
+
+/* stack_new: create a new empty stack */
+struct Stack *
+stack_new(void)
+{
+ struct Stack *stack = malloc(sizeof *stack);
+ if (stack == NULL) {
+ return NULL;
+ }
+ stack->top = stack->bottom = NULL;
+ stack->size = 0;
+ return stack;
+}
+
+/*
+ * stack_delete: free memory used by the stack and it's nodes. But don't free
+ * any data pointed to by the stack nodes
+ * good for stacks that don't store pointers to dynamically allocated buffers
+ */
+void
+stack_delete(struct Stack *stack)
+{
+ assert(stack != NULL);
+ for (struct Node *node = stack->top, *next_node;
+ node != NULL; node = next_node)
+ {
+ next_node = node->next;
+ node_free(node);
+ }
+ free(stack);
+}
+
+/*
+ * stack_free: free memory used by the stack, it's nodes, and the data stored
+ * inside each node
+ * good for stacks that store pointers to dynamically allocated buffers
+ */
+void
+stack_free(struct Stack *stack, void (*free_data)(void *))
+{
+ assert(stack != NULL);
+ assert(free_data != NULL);
+
+ for (struct Node *node = stack->top, *next_node;
+ node != NULL; node = next_node)
+ {
+ next_node = node->next;
+ free_data(node->data);
+ node_free(node);
+ }
+ free(stack);
+}
+
+/* push_stack: push a new node of value 'data' to top of 'stack' */
+void *
+stack_push(struct Stack *stack, void *data)
+{
+ assert(stack != NULL);
+ assert(data != NULL);
+
+ // create a new node with passed 'data'
+ struct Node *newtop;
+ if ((newtop = node_new()) == NULL) {
+ return NULL;
+ }
+ newtop->data = data;
+
+ // push node to stack
+ if (stack->top == NULL) {
+ // first node in stack
+ stack->top = stack->bottom = newtop;
+ } else {
+ // link new top and old top
+ newtop->next = stack->top;
+ stack->top->prev = newtop;
+ // put new node at top of stack
+ stack->top = newtop;
+ }
+ stack->size++;
+ return newtop;
+}
+
+/* stack_pop: pop data value of top node of 'stack' */
+void *
+stack_pop(struct Stack *stack)
+{
+ assert(stack != NULL);
+ struct Node *oldtop = stack->top,
+ *newtop = stack->top->next;
+ void *data = oldtop->data;
+
+ // remove reference to old top and free it
+ newtop->prev = NULL;
+ node_free(oldtop);
+ // set new stack top and size
+ stack->top = newtop;
+ stack->size--;
+ return data;
+}
+
+/* stack_size: accessor for size of stack */
+size_t
+stack_size(struct Stack *stack)
+{
+ assert(stack != NULL);
+ return stack->size;
+}
+
+/*
+ * stack_iter: loop over 'stack' nodes from top to bottom, running 'apply' on
+ * each node
+ */
+void *
+stack_iter(struct Stack *stack, void *(*apply)(void *))
+{
+ assert(stack != NULL);
+ assert(apply != NULL);
+ for (struct Node *node = stack->top; node != NULL; node = node->next) {
+ if (apply(node->data) == NULL) {
+ return NULL;
+ }
+ }
+ return stack;
+}
+
+/*
+ * stack_iter: loop over 'stack' nodes from bottom to top, running 'apply' on
+ * each node
+ */
+void *
+stack_iter_reverse(struct Stack *stack, void *(*apply)(void *))
+{
+ assert(stack != NULL);
+ assert(apply != NULL);
+ for (struct Node *node = stack->bottom; node != NULL; node = node->prev) {
+ if (apply(node->data) == NULL) {
+ return NULL;
+ }
+ }
+ return stack;
+}
diff --git a/stack.h b/stack.h
new file mode 100644
index 0000000..611a54c
--- /dev/null
+++ b/stack.h
@@ -0,0 +1,68 @@
+#ifndef STACK_H
+#define STACK_H
+
+// stack
+typedef struct Stack Stack;
+
+/*
+ * stack_new: create a new stack
+ * returns:
+ * Stack * -> the created stack
+ * NULL -> failed to allocate memory for stack
+ */
+Stack *stack_new(void);
+
+/* stack_delete: free the stack, but don't free any buffer in any of it's */
+void stack_delete(Stack *stack);
+
+/*
+ * stack_free: free the stack, each element is also freed accordingly using
+ * 'free_data'
+ * parameters:
+ * stack (Stack *) -> stack to delete
+ * free_data -> function that would free resources of each element in stack
+ */
+void stack_free(Stack *stack, void (*free_data)(void *data));
+
+/*
+ * stack_push: push element to top of stack
+ * parameters:
+ * stack (Stack *) -> stack to push data into
+ * data (void *) -> data to push
+ * returns:
+ * void * -> the pushed data
+ * NULL -> failure of allocating memory for new stack element
+ */
+void *stack_push(Stack *stack, void *data);
+
+/* stack_pop: pop element from top of stack */
+void *stack_pop(Stack *stack);
+
+/* stack_get_size: get size of stack */
+size_t stack_size(Stack *stack);
+
+/*
+ * stack_get_size: iterate over a stack top-to-bottom, running 'apply' on each
+ * stack element
+ * parameters:
+ * stack (Stack *) -> stack to iterate over
+ * apply -> function that would be passed each element in stack
+ * returns:
+ * void * -> sucess (pointer to stack)
+ * NULL -> one of the executions of 'apply' returned NULL
+ */
+void *stack_iter(Stack *stack, void *(*apply)(void *data));
+
+/*
+ * stack_iter_reverse: iterate over a stack bottom-to-top, running 'apply' on
+ * each stack element
+ * parameters:
+ * stack (Stack *) -> stack to iterate over
+ * apply -> function that would be passed each element in stack
+ * returns:
+ * void * -> sucess (pointer to stack)
+ * NULL -> one of the executions of 'apply' returned NULL
+ */
+void *stack_iter_reverse(Stack *stack, void *(*apply)(void *data));
+
+#endif /* STACK_H */
diff --git a/utils.c b/utils.c
new file mode 100644
index 0000000..e3f79f9
--- /dev/null
+++ b/utils.c
@@ -0,0 +1,25 @@
+#include <stddef.h> // for int
+#include <math.h> // for floorf()
+#include "utils.h"
+
+int
+min_int(int a, int b)
+{
+ return (a < b) ? a : b;
+}
+size_t
+min_size_t(size_t a, size_t b)
+{
+ return (a < b) ? a : b;
+}
+
+int
+max_int(int a, int b)
+{
+ return (a > b) ? a : b;
+}
+size_t
+max_size_t(size_t a, size_t b)
+{
+ return (a > b) ? a : b;
+}
diff --git a/utils.h b/utils.h
new file mode 100644
index 0000000..b214d5d
--- /dev/null
+++ b/utils.h
@@ -0,0 +1,11 @@
+#ifndef UTILS_H
+#define UTILS_H
+
+// min: get minimum of 'a' and 'b'
+int min_int(int a, int b);
+size_t min_size_t(size_t a, size_t b);
+// max: get maximum of 'a' and 'b'
+int max_int(int a, int b);
+size_t max_size_t(size_t a, size_t b);
+
+#endif /* UTILS_H */