main shrubtools / proto / parse.c
  1#include <expat.h>
  2#include <stdbool.h>
  3#include <stdio.h>
  4#include <stdlib.h>
  5#include <string.h>
  6
  7#include "parse.h"
  8#include "util.h"
  9
 10enum parse_scope {
 11	SCOPE_PROTOCOL,
 12	SCOPE_INTERFACE,
 13	SCOPE_MESSAGE,
 14	SCOPE_ENUM,
 15	SCOPE_DESCRIPTION,
 16};
 17
 18struct parser {
 19	const char *path;
 20	XML_Parser xml;
 21	struct protocol *proto;
 22	struct interface *iface;
 23	struct message *msg;
 24	struct enumdef *en;
 25	enum parse_scope stack[32];
 26	size_t depth;
 27	struct description *desc;
 28	char *text;
 29	size_t textlen;
 30	size_t textcap;
 31};
 32
 33static const char *attr(const char **, const char *);
 34static int parseint(const char *, const char *);
 35static void pushscope(struct parser *, enum parse_scope);
 36static void popscope(struct parser *);
 37static bool inscope(const struct parser *, enum parse_scope);
 38static void cleartext(struct parser *);
 39static void appendtext(struct parser *, const char *, int);
 40static char *normalizedtext(const char *);
 41static void onstart(void *, const char *, const char **);
 42static void onend(void *, const char *);
 43static void ontext(void *, const XML_Char *, int);
 44
 45static const char *
 46attr(const char **attrs, const char *name)
 47{
 48	size_t i;
 49
 50	for (i = 0; attrs && attrs[i]; i += 2) {
 51		if (strcmp(attrs[i], name) == 0)
 52			return attrs[i + 1];
 53	}
 54	return NULL;
 55}
 56
 57static int
 58parseint(const char *what, const char *s)
 59{
 60	char *end;
 61	long v;
 62
 63	if (!s)
 64		die("missing %s attribute", what);
 65	v = strtol(s, &end, 10);
 66	if (*s == '\0' || *end != '\0' || v < 0)
 67		die("invalid %s: %s", what, s);
 68	return (int)v;
 69}
 70
 71static void
 72pushscope(struct parser *p, enum parse_scope scope)
 73{
 74	if (p->depth == countof(p->stack))
 75		die("%s: XML nesting too deep", p->path);
 76	p->stack[p->depth++] = scope;
 77}
 78
 79static void
 80popscope(struct parser *p)
 81{
 82	if (p->depth > 0)
 83		--p->depth;
 84}
 85
 86static bool
 87inscope(const struct parser *p, enum parse_scope scope)
 88{
 89	return p->depth > 0 && p->stack[p->depth - 1] == scope;
 90}
 91
 92static void
 93cleartext(struct parser *p)
 94{
 95	free(p->text);
 96	p->text = NULL;
 97	p->textlen = 0;
 98	p->textcap = 0;
 99}
