X-Git-Url: https://git.cworth.org/git?p=mnemon;a=blobdiff_plain;f=mnemon.c;h=737298ed045f4877765118845e2626050ec816bc;hp=8f735f4d21750073ff5a40f58058f6f18f8b08f5;hb=abd2230ad0fc47109cbc97a767b87e70f459b053;hpb=33c6037c59e76bf85487253953aa9d09ea208532 diff --git a/mnemon.c b/mnemon.c index 8f735f4..737298e 100644 --- a/mnemon.c +++ b/mnemon.c @@ -21,21 +21,92 @@ #include #include #include +#include #include #include #include #include +#include -typedef struct _mnemon { - int dummy; -} mnemon_t; +#include +#include -typedef struct _ic { - int bin; +typedef int bool_t; + +typedef struct _item { + int count; char *challenge; char *response; -} ic_t; +} item_t; + +typedef struct _bin { + int count; + int items_size; + int num_items; + item_t **items; +} bin_t; + +typedef struct _category { + char *name; + int items_size; + int num_items; + item_t *items; +} category_t; + +typedef struct _mnemon { + char *dir_name; + + int categories_size; + int num_categories; + category_t *categories; + + int bins_size; + int num_bins; + bin_t *bins; +} mnemon_t; + +static void * +xmalloc (size_t size) +{ + void *ret; + + ret = malloc (size); + if (ret == NULL) { + fprintf (stderr, "Error: out of memory\n"); + exit (1); + } + + return ret; +} + +static void * +xrealloc (void *ptr, size_t size) +{ + void *ret; + + ret = realloc (ptr, size); + if (ret == NULL) { + fprintf (stderr, "Error: out of memory\n"); + exit (1); + } + + return ret; +} + +static char * +xstrdup (const char *s) +{ + char *ret; + + ret = strdup (s); + if (s == NULL) { + fprintf (stderr, "Error: out of memory\n"); + exit (1); + } + + return ret; +} static void xasprintf (char **strp, const char *fmt, ...) @@ -53,6 +124,273 @@ xasprintf (char **strp, const char *fmt, ...) } } +static void +item_init (item_t *item, + int count, + const char *challenge, + const char *response) +{ + item->count = count; + + item->challenge = xmalloc (strlen (challenge) + 1 + + strlen (response) + 1); + item->response = item->challenge + strlen (challenge) + 1; + + strcpy (item->challenge, challenge); + strcpy (item->response, response); +} + +static void +item_fini (item_t *item) +{ + /* item->response shares allocation with item->challenge, so + * doesn't require a separate call to free */ + free (item->challenge); +} + +static void +category_init (category_t *category, + const char *name) +{ + category->name = xstrdup (name); + + category->items_size = 0; + category->num_items = 0; + category->items = NULL; +} + +static void +category_fini (category_t *category) +{ + int i; + + for (i = 0; i < category->num_items; i++) + item_fini (&category->items[i]); + + free (category->items); + + free (category->name); +} + +static void +category_grow (category_t *category) +{ + if (category->items_size) + category->items_size *= 2; + else + category->items_size = 1; + + category->items = xrealloc (category->items, + category->items_size * sizeof (item_t)); +} + +static item_t * +category_add_item (category_t *category, + int count, + const char *challenge, + const char *response) +{ + item_t *item; + + if (category->num_items == category->items_size) + category_grow (category); + + item = &category->items[category->num_items++]; + + item_init (item, count, challenge, response); + + return item; +} + +static void +category_print (category_t *category, + FILE *file) +{ + int i; + item_t *item; + + for (i = 0; i < category->num_items; i++) { + item = &category->items[i]; + if (i != 0) + fprintf (file, "\n"); + fprintf (file, "%d\n%s\n%s\n", + item->count, + item->challenge, + item->response); + } +} + +static void +bin_init (bin_t *bin, + int count) +{ + bin->count = count; + + bin->items_size = 0; + bin->num_items = 0; + bin->items = NULL; +} + +static void +bin_fini (bin_t *bin) +{ + free (bin->items); +} + +static void +bin_grow (bin_t *bin) +{ + if (bin->items_size) + bin->items_size *= 2; + else + bin->items_size = 1; + + bin->items = xrealloc (bin->items, + bin->items_size * sizeof (item_t*)); +} + +static void +bin_add_item (bin_t *bin, + item_t *item) +{ + assert (item->count == bin->count); + + if (bin->num_items == bin->items_size) + bin_grow (bin); + + bin->items[bin->num_items++] = item; +} + +static void +bin_remove_item (bin_t *bin, + int item_index) +{ + /* Replace the current item with the last item, (no need to shift + * any more than that since we don't care about the order of the + * items within a bin). */ + bin->num_items--; + if (bin->num_items) + bin->items[item_index] = bin->items[bin->num_items]; +} + +static void +mnemon_init (mnemon_t *mnemon) +{ + char *home; + + home = getenv ("HOME"); + if (home == NULL) + home = ""; + + xasprintf (&mnemon->dir_name, "%s/.mnemon", getenv ("HOME")); + + mnemon->categories_size = 0; + mnemon->num_categories = 0; + mnemon->categories = NULL; + + mnemon->bins_size = 0; + mnemon->num_bins = 0; + mnemon->bins = NULL; +} + +static void +mnemon_fini (mnemon_t *mnemon) +{ + int i; + + for (i = 0; i < mnemon->num_bins; i++) + bin_fini (&mnemon->bins[i]); + free (mnemon->bins); + + for (i = 0; i < mnemon->num_categories; i++) + category_fini (&mnemon->categories[i]); + free (mnemon->categories); + + free (mnemon->dir_name); +} + +static void +mnemon_categories_grow (mnemon_t *mnemon) +{ + if (mnemon->categories_size) + mnemon->categories_size *= 2; + else + mnemon->categories_size = 1; + + mnemon->categories = xrealloc (mnemon->categories, + mnemon->categories_size * sizeof (category_t)); +} + +static category_t * +mnemon_get_category (mnemon_t *mnemon, + const char *name) +{ + int i; + category_t *category; + + for (i = 0; i < mnemon->num_categories; i++) + if (strcmp (mnemon->categories[i].name, name) == 0) + return &mnemon->categories[i]; + + mnemon_categories_grow (mnemon); + + category = &mnemon->categories[mnemon->num_categories++]; + + category_init (category, name); + + return category; +} + +static void +mnemon_bins_grow (mnemon_t *mnemon) +{ + if (mnemon->bins_size) + mnemon->bins_size *= 2; + else + mnemon->bins_size = 1; + + mnemon->bins = xrealloc (mnemon->bins, + mnemon->bins_size * sizeof (bin_t)); +} + +static bin_t * +mnemon_get_bin (mnemon_t *mnemon, + int count) +{ + int i; + bin_t *bin; + + for (i = 0; i < mnemon->num_bins; i++) + if (mnemon->bins[i].count == count) + return &mnemon->bins[i]; + else if (mnemon->bins[i].count > count) + break; + + mnemon_bins_grow (mnemon); + + bin = &mnemon->bins[i]; + + /* Make room to insert new bin at its sorted location. */ + memmove (bin + 1, bin, (mnemon->num_bins - i) * sizeof (bin_t)); + mnemon->num_bins++; + + bin_init (bin, count); + + return bin; +} + +static void +mnemon_remove_bin (mnemon_t *mnemon, + bin_t *bin) +{ + int i = bin - mnemon->bins; + + bin_fini (bin); + + memmove (bin, bin + 1, (mnemon->num_bins - i) * sizeof (bin_t)); + mnemon->num_bins--; +} + static void chomp (char *s) { @@ -64,14 +402,20 @@ chomp (char *s) } static void -mnemon_load_from_file (mnemon_t *mnemon, - char *path) +mnemon_load_category (mnemon_t *mnemon, + const char *name) { FILE *file; char *line = NULL, *end; size_t line_size = 0; ssize_t bytes_read; int line_count = 0; + char *path; + category_t *category; + int i; + + path = xmalloc (strlen (mnemon->dir_name) + 1 + strlen (name) + 1); + sprintf (path, "%s/%s", mnemon->dir_name, name); file = fopen (path, "r"); if (file == NULL) { @@ -80,8 +424,11 @@ mnemon_load_from_file (mnemon_t *mnemon, exit (1); } + category = mnemon_get_category (mnemon, name); + while (1) { - ic_t ic; + int count; + char *challenge, *response; /* Read bin number (ignoring blank separator lines) */ do { @@ -92,7 +439,7 @@ mnemon_load_from_file (mnemon_t *mnemon, chomp (line); } while (*line == '\0'); - ic.bin = strtol (line, &end, 10); + count = strtol (line, &end, 10); if (*end != '\0') { fprintf (stderr, "Failed to parse bin number from \"%s\" at %s:%d\n", line, path, line_count); @@ -105,7 +452,7 @@ mnemon_load_from_file (mnemon_t *mnemon, break; line_count++; chomp (line); - ic.challenge = strdup (line); + challenge = strdup (line); /* Read response */ bytes_read = getline (&line, &line_size, file); @@ -113,31 +460,43 @@ mnemon_load_from_file (mnemon_t *mnemon, break; line_count++; chomp (line); - ic.response = strdup (line); + response = line; + + category_add_item (category, count, challenge, response); - /* XXX: Add ic to mnemon here */ - printf ("%d: %s => %s\n", ic.bin, ic.challenge, ic.response); - free (ic.challenge); - free (ic.response); + free (challenge); } END_OF_FILE: free (line); fclose (file); + free (path); + + /* Resize category items to fit exactly. */ + category->items_size = category->num_items; + category->items = xrealloc (category->items, category->items_size * sizeof (item_t)); + + /* Now that the category is completely loaded, with stable + * pointers to every item, we can add each item to its appropriate + * bin. */ + for (i = 0; i < category->num_items; i++) { + item_t *item = &category->items[i]; + bin_t *bin = mnemon_get_bin (mnemon, item->count); + + bin_add_item (bin, item); + } } static void -mnemon_load_from_directory_recursive (mnemon_t *mnemon, - char *path) +mnemon_load (mnemon_t *mnemon) { DIR *dir; struct dirent *dirent; - char *child_path; - dir = opendir (path); + dir = opendir (mnemon->dir_name); if (dir == NULL) { fprintf (stderr, "Error: Failed to open directory %s: %s\n", - path, strerror (errno)); + mnemon->dir_name, strerror (errno)); exit (1); } @@ -146,43 +505,233 @@ mnemon_load_from_directory_recursive (mnemon_t *mnemon, if (dirent == NULL) break; - xasprintf (&child_path, "%s/%s", path, dirent->d_name); - if (dirent->d_type == DT_DIR) { - if (strcmp (dirent->d_name, ".") && - strcmp (dirent->d_name, "..")) - { - mnemon_load_from_directory_recursive (mnemon, child_path); - } - } else if (dirent->d_type == DT_REG) { + if (dirent->d_type == DT_REG) { /* Ignore files matching *~, (yes, this shouldn't be * hard-coded in such an ad-hoc way, but there you go. */ - if (child_path[strlen(child_path)-1] != '~') - mnemon_load_from_file (mnemon, child_path); - } else { - fprintf (stderr, "Warning: Ignoring file %s\n", child_path); + if (dirent->d_name[strlen(dirent->d_name)-1] != '~') + mnemon_load_category (mnemon, dirent->d_name); } - - free (child_path); } closedir (dir); } static void -mnemon_init (mnemon_t *mnemon) +mnemon_save (mnemon_t *mnemon) { - char *dot_mnemon; - char *home; + int i, err; + char *filename, *lock_filename; + FILE *file; + category_t *category; - home = getenv ("HOME"); - if (home == NULL) - home = ""; + for (i = 0; i < mnemon->num_categories; i++) { + category = &mnemon->categories[i]; + + xasprintf (&filename, "%s/%s", + mnemon->dir_name, category->name); + xasprintf (&lock_filename, "%s/.#%s", + mnemon->dir_name, category->name); + + file = fopen (lock_filename, "w"); + if (file == NULL) { + fprintf (stderr, "Error: Failed to open %s for writing: %s\n", + lock_filename, strerror (errno)); + continue; + } + + category_print (category, file); + + fclose (file); + + err = rename (lock_filename, filename); + if (err < 0) { + fprintf (stderr, "Error: Failed to rename %s to %s: %s\n", + lock_filename, filename, strerror (errno)); + continue; + } - xasprintf (&dot_mnemon, "%s/.mnemon", getenv ("HOME")); + free (filename); + free (lock_filename); + } +} + +/* Return a uniformly-distributed pseudo-random integer within the + * range: + * + * 0 <= result < num_values + */ +static int +rand_within (int num_values) +{ + return (int) (num_values * (rand() / (RAND_MAX + 1.0))); +} + +/* Return an exponentially-distributed pseudo-random integer within + * the range: + * + * 0 <= result < num_values + * + * The distribution is such that each successively larger value will + * occur with a probability of half of the previous value. + */ +static int +rand_within_exponential (int num_values) +{ + static int r; + static uint32_t mask = 0; + int ones; + int bit; + + /* Optimize the constant case. */ + if (num_values == 1) + return 0; + + ones = 0; + + do { + if (mask == 0) { + r = rand (); + mask = 1 << 31; + while (mask > RAND_MAX) + mask >>= 1; + } + bit = r & mask; + mask >>= 1; + if (bit) { + ones++; + if (ones == num_values) + ones = 0; + } + } while (bit); + + return ones; +} + +static void +mnemon_select_item (mnemon_t *mnemon, + bin_t **bin_ret, + int *item_index_ret) +{ + int bin_index; + bin_t *bin; + + bin_index = rand_within_exponential (mnemon->num_bins); + + bin = &mnemon->bins[bin_index]; + + *bin_ret = bin; + *item_index_ret = rand_within (bin->num_items); +} + +static void +mnemon_do_challenges (mnemon_t *mnemon, + int to_introduce) +{ + bin_t *bin; + int item_index; + item_t *item; + char *response; + bool_t correct; + int unlearned; + int i; + + /* Count the number of items with negative counts. */ + unlearned = 0; + for (i = 0; i < mnemon->num_bins; i++) { + bin = &mnemon->bins[i]; + if (bin->count >= 0) + break; + unlearned += bin->num_items; + } + + to_introduce -= unlearned; + if (to_introduce < 0) + to_introduce = 0; + + if (unlearned) { + printf ("You've got %d items to learn already. ", unlearned); + if (to_introduce) + printf ("I'll introduce %d more as we go.", to_introduce); + printf ("\n"); + } else { + printf ("Introducing %d new items.\n", to_introduce); + } + printf ("\n"); + + do { + mnemon_select_item (mnemon, &bin, &item_index); + + if (bin->count == 0) + to_introduce--; + + item = bin->items[item_index]; + + printf ("%s\n", item->challenge); + + response = readline ("> "); + if (response == NULL) { + printf ("\n"); + break; + } + + correct = (strcmp (response, item->response) == 0); - mnemon_load_from_directory_recursive (mnemon, dot_mnemon); + bin_remove_item (bin, item_index); - free (dot_mnemon); + /* If the bin is now empty, we must remove it. Also if we just + * picked the last word we'll ever pick from the bin with + * count 0, then we can remove that as well. */ + if (bin->num_items == 0 || + (bin->count == 0 && to_introduce == 0)) + { + mnemon_remove_bin (mnemon, bin); + } + + if (correct) { + item->count++; + /* We reserve an item count of 0 for an item that has + * never been asked. */ + if (item->count == 0) { + item->count = 1; + unlearned--; + printf ("You got it!"); + } else if (item->count < 0) { + printf ("Yes---just give me %d more.", + - item->count); + } else if (item->count == 1) { + printf ("On your first try, no less!"); + } else { + printf ("Masterful."); + } + } else { + printf (" %s is the correct answer.", + item->response); + if (item->count >= 0) + unlearned++; + item->count--; + /* Penalize an incorrect response by forcing the count + * negative. */ + if (item->count >= 0) { + item->count = -1; + printf ( " Oops, you knew that, right?\n "); + } + } + + printf (" ("); + if (to_introduce) + printf ("%d to come.", to_introduce); + if (to_introduce && unlearned) + printf (" "); + if (unlearned) + printf ("%d still unlearned.", unlearned); + if (to_introduce == 0 && unlearned == 0) + printf ("Great job!"); + printf (")\n\n"); + + bin = mnemon_get_bin (mnemon, item->count); + + bin_add_item (bin, item); + } while (unlearned || to_introduce); } int @@ -190,7 +739,17 @@ main (int argc, char *argv[]) { mnemon_t mnemon; + srand (time (NULL)); + mnemon_init (&mnemon); + mnemon_load (&mnemon); + + mnemon_do_challenges (&mnemon, 10); + + mnemon_save (&mnemon); + + mnemon_fini (&mnemon); + return 0; }