<commit_before>/*
 * Copyright © 2008-2011 <NAME>
 * Copyright © 2011 Intel Corporation
 * Copyright © 2015 Red Hat, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "config.h"

#include <stdbool.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <expat.h>
#include <getopt.h>
#include <limits.h>
#include <unistd.h>

#if HAVE_LIBXML
#include <libxml/parser.h>
#endif

#include "wayland-util.h"

/* Embedded wayland.dtd file, see dtddata.S */
extern char DTD_DATA_begin;
extern int DTD_DATA_len;

enum side {
	CLIENT,
	SERVER,
};

static int
usage(int ret)
{
	fprintf(stderr, "usage: ./scanner [OPTION] [client-header|server-header|code]"
		" [input_file output_file]\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "Converts XML protocol descriptions supplied on "
			"stdin or input file to client\n"
			"headers, server headers, or protocol marshalling code.\n\n");
	fprintf(stderr, "options:\n");
	fprintf(stderr, "    -h,  --help                  display this help and exit.\n"
		        "    -c,  --include-core-only     include the core version of the headers,\n"
	                "                                 that is e.g. wayland-client-core.h instead\n"
	                "                                 of wayland-client.h.\n");
	exit(ret);
}

static bool
is_dtd_valid(FILE *input)
{
	bool rc = true;
#if HAVE_LIBXML
	xmlParserCtxtPtr ctx = NULL;
	xmlDocPtr doc = NULL;
	xmlDtdPtr dtd = NULL;
	xmlValidCtxtPtr	dtdctx;
	xmlParserInputBufferPtr	buffer;
	int fd = fileno(input);

	dtdctx = xmlNewValidCtxt();
	ctx = xmlNewParserCtxt();
	if (!ctx || !dtdctx)
		abort();

	buffer = xmlParserInputBufferCreateMem(&DTD_DATA_begin,
					       DTD_DATA_len,
					       XML_CHAR_ENCODING_UTF8);
	if (!buffer) {
		fprintf(stderr, "Failed to init buffer for DTD.\n");
		abort();
	}

	dtd = xmlIOParseDTD(NULL, buffer, XML_CHAR_ENCODING_UTF8);
	if (!dtd) {
		fprintf(stderr, "Failed to parse DTD.\n");
		abort();
	}

	doc = xmlCtxtReadFd(ctx, fd, "protocol", NULL, 0);
	if (!doc) {
		fprintf(stderr, "Failed to read XML\n");
		abort();
	}

	rc = xmlValidateDtd(dtdctx, doc, dtd);
	xmlFreeDoc(doc);
	xmlFreeParserCtxt(ctx);
	xmlFreeValidCtxt(dtdctx);
	/* xmlIOParseDTD consumes buffer */

	if (lseek(fd, 0, SEEK_SET) != 0) {
		fprintf(stderr, "Failed to reset fd, output would be garbage.\n");
		abort();
	}
#endif
	return rc;
}

#define XML_BUFFER_SIZE 4096

struct location {
	const char *filename;
	int line_number;
};

struct description {
	char *summary;
	char *text;
};

struct protocol {
	char *name;
	char *uppercase_name;
	struct wl_list interface_list;
	int type_index;
	int null_run_length;
	char *copyright;
	struct description *description;
	bool core_headers;
};

struct interface {
	struct location loc;
	char *name;
	char *uppercase_name;
	int version;
	int since;
	struct wl_list request_list;
	struct wl_list event_list;
	struct wl_list enumeration_list;
	struct wl_list link;
	struct description *description;
};

struct message {
	struct location loc;
	char *name;
	char *uppercase_name;
	struct wl_list arg_list;
	struct wl_list link;
	int arg_count;
	int new_id_count;
	int type_index;
	int all_null;
	int destructor;
	int since;
	struct description *description;
};

enum arg_type {
	NEW_ID,
	INT,
	UNSIGNED,
	FIXED,
	STRING,
	OBJECT,
	ARRAY,
	FD
};

struct arg {
	char *name;
	enum arg_type type;
	int nullable;
	char *interface_name;
	struct wl_list link;
	char *summary;
	char *enumeration_name;
};