100
101static void
102appendtext(struct parser *p, const char *s, int n)
103{
104	size_t need;
105
106	if (n <= 0)
107		return;
108	need = p->textlen + (size_t)n + 1;
109	if (need > p->textcap) {
110		p->textcap = p->textcap ? p->textcap * 2 : 128;
111		while (p->textcap < need)
112			p->textcap *= 2;
113		p->text = xreallocarray(p->text, p->textcap, sizeof(p->text[0]));
114	}
115	memcpy(p->text + p->textlen, s, (size_t)n);
116	p->textlen += (size_t)n;
117	p->text[p->textlen] = '\0';
118}
119
120static char *
121normalizedtext(const char *s)
122{
123	const char *start, *end, *lineend;
124	char *out;
125	size_t cap, len, n;
126
127	if (!s)
128		return NULL;
129	start = s;
130	while (*start == '\n' || *start == '\r' || *start == '\t' || *start == ' ')
131		++start;
132	end = s + strlen(s);
133	while (end > start && (end[-1] == '\n' || end[-1] == '\r' || end[-1] == '\t' || end[-1] == ' '))
134		--end;
135	if (start == end)
136		return NULL;
137
138	cap = (size_t)(end - start) + 1;
139	out = xmalloc(cap);
140	len = 0;
141	while (start < end) {
142		while (start < end && (*start == '\n' || *start == '\r'))
143			++start;
144		lineend = start;
145		while (lineend < end && *lineend != '\n' && *lineend != '\r')
146			++lineend;
147		while (start < lineend && (*start == ' ' || *start == '\t'))
148			++start;
149		while (lineend > start && (lineend[-1] == ' ' || lineend[-1] == '\t'))
150			--lineend;
151		n = (size_t)(lineend - start);
152		if (n > 0) {
153			if (len > 0)
154				out[len++] = '\n';
155			memcpy(out + len, start, n);
156			len += n;
157		}
158		start = lineend;
159		while (start < end && *start != '\n' && *start != '\r')
160			++start;
161	}
162	if (len == 0) {
163		free(out);
164		return NULL;
165	}
166	out[len] = '\0';
167	return out;
168}
169
170static void
171onstart(void *data, const char *name, const char **attrs)
172{
173	struct parser *p = data;
174	const char *aname, *atype, *iface, *allow_null, *version, *type, *summary;
175
176	if (strcmp(name, "protocol") == 0) {
177		if (p->proto->name)
178			die("%s:%lu: duplicate protocol element",
179				p->path, XML_GetCurrentLineNumber(p->xml));
180		aname = attr(attrs, "name");
181		if (!aname)
182			die("%s:%lu: protocol missing name",
183				p->path, XML_GetCurrentLineNumber(p->xml));
184		p->proto->name = xstrdup(aname);
185		pushscope(p, SCOPE_PROTOCOL);
186		return;
187	}
188
189	if (strcmp(name, "interface") == 0 && inscope(p, SCOPE_PROTOCOL)) {
190		aname = attr(attrs, "name");
191		version = attr(attrs, "version");
192		if (!aname || !version)
193			die("%s:%lu: interface missing name/version",
194				p->path, XML_GetCurrentLineNumber(p->xml));
195		p->iface = proto_add_interface(p->proto, aname, parseint("version", version));
196		pushscope(p, SCOPE_INTERFACE);
197		return;
198	}
199
200	if ((strcmp(name, "request") == 0 || strcmp(name, "event") == 0) && inscope(p, SCOPE_INTERFACE)) {
201		aname = attr(attrs, "name");
202		type = attr(attrs, "type");
203		if (!aname)
204			die("%s:%lu: message missing name",
205				p->path, XML_GetCurrentLineNumber(p->xml));
206		p->msg = iface_add_message(p->iface,
207			strcmp(name, "request") == 0 ? MSG_REQUEST : MSG_EVENT, aname);
208		p->msg->destructor = type && strcmp(type, "destructor") == 0;
209		pushscope(p, SCOPE_MESSAGE);
210		return;
211	}
212
213	if (strcmp(name, "arg") == 0 && inscope(p, SCOPE_MESSAGE)) {
214		aname = attr(attrs, "name");
215		atype = attr(attrs, "type");
216		iface = attr(attrs, "interface");
217		allow_null = attr(attrs, "allow-null");
218		if (!aname || !atype)
219			die("%s:%lu: arg missing name/type",
220				p->path, XML_GetCurrentLineNumber(p->xml));
221		message_add_arg(p->msg, aname, atype, iface,
222			allow_null && strcmp(allow_null, "true") == 0);
223		return;
224	}
225
226	if (strcmp(name, "enum") == 0 && inscope(p, SCOPE_INTERFACE)) {
227		aname = attr(attrs, "name");
228		if (!aname)
229			die("%s:%lu: enum missing name",
230				p->path, XML_GetCurrentLineNumber(p->xml));
231		iface_add_enum(p->iface, aname);
232		p->en = iface_last_enum(p->iface);
233		pushscope(p, SCOPE_ENUM);
234		return;
235	}
236
237	if (strcmp(name, "description") == 0) {
238		if (inscope(p, SCOPE_MESSAGE))
239			p->desc = &p->msg->desc;
240		else if (inscope(p, SCOPE_ENUM))
241			p->desc = &p->en->desc;
242		else if (inscope(p, SCOPE_INTERFACE))
243			p->desc = &p->iface->desc;
244		else if (inscope(p, SCOPE_PROTOCOL))
245			p->desc = &p->proto->desc;
246		else
247			return;
248		summary = attr(attrs, "summary");
249		free(p->desc->summary);
250		p->desc->summary = summary ? xstrdup(summary) : NULL;
251		cleartext(p);
252		pushscope(p, SCOPE_DESCRIPTION);
253		return;
254	}
255}
256
257static void
258onend(void *data, const char *name)
259{
260	struct parser *p = data;
261
262	if (strcmp(name, "request") == 0 || strcmp(name, "event") == 0) {
263		if (inscope(p, SCOPE_MESSAGE)) {
264			p->msg = NULL;
265			popscope(p);
266		}
267		return;
268	}
269	if (strcmp(name, "enum") == 0) {
270		if (inscope(p, SCOPE_ENUM))
271		{
272			p->en = NULL;
273			popscope(p);
274		}
275		return;
276	}
277	if (strcmp(name, "description") == 0) {
278		if (inscope(p, SCOPE_DESCRIPTION)) {
279			free(p->desc->text);
280			p->desc->text = normalizedtext(p->text);
281			p->desc = NULL;
282			cleartext(p);
283			popscope(p);
284		}
285		return;
286	}
287	if (strcmp(name, "interface") == 0) {
288		if (inscope(p, SCOPE_INTERFACE)) {
289			p->iface = NULL;
290			popscope(p);
291		}
292		return;
293	}
294	if (strcmp(name, "protocol") == 0) {
295		if (inscope(p, SCOPE_PROTOCOL))
296			popscope(p);
297		return;
298	}
299}
300
301static void
302ontext(void *data, const XML_Char *s, int len)
303{
304	struct parser *p = data;
305
306	if (inscope(p, SCOPE_DESCRIPTION))
307		appendtext(p, s, len);
308}
309
310void
311parsefile(const char *path, struct protocol *proto)
312{
313	struct parser p;
314	FILE *fp;
315	void *buf;
316	int done;
317
318	memset(&p, 0, sizeof(p));
319	p.path = path;
320	p.proto = proto;
321	p.xml = XML_ParserCreate(NULL);
322	if (!p.xml)
323		die("XML_ParserCreate:");
324	XML_SetUserData(p.xml, &p);
325	XML_SetElementHandler(p.xml, onstart, onend);
326	XML_SetCharacterDataHandler(p.xml, ontext);
327
328	fp = fopen(path, "rb");
329	if (!fp) {
330		XML_ParserFree(p.xml);
331		die("open %s:", path);
332	}
333
334	done = 0;
335	while (!done) {
336		size_t nread;
337
338		buf = XML_GetBuffer(p.xml, 4096);
339		if (!buf)
340			die("%s: XML_GetBuffer failed", path);
341		nread = fread(buf, 1, 4096, fp);
342		done = nread < 4096;
343		if (ferror(fp))
344			die("read %s:", path);
345		if (XML_ParseBuffer(p.xml, (int)nread, done) == XML_STATUS_ERROR) {
346			die("%s:%lu:%lu: %s", path,
347				XML_GetCurrentLineNumber(p.xml),
348				XML_GetCurrentColumnNumber(p.xml),
349				XML_ErrorString(XML_GetErrorCode(p.xml)));
350		}
351	}
352
353	fclose(fp);
354	XML_ParserFree(p.xml);
355	cleartext(&p);
356
357	if (!proto->name)
358		die("%s: missing protocol element", path);
359	proto_resolve(proto);
360}