497 wiersze
13 KiB
C++
497 wiersze
13 KiB
C++
|
|
#include "json.h"
|
|||
|
|
#include <assert.h>
|
|||
|
|
#include <math.h>
|
|||
|
|
#include <pthread.h>
|
|||
|
|
#include <stdio.h>
|
|||
|
|
#include <stdlib.h>
|
|||
|
|
#include <string.h>
|
|||
|
|
|
|||
|
|
const char* jsonErrorString(jsonResult_t res) {
|
|||
|
|
switch (res) {
|
|||
|
|
case jsonSuccess:
|
|||
|
|
return "jsonSuccess";
|
|||
|
|
case jsonFileError:
|
|||
|
|
return "jsonFileError";
|
|||
|
|
case jsonUnknownStateError:
|
|||
|
|
return "jsonUnknownStateError";
|
|||
|
|
case jsonEmptyStateError:
|
|||
|
|
return "jsonEmptyStateError";
|
|||
|
|
case jsonExpectedNonNoneStateError:
|
|||
|
|
return "jsonExpectedNonNoneStateError";
|
|||
|
|
case jsonMemoryError:
|
|||
|
|
return "jsonMemoryError";
|
|||
|
|
case jsonStringOverflowError:
|
|||
|
|
return "jsonStringOverflowError";
|
|||
|
|
case jsonStringBadChar:
|
|||
|
|
return "jsonStringBadChar";
|
|||
|
|
case jsonLockError:
|
|||
|
|
return "jsonLockError";
|
|||
|
|
default:
|
|||
|
|
return "unknown json error";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// We use these statics to mantain a stack of states where we are writing.
|
|||
|
|
typedef struct jsonFileOutput {
|
|||
|
|
jsonState_t* states;
|
|||
|
|
size_t state_cap; // Allocated stack capacity
|
|||
|
|
size_t state_n; // # of items in the stack.
|
|||
|
|
FILE* fp;
|
|||
|
|
pthread_mutex_t mutex;
|
|||
|
|
} jsonFileOutput;
|
|||
|
|
|
|||
|
|
jsonResult_t jsonInitFileOutput(jsonFileOutput** jfo, const char* outfile) {
|
|||
|
|
jsonFileOutput* new_jfo = (jsonFileOutput*)malloc(sizeof(jsonFileOutput));
|
|||
|
|
if (new_jfo == NULL) {
|
|||
|
|
return jsonMemoryError;
|
|||
|
|
}
|
|||
|
|
if (pthread_mutex_init(&new_jfo->mutex, NULL) != 0) {
|
|||
|
|
free(new_jfo);
|
|||
|
|
*jfo = 0;
|
|||
|
|
return jsonLockError;
|
|||
|
|
}
|
|||
|
|
new_jfo->states = NULL;
|
|||
|
|
new_jfo->state_cap = 0;
|
|||
|
|
new_jfo->state_n = 0;
|
|||
|
|
new_jfo->fp = fopen(outfile, "w");
|
|||
|
|
if (new_jfo->fp == NULL) {
|
|||
|
|
free(new_jfo);
|
|||
|
|
*jfo = 0;
|
|||
|
|
return jsonFileError;
|
|||
|
|
}
|
|||
|
|
*jfo = new_jfo;
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
jsonResult_t jsonNewline(jsonFileOutput* jfo) {
|
|||
|
|
fprintf(jfo->fp, "\n");
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
jsonResult_t jsonFlushOutput(jsonFileOutput* jfo) {
|
|||
|
|
fflush(jfo->fp);
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
jsonResult_t jsonLockOutput(jsonFileOutput* jfo) {
|
|||
|
|
if (pthread_mutex_lock(&jfo->mutex) != 0) {
|
|||
|
|
return jsonLockError;
|
|||
|
|
}
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
jsonResult_t jsonUnlockOutput(jsonFileOutput* jfo) {
|
|||
|
|
if (pthread_mutex_unlock(&jfo->mutex) != 0) {
|
|||
|
|
return jsonLockError;
|
|||
|
|
}
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
jsonResult_t jsonFinalizeFileOutput(jsonFileOutput* jfo) {
|
|||
|
|
// Really should probably complain if we aren't in a valid state
|
|||
|
|
|
|||
|
|
if (pthread_mutex_destroy(&jfo->mutex) != 0) {
|
|||
|
|
free(jfo);
|
|||
|
|
return jsonLockError;
|
|||
|
|
}
|
|||
|
|
if (jfo->states != NULL) {
|
|||
|
|
free(jfo->states);
|
|||
|
|
}
|
|||
|
|
jfo->states = NULL;
|
|||
|
|
jfo->state_cap = 0;
|
|||
|
|
jfo->state_n = 0;
|
|||
|
|
if (jfo->fp) {
|
|||
|
|
fclose(jfo->fp);
|
|||
|
|
jfo->fp = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
free(jfo);
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static int utf8copy(unsigned char* out, int out_lim, const unsigned char* in) {
|
|||
|
|
int copy_len;
|
|||
|
|
if ((in[0] & 0xE0) == 0xC0) {
|
|||
|
|
// 2-byte sequence
|
|||
|
|
if ((in[1] & 0xC0) != 0x80 || out_lim < 2) {
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
copy_len = 2;
|
|||
|
|
} else if ((in[0] & 0xF0) == 0xE0) {
|
|||
|
|
// 3-byte sequence
|
|||
|
|
if ((in[1] & 0xC0) != 0x80 || (in[2] & 0xC0) != 0x80 || out_lim < 3) {
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
copy_len = 3;
|
|||
|
|
} else if ((in[0] & 0xF8) == 0xF0) {
|
|||
|
|
// 4-byte sequence
|
|||
|
|
if ((in[1] & 0xC0) != 0x80 || (in[2] & 0xC0) != 0x80 || (in[3] & 0xC0) != 0x80 || out_lim < 4) {
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
copy_len = 4;
|
|||
|
|
} else {
|
|||
|
|
// Invalid start byte
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (int i = 0; i < copy_len; ++i) {
|
|||
|
|
out[i] = in[i];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return copy_len;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// This tries to sanitize/quote a string from 'in' into 'out',
|
|||
|
|
// assuming 'out' has length 'lim'. We mainly quote ",/,\,\t,\n, and
|
|||
|
|
// bail if we encounter non-printable stuff or non-ASCII stuff.
|
|||
|
|
// 'in' should be null-terminated, of course.
|
|||
|
|
//
|
|||
|
|
// We return false if we were not able to copy all of 'in', either for
|
|||
|
|
// length reasons or for unhandled characters.
|
|||
|
|
static jsonResult_t sanitizeJson(unsigned char out[], int lim, const unsigned char* in) {
|
|||
|
|
int c = 0;
|
|||
|
|
while (*in) {
|
|||
|
|
if (c + 1 >= lim) {
|
|||
|
|
out[c] = 0;
|
|||
|
|
return jsonStringOverflowError;
|
|||
|
|
}
|
|||
|
|
switch (*in) {
|
|||
|
|
case '"':
|
|||
|
|
case '\\':
|
|||
|
|
case '/':
|
|||
|
|
case '\t':
|
|||
|
|
case '\n':
|
|||
|
|
if (c + 2 > lim) {
|
|||
|
|
out[c] = 0;
|
|||
|
|
return jsonStringOverflowError;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
out[c++] = '\\';
|
|||
|
|
if (*in == '\n') {
|
|||
|
|
out[c++] = 'n';
|
|||
|
|
} else if (*in == '\t') {
|
|||
|
|
out[c++] = 't';
|
|||
|
|
} else {
|
|||
|
|
out[c++] = *in;
|
|||
|
|
}
|
|||
|
|
++in;
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
if (*in <= 0x1F) {
|
|||
|
|
out[c] = 0;
|
|||
|
|
return jsonStringBadChar;
|
|||
|
|
} else if (*in <= 0x7F) {
|
|||
|
|
out[c++] = *in;
|
|||
|
|
++in;
|
|||
|
|
} else {
|
|||
|
|
const int utf8len = utf8copy(out + c, lim - c - 1, in);
|
|||
|
|
if (utf8len == 0) {
|
|||
|
|
out[c] = 0;
|
|||
|
|
return jsonStringBadChar;
|
|||
|
|
}
|
|||
|
|
c += utf8len;
|
|||
|
|
in += utf8len;
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
out[c] = 0;
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static size_t max(size_t a, size_t b) {
|
|||
|
|
if (a < b) {
|
|||
|
|
return b;
|
|||
|
|
}
|
|||
|
|
return a;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Push state onto the state stack. Reallocate for extra storage if needed.
|
|||
|
|
// Because JSON_NONE is a pseudo-state, don't allow it to be pushed.
|
|||
|
|
static jsonResult_t jsonPushState(jsonFileOutput* jfo, jsonState_t state) {
|
|||
|
|
if (state == JSON_NONE) {
|
|||
|
|
return jsonExpectedNonNoneStateError;
|
|||
|
|
}
|
|||
|
|
if (jfo->state_cap <= (jfo->state_n + 1)) {
|
|||
|
|
jfo->state_cap = max((size_t)16, jfo->state_cap * 2);
|
|||
|
|
jfo->states = (jsonState_t*)realloc(jfo->states, sizeof(jsonState_t) * jfo->state_cap);
|
|||
|
|
if (jfo->states == 0) {
|
|||
|
|
return jsonMemoryError;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
jfo->states[jfo->state_n++] = state;
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Return the current state at the top of the stack
|
|||
|
|
static jsonState_t jsonCurrState(const jsonFileOutput* jfo) {
|
|||
|
|
if (jfo->state_n == 0) {
|
|||
|
|
return JSON_NONE;
|
|||
|
|
}
|
|||
|
|
return jfo->states[jfo->state_n - 1];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Replace the stack with state (equivalent to a pop & push if stack is not empty)
|
|||
|
|
static jsonResult_t jsonReplaceState(jsonFileOutput* jfo, jsonState_t state) {
|
|||
|
|
if (state == JSON_NONE) {
|
|||
|
|
return jsonExpectedNonNoneStateError;
|
|||
|
|
}
|
|||
|
|
if (jfo->state_n == 0) {
|
|||
|
|
return jsonEmptyStateError;
|
|||
|
|
}
|
|||
|
|
jfo->states[jfo->state_n - 1] = state;
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Pop the top state off the stack, or return that the state is empty
|
|||
|
|
static jsonState_t jsonPopState(jsonFileOutput* jfo) {
|
|||
|
|
if (jfo->state_n == 0) {
|
|||
|
|
return JSON_NONE;
|
|||
|
|
}
|
|||
|
|
return jfo->states[--jfo->state_n];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Emit a key and separator. Santize the key.
|
|||
|
|
// This is only acceptable if the top state is an object
|
|||
|
|
// Emit a ',' separator of we aren't the first item.
|
|||
|
|
jsonResult_t jsonKey(jsonFileOutput* jfo, const char* name) {
|
|||
|
|
switch (jsonCurrState(jfo)) {
|
|||
|
|
case JSON_OBJECT_EMPTY:
|
|||
|
|
jsonReplaceState(jfo, JSON_OBJECT_SOME);
|
|||
|
|
break;
|
|||
|
|
case JSON_OBJECT_SOME:
|
|||
|
|
fprintf(jfo->fp, ",");
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
return jsonUnknownStateError;
|
|||
|
|
}
|
|||
|
|
unsigned char tmp[2048];
|
|||
|
|
const jsonResult_t res = sanitizeJson(tmp, sizeof(tmp), (const unsigned char*)name);
|
|||
|
|
if (res != jsonSuccess) {
|
|||
|
|
return res;
|
|||
|
|
}
|
|||
|
|
fprintf(jfo->fp, "\"%s\":", tmp);
|
|||
|
|
jsonPushState(jfo, JSON_KEY);
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Helper function for inserting values.
|
|||
|
|
// Only acceptable after keys, top-level, or in lists.
|
|||
|
|
// Emit preceeding ',' if in a list and not first item.
|
|||
|
|
static jsonResult_t jsonValHelper(jsonFileOutput* jfo) {
|
|||
|
|
switch (jsonCurrState(jfo)) {
|
|||
|
|
case JSON_LIST_EMPTY:
|
|||
|
|
jsonReplaceState(jfo, JSON_LIST_SOME);
|
|||
|
|
break;
|
|||
|
|
case JSON_LIST_SOME:
|
|||
|
|
fprintf(jfo->fp, ",");
|
|||
|
|
break;
|
|||
|
|
case JSON_KEY:
|
|||
|
|
jsonPopState(jfo);
|
|||
|
|
break;
|
|||
|
|
case JSON_NONE:
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
return jsonUnknownStateError;
|
|||
|
|
}
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Start an object
|
|||
|
|
jsonResult_t jsonStartObject(jsonFileOutput* jfo) {
|
|||
|
|
const jsonResult_t res = jsonValHelper(jfo);
|
|||
|
|
if (res != jsonSuccess) {
|
|||
|
|
return res;
|
|||
|
|
}
|
|||
|
|
fprintf(jfo->fp, "{");
|
|||
|
|
return jsonPushState(jfo, JSON_OBJECT_EMPTY);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Close an object
|
|||
|
|
jsonResult_t jsonFinishObject(jsonFileOutput* jfo) {
|
|||
|
|
switch (jsonPopState(jfo)) {
|
|||
|
|
case JSON_OBJECT_EMPTY:
|
|||
|
|
case JSON_OBJECT_SOME:
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
return jsonUnknownStateError;
|
|||
|
|
}
|
|||
|
|
fprintf(jfo->fp, "}");
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Start a list
|
|||
|
|
jsonResult_t jsonStartList(jsonFileOutput* jfo) {
|
|||
|
|
const jsonResult_t res = jsonValHelper(jfo);
|
|||
|
|
if (res != jsonSuccess) {
|
|||
|
|
return res;
|
|||
|
|
}
|
|||
|
|
fprintf(jfo->fp, "[");
|
|||
|
|
return jsonPushState(jfo, JSON_LIST_EMPTY);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Close a list
|
|||
|
|
jsonResult_t jsonFinishList(jsonFileOutput* jfo) {
|
|||
|
|
switch (jsonPopState(jfo)) {
|
|||
|
|
case JSON_LIST_EMPTY:
|
|||
|
|
case JSON_LIST_SOME:
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
return jsonUnknownStateError;
|
|||
|
|
}
|
|||
|
|
fprintf(jfo->fp, "]");
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Write a null value
|
|||
|
|
jsonResult_t jsonNull(jsonFileOutput* jfo) {
|
|||
|
|
const jsonResult_t res = jsonValHelper(jfo);
|
|||
|
|
if (res != jsonSuccess) {
|
|||
|
|
return res;
|
|||
|
|
}
|
|||
|
|
fprintf(jfo->fp, "null");
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Write a (sanititzed) string
|
|||
|
|
jsonResult_t jsonStr(jsonFileOutput* jfo, const char* str) {
|
|||
|
|
if (str == NULL) {
|
|||
|
|
jsonNull(jfo);
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
const jsonResult_t res = jsonValHelper(jfo);
|
|||
|
|
if (res != jsonSuccess) {
|
|||
|
|
return res;
|
|||
|
|
}
|
|||
|
|
unsigned char tmp[2048];
|
|||
|
|
const jsonResult_t san_res = sanitizeJson(tmp, sizeof(tmp), (const unsigned char*)str);
|
|||
|
|
if (san_res != jsonSuccess) {
|
|||
|
|
return san_res;
|
|||
|
|
}
|
|||
|
|
fprintf(jfo->fp, "\"%s\"", tmp);
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Write a bool as "true" or "false" strings.
|
|||
|
|
jsonResult_t jsonBool(jsonFileOutput* jfo, bool val) {
|
|||
|
|
return jsonStr(jfo, val ? "true" : "false");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Write an integer value
|
|||
|
|
jsonResult_t jsonInt(jsonFileOutput* jfo, const int val) {
|
|||
|
|
const jsonResult_t res = jsonValHelper(jfo);
|
|||
|
|
if (res != jsonSuccess) {
|
|||
|
|
return res;
|
|||
|
|
}
|
|||
|
|
fprintf(jfo->fp, "%d", val);
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Write an integer value
|
|||
|
|
jsonResult_t jsonUint32(jsonFileOutput* jfo, const uint32_t val) {
|
|||
|
|
const jsonResult_t res = jsonValHelper(jfo);
|
|||
|
|
if (res != jsonSuccess) {
|
|||
|
|
return res;
|
|||
|
|
}
|
|||
|
|
fprintf(jfo->fp, "%u", val);
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
// Write an integer value
|
|||
|
|
jsonResult_t jsonUint64(jsonFileOutput* jfo, const uint64_t val) {
|
|||
|
|
const jsonResult_t res = jsonValHelper(jfo);
|
|||
|
|
if (res != jsonSuccess) {
|
|||
|
|
return res;
|
|||
|
|
}
|
|||
|
|
fprintf(jfo->fp, "%lu", val);
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Write a size_t value
|
|||
|
|
jsonResult_t jsonSize_t(jsonFileOutput* jfo, const size_t val) {
|
|||
|
|
const jsonResult_t res = jsonValHelper(jfo);
|
|||
|
|
if (res != jsonSuccess) {
|
|||
|
|
return res;
|
|||
|
|
}
|
|||
|
|
fprintf(jfo->fp, "%zu", val);
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Write a double value
|
|||
|
|
jsonResult_t jsonDouble(jsonFileOutput* jfo, const double val) {
|
|||
|
|
const jsonResult_t res = jsonValHelper(jfo);
|
|||
|
|
if (res != jsonSuccess) {
|
|||
|
|
return res;
|
|||
|
|
}
|
|||
|
|
if (val != val) {
|
|||
|
|
fprintf(jfo->fp, "\"nan\"");
|
|||
|
|
} else {
|
|||
|
|
fprintf(jfo->fp, "%lf", val);
|
|||
|
|
}
|
|||
|
|
return jsonSuccess;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#ifdef DO_JSON_TEST
|
|||
|
|
// compile with
|
|||
|
|
// gcc json.cc -Iinclude/ -DDO_JSON_TEST -o json_test
|
|||
|
|
// run with:
|
|||
|
|
// ./json_test
|
|||
|
|
// if something fails, it will print out the error
|
|||
|
|
// if it all works, print out "output matches reference"
|
|||
|
|
#define JSONCHECK(expr) \
|
|||
|
|
do { \
|
|||
|
|
const jsonResult_t res = (expr); \
|
|||
|
|
if (res != jsonSuccess) { \
|
|||
|
|
fprintf(stderr, "jsonError: %s\n", jsonErrorString(res)); \
|
|||
|
|
exit(1); \
|
|||
|
|
} \
|
|||
|
|
} while (0)
|
|||
|
|
|
|||
|
|
int main() {
|
|||
|
|
|
|||
|
|
const char refstr[] =
|
|||
|
|
"{\"number\":123,\"utfstring\":\"∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ "
|
|||
|
|
"¬β = ¬(¬α ∨ β),\",\"list\":[\"true\",null,9423812381231,3123111,0.694234]}";
|
|||
|
|
|
|||
|
|
jsonFileOutput* jfo;
|
|||
|
|
JSONCHECK(jsonInitFileOutput(&jfo, "test.json"));
|
|||
|
|
JSONCHECK(jsonStartObject(jfo));
|
|||
|
|
JSONCHECK(jsonKey(jfo, "number"));
|
|||
|
|
JSONCHECK(jsonInt(jfo, 123));
|
|||
|
|
JSONCHECK(jsonKey(jfo, "utfstring"));
|
|||
|
|
JSONCHECK(
|
|||
|
|
jsonStr(jfo, "∮ E⋅da = Q, n → ∞, ∑ f(i) = ∏ g(i), ∀x∈ℝ: ⌈x⌉ = −⌊−x⌋, α ∧ ¬β = ¬(¬α ∨ β),"));
|
|||
|
|
JSONCHECK(jsonKey(jfo, "list"));
|
|||
|
|
JSONCHECK(jsonStartList(jfo));
|
|||
|
|
JSONCHECK(jsonBool(jfo, true));
|
|||
|
|
JSONCHECK(jsonNull(jfo));
|
|||
|
|
JSONCHECK(jsonUint64(jfo, 9423812381231ULL));
|
|||
|
|
JSONCHECK(jsonSize_t(jfo, 3123111));
|
|||
|
|
JSONCHECK(jsonDouble(jfo, 0.69423413));
|
|||
|
|
JSONCHECK(jsonFinishList(jfo));
|
|||
|
|
JSONCHECK(jsonFinishObject(jfo));
|
|||
|
|
JSONCHECK(jsonFinalizeFileOutput(jfo));
|
|||
|
|
|
|||
|
|
FILE* fp = fopen("test.json", "r");
|
|||
|
|
|
|||
|
|
const size_t reflen = sizeof(refstr) / sizeof(char);
|
|||
|
|
|
|||
|
|
char buffer[reflen];
|
|||
|
|
|
|||
|
|
fread(buffer, sizeof(char), reflen, fp);
|
|||
|
|
|
|||
|
|
fclose(fp);
|
|||
|
|
|
|||
|
|
if (memcmp(buffer, refstr, reflen) == 0) {
|
|||
|
|
printf("output matches reference\n");
|
|||
|
|
} else {
|
|||
|
|
printf("output %s\nreference %s\n", buffer, refstr);
|
|||
|
|
return 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#endif
|