struct enumeration {
	char *name;
	char *uppercase_name;
	struct wl_list entry_list;
	struct wl_list link;
	struct description *description;
	bool bitfield;
};

struct entry {
	char *name;
	char *uppercase_name;
	char *value;
	char *summary;
	struct wl_list link;
};

struct parse_context {
	struct location loc;
	XML_Parser parser;
	struct protocol *protocol;
	struct interface *interface;
	struct message *message;
	struct enumeration *enumeration;
	struct description *description;
	char character_data[8192];
	unsigned int character_data_length;
};

static void *
fail_on_null(void *p)
{
	if (p == NULL) {
		fprintf(stderr, "wayland-scanner: out of memory\n");
		exit(EXIT_FAILURE);
	}

	return p;
}

static void *
zalloc(size_t s)
{
	return calloc(s, 1);
}

static void *
xzalloc(size_t s)
{
	return fail_on_null(zalloc(s));
}

static char *
xstrdup(const char *s)
{
	return fail_on_null(strdup(s));
}

static char *
uppercase_dup(const char *src)
{
	char *u;
	int i;

	u = xstrdup(src);
	for (i = 0; u[i]; i++)
		u[i] = toupper(u[i]);
	u[i] = '\0';

	return u;
}

static const char *indent(int n)
{
	const char *whitespace[] = {
		"\t\t\t\t\t\t\t\t\t\t\t\t",
		"\t\t\t\t\t\t\t\t\t\t\t\t ",
		"\t\t\t\t\t\t\t\t\t\t\t\t  ",
		"\t\t\t\t\t\t\t\t\t\t\t\t   ",
		"\t\t\t\t\t\t\t\t\t\t\t\t    ",
		"\t\t\t\t\t\t\t\t\t\t\t\t     ",
		"\t\t\t\t\t\t\t\t\t\t\t\t      ",
		"\t\t\t\t\t\t\t\t\t\t\t\t       "
	};

	return whitespace[n % 8] + 12 - n / 8;
}

static void
desc_dump(char *desc, const char *fmt, ...) WL_PRINTF(2, 3);

static void
desc_dump(char *desc, const char *fmt, ...)
{
	va_list ap;
	char buf[128], hang;
	int col, i, j, k, startcol, newlines;

	va_start(ap, fmt);
	vsnprintf(buf, sizeof buf, fmt, ap);
	va_end(ap);

	for (i = 0, col = 0; buf[i] != '*'; i++) {
		if (buf[i] == '\t')
			col = (col + 8) & ~7;
		else
			col++;
	}

	printf("%s", buf);

	if (!desc) {
		printf("(none)\n");
		return;
	}

	startcol = col;
	col += strlen(&buf[i]);
	if (col - startcol > 2)
		hang = '\t';
	else
		hang = ' ';

	for (i = 0; desc[i]; ) {
		k = i;
		newlines = 0;
		while (desc[i] && isspace(desc[i])) {
			if (desc[i] == '\n')
				newlines++;
			i++;
		}
		if (!desc[i])
			break;

		j = i;
		while (desc[i] && !isspace(desc[i]))
			i++;

		if (newlines > 1)
			printf("\n%s*", indent(startcol));
		if (newlines > 1 || col + i - j > 72) {
			printf("\n%s*%c", indent(startcol), hang);
			col = startcol;
		}

		if (col > startcol && k > 0)
			col += printf(" ");
		col += printf("%.*s", i - j, &desc[j]);
	}
	putchar('\n');
}

static void
fail(struct location *loc, const char *msg, ...)
{
	va_list ap;

	va_start(ap, msg);
	fprintf(stderr, "%s:%d: error: ",
		loc->filename, loc->line_number);
	vfprintf(stderr, msg, ap);
	fprintf(stderr, "\n");
	va_end(ap);
	exit(EXIT_FAILURE);
}

static void
warn(struct location *loc, const char *msg, ...)
{
	va_list ap;

	va_start(ap, msg);
	fprintf(stderr, "%s:%d: warning: ",
		loc->filename, loc->line_number);
	vfprintf(stderr, msg, ap);
	fprintf(stderr, "\n");
	va_end(ap);
}

