diff --git a/c/input/line_readers.c b/c/input/line_readers.c index 2215d8b20..4a37ef6de 100644 --- a/c/input/line_readers.c +++ b/c/input/line_readers.c @@ -148,3 +148,46 @@ ssize_t local_getdelim(char** restrict pline, size_t* restrict plinecap, int del // // reuse linecap on subsequent calls. power of two above last readlen. // work autodetect deeper into the callstack + +// ---------------------------------------------------------------- +char* mlr_alloc_read_line_single_delimiter( + FILE* fp, + int delimiter, + int* preached_eof, + size_t* pold_then_new_strlen, + size_t* pold_then_new_linecap) +{ + // xxx wip + size_t linecap = power_of_two_above(*pold_then_new_strlen); + char* restrict line = mlr_malloc_or_die(linecap); + char* restrict p = line; + int reached_eof = FALSE; + int c; + + while (TRUE) { + size_t offset = p - line; + if (offset >= linecap) { + linecap = linecap << 1; + line = mlr_realloc_or_die(line, linecap); + p = line + offset; + } + c = mlr_arch_getc(fp); + if (c == delimiter) { + *(p++) = 0; + break; + } else if (c == EOF) { + *(p++) = 0; + if (p == line) + reached_eof = TRUE; + break; + } else { + *(p++) = c; + } + } + + *preached_eof = reached_eof; + *pold_then_new_strlen = p - line - 1; // exclude null terminator + *pold_then_new_linecap = linecap; + + return line; +} diff --git a/c/input/line_readers.h b/c/input/line_readers.h index 12e61b4e0..79b6eabac 100644 --- a/c/input/line_readers.h +++ b/c/input/line_readers.h @@ -24,4 +24,25 @@ char* mlr_get_sline(FILE* input_stream, char* irs, int irslen); // inside mlr_arch.c, for unit-testing visibility. ssize_t local_getdelim(char** restrict pline, size_t* restrict plinecap, int delimiter, FILE* restrict stream); +// xxx type up comments: +// * in delimiter (single/multiple) +// * in fp +// * -in do_auto_line_term- separate variant +// * -inout pctx- separate variant +// * out line +// * out reached eof +// * inout strlen (old/new). DEFAULT_SIZE @ first call +// * inout linecap (old/new) DEFAULT_SIZE @ first call +// +// reuse linecap on subsequent calls. power of two above last readlen. +// work autodetect deeper into the callstack + +#define MLR_ALLOC_READ_LINE_INITIAL_SIZE 128 +char* mlr_alloc_read_line_single_delimiter( + FILE* fp, + int delimiter, + int* preached_eof, + size_t* pold_then_new_strlen, + size_t* pold_then_new_linecap); + #endif // LINE_READERS_H diff --git a/c/unit_test/test_line_readers.c b/c/unit_test/test_line_readers.c index 824d20981..e291cbc31 100644 --- a/c/unit_test/test_line_readers.c +++ b/c/unit_test/test_line_readers.c @@ -26,6 +26,7 @@ static FILE* fopen_or_die(char* filename) { return fp; } +// ---------------------------------------------------------------- static char* test_getdelim_impl(getdelim_t* pgetdelim) { char delimiter = '\n'; char* contents = NULL; @@ -108,6 +109,92 @@ static char* test_getdelim_impl(getdelim_t* pgetdelim) { return NULL; } +// ---------------------------------------------------------------- +static char* test_mlr_alloc_read_line_single_delimiter() { +#if 0 + char delimiter = '\n'; + char* path = NULL; + FILE* fp = NULL; + char* contents = NULL; + char* line = NULL; + int reached_eof = FALSE; + size_t linelen = 0; + size_t linecap = 0; + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + contents = ""; + path = write_temp_file_or_die(contents); + fp = fopen_or_die(path); + + // Read line + linecap = 4; + line = mlr_alloc_read_line_single_delimiter(fp, delimiter, &reached_eof, &linelen, &linecap); + printf("reof=%d,linelen=%d,linecap=%d,line=\"%s\"\n", (int)reached_eof, (int)linelen, (int)linecap, line); + mu_assert_lf(line != NULL); + mu_assert_lf(streq(line, "")); + mu_assert_lf(reached_eof == TRUE); + mu_assert_lf(linelen == strlen("")); + mu_assert_lf(linecap > linelen); + + // Read past EOF + line = mlr_alloc_read_line_single_delimiter(fp, delimiter, &reached_eof, &linelen, &linecap); + mu_assert_lf(line != NULL); + mu_assert_lf(streq(line, "")); + mu_assert_lf(reached_eof == TRUE); + + fclose(fp); + unlink_file_or_die(path); + + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + contents = "\n"; + path = write_temp_file_or_die(contents); + fp = fopen_or_die(path); + + // Read line + line = NULL; + linecap = 0; + rc = (*pgetdelim)(&line, &linecap, delimiter, fp); + mu_assert_lf(rc == 1); + mu_assert_lf(streq(line, "\n")); + mu_assert_lf(linecap >= 1+strlen(contents)); + + // Read past EOF + line = NULL; + linecap = 0; + rc = (*pgetdelim)(&line, &linecap, delimiter, fp); + mu_assert_lf(rc == -1); + + + fclose(fp); + unlink_file_or_die(path); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + contents = "\r\n"; + path = write_temp_file_or_die(contents); + fp = fopen_or_die(path); + + // Read line + line = NULL; + linecap = 0; + rc = (*pgetdelim)(&line, &linecap, delimiter, fp); + mu_assert_lf(rc == 2); + mu_assert_lf(streq(line, "\r\n")); + mu_assert_lf(linecap >= 1+strlen(contents)); + + // Read past EOF + line = NULL; + linecap = 0; + rc = (*pgetdelim)(&line, &linecap, delimiter, fp); + mu_assert_lf(rc == -1); + + + fclose(fp); + unlink_file_or_die(path); +#endif + return NULL; +} + // ---------------------------------------------------------------- // On unixish platforms this is testing the system getdelim() which is correct by definition. // (At least, we verify it's behaving as we expect.) On Windows, this tests our local_getdelim @@ -126,7 +213,7 @@ static char* test_getdelim() { // ================================================================ static char * run_all_tests() { mu_run_test(test_getdelim); - // xxx WIP mu_run_test(test_local_getdelim); + mu_run_test(test_mlr_alloc_read_line_single_delimiter); return 0; }