From 242398584cd593395fbde9ee30802189140456cc Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Fri, 14 Jan 2022 19:12:32 +0100 Subject: [PATCH] added lina_loadMatrixFromStream --- .gitignore | 1 + build.sh | 1 + lina.c | 296 ++++++++++++++++++++++++++++++++++++++++++++++++++++- lina.h | 7 +- test2.c | 97 ++++++++++++++++++ 5 files changed, 398 insertions(+), 4 deletions(-) create mode 100644 test2.c diff --git a/.gitignore b/.gitignore index 1e3bbc1..06d79fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .swp test +test2 time .vscode \ No newline at end of file diff --git a/build.sh b/build.sh index 9632e66..196d641 100755 --- a/build.sh +++ b/build.sh @@ -1,2 +1,3 @@ gcc test.c lina.c -o test -Wall -Wextra -g +gcc test2.c lina.c -o test2 -Wall -Wextra -g gcc time.c lina.c -o time -Wall -Wextra -O3 \ No newline at end of file diff --git a/lina.c b/lina.c index bbcc740..66deed9 100644 --- a/lina.c +++ b/lina.c @@ -2,6 +2,9 @@ #include #include #include +#include +#include +#include #include "lina.h" #define check assert @@ -27,12 +30,9 @@ void lina_dot(double *A, double *B, double *C, int m, int n, int l){ pos += A[i*n + j] * B[j*l + k]; - // The usage of a support variable 'pos' - // is for safety purpose (non-zero C matrix) C[i*l + k] = pos; } } - } void lina_add(double *A, double *B, double *C, int m, int n){ @@ -97,4 +97,294 @@ void lina_transpose(double *A, double *B, int m, int n) B[1] = item; } +} + +// Returns 0 if an error occurred, 1 if an integer +// was scanned and -1 if a floating point was scanned. +static int scanValue(FILE *fp, char *buffer, int max_length, char first, char *final, char **error) +{ + assert(fp != NULL && buffer != NULL && error != NULL); + assert(isdigit(first)); + + int n = 0; + + char c = first; + + // Scan the integer portion of + // the numeric value and copy it + // into the buffer. + do + { + if(n == max_length) + // ERROR: Internal buffer is too small to hold + // the representation of this item. + return 0; + + buffer[n++] = c; + + c = getc(fp); + } + while(c != EOF && isdigit(c)); + + // Did the integer part end with + // a dot? + _Bool dot = (c == '.'); + + // Now scan and copy the decimal + // part of the numeric value if + // a dot was found. + if(dot) + { + if(n == max_length) + // ERROR: Internal buffer is too small to hold + // the representation of this item. + // (The dot doesn't fit.) + return 0; + + buffer[n++] = '.'; + + c = getc(fp); + + if(!isdigit(c)) + // ERROR: Got something other than a + // digit after the dot. + return 0; + + do + { + if(n == max_length) + // ERROR: Internal buffer is too small + // to hold the representation of + // this item. + return 0; + + buffer[n++] = c; + + c = getc(fp); + } + while(c != EOF && isdigit(c)); + } + + buffer[n] = '\0'; + + if(final != NULL) + *final = c; + + return dot ? -1 : 1; +} + +double *lina_loadMatrixFromStream(FILE *fp, int *width, int *height, char **error) +{ + assert(width != NULL && height != NULL); + + if(fp == NULL) + fp = stdin; + + char *dummy; + if(error == NULL) + error = &dummy; + else + *error = NULL; + + char c = getc(fp); + + while(c != EOF && isspace(c)) + c = getc(fp); + + if(c == EOF) + { + // ERROR: Stream ended before a matrix was + // found. + *error = "Stream ended before a matrix was found"; + return NULL; + } + + if(c != '[') + { + // ERROR: Was expected a '[' as the first + // character of a matrix, but got + // something else instead. + *error = "Got something other than a matrix " + "where one was expected"; + return NULL; + } + + c = getc(fp); + + // Skip spaces before the first element. + while(c != EOF && isspace(c)) + c = getc(fp); + + if(c == EOF) + { + // ERROR: Stream ended where a numeric value + // was expected. + *error = "Stream ended where a numeric value " + "was expected"; + return NULL; + } + + double *matrix = malloc(sizeof(matrix[0]) * 64); + + if(matrix == NULL) + { + // ERROR: Insufficient memory. + *error = "Insufficient memory"; + return NULL; + } + + int capacity = 64, size = 0, + w = -1, i = 0, j = 0; + + if(c != ']') + while(1) + { + if(!isdigit(c)) + { + // ERROR: Got something other than a digit + // where a numeric value was expected. + *error = "Got something other than a numeric " + "value where one was expected"; + return NULL; + } + + // Numeric values can't be represented + // in strings bigger than this buffer + // since they need to be copied in it + // to be converted to actual numeric + // variables. + char buffer[128]; + + int res = scanValue(fp, buffer, sizeof(buffer), c, &c, error); + + if(res == 0) + // Failed to scan the value, abort. + // NOTE: The error was already reported. + return NULL; + + assert(res == 1 || res == -1); + + // Make sure the matrix has enough space. + if(size == capacity) + { + int new_capacity = capacity * 2; + + double *temp = realloc(matrix, sizeof(double) * new_capacity); + + if(temp == NULL) + { + // ERROR: Insufficient memory. + *error = "Insufficient memory"; + free(matrix); + return NULL; + } + + matrix = temp; + capacity = new_capacity; + } + + errno = 0; + + double casted; + + if(res == 1) + casted = (double) strtoll(buffer, NULL, 10); + else + casted = strtod(buffer, NULL); + + if(errno) + { + // ERROR: Failed to convert a numeric value + // from it's string form to a numeric + // variable. + *error = "Failed to convert string to number"; + free(matrix); + return NULL; + } + + matrix[size++] = casted; + + i += 1; + + while(c != EOF && isspace(c)) + c = getc(fp); + + if(c == ']' || c == ',') + { + // The matrix's row just ended. + + if(w == -1) + // This was the first row. + w = i; + else + { + // This wasn't the first row, + // so it's possible that it's + // length is different from the + // previous ones. + assert(w > -1); + + if(i != w) + { + // ERROR: The j-th row has the wrong + // number of elements. + if(i < w) + *error = "Matrix row is too short"; + else + *error = "Matrix row is too long"; + return NULL; + } + } + + i = 0; + j += 1; + + if(c == ']') + // The whole matrix ended! + break; + + c = getc(fp); + + while(c != EOF && isspace(c)) + c = getc(fp); + } + + if(c == EOF) + { + // ERROR: Stream ended inside a matrix, where + // either ',', ']' or a numeric value was + // expected. + *error = "Stream ended inside a matrix, where either " + "',', ']' or a numeric value was expected"; + return NULL; + } + } + + if(size == 0) + { + free(matrix); + *error = "Empty matrix"; + return NULL; + } + + // If the internal fragmentation is too much, + // return a dynamic memory region with the + // exact size instead of the buffer used to + // build the matrix. + int fragm_threshold = 30; // (It's a percentage) + + if(100.0 * size/capacity < fragm_threshold) + { + int new_capacity = (size == 0) ? 1 : size; + + double *temp = realloc(matrix, new_capacity * sizeof(double)); + + if(temp != NULL) + matrix = temp; + } + + *width = w; + *height = j; + + return matrix; } \ No newline at end of file diff --git a/lina.h b/lina.h index 81a4ca8..9760d6d 100644 --- a/lina.h +++ b/lina.h @@ -13,4 +13,9 @@ void lina_scale(double *A, double *B, double k, int m, int n); // Evaluate B = A^t (the transpose of A) where A is m by n. // NOTE: B can be the same location of A. -void lina_transpose(double *A, double *B, int m, int n); \ No newline at end of file +void lina_transpose(double *A, double *B, int m, int n); + +// Load from a stream a matrix in the form [a b c.. , d e f.. , ...] +// where a,b,c,.. are either integer or floating point values. +// The returned pointer must be deallocated using `free`. +double *lina_loadMatrixFromStream(FILE *fp, int *width, int *height, char **error); \ No newline at end of file diff --git a/test2.c b/test2.c new file mode 100644 index 0000000..7927cba --- /dev/null +++ b/test2.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include "lina.h" + +#define check assert + +// This program tests the matrix-loading +// function lina_loadMatrixFromStream. + + +struct { + char *src; + double *matrix; + int w, h; +} load_tests[] = { + { .src = "", .matrix = NULL }, + { .src = " [] ", .matrix = NULL }, + { .src = " [1] ", .matrix = (double[]) {1}, .w = 1, .h = 1 }, + { .src = "[1 2 3]", .matrix = (double[]) {1, 2, 3}, .w = 3, .h = 1 }, + { .src = "[1, 2, 3]", .matrix = (double[]) {1, 2, 3}, .w = 1, .h = 3 }, + { .src = "[1 2 3, 4 5 6, 7 8 9]", .matrix = (double[]) {1, 2, 3, 4, 5, 6, 7, 8, 9}, .w = 3, .h = 3 }, +}; + +int main() +{ + int total = sizeof(load_tests) / sizeof(load_tests[0]); + int passed = 0; + + for(int i = 0; i < total; i += 1) + { + char *src = load_tests[i].src; + + FILE *stream = fmemopen(src, strlen(src), "r"); + check(stream != NULL); + + int w, h; + char *err; + double *M = lina_loadMatrixFromStream(stream, &w, &h, &err); + + double *expected_M = load_tests[i].matrix; + + if(expected_M == NULL) + { + // Was expected a failure. + if(M == NULL) + { + // And we got one. + fprintf(stderr, "Test %d passed.\n", i); + passed++; + } + else + // Yet the routine succeded. + fprintf(stderr, "Test %d failed:\n\tWas expected a failure but " + "the routine succeded to parse \"%s\"\n", i, src); + } + else + { + // Was expected a success. + if(M == NULL) + // But we got a failure. + fprintf(stderr, "Test %d failed:\n\tCouldn't parse \"%s\" (%s)\n", i, src, err); + else + { + // And we got a success! Now we need to check + // that the parsed matrix is the right one. + + int expected_w = load_tests[i].w; + int expected_h = load_tests[i].h; + + if(expected_w != w || expected_h != h) + + fprintf(stderr, "Test %d failed:\n\tParsing \"%s\" resulted in a " + "matrix %dx%d, where a matrix %dx%d was expected\n", + i, src, h, w, expected_h, expected_w); + + else if(memcmp(M, expected_M, sizeof(M[0])*w*h)) + + fprintf(stderr, "Test %d failed:\n\tParsing \"%s\" resulted in the " + "wrong matrix\n", i, src); + + else + { + fprintf(stderr, "Test %d passed.\n", i); + passed += 1; + } + + free(M); + } + } + fclose(stream); + } + + fprintf(stderr, " %d total, %d passed, %d failed\n", total, passed, total - passed); + return 0; +} \ No newline at end of file