Commit Diff


commit - 35eacac40f265aad47bf25d10f3ecd3670b79b2f
commit + b7ac144cd2d242791938b51569effb7a1378a332
blob - /dev/null
blob + 2c66a2de04966bd6fce25fd857707b6404d38389 (mode 755)
--- /dev/null
+++ 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
blob - /dev/null
blob + 6bdf994181f2a050b07fd5839f3a9d8469deff62 (mode 644)
--- /dev/null
+++ 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);
+}
blob - /dev/null
blob + 96d98c1cee5226c6f590273955f8795630a626b1 (mode 644)
--- /dev/null
+++ 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 */
blob - /dev/null
blob + 36fed4df6c583ba7b9d5f4c6ed4ada54b1e781d6 (mode 644)
--- /dev/null
+++ 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;
+}
blob - /dev/null
blob + 27bce8bd7876ef27f4da8a895c6599c815f68fc1 (mode 644)
--- /dev/null
+++ 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 */
blob - /dev/null
blob + 07af571872ae54a6f7e38300a1b3bc98ddc3b23d (mode 644)
--- /dev/null
+++ 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;
+}
blob - /dev/null
blob + 5a9bfb24ac7b89cf603b29a2add23fca6bfc9d79 (mode 644)
--- /dev/null
+++ 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 */
blob - /dev/null
blob + 178ed6ed3e5659964a23a6245691c6b693bee54a (mode 644)
--- /dev/null
+++ 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;
+}
blob - /dev/null
blob + 611a54c4022d615b2c39730534002b43e61ab8e9 (mode 644)
--- /dev/null
+++ 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 */
blob - /dev/null
blob + e3f79f930e72fa34c644f38932888514907f2fd9 (mode 644)
--- /dev/null
+++ 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;
+}
blob - /dev/null
blob + b214d5d77f46858406aeae3c09df1fab54ff408d (mode 644)
--- /dev/null
+++ 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 */