/** @std GNU C11 */ #include "lex.h" #include /* chdir (POSIX) */ #include /* mode_t (POSIX) */ #include /* umask (POSIX) */ #include /* opendir readdir closedir */ #include #include #include #include #if INT_MAX >= 100000000000 #error int_to_string requires truncation on this compiler. #endif static void int_to_string(const int *const n, char (*const a)[12]) { sprintf(*a, "%d", *n); } #define ARRAY_NAME int #define ARRAY_TYPE int #define ARRAY_EXPECT_TRAIT #include "array.h" #define ARRAY_TO_STRING &int_to_string #include "array.h" static int int_cmp(const int *const a, const int *const b) { return (*b < *a) - (*a < *b); } static int void_int_cmp(const void *const a, const void *const b) { return int_cmp(a, b); } #define ARRAY_NAME char #define ARRAY_TYPE char #include "array.h" /** Append a text file, `fn`, to `c`, and add a '\0'. @return Success. A partial read is failure. @throws[fopen, fread, malloc] @throws[EISEQ] The text file has embedded nulls. @throws[ERANGE] If the standard library does not follow POSIX. */ static int append_file(struct char_array *c, const char *const fn) { FILE *fp = 0; const size_t granularity = 1024; size_t nread; char *cursor; int success = 0; assert(c && fn); if(!(fp = fopen(fn, "r"))) goto catch; /* Read entire file in chunks. */ do if(!(cursor = char_array_buffer(c, granularity)) || (nread = fread(cursor, 1, granularity, fp), ferror(fp)) || !char_array_append(c, nread)) goto catch; while(nread == granularity); /* File to `C` string. */ if(!(cursor = char_array_new(c))) goto catch; *cursor = '\0'; /* Binary files with embedded '\0' are not allowed. */ if(strchr(c->data, '\0') != cursor) { errno = EILSEQ; goto catch; } { success = 1; goto finally; } catch: if(!errno) errno = EILSEQ; /* Will never be true on POSIX. */ finally: if(fp && fclose(fp)) success = 0; return success; } /** Is `y` a leap-year? */ static int leap(int y) { assert(y >= 1582); if(!(y % 400)) return 1; if(!(y % 100)) return 0; if(!(y % 4)) return 1; return 0; } /** Not defined for some implementations. C11 */ union date32 { uint32_t u32; struct { unsigned day : 5, month : 4, year : 23; }; }; static int date_mixup(union date32 a, union date32 b) { return a.u32 > b.u32; } static void date32_to_string(const union date32 d, char (*const z)[12]) { assert(d.year < 10000 && d.month && d.month <= 31 && d.day && d.day <= 31); sprintf(*z, "%u-%2.2u-%2.2u", d.year % 10000, d.month, d.day); } /** Convert or narrower type or return zero. */ static union date32 date_to_32(const int y, const int m, const int d) { union date32 d32 = { 0 }; /* Leap year calculations only work at y>=1 and Gregorian Calendar and max 23 bits. */ if(y < 1582 || y > 8388607 || m < 1 || m > 12 || d < 1 || d > 31) goto no; switch(m) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: break; case 4: case 6: case 9: case 11: if(d > 30) goto no; break; case 2: if(d > 28 + leap(y)) goto no; break; default: assert(0); break; } d32.year = (unsigned)y, d32.month = (unsigned)m, d32.day = (unsigned)d; no: return d32; } /** Tomohiko Sakamoto comp.lang.c 1993-04-10. */ static unsigned weekday(union date32 d) { d.year -= d.month < 3; return (d.year + d.year / 4 - d.year / 100 + d.year / 400 + "-bed=pen+mad."[d.month] + d.day) % 7; } #define ARRAY_NAME lex #define ARRAY_TYPE struct lex #include "array.h" struct page_tree_entry_c; static void entry_to_string(struct page_tree_entry_c, char (*)[12]); struct page { struct char_array entry; struct lex_array lexx; }; #define TREE_NAME page #define TREE_KEY union date32 #define TREE_VALUE struct page #define TREE_COMPARE &date_mixup #define TREE_EXPECT_TRAIT #include "tree.h" #define TREE_TO_STRING &entry_to_string #include "tree.h" static void entry_to_string(const struct page_tree_entry_c entry, char (*const z)[12]) { date32_to_string(*entry.key, z); } struct source { char *key, *desc; }; int main(int argc, char **argv) { int success = EXIT_SUCCESS; /* For reading in files, overwritten. */ DIR *dir = 0; struct dirent *de; struct int_array years = int_array(), months = int_array(), days = int_array(); int *y, *y_end; struct page_tree journal = page_tree(); errno = 0; if(argc != 2) { fprintf(stderr, "Needs journal location.\n" "(should contain //.txt)\n"); goto finally; } /* Get the years list as directories matching a year. */ if(chdir(argv[1]) == -1 || !(dir = opendir("."))) goto catch; while((de = readdir(dir))) { struct stat st; int year, *p; if(!lex_looks_like_year(de->d_name, &year)) continue; if(stat(de->d_name, &st)) goto catch; if(!S_ISDIR(st.st_mode)) continue; if(!(p = int_array_new(&years))) goto catch; *p = year; } closedir(dir), dir = 0; /* Sort the years for sensible ordering of parsing. */ qsort(years.data, years.size, sizeof *years.data, &void_int_cmp); fprintf(stderr, "Files in %s: %s.\n", argv[1], int_array_to_string(&years)); /* Go though each year. */ for(y = years.data, y_end = y + years.size; y < y_end; y++) { char fn[64]; int *m, *m_end; sprintf(fn, "%d", *y); /* Get the months as directories. */ if(chdir(fn) == -1 || !(dir = opendir("."))) goto catch; while((de = readdir(dir))) { struct stat st; int month, *p; if(!(month = lex_looks_like_month(de->d_name))) continue; if(stat(de->d_name, &st)) goto catch; if(!S_ISDIR(st.st_mode)) continue; if(!(p = int_array_new(&months))) goto catch; *p = month; } closedir(dir), dir = 0; qsort(months.data, months.size, sizeof *months.data, &void_int_cmp); fprintf(stderr, "Files in %s: %s.\n", fn, int_array_to_string(&months)); /* Go though each month. */ for(m = months.data, m_end = m + months.size; m < m_end; m++) { int *d, *d_end; sprintf(fn, "%.2d", *m); printf("month %s\n", fn); /* Get the days as files. */ if(chdir(fn) == -1 || !(dir = opendir("."))) goto catch; while((de = readdir(dir))) { struct stat st; int day, *p; /* fixme: Have yyyy-mm-dd to figure out how many days. */ if(!(day = lex_looks_like_day(de->d_name))) continue; if(stat(de->d_name, &st)) goto catch; if(S_ISDIR(st.st_mode)) continue; if(!(p = int_array_new(&days))) goto catch; *p = day; } closedir(dir), dir = 0; qsort(days.data, days.size, sizeof *days.data, &void_int_cmp); fprintf(stderr, "Files in %s: %s.\n", fn, int_array_to_string(&days)); for(d = days.data, d_end = d + days.size; d < d_end; d++) { union date32 d32; struct page *page = 0; printf("Date: %d-%.2d-%.2d\n", *y, *m, *d); if(!(d32 = date_to_32(*y, *m, *d)).year) { errno = EILSEQ; goto catch; } sprintf(fn, "%.2d.txt", *d); if(page_tree_bulk_add(&journal, d32, &page) != TREE_UNIQUE) { if(!errno) fprintf(stderr, "Not unique?\n"), errno = EDOM; goto catch; } page->entry = char_array(); page->lexx = lex_array(); if(!append_file(&page->entry, fn)) goto catch; printf("%s", page->entry.data); printf("Lexing:\n"); for(lex_reset(page->entry.data); ; ) { struct lex *lex; if(!(lex = lex_array_new(&page->lexx))) goto catch; if(!lex_next(lex)) { if(lex->symbol != END) { errno = EILSEQ; goto catch; } printf("Lexing finished: %s on %lu.\n", lex_symbols[lex->symbol], lex->line); break; } printf("%lu: %s", (unsigned long)lex->line, lex_symbols[lex->symbol]); if(lex->symbol == TEXT || lex->symbol == ARG_KEYWORD || lex->symbol == ARG_DATE || lex->symbol == ARG_FREEFORM || lex->symbol == CAPTION) { if(lex->s0 + INT_MAX < lex->s1) { errno = EILSEQ; goto catch; } printf(" <<%.*s>>", (int)(lex->s1 - lex->s0), lex->s0); } printf(".\n"); } /* Transfer the temporary page to the journal. */ page = 0; } int_array_clear(&days); if(chdir("..") == -1) goto catch; } int_array_clear(&months); if(chdir("..") == -1) goto catch; break; /* fixme */ } page_tree_bulk_finish(&journal); printf("Journal has entries: %s\n", page_tree_to_string(&journal)); goto finally; catch: success = EXIT_FAILURE; perror("interpret"); finally: if(dir && closedir(dir)) success = EXIT_FAILURE, perror("dir"); int_array_(&years); int_array_(&months); int_array_(&days); struct page_tree_entry entry; for(struct page_tree_iterator it = page_tree_begin(&journal); (entry = page_tree_next(&it)).key; ) { struct page *const page = entry.value; char z[12]; date32_to_string(*entry.key, &z); printf("Freeing %s.\n", z); lex_array_(&page->lexx); char_array_(&page->entry); } return success; } /* Popular KJV 738137 words. */