Files
zeekay a4debdba80 Read a .zap schema the way every other ZAP language reads it
C had no front end for the schema language at all. The compiler directory
holds a code generator plugin, which is handed a request by another tool
and never sees schema text, so a .zap file could not be read here in
either form -- indentation or braces.

zapgen reads it. Whitespace-significant source is rewritten into the brace
form BEFORE the parser, exactly as the Go reference does it: a near
identity that only adds the two tokens the brace grammar requires where
the indented form leaves them out. A file already in braces comes back
byte for byte, so one grammar reads both styles and neither is a dialect
of the other.

The identifier grammar is not restated. Its Unicode ranges are taken from
Go's own tables by gen/table, so a name this parser accepts is a name the
Go parser accepts, and a Go release that moves Unicode moves both.

The generated header gives each struct a view over the message bytes and
one reader per field at a fixed offset. Reading is in place and total: a
field past the end reads zero, a pointer into the head or out of the
message reads null. zap_view.h is the one place that says so.

Checked against the Go front end on every .zap in the estate plus 55
adversarial cases and 15000 random ones: the brace form each writes is
byte-identical, the layout each reads is field-for-field the same, and
they accept and refuse the same files for the same stated reason. A
message built by the Go runtime reads back through these bindings with
every field equal.
2026-09-15 11:32:12 -07:00

168 lines
3.4 KiB
C

/* SPDX-License-Identifier: BSD-3-Clause-Eco */
/*
* zapgen - read a .zap schema, write the C it describes.
*
* The schema may be written either way. The whitespace-significant form is
* the one authors write; the brace form is what the grammar reads, and a
* file already in it passes through untouched.
*
* zapgen schema.zap write schema.zap.h beside the input
* zapgen -o gen schema.zap write it into gen/
* zapgen -c schema.zap read it and say nothing if it holds
* zapgen -d schema.zap write the brace form to stdout
*/
#include "desugar.h"
#include "emit.h"
#include "parse.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void usage(void)
{
fputs("usage: zapgen [-o DIR] [-c] [-d] SCHEMA.zap\n"
" -o DIR write into DIR (default: the input's directory)\n"
" -c read the schema only; write nothing\n"
" -d write the brace form of the schema to stdout\n",
stderr);
}
static char *slurp(const char *path, size_t *len)
{
FILE *f = fopen(path, "rb");
size_t cap = 1 << 16, n = 0;
char *buf;
if (!f)
return NULL;
buf = malloc(cap);
for (;;) {
size_t got;
if (!buf) {
fclose(f);
return NULL;
}
if (n == cap) {
char *p = realloc(buf, cap *= 2);
if (!p) {
free(buf);
fclose(f);
return NULL;
}
buf = p;
}
got = fread(buf + n, 1, cap - n, f);
n += got;
if (got == 0)
break;
}
fclose(f);
*len = n;
return buf;
}
/* out_path is the input path with ".h" appended, in dir when one is given.
* A schema named foo.zap yields foo.zap.h, matching what the other
* generators in this repository name their output. */
static void out_path(const char *in, const char *dir, char *out, size_t n)
{
const char *base = in, *p;
for (p = in; *p; p++)
if (*p == '/' || *p == '\\')
base = p + 1;
if (dir)
snprintf(out, n, "%s/%s.h", dir, base);
else
snprintf(out, n, "%s.h", in);
}
int main(int argc, char **argv)
{
const char *dir = NULL, *in = NULL;
int only_read = 0, only_desugar = 0, i;
char err[ERRLEN], path[1024];
struct zap_file *f;
char *src, *brace;
size_t len, bracelen;
FILE *out;
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "-o") == 0 && i + 1 < argc) {
dir = argv[++i];
} else if (strcmp(argv[i], "-c") == 0) {
only_read = 1;
} else if (strcmp(argv[i], "-d") == 0) {
only_desugar = 1;
} else if (argv[i][0] == '-' && argv[i][1]) {
usage();
return 2;
} else if (!in) {
in = argv[i];
} else {
usage();
return 2;
}
}
if (!in) {
usage();
return 2;
}
src = slurp(in, &len);
if (!src) {
fprintf(stderr, "zapgen: cannot read %s\n", in);
return 1;
}
if (only_desugar) {
if (desugar(src, len, &brace, &bracelen, err)) {
fprintf(stderr, "zapgen: %s\n", err);
free(src);
return 1;
}
fwrite(brace, 1, bracelen, stdout);
free(brace);
free(src);
return 0;
}
f = parse(in, src, len, err);
free(src);
if (!f) {
fprintf(stderr, "zapgen: %s\n", err);
return 1;
}
if (only_read) {
int rc = check(f, err);
if (rc)
fprintf(stderr, "zapgen: %s\n", err);
file_free(f);
return rc ? 1 : 0;
}
out_path(in, dir, path, sizeof(path));
out = fopen(path, "w");
if (!out) {
fprintf(stderr, "zapgen: cannot write %s\n", path);
file_free(f);
return 1;
}
if (emit(f, out, err)) {
fprintf(stderr, "zapgen: %s\n", err);
fclose(out);
remove(path);
file_free(f);
return 1;
}
fclose(out);
file_free(f);
return 0;
}