futhark-0.26.2: rts/c/values.h
// Start of values.h.
//// Text I/O
typedef int (*writer)(FILE*, const void*);
typedef int (*bin_reader)(void*);
typedef int (*str_reader)(char *, void*);
struct array_reader {
char* elems;
int64_t n_elems_space;
int64_t elem_size;
int64_t n_elems_used;
int64_t *shape;
str_reader elem_reader;
};
static void skipspaces(FILE *f) {
int c;
do {
c = getc(f);
} while (isspace(c));
if (c != EOF) {
ungetc(c, f);
}
}
static int constituent(char c) {
return isalnum(c) || c == '.' || c == '-' || c == '+' || c == '_';
}
// Produces an empty token only on EOF.
static void next_token(FILE *f, char *buf, int bufsize) {
start:
skipspaces(f);
int i = 0;
while (i < bufsize) {
int c = getc(f);
buf[i] = (char)c;
if (c == EOF) {
buf[i] = 0;
return;
} else if (c == '-' && i == 1 && buf[0] == '-') {
// Line comment, so skip to end of line and start over.
for (; c != '\n' && c != EOF; c = getc(f));
goto start;
} else if (!constituent((char)c)) {
if (i == 0) {
// We permit single-character tokens that are not
// constituents; this lets things like ']' and ',' be
// tokens.
buf[i+1] = 0;
return;
} else {
ungetc(c, f);
buf[i] = 0;
return;
}
}
i++;
}
buf[bufsize-1] = 0;
}
static int next_token_is(FILE *f, char *buf, int bufsize, const char* expected) {
next_token(f, buf, bufsize);
return strcmp(buf, expected) == 0;
}
static void remove_underscores(char *buf) {
char *w = buf;
for (char *r = buf; *r; r++) {
if (*r != '_') {
*w++ = *r;
}
}
*w++ = 0;
}
static int read_str_elem(char *buf, struct array_reader *reader) {
int ret;
if (reader->n_elems_used == reader->n_elems_space) {
reader->n_elems_space *= 2;
reader->elems = (char*) realloc(reader->elems,
(size_t)(reader->n_elems_space * reader->elem_size));
}
ret = reader->elem_reader(buf, reader->elems + reader->n_elems_used * reader->elem_size);
if (ret == 0) {
reader->n_elems_used++;
}
return ret;
}
static int read_str_array_elems(FILE *f,
char *buf, int bufsize,
struct array_reader *reader, int64_t dims) {
int ret = 1;
int expect_elem = 1;
char *knows_dimsize = (char*) calloc((size_t)dims, sizeof(char));
int cur_dim = (int)dims-1;
int64_t *elems_read_in_dim = (int64_t*) calloc((size_t)dims, sizeof(int64_t));
while (1) {
next_token(f, buf, bufsize);
if (strcmp(buf, "]") == 0) {
expect_elem = 0;
if (knows_dimsize[cur_dim]) {
if (reader->shape[cur_dim] != elems_read_in_dim[cur_dim]) {
ret = 1;
break;
}
} else {
knows_dimsize[cur_dim] = 1;
reader->shape[cur_dim] = elems_read_in_dim[cur_dim];
}
if (cur_dim == 0) {
ret = 0;
break;
} else {
cur_dim--;
elems_read_in_dim[cur_dim]++;
}
} else if (!expect_elem && strcmp(buf, ",") == 0) {
expect_elem = 1;
} else if (expect_elem) {
if (strcmp(buf, "[") == 0) {
if (cur_dim == dims - 1) {
ret = 1;
break;
}
cur_dim++;
elems_read_in_dim[cur_dim] = 0;
} else if (cur_dim == dims - 1) {
ret = read_str_elem(buf, reader);
if (ret != 0) {
break;
}
expect_elem = 0;
elems_read_in_dim[cur_dim]++;
} else {
ret = 1;
break;
}
} else {
ret = 1;
break;
}
}
free(knows_dimsize);
free(elems_read_in_dim);
return ret;
}
static int read_str_empty_array(FILE *f, char *buf, int bufsize,
const char *type_name, int64_t *shape, int64_t dims) {
if (strlen(buf) == 0) {
// EOF
return 1;
}
if (strcmp(buf, "empty") != 0) {
return 1;
}
if (!next_token_is(f, buf, bufsize, "(")) {
return 1;
}
for (int i = 0; i < dims; i++) {
if (!next_token_is(f, buf, bufsize, "[")) {
return 1;
}
next_token(f, buf, bufsize);
if (sscanf(buf, "%"SCNu64, (uint64_t*)&shape[i]) != 1) {
return 1;
}
if (!next_token_is(f, buf, bufsize, "]")) {
return 1;
}
}
if (!next_token_is(f, buf, bufsize, type_name)) {
return 1;
}
if (!next_token_is(f, buf, bufsize, ")")) {
return 1;
}
// Check whether the array really is empty.
for (int i = 0; i < dims; i++) {
if (shape[i] == 0) {
return 0;
}
}
// Not an empty array!
return 1;
}
static int read_str_array(FILE *f,
int64_t elem_size, str_reader elem_reader,
const char *type_name,
void **data, int64_t *shape, int64_t dims) {
int ret;
struct array_reader reader;
char buf[100];
int dims_seen;
for (dims_seen = 0; dims_seen < dims; dims_seen++) {
if (!next_token_is(f, buf, sizeof(buf), "[")) {
break;
}
}
if (dims_seen == 0) {
return read_str_empty_array(f, buf, sizeof(buf), type_name, shape, dims);
}
if (dims_seen != dims) {
return 1;
}
reader.shape = shape;
reader.n_elems_used = 0;
reader.elem_size = elem_size;
reader.n_elems_space = 16;
reader.elems = (char*) realloc(*data, (size_t)(elem_size*reader.n_elems_space));
reader.elem_reader = elem_reader;
ret = read_str_array_elems(f, buf, sizeof(buf), &reader, dims);
*data = reader.elems;
return ret;
}
#define READ_STR(MACRO, PTR, SUFFIX) \
remove_underscores(buf); \
int j; \
if (sscanf(buf, "%"MACRO"%n", (PTR*)dest, &j) == 1) { \
return !(strcmp(buf+j, "") == 0 || strcmp(buf+j, SUFFIX) == 0); \
} else { \
return 1; \
}
static int read_str_i8(char *buf, void* dest) {
// Some platforms (WINDOWS) does not support scanf %hhd or its
// cousin, %SCNi8. Read into int first to avoid corrupting
// memory.
//
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63417
remove_underscores(buf);
int j, x;
if (sscanf(buf, "%i%n", &x, &j) == 1) {
*(int8_t*)dest = (int8_t)x;
return !(strcmp(buf+j, "") == 0 || strcmp(buf+j, "i8") == 0);
} else {
return 1;
}
}
static int read_str_u8(char *buf, void* dest) {
// Some platforms (WINDOWS) does not support scanf %hhd or its
// cousin, %SCNu8. Read into int first to avoid corrupting
// memory.
//
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63417
remove_underscores(buf);
int j, x;
if (sscanf(buf, "%i%n", &x, &j) == 1) {
*(uint8_t*)dest = (uint8_t)x;
return !(strcmp(buf+j, "") == 0 || strcmp(buf+j, "u8") == 0);
} else {
return 1;
}
}
static int read_str_i16(char *buf, void* dest) {
READ_STR(SCNi16, int16_t, "i16");
}
static int read_str_u16(char *buf, void* dest) {
READ_STR(SCNi16, int16_t, "u16");
}
static int read_str_i32(char *buf, void* dest) {
READ_STR(SCNi32, int32_t, "i32");
}
static int read_str_u32(char *buf, void* dest) {
READ_STR(SCNi32, int32_t, "u32");
}
static int read_str_i64(char *buf, void* dest) {
READ_STR(SCNi64, int64_t, "i64");
}
static int read_str_u64(char *buf, void* dest) {
// FIXME: This is not correct, as SCNu64 only permits decimal
// literals. However, SCNi64 does not handle very large numbers
// correctly (it's really for signed numbers, so that's fair).
READ_STR(SCNu64, uint64_t, "u64");
}
static int read_str_f16(char *buf, void* dest) {
remove_underscores(buf);
if (strcmp(buf, "f16.nan") == 0) {
*(uint16_t*)dest = float2halfbits(NAN);
return 0;
} else if (strcmp(buf, "f16.inf") == 0) {
*(uint16_t*)dest = float2halfbits(INFINITY);
return 0;
} else if (strcmp(buf, "-f16.inf") == 0) {
*(uint16_t*)dest = float2halfbits(-INFINITY);
return 0;
} else {
int j;
float x;
if (sscanf(buf, "%f%n", &x, &j) == 1) {
if (strcmp(buf+j, "") == 0 || strcmp(buf+j, "f16") == 0) {
*(uint16_t*)dest = float2halfbits(x);
return 0;
}
}
return 1;
}
}
static int read_str_f32(char *buf, void* dest) {
remove_underscores(buf);
if (strcmp(buf, "f32.nan") == 0) {
*(float*)dest = (float)NAN;
return 0;
} else if (strcmp(buf, "f32.inf") == 0) {
*(float*)dest = (float)INFINITY;
return 0;
} else if (strcmp(buf, "-f32.inf") == 0) {
*(float*)dest = (float)-INFINITY;
return 0;
} else {
READ_STR("f", float, "f32");
}
}
static int read_str_f64(char *buf, void* dest) {
remove_underscores(buf);
if (strcmp(buf, "f64.nan") == 0) {
*(double*)dest = (double)NAN;
return 0;
} else if (strcmp(buf, "f64.inf") == 0) {
*(double*)dest = (double)INFINITY;
return 0;
} else if (strcmp(buf, "-f64.inf") == 0) {
*(double*)dest = (double)-INFINITY;
return 0;
} else {
READ_STR("lf", double, "f64");
}
}
static int read_str_bool(char *buf, void* dest) {
if (strcmp(buf, "true") == 0) {
*(char*)dest = 1;
return 0;
} else if (strcmp(buf, "false") == 0) {
*(char*)dest = 0;
return 0;
} else {
return 1;
}
}
static int read_str_unit(char *buf, void* dest) {
(void)dest;
if (strcmp(buf, "()") == 0) {
return 0;
} else {
return 1;
}
}
static int write_str_i8(FILE *out, const int8_t *src) {
return fprintf(out, "%hhdi8", *src);
}
static int write_str_u8(FILE *out, const uint8_t *src) {
return fprintf(out, "%hhuu8", *src);
}
static int write_str_i16(FILE *out, const int16_t *src) {
return fprintf(out, "%hdi16", *src);
}
static int write_str_u16(FILE *out, const uint16_t *src) {
return fprintf(out, "%huu16", *src);
}
static int write_str_i32(FILE *out, const int32_t *src) {
return fprintf(out, "%di32", *src);
}
static int write_str_u32(FILE *out, const uint32_t *src) {
return fprintf(out, "%uu32", *src);
}
static int write_str_i64(FILE *out, const int64_t *src) {
return fprintf(out, "%"PRIi64"i64", *src);
}
static int write_str_u64(FILE *out, const uint64_t *src) {
return fprintf(out, "%"PRIu64"u64", *src);
}
// FLT_DECIMAL_DIG and DBL_DECIMAL_DIG are defined in C11.
// If we want C99 compatibility, we must define them ourselves.
// We choose the standard values on platforms that use the IEEE754 defaults, with fallback to an overestimate.
#ifndef FLT_DECIMAL_DIG
#if FLT_RADIX == 2 && FLT_MANT_DIG <= 24 && 9 < DECIMAL_DIG
#define FLT_DECIMAL_DIG 9
#else
#define FLT_DECIMAL_DIG DECIMAL_DIG
#endif
#endif
#ifndef DBL_DECIMAL_DIG
#if FLT_RADIX == 2 && DBL_MANT_DIG <= 53 && 17 < DECIMAL_DIG
#define DBL_DECIMAL_DIG 17
#else
#define DBL_DECIMAL_DIG DECIMAL_DIG
#endif
#endif
static int write_str_f16(FILE *out, const uint16_t *src) {
float x = halfbits2float(*src);
if (isnan(x)) {
return fprintf(out, "f16.nan");
} else if (isinf(x) && x >= 0) {
return fprintf(out, "f16.inf");
} else if (isinf(x)) {
return fprintf(out, "-f16.inf");
} else {
return fprintf(out, "%.*gf16", FLT_DECIMAL_DIG, x);
}
}
static int write_str_f32(FILE *out, const float *src) {
float x = *src;
if (isnan(x)) {
return fprintf(out, "f32.nan");
} else if (isinf(x) && x >= 0) {
return fprintf(out, "f32.inf");
} else if (isinf(x)) {
return fprintf(out, "-f32.inf");
} else {
return fprintf(out, "%.*gf32", FLT_DECIMAL_DIG, x);
}
}
static int write_str_f64(FILE *out, const double *src) {
double x = *src;
if (isnan(x)) {
return fprintf(out, "f64.nan");
} else if (isinf(x) && x >= 0) {
return fprintf(out, "f64.inf");
} else if (isinf(x)) {
return fprintf(out, "-f64.inf");
} else {
return fprintf(out, "%.*gf64", DBL_DECIMAL_DIG, x);
}
}
static int write_str_bool(FILE *out, const void *src) {
return fprintf(out, *(char*)src ? "true" : "false");
}
static int write_str_unit(FILE *out, const void *src) {
(void)src;
return fprintf(out, "()");
}
//// Binary I/O
#define BINARY_FORMAT_VERSION 2
#define IS_BIG_ENDIAN (!*(unsigned char *)&(uint16_t){1})
static void flip_bytes(size_t elem_size, unsigned char *elem) {
for (size_t j=0; j<elem_size/2; j++) {
unsigned char head = elem[j];
size_t tail_index = elem_size-1-j;
elem[j] = elem[tail_index];
elem[tail_index] = head;
}
}
// On Windows we need to explicitly set the file mode to not mangle
// newline characters. On *nix there is no difference.
#ifdef _WIN32
#include <io.h>
#include <fcntl.h>
static void set_binary_mode(FILE *f) {
setmode(fileno(f), O_BINARY);
}
#else
static void set_binary_mode(FILE *f) {
(void)f;
}
#endif
static int read_byte(FILE *f, void* dest) {
size_t num_elems_read = fread(dest, 1, 1, f);
return num_elems_read == 1 ? 0 : 1;
}
//// Types
struct primtype_info_t {
const char binname[5]; // Used for parsing binary data.
const char* type_name; // Same name as in Futhark.
const int64_t size; // in bytes
const writer write_str; // Write in text format.
const str_reader read_str; // Read in text format.
};
static const struct primtype_info_t i8_info =
{.binname = " i8", .type_name = "i8", .size = 1,
.write_str = (writer)write_str_i8, .read_str = (str_reader)read_str_i8};
static const struct primtype_info_t i16_info =
{.binname = " i16", .type_name = "i16", .size = 2,
.write_str = (writer)write_str_i16, .read_str = (str_reader)read_str_i16};
static const struct primtype_info_t i32_info =
{.binname = " i32", .type_name = "i32", .size = 4,
.write_str = (writer)write_str_i32, .read_str = (str_reader)read_str_i32};
static const struct primtype_info_t i64_info =
{.binname = " i64", .type_name = "i64", .size = 8,
.write_str = (writer)write_str_i64, .read_str = (str_reader)read_str_i64};
static const struct primtype_info_t u8_info =
{.binname = " u8", .type_name = "u8", .size = 1,
.write_str = (writer)write_str_u8, .read_str = (str_reader)read_str_u8};
static const struct primtype_info_t u16_info =
{.binname = " u16", .type_name = "u16", .size = 2,
.write_str = (writer)write_str_u16, .read_str = (str_reader)read_str_u16};
static const struct primtype_info_t u32_info =
{.binname = " u32", .type_name = "u32", .size = 4,
.write_str = (writer)write_str_u32, .read_str = (str_reader)read_str_u32};
static const struct primtype_info_t u64_info =
{.binname = " u64", .type_name = "u64", .size = 8,
.write_str = (writer)write_str_u64, .read_str = (str_reader)read_str_u64};
static const struct primtype_info_t f16_info =
{.binname = " f16", .type_name = "f16", .size = 2,
.write_str = (writer)write_str_f16, .read_str = (str_reader)read_str_f16};
static const struct primtype_info_t f32_info =
{.binname = " f32", .type_name = "f32", .size = 4,
.write_str = (writer)write_str_f32, .read_str = (str_reader)read_str_f32};
static const struct primtype_info_t f64_info =
{.binname = " f64", .type_name = "f64", .size = 8,
.write_str = (writer)write_str_f64, .read_str = (str_reader)read_str_f64};
static const struct primtype_info_t bool_info =
{.binname = "bool", .type_name = "bool", .size = 1,
.write_str = (writer)write_str_bool, .read_str = (str_reader)read_str_bool};
static const struct primtype_info_t unit_info =
{.binname = "bool", .type_name = "unit", .size = 1,
.write_str = (writer)write_str_unit, .read_str = (str_reader)read_str_unit};
static const struct primtype_info_t* primtypes[] = {
&i8_info, &i16_info, &i32_info, &i64_info,
&u8_info, &u16_info, &u32_info, &u64_info,
&f16_info, &f32_info, &f64_info,
&bool_info,
NULL // NULL-terminated
};
// General value interface. All endian business taken care of at
// lower layers.
static int read_is_binary(FILE *f) {
skipspaces(f);
int c = getc(f);
if (c == 'b') {
int8_t bin_version;
int ret = read_byte(f, &bin_version);
if (ret != 0) { futhark_panic(1, "binary-input: could not read version.\n"); }
if (bin_version != BINARY_FORMAT_VERSION) {
futhark_panic(1, "binary-input: File uses version %i, but I only understand version %i.\n",
bin_version, BINARY_FORMAT_VERSION);
}
return 1;
}
ungetc(c, f);
return 0;
}
static const struct primtype_info_t* read_bin_read_type_enum(FILE *f) {
char read_binname[4];
int num_matched = fscanf(f, "%4c", read_binname);
if (num_matched != 1) { futhark_panic(1, "binary-input: Couldn't read element type.\n"); }
const struct primtype_info_t **type = primtypes;
for (; *type != NULL; type++) {
// I compare the 4 characters manually instead of using strncmp because
// this allows any value to be used, also NULL bytes
if (memcmp(read_binname, (*type)->binname, 4) == 0) {
return *type;
}
}
futhark_panic(1, "binary-input: Did not recognize the type '%s'.\n", read_binname);
return NULL;
}
static void read_bin_ensure_scalar(FILE *f, const struct primtype_info_t *expected_type) {
int8_t bin_dims;
int ret = read_byte(f, &bin_dims);
if (ret != 0) { futhark_panic(1, "binary-input: Couldn't get dims.\n"); }
if (bin_dims != 0) {
futhark_panic(1, "binary-input: Expected scalar (0 dimensions), but got array with %i dimensions.\n",
bin_dims);
}
const struct primtype_info_t *bin_type = read_bin_read_type_enum(f);
if (bin_type != expected_type) {
futhark_panic(1, "binary-input: Expected scalar of type %s but got scalar of type %s.\n",
expected_type->type_name,
bin_type->type_name);
}
}
//// High-level interface
static int read_bin_array(FILE *f,
const struct primtype_info_t *expected_type, void **data, int64_t *shape, int64_t dims) {
int ret;
int8_t bin_dims;
ret = read_byte(f, &bin_dims);
if (ret != 0) { futhark_panic(1, "binary-input: Couldn't get dims.\n"); }
if (bin_dims != dims) {
futhark_panic(1, "binary-input: Expected %i dimensions, but got array with %i dimensions.\n",
dims, bin_dims);
}
const struct primtype_info_t *bin_primtype = read_bin_read_type_enum(f);
if (expected_type != bin_primtype) {
futhark_panic(1, "binary-input: Expected %iD-array with element type '%s' but got %iD-array with element type '%s'.\n",
dims, expected_type->type_name, dims, bin_primtype->type_name);
}
int64_t elem_count = 1;
for (int i=0; i<dims; i++) {
int64_t bin_shape;
ret = (int)fread(&bin_shape, sizeof(bin_shape), 1, f);
if (ret != 1) {
futhark_panic(1, "binary-input: Couldn't read size for dimension %i of array.\n", i);
}
if (IS_BIG_ENDIAN) {
flip_bytes(sizeof(bin_shape), (unsigned char*) &bin_shape);
}
elem_count *= bin_shape;
shape[i] = bin_shape;
}
int64_t elem_size = expected_type->size;
void* tmp = realloc(*data, (size_t)(elem_count * elem_size));
if (tmp == NULL) {
futhark_panic(1, "binary-input: Failed to allocate array of size %i.\n",
elem_count * elem_size);
}
*data = tmp;
int64_t num_elems_read = (int64_t)fread(*data, (size_t)elem_size, (size_t)elem_count, f);
if (num_elems_read != elem_count) {
futhark_panic(1, "binary-input: tried to read %i elements of an array, but only got %i elements.\n",
elem_count, num_elems_read);
}
// If we're on big endian platform we must change all multibyte elements
// from using little endian to big endian
if (IS_BIG_ENDIAN && elem_size != 1) {
flip_bytes((size_t)elem_size, (unsigned char*) *data);
}
return 0;
}
static int read_array(FILE *f, const struct primtype_info_t *expected_type, void **data, int64_t *shape, int64_t dims) {
if (!read_is_binary(f)) {
return read_str_array(f, expected_type->size, (str_reader)expected_type->read_str, expected_type->type_name, data, shape, dims);
} else {
return read_bin_array(f, expected_type, data, shape, dims);
}
}
static int end_of_input(FILE *f) {
skipspaces(f);
char token[2];
next_token(f, token, sizeof(token));
if (strcmp(token, "") == 0) {
return 0;
} else {
return 1;
}
}
static int write_str_array(FILE *out,
const struct primtype_info_t *elem_type,
const unsigned char *data,
const int64_t *shape,
int8_t rank) {
if (rank==0) {
elem_type->write_str(out, (const void*)data);
} else {
int64_t len = (int64_t)shape[0];
int64_t slice_size = 1;
int64_t elem_size = elem_type->size;
for (int8_t i = 1; i < rank; i++) {
slice_size *= shape[i];
}
if (len*slice_size == 0) {
fprintf(out, "empty(");
for (int64_t i = 0; i < rank; i++) {
fprintf(out, "[%"PRIi64"]", shape[i]);
}
fprintf(out, "%s", elem_type->type_name);
fprintf(out, ")");
} else if (rank==1) {
fputc('[', out);
for (int64_t i = 0; i < len; i++) {
elem_type->write_str(out, (const void*) (data + i * elem_size));
if (i != len-1) {
fprintf(out, ", ");
}
}
fputc(']', out);
} else {
fputc('[', out);
for (int64_t i = 0; i < len; i++) {
write_str_array(out, elem_type, data + i * slice_size * elem_size, shape+1, rank-1);
if (i != len-1) {
fprintf(out, ", ");
}
}
fputc(']', out);
}
}
return 0;
}
static int write_bin_array(FILE *out,
const struct primtype_info_t *elem_type,
const unsigned char *data,
const int64_t *shape,
int8_t rank) {
int64_t num_elems = 1;
for (int64_t i = 0; i < rank; i++) {
num_elems *= shape[i];
}
fputc('b', out);
fputc((char)BINARY_FORMAT_VERSION, out);
fwrite(&rank, sizeof(int8_t), 1, out);
fwrite(elem_type->binname, 4, 1, out);
if (shape != NULL) {
fwrite(shape, sizeof(int64_t), (size_t)rank, out);
}
if (IS_BIG_ENDIAN) {
for (int64_t i = 0; i < num_elems; i++) {
const unsigned char *elem = data+i*elem_type->size;
for (int64_t j = 0; j < elem_type->size; j++) {
fwrite(&elem[elem_type->size-j], 1, 1, out);
}
}
} else {
fwrite(data, (size_t)elem_type->size, (size_t)num_elems, out);
}
return 0;
}
static int write_array(FILE *out, int write_binary,
const struct primtype_info_t *elem_type,
const void *data,
const int64_t *shape,
const int8_t rank) {
if (write_binary) {
return write_bin_array(out, elem_type, data, shape, rank);
} else {
return write_str_array(out, elem_type, data, shape, rank);
}
}
static int read_scalar(FILE *f,
const struct primtype_info_t *expected_type, void *dest) {
if (!read_is_binary(f)) {
char buf[100];
next_token(f, buf, sizeof(buf));
return expected_type->read_str(buf, dest);
} else {
read_bin_ensure_scalar(f, expected_type);
size_t elem_size = (size_t)expected_type->size;
size_t num_elems_read = fread(dest, elem_size, 1, f);
if (IS_BIG_ENDIAN) {
flip_bytes(elem_size, (unsigned char*) dest);
}
return num_elems_read == 1 ? 0 : 1;
}
}
static int write_scalar(FILE *out, int write_binary, const struct primtype_info_t *type, void *src) {
if (write_binary) {
return write_bin_array(out, type, src, NULL, 0);
} else {
return type->write_str(out, src);
}
}
// End of values.h.