static bool
is_nullable_type(struct arg *arg)
{
	switch (arg->type) {
	/* Strings, objects, and arrays are possibly nullable */
	case STRING:
	case OBJECT:
	case NEW_ID:
	case ARRAY:
		return true;
	default:
		return false;
	}
}

static struct message *
create_message(struct location loc, const char *name)
{
	struct message *message;

	message = xzalloc(sizeof *message);
	message->loc = loc;
	message->name = xstrdup(name);
	message->uppercase_name = uppercase_dup(name);
	wl_list_init(&message->arg_list);

	return message;
}

static void
free_arg(struct arg *arg)
{
	free(arg->name);
	free(arg->interface_name);
	free(arg->summary);
	free(arg);
}

static struct arg *
create_arg(const char *name)
{
	struct arg *arg;

	arg = xzalloc(sizeof *arg);
	arg->name = xstrdup(name);

	return arg;
}

static bool
set_arg_type(struct arg *arg, const char *type)
{
	if (strcmp(type, "int") == 0)
		arg->type = INT;
	else if (strcmp(type, "uint") == 0)
		arg->type = UNSIGNED;
	else if (strcmp(type, "fixed") == 0)
		arg->type = FIXED;
	else if (strcmp(type, "string") == 0)
		arg->type = STRING;
	else if (strcmp(type, "array") == 0)
		arg->type = ARRAY;
	else if (strcmp(type, "fd") == 0)
		arg->type = FD;
	else if (strcmp(type, "new_id") == 0)
		arg->type = NEW_ID;
	else if (strcmp(type, "object") == 0)
		arg->type = OBJECT;
	else
		return false;

	return true;
}

static void
free_description(struct description *desc)
{
	if (!desc)
		return;

	free(desc->summary);
	free(desc->text);

	free(desc);
}

static void
free_message(struct message *message)
{
	struct arg *a, *a_next;

	free(message->name);
	free(message->uppercase_name);
	free_description(message->description);

	wl_list_for_each_safe(a, a_next, &message->arg_list, link)
		free_arg(a);

	free(message);
}

static struct enumeration *
create_enumeration(const char *name)
{
	struct enumeration *enumeration;

	enumeration = xzalloc(sizeof *enumeration);
	enumeration->name = xstrdup(name);
	enumeration->uppercase_name = uppercase_dup(name);

	wl_list_init(&enumeration->entry_list);

	return enumeration;
}

static struct entry *
create_entry(const char *name, const char *value)
{
	struct entry *entry;

	entry = xzalloc(sizeof *entry);
	entry->name = xstrdup(name);
	entry->uppercase_name = uppercase_dup(name);
	entry->value = xstrdup(value);

	return entry;
}

static void
free_entry(struct entry *entry)
{
	free(entry->name);
	free(entry->uppercase_name);
	free(entry->value);
	free(entry->summary);

	free(entry);
}

static void
free_enumeration(struct enumeration *enumeration)
{
	struct entry *e, *e_next;

	free(enumeration->name);
	free(enumeration->uppercase_name);
	free_description(enumeration->description);

	wl_list_for_each_safe(e, e_next, &enumeration->entry_list, link)
		free_entry(e);

	free(enumeration);
}

static struct interface *
create_interface(struct location loc, const char *name, int version)
{
	struct interface *interface;

	interface = xzalloc(sizeof *interface);
	interface->loc = loc;
	interface->name = xstrdup(name);
	interface->uppercase_name = uppercase_dup(name);
	interface->version = version;
	interface->since = 1;
	wl_list_init(&interface->request_list);
	wl_list_init(&interface->event_list);
	wl_list_init(&interface->enumeration_list);

	return interface;
}

static void
free_interface(struct interface *interface)
{
	struct message *m, *next_m;
	struct enumeration *e, *next_e;

	free(interface->name);
	free(interface->uppercase_name);
	free_description(interface->description);

	wl_list_for_each_safe(m, next_m, &interface->request_list, link)
		free_message(m);
	wl_list_for_each_safe(m, next_m, &interface->event_list, link)
		free_message(m);
	wl_list_for_each_safe(e, next_e, &interface->enumeration_list, link)
		free_enumeration(e);

	free(interface);
}

