#include "lex.h" #include /* chdir (POSIX) */ #include /* mode_t (POSIX) */ #include /* umask (POSIX) */ #include /* opendir readdir closedir */ #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. */ struct date32 { unsigned year : 23, month : 4, day : 5; }; /** Convert or narrower type or return zero. */ static struct date32 date_to_32(const int y, const int m, const int d) { struct date32 d32 = { 0, 0, 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) return d32; 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) return d32; break; case 2: if(d > 28 + leap(y)) return d32; default: assert(0); break; } d32.year = (unsigned)y, d32.month = (unsigned)m, d32.day = (unsigned)d; return d32; } /** Tomohiko Sakamoto comp.lang.c 1993-04-10. */ static unsigned weekday(struct 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 { struct date32 date; struct char_array entry; struct lex_array lexx; }; #define ARRAY_NAME page #define ARRAY_TYPE struct page #include "array.h" struct source { char *key, *desc; }; int main(int argc, char **argv) { int success = EXIT_FAILURE; DIR *dir = 0; struct dirent *de; struct int_array years = ARRAY_IDLE, months = ARRAY_IDLE, days = ARRAY_IDLE; struct page_array pages = ARRAY_IDLE; int *y, *y_end; /* Get the years list as directories matching a year in order. */ errno = 0; if(argc != 2) { fprintf(stderr, "Needs journal location.\n" "(should contain //.txt)\n"); goto finally; } 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; 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); /* 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++) { struct page *page; if(!(page = page_array_new(&pages))) goto catch; char_array(&page->entry); lex_array(&page->lexx); printf("Date: %d-%.2d-%.2d\n", *y, *m, *d); if(!((page->date = date_to_32(*y, *m, *d))).year) { errno = EILSEQ; goto catch; } sprintf(fn, "%.2d.txt", *d); 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"); } } int_array_clear(&days); if(chdir("..") == -1) goto catch; } int_array_clear(&months); if(chdir("..") == -1) goto catch; break; /* fixme */ } { success = EXIT_SUCCESS; goto finally; } catch: perror("interpret"); finally: if(dir && closedir(dir)) success = EXIT_FAILURE, perror("dir"); int_array_(&years); int_array_(&months); int_array_(&days); { struct page *page; while(page = page_array_pop(&pages)) lex_array_(&page->lexx), char_array_(&page->entry); } return success; }