/* convert string to unsigned integer,
 * in the case of error, return -1 */
static int
strtouint(const char *str)
{
	long int ret;
	char *end;
	int prev_errno = errno;

	errno = 0;
	ret = strtol(str, &end, 10);
	if (errno != 0 || end == str || *end != '\0')
		return -1;

	/* check range */
	if (ret < 0 || ret > INT_MAX) {
		return -1;
	}

	errno = prev_errno;
	return (int)ret;
}

static void
start_element(void *data, const char *element_name, const char **atts)
{
	struct parse_context *ctx = data;
	struct interface *interface;
	struct message *message;
	struct arg *arg;
	struct enumeration *enumeration;
	struct entry *entry;
	struct description *description = NULL;
	const char *name = NULL;
	const char *type = NULL;
	const char *interface_name = NULL;
	const char *value = NULL;
	const char *summary = NULL;
	const char *since = NULL;
	const char *allow_null = NULL;
	const char *enumeration_name = NULL;
	const char *bitfield = NULL;
	int i, version = 0;

	ctx->loc.line_number = XML_GetCurrentLineNumber(ctx->parser);
	for (i = 0; atts[i]; i += 2) {
		if (strcmp(atts[i], "name") == 0)
			name = atts[i + 1];
		if (strcmp(atts[i], "version") == 0)
			version = strtouint(atts[i + 1]);
			if (version == -1)
				fail(&ctx->loc, "wrong version (%s)", atts[i + 1]);
		if (strcmp(atts[i], "type") == 0)
			type = atts[i + 1];
		if (strcmp(atts[i], "value") == 0)
			value = atts[i + 1];
		if (strcmp(atts[i], "interface") == 0)
			interface_name = atts[i + 1];
		if (strcmp(atts[i], "summary") == 0)
			summary = atts[i + 1];
		if (strcmp(atts[i], "since") == 0)<commit_msg>scanner: Add missing brackets<commit_after>/*
 * Copyright © 2008-2011 <NAME>
 * Copyright © 2011 Intel Corporation
 * Copyright © 2015 Red Hat, Inc.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "config.h"

#include <stdbool.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <expat.h>
#include <getopt.h>
#include <limits.h>
#include <unistd.h>

#if HAVE_LIBXML
#include <libxml/parser.h>
#endif

#include "wayland-util.h"

/* Embedded wayland.dtd file, see dtddata.S */
extern char DTD_DATA_begin;
extern int DTD_DATA_len;

enum side {
	CLIENT,
	SERVER,
};

static int
usage(int ret)
{
	fprintf(stderr, "usage: ./scanner [OPTION] [client-header|server-header|code]"
		" [input_file output_file]\n");
	fprintf(stderr, "\n");
	fprintf(stderr, "Converts XML protocol descriptions supplied on "
			"stdin or input file to client\n"
			"headers, server headers, or protocol marshalling code.\n\n");
	fprintf(stderr, "options:\n");
	fprintf(stderr, "    -h,  --help                  display this help and exit.\n"
		        "    -c,  --include-core-only     include the core version of the headers,\n"
	                "                                 that is e.g. wayland-client-core.h instead\n"
	                "                                 of wayland-client.h.\n");
	exit(ret);
}

static bool
is_dtd_valid(FILE *input)
{
	bool rc = true;
#if HAVE_LIBXML
	xmlParserCtxtPtr ctx = NULL;
	xmlDocPtr doc = NULL;
	xmlDtdPtr dtd = NULL;
	xmlValidCtxtPtr	dtdctx;
	xmlParserInputBufferPtr	buffer;
	int fd = fileno(input);

	dtdctx = xmlNewValidCtxt();
	ctx = xmlNewParserCtxt();
	if (!ctx || !dtdctx)
		abort();

	buffer = xmlParserInputBufferCreateMem(&DTD_DATA_begin,
					       DTD_DATA_len,
					       XML_CHAR_ENCODING_UTF8);
	if (!buffer) {
		fprintf(stderr, "Failed to init buffer for DTD.\n");
		abort();
	}

	dtd = xmlIOParseDTD(NULL, buffer, XML_CHAR_ENCODING_UTF8);
	if (!dtd) {
		fprintf(stderr, "Failed to parse DTD.\n");
		abort();
	}

	doc = xmlCtxtReadFd(ctx, fd, "protocol", NULL, 0);
	if (!doc) {
		fprintf(stderr, "Failed to read XML\n");
		abort();
	}

	rc = xmlValidateDtd(dtdctx, doc, dtd);
	xmlFreeDoc(doc);
	xmlFreeParserCtxt(ctx);
	xmlFreeValidCtxt(dtdctx);
	/* xmlIOParseDTD consumes buffer */

	if (lseek(fd, 0, SEEK_SET) != 0) {
		fprintf(stderr, "Failed to reset fd, output would be garbage.\n");
		abort();
	}
#endif
	return rc;
}

#define XML_BUFFER_SIZE 4096

struct location {
	const char *filename;
	int line_number;
};

struct description {
	char *summary;
	char *text;
};

struct protocol {
	char *name;
	char *uppercase_name;
	struct wl_list interface_list;
	int type_index;
	int null_run_length;
	char *copyright;
	struct description *description;
	bool core_headers;
};

struct interface {
	struct location loc;
	char *name;
	char *uppercase_name;
	int version;
	int since;
	struct wl_list request_list;
	struct wl_list event_list;
	struct wl_list enumeration_list;
	struct wl_list link;
	struct description *description;
};

struct message {
	struct location loc;
	char *name;
	char *uppercase_name;
	struct wl_list arg_list;
	struct wl_list link;
	int arg_count;
	int new_id_count;
	int type_index;
	int all_null;
	int destructor;
	int since;
	struct description *description;
};

enum arg_type {
	NEW_ID,
	INT,
	UNSIGNED,
	FIXED,
	STRING,
	OBJECT,
	ARRAY,
	FD
};

struct arg {
	char *name;
	enum arg_type type;
	int nullable;
	char *interface_name;
	struct wl_list link;
	char *summary;
	char *enumeration_name;
};

struct enumeration {
	char *name;
	char *uppercase_name;
	struct wl_list entry_list;
	struct wl_list link;
	struct description *description;
	bool bitfield;
};

struct entry {
	char *name;
	char *uppercase_name;
	char *value;
	char *summary;
	struct wl_list link;
};

struct parse_context {
	struct location loc;
	XML_Parser parser;
	struct protocol *protocol;
	struct interface *interface;
	struct message *message;
	struct enumeration *enumeration;
	struct description *description;
	char character_data[8192];
	unsigned int character_data_length;
};

static void *
fail_on_null(void *p)
{
	if (p == NULL) {
		fprintf(stderr, "wayland-scanner: out of memory\n");
		exit(EXIT_FAILURE);
	}

	return p;
}

static void *
zalloc(size_t s)
{
	return calloc(s, 1);
}

static void *
xzalloc(size_t s)
{
	return fail_on_null(zalloc(s));
}

static char *
xstrdup(const char *s)
{
	return fail_on_null(strdup(s));
}

static char *
uppercase_dup(const char *src)
{
	char *u;
	int i;

	u = xstrdup(src);
	for (i = 0; u[i]; i++)
		u[i] = toupper(u[i]);
	u[i] = '\0';

	return u;
}

static const char *indent(int n)
{
	const char *whitespace[] = {
		"\t\t\t\t\t\t\t\t\t\t\t\t",
		"\t\t\t\t\t\t\t\t\t\t\t\t ",
		"\t\t\t\t\t\t\t\t\t\t\t\t  ",
		"\t\t\t\t\t\t\t\t\t\t\t\t   ",
		"\t\t\t\t\t\t\t\t\t\t\t\t    ",
		"\t\t\t\t\t\t\t\t\t\t\t\t     ",
		"\t\t\t\t\t\t\t\t\t\t\t\t      ",
		"\t\t\t\t\t\t\t\t\t\t\t\t       "
	};

	return whitespace[n % 8] + 12 - n / 8;
}

static void
desc_dump(char *desc, const char *fmt, ...) WL_PRINTF(2, 3);

static void
desc_dump(char *desc, const char *fmt, ...)
{
	va_list ap;
	char buf[128], hang;
	int col, i, j, k, startcol, newlines;

	va_start(ap, fmt);
	vsnprintf(buf, sizeof buf, fmt, ap);
	va_end(ap);

	for (i = 0, col = 0; buf[i] != '*'; i++) {
		if (buf[i] == '\t')
			col = (col + 8) & ~7;
		else
			col++;
	}

	printf("%s", buf);

	if (!desc) {
		printf("(none)\n");
		return;
	}

	startcol = col;
	col += strlen(&buf[i]);
	if (col - startcol > 2)
		hang = '\t';
	else
		hang = ' ';

	for (i = 0; desc[i]; ) {
		k = i;
		newlines = 0;
		while (desc[i] && isspace(desc[i])) {
			if (desc[i] == '\n')
				newlines++;
			i++;
		}
		if (!desc[i])
			break;

		j = i;
		while (desc[i] && !isspace(desc[i]))
			i++;

		if (newlines > 1)
			printf("\n%s*", indent(startcol));
		if (newlines > 1 || col + i - j > 72) {
			printf("\n%s*%c", indent(startcol), hang);
			col = startcol;
		}

		if (col > startcol && k > 0)
			col += printf(" ");
		col += printf("%.*s", i - j, &desc[j]);
	}
	putchar('\n');
}

static void
fail(struct location *loc, const char *msg, ...)
{
	va_list ap;

	va_start(ap, msg);
	fprintf(stderr, "%s:%d: error: ",
		loc->filename, loc->line_number);
	vfprintf(stderr, msg, ap);
	fprintf(stderr, "\n");
	va_end(ap);
	exit(EXIT_FAILURE);
}

static void
warn(struct location *loc, const char *msg, ...)
{
	va_list ap;

	va_start(ap, msg);
	fprintf(stderr, "%s:%d: warning: ",
		loc->filename, loc->line_number);
	vfprintf(stderr, msg, ap);
	fprintf(stderr, "\n");
	va_end(ap);
}

static bool
is_nullable_type(struct arg *arg)
{
	switch (arg->type) {
	/* Strings, objects, and arrays are possibly nullable */
	case STRING:
	case OBJECT:
	case NEW_ID:
	case ARRAY:
		return true;
	default:
		return false;
	}
}

static struct message *
create_message(struct location loc, const char *name)
{
	struct message *message;

	message = xzalloc(sizeof *message);
	message->loc = loc;
	message->name = xstrdup(name);
	message->uppercase_name = uppercase_dup(name);
	wl_list_init(&message->arg_list);

	return message;
}

static void
free_arg(struct arg *arg)
{
	free(arg->name);
	free(arg->interface_name);
	free(arg->summary);
	free(arg);
}

static struct arg *
create_arg(const char *name)
{
	struct arg *arg;

	arg = xzalloc(sizeof *arg);
	arg->name = xstrdup(name);

	return arg;
}

static bool
set_arg_type(struct arg *arg, const char *type)
{
	if (strcmp(type, "int") == 0)
		arg->type = INT;
	else if (strcmp(type, "uint") == 0)
		arg->type = UNSIGNED;
	else if (strcmp(type, "fixed") == 0)
		arg->type = FIXED;
	else if (strcmp(type, "string") == 0)
		arg->type = STRING;
	else if (strcmp(type, "array") == 0)
		arg->type = ARRAY;
	else if (strcmp(type, "fd") == 0)
		arg->type = FD;
	else if (strcmp(type, "new_id") == 0)
		arg->type = NEW_ID;
	else if (strcmp(type, "object") == 0)
		arg->type = OBJECT;
	else
		return false;

	return true;
}

static void
free_description(struct description *desc)
{
	if (!desc)
		return;

	free(desc->summary);
	free(desc->text);

	free(desc);
}

static void
free_message(struct message *message)
{
	struct arg *a, *a_next;

	free(message->name);
	free(message->uppercase_name);
	free_description(message->description);

	wl_list_for_each_safe(a, a_next, &message->arg_list, link)
		free_arg(a);

	free(message);
}

static struct enumeration *
create_enumeration(const char *name)
{
	struct enumeration *enumeration;

	enumeration = xzalloc(sizeof *enumeration);
	enumeration->name = xstrdup(name);
	enumeration->uppercase_name = uppercase_dup(name);

	wl_list_init(&enumeration->entry_list);

	return enumeration;
}

static struct entry *
create_entry(const char *name, const char *value)
{
	struct entry *entry;

	entry = xzalloc(sizeof *entry);
	entry->name = xstrdup(name);
	entry->uppercase_name = uppercase_dup(name);
	entry->value = xstrdup(value);

	return entry;
}

static void
free_entry(struct entry *entry)
{
	free(entry->name);
	free(entry->uppercase_name);
	free(entry->value);
	free(entry->summary);

	free(entry);
}

static void
free_enumeration(struct enumeration *enumeration)
{
	struct entry *e, *e_next;

	free(enumeration->name);
	free(enumeration->uppercase_name);
	free_description(enumeration->description);

	wl_list_for_each_safe(e, e_next, &enumeration->entry_list, link)
		free_entry(e);

	free(enumeration);
}

static struct interface *
create_interface(struct location loc, const char *name, int version)
{
	struct interface *interface;

	interface = xzalloc(sizeof *interface);
	interface->loc = loc;
	interface->name = xstrdup(name);
	interface->uppercase_name = uppercase_dup(name);
	interface->version = version;
	interface->since = 1;
	wl_list_init(&interface->request_list);
	wl_list_init(&interface->event_list);
	wl_list_init(&interface->enumeration_list);

	return interface;
}

static void
free_interface(struct interface *interface)
{
	struct message *m, *next_m;
	struct enumeration *e, *next_e;

	free(interface->name);
	free(interface->uppercase_name);
	free_description(interface->description);

	wl_list_for_each_safe(m, next_m, &interface->request_list, link)
		free_message(m);
	wl_list_for_each_safe(m, next_m, &interface->event_list, link)
		free_message(m);
	wl_list_for_each_safe(e, next_e, &interface->enumeration_list, link)
		free_enumeration(e);

	free(interface);
}

/* convert string to unsigned integer,
 * in the case of error, return -1 */
static int
strtouint(const char *str)
{
	long int ret;
	char *end;
	int prev_errno = errno;

	errno = 0;
	ret = strtol(str, &end, 10);
	if (errno != 0 || end == str || *end != '\0')
		return -1;

	/* check range */
	if (ret < 0 || ret > INT_MAX) {
		return -1;
	}

	errno = prev_errno;
	return (int)ret;
}

static void
start_element(void *data, const char *element_name, const char **atts)
{
	struct parse_context *ctx = data;
	struct interface *interface;
	struct message *message;
	struct arg *arg;
	struct enumeration *enumeration;
	struct entry *entry;
	struct description *description = NULL;
	const char *name = NULL;
	const char *type = NULL;
	const char *interface_name = NULL;
	const char *value = NULL;
	const char *summary = NULL;
	const char *since = NULL;
	const char *allow_null = NULL;
	const char *enumeration_name = NULL;
	const char *bitfield = NULL;
	int i, version = 0;

	ctx->loc.line_number = XML_GetCurrentLineNumber(ctx->parser);
	for (i = 0; atts[i]; i += 2) {
		if (strcmp(atts[i], "name") == 0)
			name = atts[i + 1];
		if (strcmp(atts[i], "version") == 0) {
			version = strtouint(atts[i + 1]);
			if (version == -1)
				fail(&ctx->loc, "wrong version (%s)", atts[i + 1]);
		}
		if (strcmp(atts[i], "type") == 0)
			type = atts[i + 1];
		if (strcmp(atts[i], "value") == 0)
			value = atts[i + 1];
		if (strcmp(atts[i], "interface") == 0)
			interface_name = atts[i + 1];
		if (strcmp(atts[i], "summary") == 0)
			summary = atts[i + 1];
		if (strcmp(atts[i], "since") == 0)
{'fraction_non_alphanumeric': 0.1476869699306091, 'fraction_numerical': 0.006861989205859676, 'mean_word_length': 3.8834713855421685, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 157, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 32}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5235399', 'n_tokens_mistral': 10259, 'n_tokens_neox': 9674, 'n_words': 2628}